MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载、侵入式加载与深度延迟加载
延迟加载策略需要在Mybatis的全局配置文件中,通过标签进行设置。
通过对全局参数:lazyLoadingEnabled进行设置,默认就是false。
直接加载
<settings>
<setting name="lazyLoadingEnabled" value="false"/>
settings>
侵入式延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
settings>
深度延迟加载
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
N+1问题
因此,只有关联数据少的情况才适合使用延迟加载
Mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
Mybatis的查询缓存总共有两级,我们称之为一级缓存和二级缓存,如图:
Mybatis默认开启了一级缓存
原理图
测试1
@Test
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
测试2
@Test
public void testOneLevelCache2() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
User user = new User();
user.setUsername("隔壁老王");
user.setAddress("隔壁");
// 执行增删改操作,清空缓存
mapper.insertUser(user);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
具体应用
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。一个service方法中包括 很多mapper方法调用:
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭
}
如果是执行两次service调用查询相同的用户信息,是不走一级缓存的,因为mapper方法结束,sqlSession就关闭,一级缓存就清空。
原理
二级缓存是mapper(namespace)级别的。下图是多个sqlSession请求UserMapper的二级缓存图解。
说明:
开启二级缓存
Mybatis默认是没有开启二级缓存,开启步骤如下:
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
<cache>cache>
实现序列化
由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,比如说存储到文件系统中,所以需要给缓存的对象执行序列化。如果该类存在父类,那么父类也要实现序列化。
@Test
public void testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭SqlSession1
sqlSession1.close();
// 第二次查询ID为1的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭SqlSession2
sqlSession2.close();
}
Cache Hit Radio : 缓存命中率
测试2
@Test
public void testTwoLevelCache2() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭SqlSession1
sqlSession1.close();
//修改查询出来的user1对象,作为插入语句的参数
user1.setUsername("隔壁老王");
user1.setAddress("隔壁");
mapper3.insertUser(user1);
// 提交事务
sqlSession3.commit();
// 关闭SqlSession3
sqlSession3.close();
// 第二次查询ID为1的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭SqlSession2
sqlSession2.close();
}
禁用二级缓存
默认二级缓存的粒度是Mapper级别的,但是如果在同一个Mapper文件中某个查询不想使用二级缓存的话,就需要对缓存的控制粒度更细。
在select标签中设置useCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
<select id="findUserById" parameterType="int"
resultType="com.kkb.mybatis.po.User" useCache="true">
SELECT * FROM user WHERE id = #{id}
select>
刷新二级缓存
通过flushCache属性,可以控制select、insert、update、delete标签是否刷新二级缓存
默认设置
默认配置解读
flushCache设置如下:
<select id="findUserById" parameterType="int"
resultType="com.kkb.mybatis.po.User" useCache="true" flushCache="true">
SELECT * FROM user WHERE id = #{id}
select>
应用场景
对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。
在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。
局限性
Mybatis二级缓存对细粒度的数据级别的缓存实现不好。
场景:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。
解决方法:
此类问题,需要在业务层根据需要对数据有针对性的缓存。比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。