Mybatis缓存
1.Mybatis缓存介绍
众所周知频繁的对数据库进行查询操作,存在严重的性能问题,而Mybatis作为一个持久层的框架,为了解决这种问题,像其他框架一样,提供了缓存的技术,这里稍微提一下,MyBatis的持久层操作是通过代理对象实现的。
Mybatis提供了两种缓存:
- 一级缓存
一级缓存为一级缓存是SqlSession级别的缓存,只要当前SqlSession没有commit或close,它就存在,当然当前SqlSession对数据库执行增删改操作时,一级缓存也会相应清空。一级缓存与生俱来,默认即开启并使用。
这里使用代码证明:
public void testFindAll(){
User user1 = new User();
user1.setId(41);
user1.setUsername("张三");
User user2 = new User();
user1.setId(42);
user1.setUsername("张三");
//sqlSession对象1
SqlSession sqlSession = factory.openSession();
//sqlSession对象2
SqlSession sqlSession2 = factory.openSession();
//获取代理对象1
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//获取代理对象2
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
//代理对象1查询
List<User> users1 = mapper.findAll();
List<User> users2 = mapper.findAll();
//提交
sqlSession.commit();
//提交后再次查询
List<User> users3=mapper.findAll();
//sqlSession对象2更新
mapper2.update(user1);
sqlSession2.commit();
List<User> users4=mapper.findAll();
//sqlsession对象1更新
mapper.update(user2);
List<User> users5=mapper.findAll();
System.out.println("未commit:"+(users1==users2));
System.out.println("commit:"+(users1==users3));
System.out.println("sqlsession2执行更新:"+(users3==users4));
System.out.println("sqlsession1执行更新:"+(users3==users5));
}
输出结果:
未commit:true
commit:false
sqlsession2执行更新:true
sqlsession1执行更新:false
以上代码结果证明:
1.一级缓存在执行commit()会被清除,其实close(),clearCache()方法,同样会清空一级缓存。
2.一级缓存在sqlSession执行修改操作时,会清空一级缓存-----注意:这里的mapper代理对象执行命令底层实际还是调用了sqlSession。
3.另一个sqlSession对象对数据库执行增删改方法时,并不会清除此sqlSession对象的一级缓存。所以这个安全问题需要注意,虽然正常并不会有人这样写代码。
4.上面的比较使用的是“==”,所以比较的是地址,即一级缓存缓存的是对象,所以才可能实现地址相同。
- 二级缓存
二级缓存为Mapper级别的缓存,即只要是属于同一个Mapper的查询操作就可以共享缓存。
关系如下图所示:
二级缓存有几个特点:
1.缓存的是数据,而不是对象(这一点与一级缓存不同),需要注意。
2.二级缓存在sqlsession执行commit()方法后才会被写入。
3.不同的sqlsession执行增删改方法,只要操作的是同一个mapper就会清空二级缓存区域。
4.查询数据时,优先从二级缓存区获取数据,如果二级缓存区没有数据,才会找一级缓存区。
下面使用代码证明:
注意:我这里开启了二级缓存。
public void testFindAll(){
User user1 = new User();
user1.setId(41);
user1.setUsername("张wu");
//sqlSession对象1
SqlSession sqlSession = factory.openSession();
//sqlSession对象2
SqlSession sqlSession2 = factory.openSession();
//获取代理对象1
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//获取代理对象2
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
//代理对象1查询
List<User> users1 = mapper.findAll();
List<User> users2 = mapper.findAll();
//提交
sqlSession.commit();
//提交后再次查询(两次)
List<User> users3=mapper.findAll();
List<User> users4=mapper.findAll();
//sqlSession对象2更新
mapper2.update(user1);
sqlSession2.commit();
List<User> users5=mapper.findAll();
System.out.println("未执行commit:"+(users1==users2));
System.out.println("执行commit:"+(users1==users3));
System.out.println("二级缓存与一级缓存共存时:"+(users3==users4));
System.out.println("sqlsession2执行更新操作:"+(users4==users5));
}
运行结果如下:
未执行commit:true
执行commit:false
二级缓存与一级缓存共存时:false
sqlsession2执行更新操作:false
与上述的log一并分析:
1.输出结果一为位执行commit()时,使用一级缓存查询的结果,对象地址相同。
2.输出结果为,执行commit()前后,使用使用二级缓存获得的数据(对应log中的0.33333),封装为新对象。
3.输出结果为:二级缓存和一级缓存(查询users3时存在的一级缓存)并存时,从二级缓存拿数据(对应log中的0.5)
4.输出结果为:sqlsession2执行更新操作后,重新进行查询封装。参照log红框中标注的sql语句(重新从数据库查询)。
2.如何开启二级缓存
1.配置文件方式:
主配置文件中添加如下标签:
<settings>
<setting name="cachedEnabled" value="true"> <!--(默认为true,可以不设置)-->
</settings>
Mapper映射文件添加如下标签:
<cache /> <!--不能省略-->
<select id="" resultType="" useCache="true"> <!-- sql映射select语句中的useCache属性 (默认为true,可以不设置)-->
</select>
2.注解方式:
@CacheNamespace(blocking = true)
将此注解加在Mapper类上,即表明此Mapper类启用二级缓存