Mybatis的缓存机制Cache
Mybatis提供对缓存的支持,分为一级缓存和二级缓存,在没有配置的情况下,系统默认会使用一级缓存。
一级缓存(SqlSession级别)
我们都知道每个SqlSession对象之间的缓存是互不影响的,当同一个SqlSession执行多次相同的SQL语句时(主要针对select),系统只会到底层访问数据库一次,后续执行sql时,会从一级缓存里面读取第一次访问的数据。当这个SqlSession对象执行close(),或者显示声明清空缓存时,对应的一级缓存也会清空,从而提高效率。值得注意的是,如果SqlSession执行DML操作(即insert、update、delete),并提交到数据库时,也会起到清空该SqlSession对象对应的一级缓存的作用,目的是为了保证缓存里面的数据是最新的。,避免脏读现象。
二级缓存(SqlSessionFactory级别)
有些书籍说二级缓存是Mapper级别,可能是依据二级缓存的配置是在xxxMapper.xml上配置的吧,不过本人更认同是SqlSessionFactory级别,为什么呢?因为同一个Configuration里面是创建一个SqlSessionFactory,SqlSessionFactory是属于线程安全的,SqlSessionFactory可以创建很多个SqlSession来进行事务操作,也就是说,SqlSessionFactory是由很多个SqlSession对象共享的,同样的,二级缓存的数据也是由很多个SqlSession共享的。如何才能让系统操作二级缓存呢?原理很简单,不同的SqlSession对象执行相同的namespace下的sql语句,当第一个SqlSession对象调用close()关闭一级缓存时,第一次查询得到的数据将会被保存到二级缓存,后面的SqlSession对象调用相同sql语句时,就会从二级缓存中获取数据。当然,使用二级缓存,需要对应的返回对象(即POJO)实现序列化。
配置:在需要使用的xxxMapper.xml里面配置<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true" />
eviction:代表回收策略,目前支持4种策略
1.LRU,最近最少使用的,移除最长时间不用的对象;
2.FIFO,先进先出;
3.SOFT,移除基于垃圾回收器状态和软引用规则的对象;
4.WEAK,更积极移除基于垃圾回收器状态和弱引用规则的对象。
flushInterval:代表刷新时长,单位毫秒
size:代表缓存最多可以存储多少个对象,注意,设置过大会导致内存溢出
readOnly:意味着缓存数据只能读取,不能修改
上面是较为详细配置,当然也可以简单一点,直接写上<cache />就可以了。
下面给出一个较为有意思的栗子:
xml配置代码如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.learn.mapper.EmployeeMapper"> <cache /> <select id="getEmpList" resultType="employee"> select * from tb_employee </select> </mapper>
JUnit4测试代码如下:
@Test public void testCache(){ SqlSession ss1 = null; SqlSession ss2 = null; try{ ss1 = SqlSessionFactoryUtil.initSqlSessionFactory().openSession(); ss2 = SqlSessionFactoryUtil.initSqlSessionFactory().openSession(); EmployeeMapper em1 = ss1.getMapper(EmployeeMapper.class); EmployeeMapper em2 = ss2.getMapper(EmployeeMapper.class); List<Employee> list1 = em1.getEmpList(); //ss1.close(); list1 = em2.getEmpList(); }catch(Exception e){ ss1.rollback(); ss2.rollback(); e.printStackTrace(); }finally{ if(ss1 != null){ ss1.close(); } if(ss2 != null){ ss2.close(); } } }
代码中实例化两个SqlSession对象,分别是ss1和ss2,用户检验数据读取的操作。留意上面注释了ss1.close();这一行。
下面是执行上面测试代码的日志结果:
Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.0 Opening JDBC Connection Created connection 275310919. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] ==> Preparing: select * from tb_employee ==> Parameters: <== Total: 6 Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.0 Opening JDBC Connection Created connection 1948863195. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@74294adb] ==> Preparing: select * from tb_employee ==> Parameters: <== Total: 6 Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Returned connection 275310919 to pool. Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@74294adb] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@74294adb] Returned connection 1948863195 to pool.
可以看到即使配置了<cache />二级缓存,第一个SqlSession对象ss1没有手动执行close()方法时,ss1对应的一级缓存数据仍然没有保存到二级缓存里面,即二级缓存里面没有数据,当第二个SqlSession对象ss2执行相同的sql语句时,会找一级缓存有没有数据,因为第一次执行,当然没有数据了,然后找二级缓存,刚才说过了,ss1的一级缓存数据并没有保存到二级缓存里面,所以二级缓存也没有数据,因此ss2就会再次操作数据库。可以看到log日志会有两条select语句。
此时,如果将close();的注释去掉,再执行一下测试代码,日志结果如下:
Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.0 Opening JDBC Connection Created connection 275310919. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] ==> Preparing: select * from tb_employee ==> Parameters: <== Total: 6 Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Returned connection 275310919 to pool. Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.5
显而易见,此时只访问一次数据库。