一、前言
缓存在实际的系统开发中经常用到,因为使用缓存可以更快的获取数据,避免频繁的数据库交互从而影响效率。
Mybatis作为持久化框架提供了非常强大的查询缓存的特性,一般说Mybatis的缓存都是指二级缓存,因为其一级缓存(也叫本地缓存)会被默认启用。
因此本节内容主要讲解二级缓存的基本配置用法,还会介绍Redis集成Mybatis框架搭建缓存。
二、一级缓存
缓存生效是怎么生效的?
首先上测试代码。
@Test
public void test28() throws IOException {
//1. 读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3. 使用工厂生产一个SqlSession对象
SqlSession session = factory.openSession();
//4. 使用SqlSession创建Dao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//5.第一次根据id值查询
User byIdTest = userMapper.findById(1);
System.out.println(byIdTest);
//6.第二次根据id值查询
User byIdTest2 = userMapper.findById(1);
System.out.println(byIdTest2);
//7. 释放资源
session.close();
in.close();
}
控制台打印的测试结果如下。
2021-08-25 14:11:31,754 [main] DEBUG - ==> Preparing: select * from user where id = ?
2021-08-25 14:11:31,773 [main] DEBUG - ==> Parameters: 1(Integer)
2021-08-25 14:11:31,783 [main] DEBUG - <== Total: 1
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
在测试方法中第五步和第六步都调用了userMapper中的findById()方法,但是对比控制台的打印结果可以知道实际上Mybatis只执行了一次数据库的查询操作。
也就是第二次调用findById时,没有从数据库中查询数据而是从缓存中查询,这个起作用的缓存也就是Mybatis的一级缓存。
需要注意的是Mybatis的一级缓存是框架自动开启的。
一级缓存是SqlSession级别的缓存
下面将测试方法做出如下更改。
@Test
public void test29() throws IOException {
//1. 读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3. 使用工厂生产一个SqlSession对象
SqlSession session = factory.openSession();
//4. 使用SqlSession创建Dao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//5.第一次根据id值查询
User byIdTest = userMapper.findById(1);
System.out.println(byIdTest);
SqlSession session2 = factory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//6.第二次根据id值查询
User byIdTest2 = userMapper2.findById(1);
System.out.println(byIdTest2);
//7. 释放资源
session.close();
in.close();
}
测试结果为
2021-08-25 14:29:35,832 [main] DEBUG - ==> Preparing: select * from user where id = ?
2021-08-25 14:29:35,849 [main] DEBUG - ==> Parameters: 1(Integer)
2021-08-25 14:29:35,859 [main] DEBUG - <== Total: 1
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
2021-08-25 14:29:35,873 [main] DEBUG - ==> Preparing: select * from user where id = ?
2021-08-25 14:29:35,873 [main] DEBUG - ==> Parameters: 1(Integer)
2021-08-25 14:29:35,874 [main] DEBUG - <== Total: 1
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
一级缓存第二个要理解的点是就是一级缓存是SqlSession级别的缓存。
首先从测试结果的控制台打印结果可以看出测试方法两次查询了数据库,这是因为在第二次查询前,我们生成了新的SqlSession对象session2。
并用session2生成userMapper2,userMapper2调用findById()方法。
很显然,这两次调用使用了不同的SqlSession对象,出现了缓存失效的情况,也就是说Mybatis的一级缓存是SqlSession级别的,只有在同一个SqlSession对象中相同条件下的查询结果才会被缓存。
增删改会清理一级缓存
测试方法修改如下
@Test
public void test29() throws IOException {
//1. 读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3. 使用工厂生产一个SqlSession对象
SqlSession session = factory.openSession();
//4. 使用SqlSession创建Dao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//5.第一次根据id值查询
User byIdTest = userMapper.findById(1);
System.out.println(byIdTest);
//增加用户操作
userMapper.insert(new User("zhangfei","hello123"));
//6.第二次根据id值查询
User byIdTest2 = userMapper.findById(1);
System.out.println(byIdTest2);
//7. 释放资源
session.close();
in.close();
}
控制台打印结果为
2021-08-25 15:01:09,228 [main] DEBUG - ==> Preparing: select * from user where id = ?
2021-08-25 15:01:09,243 [main] DEBUG - ==> Parameters: 1(Integer)
2021-08-25 15:01:09,253 [main] DEBUG - <== Total: 1
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
2021-08-25 15:01:09,253 [main] DEBUG - ==> Preparing: insert into user(id, username,password) values(?, ?, ?)
2021-08-25 15:01:09,253 [main] DEBUG - ==> Parameters: 0(Integer), zhangfei(String), hello123(String)
2021-08-25 15:01:09,255 [main] DEBUG - <== Updates: 1
2021-08-25 15:01:09,255 [main] DEBUG - ==> Preparing: select * from user where id = ?
2021-08-25 15:01:09,255 [main] DEBUG - ==> Parameters: 1(Integer)
2021-08-25 15:01:09,255 [main] DEBUG - <== Total: 1
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
通过控制台的打印结果可以发现Mybatis总共执行了两次查询,在两次查询中执行了一次插入操作,这就说明即使在同一个SqlSession执行的查询操作中有增删改操作,下一次查询都会从数据库中得到数据。
这种设计也是合理的,因为增删改会对数据库的数据造成更新,因此再次查询时需要从数据库中获得最新的数据。
三、二级缓存
Mybatis的二级缓存非常强大,它不同于一级缓存只存在于SqlSession对象的生命周期,而是可以理解为存在于SqlSessionFactorySql对象中,因此作用域比一级缓存大。
第一步在全局配置文件SqlMapConfig.xml文件中配置二级缓存开启
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
第二步在对应的Mapper.xml文件中开启缓存,此处我们在UserMapper.xml文件中增加如下标签
<cache></cache>
这个标签可以如上述这样配置,那么Mybatis框架会默认赋值。当然也可以显示设置一些值如下所示
<cache
blocking="" type="" eviction="FIFO"
flushInterval="60000" readOnly="false" size="512">
</cache>
元素eviction表示清理缓存的方式,FIFO表示按对象进入缓存的顺序来移除它们。
元素flushInterval表示刷新间隔的时间,60000表示60秒。
readOnly的值有两个分别是true和false。
当为true 时,Mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。Mybatis会将缓存中的数据直接交给用户,不安全但是速度快。
当值为false时,Mybatis会利用序列化和反序列化技术克隆一份新的数据给用户。
size值表示缓存中可存放的资源大小,默认为1024。
type值表示可以整合第三方自定义的缓存方式,通常实现了Cache类的全限定类名。
第三步对应实体类实现序列化接口
第四步测试方法
@Test
public void test30() throws IOException {
//1. 读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3. 使用工厂生产一个SqlSession对象
SqlSession session = factory.openSession();
//4. 使用SqlSession创建Dao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//5.第一次根据id值查询
User byIdTest = userMapper.findById(1);
System.out.println(byIdTest);
session.close();
SqlSession session2 = factory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//6.第二次根据id值查询
User byIdTest2 = userMapper2.findById(1);
System.out.println(byIdTest2);
//7. 释放资源
session2.close();
in.close();
}
第五步测试结果
2021-08-26 16:45:52,985 [main] DEBUG - Cache Hit Ratio [com.zssj.dao.UserMapper]: 0.0
2021-08-26 16:45:53,160 [main] DEBUG - ==> Preparing: select * from user where id = ?
2021-08-26 16:45:53,177 [main] DEBUG - ==> Parameters: 1(Integer)
2021-08-26 16:45:53,193 [main] DEBUG - <== Total: 1
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
2021-08-26 16:45:53,199 [main] DEBUG - Cache Hit Ratio [com.zssj.dao.UserMapper]: 0.5
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
从测试结果可以看出Mybatis值进行了一次查库操作,第二次是从缓存中取出的相同数据。
而我们在测试方法中第二次查询时启用了新的SqlSession对象,说明Mybatis不受SqlSession对象的生存周期影响。
一旦开启了二级缓存,相同条件的查询结果就会从缓存中拿,这也是和一级缓存不同的地方。
三、整合redis缓存
第一步首先在pom.xml文件中引入依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
第二步启动本机redis服务
第三步在/src/main/resources目录下新增redis.properties文件
host=localhost
port=6379
connectionTimeout=5000
soTimeout=5000
password=
database=0
clientName=
如果redis配置了密码。需要在password赋密码值。
第四步修改UserMapper.xml文件的cache配置
<cache
type="org.mybatis.caches.redis.RedisCache">
</cache>
type表示引入redis这种缓存类型。
第五步测试方法为
@Test
public void test30() throws IOException {
//1. 读取核心配置文件SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3. 使用工厂生产一个SqlSession对象
SqlSession session = factory.openSession();
//4. 使用SqlSession创建Dao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//5.第一次根据id值查询
User byIdTest = userMapper.findById(1);
System.out.println(byIdTest);
session.close();
SqlSession session2 = factory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//6.第二次根据id值查询
User byIdTest2 = userMapper2.findById(1);
System.out.println(byIdTest2);
//7. 释放资源
session2.close();
in.close();
}
测试结果为
2021-08-27 11:03:50,757 [main] DEBUG - Cache Hit Ratio [com.zssj.dao.UserMapper]: 1.0
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
2021-08-27 11:03:50,758 [main] DEBUG - Cache Hit Ratio [com.zssj.dao.UserMapper]: 1.0
User{id=1, username='liuchan', password='hello789', birthday=null, mailCode='null', deptId=0, department=null}
从测试结果中查询出的数据可以发现测试结果没有查库的记录,都是从redis缓存中得到数据信息。