^ _ ^
前言
这篇文章记录了跟着狂神的MyBatis视频学习所进行的步骤,博客大纲也同狂神的视频一致。
狂神MyBatis视频链接:https://www.bilibili.com/video/BV1NE411Q7Nx?p=2
第一个MyBatis程序
1. 搭建环境
- File -> New -> Project
- 选择Maven项目,不勾选
Create from archetype
,表示创建一个基本Maven项目 - 为项目命名为MyBatis-Study,将groupId更改为org.llunch4w
- 项目创建成功后,将项目目录下的src文件夹删除,目的是使MyBatis-Study用作一个父工程,起一个容器的作用。具体的代码在其子模块中编写
- 编写pom.xml(MyBatis-Study)文件:配置子模块公共依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<!--导入依赖-->
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies> - 创建子模块
- MyBatis-Study项目处右键 -> New -> Module
- 选择Maven项目,不勾选
Create from archetype
- 配置模块名称
- 子模块中已自动包含父工程中配置好的依赖
- 观察父工程pom.xml文件的变化
2. 编写MyBatis核心配置文件
此步骤的操作对象是mybatis-01子模块
- 在 mybatis-01/src/main/resources 目录下创建mybatis核心配置文件 mybatis-config.xml (这个名字可以自定义,但一般使用这个命名)
- 在 mybatis-config.xml 文件中填写如下基本代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<!--environments中可以存在多套环境,默认选择的环境根据其default选项指定
比如test就可以是另一套环境-->
<environment id="test">
<transactionManager />
<dataSource />
</environment>
</environments>
</configuration> - 通过硬编码将数据库信息换成具体的值,具体值如下
1
2
3
4driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3307/mybatis?useSSL=false&useUnicode=true&charsetEncoding=UTF-8&serverTimezone=UTC
username = root
password = MyNewPass
3. 编写读取配置文件工具类
1 | public class MyBatisUtil { |
4. 创建数据库mybatis,并在其中添加数据表user
1 | create database mybatis; |
5. 添加对应于user表的实体类User
6. 添加操纵User的Dao接口UserDao
1 | public interface UserDao { |
7. 编写对应于UserDao的映射文件 userMapper.xml
1 |
|
9. 将 user-mapper.xml 文件在核心配置文件 mybatis-config.xml 中注册
1 | <!--每一个 mapper.xml 文件都需要在核心配置文件中注册--> |
PS 如果不配置的话,会出现如下错误:
org.apache.ibatis.binding.BindingException: Type interface com.llunch4w.dao.UserDao is not known to the MapperRegistry.
10. 通过配置父工程的 pom.xml 文件制定编译条件,防止出现jdk1.5的警告
1 | <!--编译环境--> |
如果不配置的话,在运行java程序时,会出现如下警告:
11. 通过配置父工程的 pom.xml 文件制定资源过滤条件,防止 Maven 项目下资源导出失败问题
1 | <!--在build中配置resources,来放置资源导出失败问题--> |
PS 如果不配置的话,会出现如下错误:
Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/llunch4w/dao/userMapper.xml
这个问题也可以通过将mapper.xml文件放到resources目录下的方式来解决(注意最好有一定的目录结构,例如resources/com/mapper/userMapper.xml)
12. 编写测试文件进行测试
1 | // 测试 UserDao 的 getAllUsers 方法 |
补充,还有另一种不获取userDao直接用sqlSession进行查询的方法,如下:
1 List<User> userList = sqlSession.selectList("com.llunch4w.dao.UserDao.getAllUsers");但这种方式不推荐使用,了解即可
CRUD
create、read、 update、delete
基本增删改查实现
1. 检查namespace
nampespace中的包名要和Dao接口的包名一致
1 | <mapper namespace="com.llunch4w.dao.UserDao"> |
2. 在Dao接口中添加方法申明
1 | public interface UserDao { |
3. 在 userMapper.xml 文件中的 mapper 标签下添加操作标签
1 | <!--返回类型和参数类型都应写全限定名--> |
4. 测试:以更新为例
注意:
- sqlSession应及时关闭
- 对于增加、删除、更新操作,sqlSession需要执行commmit后修改才会生效(事务才会提交)
1 |
|
map
1. UserDao中声明传参为map的接口
1 | int updateByMap(Map<String,Object> map); |
2. userMapper.xml中添加对应的操作标签
1 | <update id="updateByMap" parameterType="map"> |
3. 编写测试函数
1 |
|
模糊查询
1. UserDao中添加根据名称模糊查询的接口
1 | // 根据用户名称进行模糊查询 |
方式一
在调用方法时添加模糊匹配字符 %
2. 在userMapper.xml中添加模糊查询的操作标签
1 | <!--参数类型为String,不需要特别声明--> |
3. 测试类调用
1 |
|
方式二
在userMapper.xml中直接编码 %
2. 在userMapper.xml中添加模糊查询的操作标签
1 | <!--直接拼接方式1--> |
3. 测试类调用
1 | List<User> userList = userDao.getByNameInDim("谢"); |
配置解析
项目准备
- 在 MyBatis-Study 父工程下新建一个子模块 mybatis-02,也是普通Maven工程
- 将 mybatis-01 中的代码拷贝一份到mybatis-02
- 将 mybatis-02 中 UserDao、userMapper.xml 中除了基本增删改查外的其他操作相关内容去除,将UserDaoTest中除了getAllTest之外的函数都去除
- 测试getAllTest的执行,执行成功则说明项目准备完成
环境配置(enviroments)
1 | <!--MyBatis可以配置多种环境以适应多种环境(开发、测试、生产等)的需要 |
属性(properties)
- 可以通过properties属性实现引用配置文件
- 这些属性都是可外部配置和动态替换的:既可通过java属性文件配置,也可通过properties元素的子元素来传递
引用Java属性文件进行配置
1. 编写一个数据库属性配置文件 jdbc.properties
1 | driver = com.mysql.jdbc.Driver |
2. 在核心配置文件 mybatis-config.xml 中引用
1 | <!--引入外部配置文件--> |
3. 运行UserDaoTest中的方法进行测试
通过properties元素子元素进行配置
注意:&符号在xml文件中是非法的,应该用&代替,而在properties文件中则不需要
1 | <properties> |
优先级比较实验
1. 对mybatis-config.xml文件和jdbc.properties进行如下配置
1 | <!--mybatis-config.xml--> |
1 | # jdbc.properties |
2. 测试UserDaoTest运行结果
无错误
3. 调换mybatis-config.xml文件和jdbc.properties的密码配置
1 | <!--mybatis-config.xml--> |
1 | # jdbc.properties |
4. 测试UserDaoTest运行结果
出现错误
5. 得出结论
properties中引用的resources优先级最高
类型别名(typeAliases)
类型别名是为Java类型设置一个短的名字。它只和XML配置有关,存在的意义仅在于减少类全限定名的冗余
指定类别名
1. 在 mybatis-config.xml 中添加别名配置
1 | <typeAliases> |
2. 在 userMapper.xml 中就可将全限定类名用别名代替了
1 | <select id="getAll" resultType="User"> |
指定搜索包
可以指定一个包名,MyBatis会在包名下搜索需要的JavaBean
1. 在 mybatis-config.xml 中添加别名配置
1 | <typeAliases> |
2. 在 userMapper.xml 中就可将全限定类名用类名(首字母小写)代替了
默认别名是类名的首字母小写,但是直接使用类名也是可以的
一般是用小写,表名该类型是扫描包的
PS:如果实体类有注解@Alias的话,那么别名是注解值
1 | <select id="getAll" resultType="user"> |
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为
常见设置
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存 | true / false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 | true / false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找 | SLF4J / LOG4J / LOG4J2 / JDK_LOGGING / COMMONS_LOGGING / STDOUT_LOGGING / NO_LOGGING | 未设置 |
映射器(mappers)
映射器告诉MyBatis去哪里找SQL映射语句
方式一:使用相对于类路径的资源引用
推荐使用这种方式
1 | <mappers> |
方式二:使用映射器接口实现类的完全限定类名
这种方式要求接口和其映射文件的名称相同且必须在同一包下
例如:映射文件是userMapper.xml,那么类名应该是UserMapper
1 | <mappers> |
方式三:将包内的映射器接口实现全部注册为映射器
这种方式要求包内所有接口和其对应映射文件的名称相同,且必须在同一包下
1 | <mappers> |
顺序
核心配置文件 mybatis-config.xml 中各项配置是有一定先后顺序的
The content of element type “configuration” must match “(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)”.
生命周期和作用域
生命周期、作用域,是至关重要的,因为错误的使用会导致非常严重的 并发问题
SqlSessionFactoryBuilder
- 一旦创建了SqlSessionFactory后,就不再需要它了
- 最好作为局部变量存在
SqlSessionFactory
- 类似于数据库连接池
- SqlSessionFactory一旦被创建就应该在应用运行期间一直存在,没有任何理由丢弃它或重新创建一个实例
- 因此SqlSessionFactory的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式
SqlSession
- 连接到连接池的一个请求
- SqlSession的实例不是线程安全的,因此是不能被共享的
- 因此SqlSession的最佳作用域是请求或方法作用域
- 用完之后需要赶紧关闭,否则资源被占用
这里面的每一个Mapper,就代表着一个具体的业务。
资料
mybatis官方文档:https://mybatis.org/mybatis-3/zh/configuration.html
ResultMap
结果集映射,用于解决数据库表名和对应JavaBean中字段名称不统一的问题
0. 新建模块
在MyBatis-Study父工程中新建一个子模块mybatis-03,然后将mybatis-02中的文件拷贝过去,删除一些配置使项目达到最简状态。
1. 提出问题
在数据库中,对于长字段通常是用下划线分割而不是使用驼峰命名法。原因是:数据库中并不区别大小写,驼峰命名法实际上起不到分割单词的作用;但在java中,变量的命名通常是驼峰命名
在MyBatis中,JavaBean的属性名必须和数据库中的字段是完全一致的,否则就会找不到对应字段。
所以,该如何统一两者之间的命名差异呢?
下面演示这种情况
(1)数据库表名字段和对应JavaBean属性不一致
(2)执行UserTestDao的getAllTest方法进行测试
(3)得出结论:数据库表名字段和对应JavaBean属性不一致时无对应字段的属性无法被赋值
2. 解决方案一:修改userMapper.xml中的查询语句
3. 解决方案二:使用ResultMap
设计思想
对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句,只需要描述他们的关系即可
日志
标准日志工厂
如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的助手
0. 新建模块
在MyBatis-Study父工程中新建一个子模块mybatis-04,然后将mybatis-03中的文件拷贝过去,删除一些配置使项目达到最简状态。然后将UserDaoTest中的测试函数换为getByIdTest
1. 测试UserDaoTest的运行效果
2. 在核心配置文件 mybatis-config.xml 中添加日志配置
1 | <settings> |
3. 测试UserDaoTest的运行效果
所有的日志工厂
- SLF4J
- LOG4J 【掌握】:log4j日志工厂,需要导包
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING 【掌握】:标准日志工厂,不需要导包依赖
- NO_LOGGING
LOG4J
Log4j是Apache的一个开源项目。通过使用Log4j,我们可以控制日志信息输送的目的地使控制台、文件、GUI组件
也可以控制每一条日志的输出格式
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
通过一个配置文件
来灵活地进行配置,而不需要修改应用的代码
1. 在mybatis-04模块的pom.xml中引入log4j依赖
1 | <!-- https://mvnrepository.com/artifact/log4j/log4j --> |
2. 编写log4j.properties文件
1 | # 将等级为DEBUG的日志信息输出到 console 和 logFile 这两个目的地,console 和 logFile 的定义在下面的代码 |
3. 在核心配置文件 mybatis-config.xml 中添加log4j日志配置
1 | <settings> |
4. 运行UserDaoTest进行测试
5. 观察logFile文件的生成
补充:log4j.properties中格式化符号的说明
6. 在UserTestDao中使用Logger添加一些自定义输出
7. 清空log.log4j文件后运行log4jTest函数进行测试,完成后观察log.log4j文件内容
分页
limit分页
1. 在UserMapper接口中添加分页函数
1 | List<User> getInLimit(Map<String,Object> params); |
2. 在userMapper.xml中添加数据库操作标签
1 | <select id="getInLimit" parameterType="map" resultMap="userMap"> |
3. 在UserDaoTest类中添加测试函数
1 | // limit分页查询测试 |
RowBounds分页
核心思想:在java层面实现分页
这种方式不推荐使用,仅作了解
1 | // RowBound分页查询测试 |
分页插件
MyBatis分页插件PageHelper
PageHelper官方地址:https://pagehelper.github.io/
MyBatis执行流程
注解
0. 在MyBatis-Study父工程下新建一个子模块mybatis-05
- 将mybatis-04中resources目录下的jdbc.properties和mybatis-config.xml移动到mybatis-05的resources目录下,并将mybatis-config.xml中的setting设置logImpl的值改为STDOUT_LOGGING
- 将mybatis-04中java目录下的包移动到mybatis-05中,将dao包中的类和xml文件删除
- 将mybatis-01模块中test目录下的包拷贝到mybatis-05中test目录下,并对其中测试函数进行删减,使其满足只具有基本的增删改查功能
1. 在dao包下添加UserDao接口
1 | public interface UserDao { |
2. 通过MyBatisUtil类中获取SqlSession时设置自动提交为true
1 | public static SqlSession getSqlSession(){ |
3. 在UserTestDao中测试接口各个功能
4. 注意:mybatis-config.xml中必须包含接口的映射路径
1 | <mappers> |
Param注解基本规则
- 基本类型或String类型的参数,需要加上;但如果只有一个基本类型的话,可以不加(但建议加上)
- 引用类型不需要加
- 在SQL中的引用就是在@Param中设定的属性名
Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
1. 在mybatis-05的pom.xml中导入Maven依赖
1 | <dependency> |
2. 在IDEA中安装lombok插件
File -> Settings -> Plugins -> 搜索lombok
3. 使用lombok标签简化开发
4. 对于User类只保留属性
常用注解
1 | // 无参构造、set(@Set)、get(@Get)、toString(@ToString)、hashcode、equals(@EqualsAndHashCode) |
5. 添加lombok的Data注解
添加Data注解后,lombok自动为User类添加了很多方法
复杂查询
环境构建
1. 在数据库mybatis中创建teacher表和student表并向其中插入数据
1 | create table teacher( |
2. 在MyBatis-Study父工程下建立子模块mybatis-06
- 将mybatis-05的pom.xml中包含的lombok依赖复制到mybatis-06的pom.xml文件中
- 将mybatis-05的resources目录下文件复制到mybatis-06的resources目录下
- 将mybatis-05 java目录下的包复制到mybatis-06的java目录下,并删除dao包和pojo包中的类
3. 在pojo包中添加实体类 Teacher 和 Student
1 |
|
1 |
|
4. 在dao包下添加数据库映射接口
1 | public interface TeacherMapper {} |
1 | public interface StudentMapper {} |
5. 在resources目录下建立com.llunch4w.dao目录
这个目录结构与存放Mapper类源码的路径最好一致,用于存放mapper.xml文件
在该目录下添加 studentMapper.xml 和 teacherMapper.xml 文件
1 |
|
1 |
|
6. 在mybatis-config文件中添加studentMapper.xml和teacherMapper.xml的绑定
1 | <mappers> |
7. 编写测试类进行测试,检查环境是否配置成功
- TeacherMapper中添加方法getById
1
2
3// 根据ID获取教师
"select * from teacher where id=#{id}") (
Teacher getById(@Param("id")int id); - test目录下测试类com.llunch4w.dao.TeacherMapperTest中添加函数
1
2
3
4
5
6
7
8
9
10
public void getByIdTest(){
SqlSession sqlSession = MyBatisUtil.getSqlSession();
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = teacherMapper.getById(1);
System.out.println(teacher);
sqlSession.close();
}
多对一处理
1. 提出问题
在Java类Student中包含一个对象属性Teacher,在数据库层面Teacher信息可以通过多表连接查询得到,但是又该如何将查询结果映射映射到Student的Teacher属性上呢?
2. 为StudentMapper类添加查询所有学生的接口
1 | List<Student> getAll(); |
3. 编写studentMapper.xml文件
1 | <select id="getAll" resultType="Student"> |
4. 编写StudentMapperTest测试查询所有学生
1 |
|
查询结果
发现:student的teacher属性不能被赋值
方案一
5. 改写studentMapper.xml文件
1 | <select id="getAll" resultMap="StudentTeacher"> |
6. 运行StudentMapperTest的getAllTest函数
查询结果
结论:teacher属性赋值成功
方案二
5. 改写studentMapper.xml文件
1 | <select id="getAll" resultMap="StudentTeacher"> |
6. 运行StudentMapperTest的getAllTest函数
查询结果:teacher属性赋值成功
一对多处理
0. 重复一遍构建环境的过程,得到mybatis-07
- 将mybatis-06的pom.xml中包含的lombok依赖复制到mybatis-07的pom.xml文件中
- 将mybatis-06的resources目录下文件复制到mybatis-07的resources目录下
- 将mybatis-06 java目录下的包复制到mybatis-07的java目录下,并删除pojo包中的类,清空dao包中接口包含的方法
1. 在pojo包中添加实体类 Teacher 和 Student
1 |
|
1 |
|
2. 为TeacherMapper编写一个按ID查询的方法
1 | // 根据ID获取教师 |
方案一
3. 编写teacherMapper.xml
1 | <select id="getById" resultMap="TeacherStudent"> |
4. 运行TeacherMapperTest中的getByIdTest函数进行测试
Teacher类中的students属性被顺利赋值
方案二
3. 编写teacherMapper.xml
1 | <select id="getById" resultMap="TeacherStudent"> |
4. 运行TeacherMapperTest中的getByIdTest函数进行测试
Teacher类中的students属性被顺利赋值
但Teacher的id属性值不对,解决方式是在resultMap中添加
1 <result property="id" column="id" />
动态SQL
动态SQL就是根据不同的条件生成不同的SQL语句
搭建环境
1. 在mybatis数据库中创建数据表blog
1 | create table blog( |
2. 在MyBatis-Study父工程下创建子模块mybatis-08
- 将mybatis-07中pom.xml的lombok依赖拷贝到mybatis-08的pom.xml文件中
- 将mybatis-07 resources目录下的jdbc.properties文件和mybatis-config.xml文件拷贝到mybatis-08 resources目录下
- 将mybatis-07 java目录下的包拷贝到mybatis-08的java目录下,并清空pojo包和dao包中的类
3. 在pojo包中编写Blog类
1 |
|
4. 在utils包中添加IdUtil用于随机生产ID
1 | public class IdUtil { |
5. 在mybatis-config.xml中添加mapUnderscoreToCamelCase
的setting设置
此设置可以使驼峰命名的Java属性和下划线命名的数据库列名相对应
1 | <!--是否开启自动驼峰命名规则映射--> |
6. 在dao包下添加接口BlogMapper
1 | public interface BlogMapper { |
7. 在resources目录下的com/llunch4w/dao目录下添加blogMapper.xml
1 |
|
8. 修改mybatis-config.xml中mapper配置
1 | <mappers> |
9. 在test目录下编写com.llunch4w.dao.BlogMapperTest类进行测试
1 |
|
IF
1. 在BlogMapper中添加函数getWithIf
1 | List<Blog> getWithIf(Map<String,Object> params); |
2. 在blogMapper.xml中添加对应的查询语句
1 | <select id="getWithIf" parameterType="map" resultType="Blog"> |
3. 在BlogMapperTest类中添加测试方法getWithIfTest
1 | public void getWithIfTest(){ |
以上为不添加任何参数的情况,查询结果为:
结论:查出了所有数据
4. 在getWithIfTest方法里params变量添加title参数限定
1 | params.put("title","MyBatis如此困难"); |
查询结果为:
结论:限定了title的值进行查询
trim
WHERE + IF
1. 提出问题
对于IF的使用,考虑这样一种情况:
1 | <select id="getWithIfProblem" parameterType="map" resultType="Blog"> |
如果此时传来的参数中title为null,author不为null的话,sql语句就会被动态拼接成
1 | select * from blog where and author = ? |
显然,这样是不正确的
2. 解决问题
所以引入了where元素,where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
1 | <select id="getWithTrim" parameterType="map" resultType="Blog"> |
问题被解决了
Where + Set
对于更新语句来说,需要用Where + Set的组合来避免SQL动态组合出错的情况
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)
1 | <update id="updateWithTrim" parameterType="map"> |
更新测试函数执行前:
更新测试函数执行后:
定制过滤条件
1. 可以通过trim定制where过滤条件
1 | <trim prefix="WHERE" prefixOverrides="AND |OR "> |
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)
2. 可以通过trim定制set过滤条件
1 | <trim prefix="SET" suffixOverrides=","> |
choose(When + Otherwise)
choose标签类似于java语句中的switch:会在多个when中选择一个符合条件的执行,如果都不符合则执行otherwise标签中定义的语句
1 | <select id="getWithChoose" parameterType="map" resultType="Blog"> |
1. 测试只添加view参数时
会走otherwise路径
2. 测试只添加author参数
3. 添加author参数和title参数限定
发现只有title参数有效,说明choose标签会顺序判断所有when标签中条件是否满足,若满足则执行该标签,不会再考虑后面的when标签条件是否满足
SQL片段
用于将公共的SQL片段提取出来,方便复用
1 | <sql id="if-title-author"> |
注意事项
- 最好基于单表来定义SQL片段
- 不要存在where标签
foreach
动态SQL的另一个常用需求是对一个集合进行遍历,通常是在构建IN条件语句时
1. blogMapper.xml中定义查询语句
1 | <!-- |
2. BlogMapper接口中定义函数
1 | // 查询Blog(伴随ForEach) |
3. 编写测试函数
1 |
|
查询成功
缓存
简介
- 什么是缓存?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
- 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据
MyBatis缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启(
SqlSession
级别的缓存,也称为本地缓存) - 二级缓存需要手动开启和配置,它是基于
namespace
级别的缓存 - 为了提高扩展性,MyBatis定义了缓存接口
Cache
。我们可以通过实现Cache接口来自定义二级缓存
环境搭建
0. 在MyBatis-Study父工程下创建子模块mybatis-09
- 将mybatis-08的pom.xml文件中的lombok依赖复制到mybatis-09的pom.xml文件中
- 将mybatis-08的resources文件夹下内容复制到mybatis-09的resources文件夹
- 并删除其中com.llunch4w.dao中的xml文件
- 并对其mybatis-config.xml文件中的mapper路径进行对应修改
- 将mybatis-08的java文件夹下的包复制到mybatis-09的java文件夹下,并删除pojo包和dao包中的内容
1. pojo包下创建User类
1 |
|
3. dao包下创建UserMapper接口
1 | public interface UserMapper { |
4. resources文件下com/llunch4w/dao目录下创建userMapper.xml文件
1 |
|
一级缓存
一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
1. 测试同一SqlSession下的相同查询的缓存情况
1 |
|
查询结果
结论:同一个SqlSession内的相同查询,在1+次查询时会直接使用缓存中的结果,不会重新查询
2. 测试更新操作使缓存失效的情况
同一SqlSession内的相同查询中间插入一个更新操作,缓存是否仍旧有效?
1 | User user1 = userMapper.getById(1); |
查询结果
结论:更新(同理还有增加和删除)操作可以使缓存失效
3. 测试手动清理缓存使缓存失效的情况
1 | sqlSession.clearCache(); |
查询结果
结论:手动清理缓存可以使缓存失效
二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但我们希望的是:会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取
- 不同mapper查出的数据会放在自己对应的缓存中
1. 在mybatis-config.xml文件中显式的开启
1 | <!--显式开启全局缓存--> |
2. 在当前mapper.xml中使用二级缓存
1 | <cache /> |
也可以自定义参数
1 | <cache |
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对
3. 测试二级缓存
1 |
|
查询结果
结论:二级缓存已经失效,所以在第二个SqlSession中可以访问到第一个SqlSession查询到的数据结果
小结
- 只要开启的二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只有当会话提交(或关闭)时,才会提交到二级缓存中
MyBatis缓存原理
自定义缓存Ehcache
Ehcache是一种广泛使用的Java分布式缓存,主要用于面向缓存