缓存
MyBatis缓存机制,是为了加快sql语句的执行速度,将所查询过的语句存储在缓存区中,如果执行相同的语句可直接调用结果,不需要重复与数据库交互
MyBatis有两级缓存
- 一级缓存:缓存区存在于sqlsession中,MyBatis始终开启这个缓存
- 二级缓存:缓存区存在于namespace中,需要手动配置开启
一级缓存
通过调用同样的sql语句查询员工信息
@Test
public void FirstCacheTest() throws IOException {
SqlSessionFactory sqlSessionFactory = MyBatisTest.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
Employee emp1 = mapper.getEmpById(1);
System.out.println(emp1);
//做一些其他事情
Employee emp2 = mapper.getEmpById(1);
System.out.println(emp2);
System.out.println(emp1==emp2);
}finally {
}
}
返回值及日志输出如下,可以看到只向数据库发送了一次请求
14:22:02.998 [main] DEBUG com.yellowstar.mybatis.dao.EmployeeDao.getEmpById - ==> Preparing: select * from employee where id = ?
14:22:03.031 [main] DEBUG com.yellowstar.mybatis.dao.EmployeeDao.getEmpById - ==> Parameters: 1(Integer)
14:22:03.068 [main] DEBUG com.yellowstar.mybatis.dao.EmployeeDao.getEmpById - <== Total: 1
Employee{id=1, lastName='Tomdam', gender='1', email='Tomdam@123.com'}
Employee{id=1, lastName='Tomdam', gender='1', email='Tomdam@123.com'}
true
什么情况下一级缓存会失效呢?
- 两个不同的sqlsession发送的请求。自然是存储在两个不同的缓存区中,所以一级缓存会失效
- sqlsession相同,但查询条件不同。
- sqlsession相同,但两次操作之间执行了增删改操作。系统会不知道增删改操作有没有影响到已经缓存的数据,所以会再次查询
- sqlsession相同,但两次操作直接调用了sqlsession.clearCache()方法,此方法效果是清空当前缓存区
二级缓存
二级缓存作用于namespace中,如果设置了二级缓存,当一级缓存的sqlsession关闭时,才会将一级缓存区的数据存储到二级缓存区。
设置二级缓存
-
全局配置文件中,设置开启二级缓存(默认是开启的,但还是建议显式的定义)
<setting name="cacheEnabled" value="true"/>
-
在需要开启二级缓存的映射文件中配置cache标签,在这个标签中mybatis都有帮我们配置默认属性
<mapper namespace="com.yellowstar.mybatis.dao.EmployeeDao"> <cache/> </mapper>
-
让bean对象实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。
public class Employee implements Serializable
使用二级缓存
还是查询同一个员工信息,但使用两个不同的sqlsession来查询
@Test
public void SecondCacheTest() throws IOException {
SqlSessionFactory sqlSessionFactory = MyBatisTest.getSqlSessionFactory();
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
try {
EmployeeDao mapper1 = sqlSession1.getMapper(EmployeeDao.class);
EmployeeDao mapper2 = sqlSession2.getMapper(EmployeeDao.class);
Employee emp1 = mapper1.getEmpById(1);
System.out.println(emp1);
sqlSession1.close();
//做一些其他事情
Employee emp2 = mapper2.getEmpById(1);
System.out.println(emp2);
System.out.println(emp1==emp2);
sqlSession2.close();
}finally {
}
}
以下是输出信息及日志,注意倒数第四行,Cache Hit Ratio从缓存区中取得
14:46:34.682 [main] DEBUG com.yellowstar.mybatis.dao.EmployeeDao.getEmpById - ==> Preparing: select * from employee where id = ?
14:46:34.709 [main] DEBUG com.yellowstar.mybatis.dao.EmployeeDao.getEmpById - ==> Parameters: 1(Integer)
14:46:34.745 [main] DEBUG com.yellowstar.mybatis.dao.EmployeeDao.getEmpById - <== Total: 1
Employee{id=1, lastName='Tomdam', gender='1', email='Tomdam@123.com'}
14:46:34.750 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5d20e46]
14:46:34.751 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5d20e46]
14:46:34.751 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 97652294 to pool.
14:46:34.752 [main] WARN org.apache.ibatis.io.SerialFilterChecker - As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
14:46:34.755 [main] DEBUG com.yellowstar.mybatis.dao.EmployeeDao - Cache Hit Ratio [com.yellowstar.mybatis.dao.EmployeeDao]: 0.5
Employee{id=1, lastName='Tomdam', gender='1', email='Tomdam@123.com'}
false
关于缓存的一些相关配置
-
cacheEnabled设置为false,二级缓存失效但不影响一级缓存
-
每个select标签中都有一个useCache属性,默认为true,将他设置为false。二级缓存失效,但不影响一级缓存
<select id="getEmpById" resultType="com.yellowstar.mybatis.bean.Employee" useCache="false"> select * from employee where id = #{id} </select>
-
执行完增删改操作后,都会清除缓存
-
sqlsession.clearCache(),只会清楚一级缓存
-
设置全局配置文件中localCacheScope为STATEMENT,禁用一级缓存
整合第三方缓存ehcache
-
在pom.xml文件中导入ehcache依赖
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency>
-
设置映射文件的cache标签中的type属性
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
-
设置ehcache配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="D:\44\ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache> <!-- 属性说明: l diskStore:指定数据在磁盘中的存储位置。 l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略 以下属性是必须的: l maxElementsInMemory - 在内存中缓存的element的最大数目 l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大 l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断 l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 以下属性是可选的: l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大 l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大 diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区. l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。 l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作 l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出) -->