MyBatis 包含了一个很是强大的查询缓存特性,它能够很是方便地配置和定制。MyBatis 3 中的缓存实现的不少改进都已经实现了,使得它更增强大并且易于配置。mybatis默认状况下只会开启一级缓存,也就是局部的 session 会话缓存。java
首先咱们要知道什么是查询缓存?查询缓存又有什么做用?mysql
以下图,每个 session 会话都会有各自的缓存,这缓存是局部的,也就是所谓的一级缓存:git
一级缓存是SqlSession级别的缓存。咱们都知道在操做数据库时须要构造 sqlSession对象,而在sqlSession对象中有一个数据结构(HashMap)用于存储缓存数据。算法
以下图:sql
从图上,咱们能够看出,一级缓存区域是根据SqlSession为单位划分的。每次查询都会先从缓存区域找,若是找不到就会从数据库查询数据,而后将查询到的数据写入一级缓存中。Mybatis内部存储缓存使用的是一个HashMap对象,key为 hashCode + sqlId + sql 语句。而value值就是从查询出来映射生成的java对象。而为了保证缓存里面的数据确定是准确数据避免脏读,每次咱们进行数据修改后(增、删、改操做)就会执行commit操做,清空缓存区域。数据库
咱们能够来写一个测试用例,测试一下同一个数据的第二次查询是否没有访问数据库,代码以下:apache
package org.zero01.test; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.*; import org.json.JSONObject; import org.junit.Test; import org.zero01.dao.StudentMapper; import org.zero01.pojo.Student; import java.io.IOException; import java.io.InputStream; public class TestMybatisCache { @Test public void testMybatisCache() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); // 进行第一次查询 Student student1 = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student1)); // 进行第二次查询 Student student2 = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第二次查询:" + new JSONObject(student2)); sqlSession.close(); } }
控制台打印结果:json
如上,能够看到只有第一次查询访问了数据库。第二次查询则没有访问数据库,是从内存中直接读取出来的数据。缓存
咱们上面也提到了,若是进行了增、删、改的sql操做并进行了事务的commit提交操做后,SqlSession中的一级缓存就会被清空,不会致使脏数据的出现。一样的,咱们可使用测试用例来演示这一点,修改测试代码以下:安全
est public void testMybatisCache() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); // 进行第一次查询 Student student1 = studentMapper.selectByPrimaryKey(2); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student1)); Student stuUpdate = new Student(); stuUpdate.setSid(2); stuUpdate.setSname("渣渣辉"); stuUpdate.setAge(21); int rowCount = studentMapper.updateByPrimaryKeySelective(stuUpdate); if (rowCount > 0) { sqlSession.commit(); System.out.println("更新student数据成功"); } // 进行第二次查询 Student student2 = studentMapper.selectByPrimaryKey(2); System.out.println("sqlSession1 第二次查询:" + new JSONObject(student2)); sqlSession.close(); }
控制台打印结果:
如上,能够看到当数据更新成功并commit后,会清空SqlSession中的一级缓存,第二次查询就会访问数据库查询最新的数据了。
不一样的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。因此在这种状况下,是不能实现跨表的session共享的。有一点值得注意的是,因为不一样的sqlSession之间的缓存数据区域不共享,若是使用多个SqlSession对数据库进行操做时,就会出现脏数据。咱们能够修改以前的测试用例来演示这个现象,修改测试代码以下:
@Test public void testMybatisCache() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); // 使用sqlSession1进行第一次查询 StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); Student student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student)); // 使用sqlSession2进行数据的更新 StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); Student student2 = new Student(); student2.setSid(1); student2.setSname("渣渣辉"); student2.setAge(21); int rowCount = studentMapper2.updateByPrimaryKeySelective(student2); if (rowCount > 0) { sqlSession2.commit(); System.out.println("sqlSession2 更新student数据成功"); } // 使用sqlSession1进行第二次查询 student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第二次查询:" + new JSONObject(student)); sqlSession1.close(); sqlSession2.close(); }
控制台打印结果:
sqlSession1 第一次查询:{"address":"湖南","sname":"小明","sex":"男","age":16,"sid":1,"cid":1} sqlSession2 更新student数据成功 sqlSession1 第二次查询:{"address":"湖南","sname":"小明","sex":"男","age":16,"sid":1,"cid":1}
因而可知,Mybatis的一级缓存只存在于SqlSession中,能够提升咱们的查询性能,下降数据库压力,可是不能实现多sql的session共享,因此使用多个SqlSession操做数据库会产生脏数据。
二级缓存是mapper级别的缓存,多个SqlSession去操做同一个Mapper的sql语句,多个SqlSession能够共用二级缓存,二级缓存是能够横跨跨SqlSession的。
示意图:
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,若是使用mapper代理方法每一个mapper的namespace都不一样,此时能够理解为二级缓存区域是根据mapper划分,也就是根据命名空间来划分的,若是两个mapper文件的命名空间同样,那样,不一样的SqlSession之间就能够共享一个mapper缓存。
示意图:
在默认状况下是没有开启二级缓存的,除了局部的 session 缓存。而在一级缓存中咱们也介绍了,不一样的SqlSession之间的一级缓存是不共享的,因此若是咱们用两个SqlSession去查询同一个数据,都会往数据库发送sql。这一点,咱们也能够经过测试用例进行测试,测试代码以下:
@Test public void testMybatisCache2() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); // 使用sqlSession1进行第一次查询 StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); Student student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student)); sqlSession1.close(); // 使用sqlSession2进行第一次查询 StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); Student student2 = studentMapper2.selectByPrimaryKey(1); System.out.println("sqlSession2 第一次查询:" + new JSONObject(student2)); sqlSession2.close(); }
控制台输出结果:
若是想要开启二级缓存,你须要在你的mybatis主配置文件里加入:
<settings> <!-- 对在此配置文件下的全部cache进行全局性的开/关设置。默认值:true --> <setting name="cacheEnabled" value="true"/> </settings>
而后在须要被缓存的 SQL 映射文件中添加一行cache配置便可:
... <mapper namespace="org.zero01.dao.StudentMapper"> ... <cache/> ... </mapper>
字面上看就是这样。这个简单语句的效果以下:
注:缓存只适用于缓存标记所在的映射文件中声明的语句。若是你使用的是java的API和XML映射文件一块儿,默认状况下不会缓存接口中声明的语句。你须要把缓存区使用@CacheNamespaceRef注解进行声明。
假如说,已开启二级缓存的Mapper中有个statement要求禁用怎么办,那也不难,只须要在statement中设置useCache="false"
就能够禁用当前select语句的二级缓存,也就是每次都会生成sql去查询,ps:默认状况下默认是true,也就是默认使用二级缓存。以下示例:
<select id="findAll" resultMap="BaseResultMap" useCache="false"> select <include refid="Base_Column_List"/> from student </select>
除此以外,还有个flushCache属性,该属性用于刷新缓存,将其设置为 true时,任什么时候候只要语句被调用,都会致使一级缓存和二级缓存都会被清空,默认值:false。在mapper的同一个namespace中,若是有其余insert、update、delete操做后都须要执行刷新缓存操做,来避免脏读。这时咱们只须要设置statement配置中的flushCache="true"
属性,就会默认刷新缓存,相反若是是false就不会了。固然,无论开不开缓存刷新功能,你要是手动更改数据库表,那都确定不能避免脏读的发生。以下示例:
<select id="findAll" resultMap="BaseResultMap" flushCache="true"> ... </select>
那既然可以刷新缓存,能定时刷新吗?也就是设置时间间隔来刷新缓存,答案是确定的。咱们在mapper映射文件中添加<cache/>
来表示开启缓存,因此咱们就能够经过<cache/>
元素的属性来进行配置。好比:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置建立了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,并且返回的对象被认为是只读的,所以在不一样线程中的调用者之间修改它们会致使冲突。
可用的收回策略有:
开启了二级缓存以后,咱们再来进行测试,可是在运行测试用例以前,咱们须要给pojo类加上实现序列化接口的代码,否则在关闭SqlSession的时候就会报错,代码以下:
package org.zero01.pojo; import java.io.Serializable; public class Student implements Serializable { ... }
测试代码不变,运行后,控制台输出结果以下:
能够看到,开启二级缓存后,SqlSession之间的数据就能够经过二级缓存共享了,和一级缓存同样,当执行了insert、update、delete等操做并commit提交后就会清空二级缓存区域。当一级缓存和二级缓存同时存在时,会先访问二级缓存,再去访问各自的一级缓存,若是都没有须要的数据,才会往数据库发送sql进行查询。这一点,咱们也能够经过测试用例来进行测试,测试代码以下:
@Test public void testMybatisCache() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); // 使用sqlSession1进行第一次查询 StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); Student student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student)); // 使用sqlSession2进行数据的更新 StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); Student student2 = new Student(); student2.setSid(1); student2.setSname("小明"); student2.setAge(16); int rowCount = studentMapper2.updateByPrimaryKeySelective(student2); if (rowCount > 0) { sqlSession2.commit(); System.out.println("sqlSession2 更新student数据成功"); } // 使用sqlSession1进行第二次查询 student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第二次查询:" + new JSONObject(student)); // 使用sqlSession2进行第一次查询 student2 = studentMapper2.selectByPrimaryKey(1); System.out.println("sqlSession2 第一次查询:" + new JSONObject(student2)); // 关闭会话 sqlSession1.close(); sqlSession2.close(); }
运行测试代码后,控制台输出结果以下:
经过此测试用例能够看出两点:
flushCache="true"
,这样每次都会清除缓存,并向数据发送sql来进行查询。或者全局关闭本地、二级缓存:
<settings> <setting name="cacheEnabled" value="false"/> <setting name="localCacheScope" value="STATEMENT"/> </settings>
可是在使用多个sqlSession操做数据库的时候,还有一个须要注意的问题,那就是事务隔离级别,mysql的默认事务隔离级别是REPEATABLE-READ(可重复读)。这样当多个sqlsession操做同一个数据的时候,可能会致使两个不一样的事务查询出来的数据不一致,例如,sqlsession1 在同一个事务中读取了两次数据,而 sqlsession2 在 sqlsession1 第一次查询以后就更新了数据,那么因为可重复读的缘由,sqlsession1 第二次查询到的依旧是以前的数据。
咱们可使用测试用例来测试一下,首先得关闭缓存或者在相应的statement中设置flushCache属性值为true(此时没有缓存),测试用例代码以下:
@Test public void testMybatisCache() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); // 使用sqlSession1进行第一次查询 StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); Student student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student)); // 使用sqlSession2进行数据的更新 StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); Student student2 = new Student(); student2.setSid(1); student2.setSname("小明"); student2.setAge(16); int rowCount = studentMapper2.updateByPrimaryKeySelective(student2); if (rowCount > 0) { sqlSession2.commit(); System.out.println("sqlSession2 更新student数据成功"); } // 使用sqlSession1进行第二次查询 student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第二次查询:" + new JSONObject(student)); // 使用sqlSession2进行第一次查询 student2 = studentMapper2.selectByPrimaryKey(1); System.out.println("sqlSession2 第一次查询:" + new JSONObject(student2)); // 关闭会话 sqlSession1.close(); sqlSession2.close(); }
控制台输出结果:
这就是mysql默认事务隔离级别REPEATABLE-READ(可重复读)致使的现象,这种隔离级别可以保证同一个事务的生命周期内,读取的数据是一致的,可是两个不一样的事务之间读取出来的数据就可能不一致。
不过,若是你但愿在不一样的事务的生命周期内读取的数据一致的话,就须要把事务隔离级别改为READ-COMMITTED(读已提交),该级别会致使不可重复读,也就是说在同一个事务的生命周期内读取到的数据多是不一致的,而在两个不一样的事务之间读取的数据则是一致的。一样的咱们可使用测试用例进行测试,修改测试代码以下:
@Test public void testMybatisCache() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 设置事务隔离级别为读已提交 SqlSession sqlSession1 = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED); SqlSession sqlSession2 = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED); // 使用sqlSession1进行第一次查询 StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); Student student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student)); // 使用sqlSession2进行数据的更新 StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); Student student2 = new Student(); student2.setSid(1); student2.setSname("阿基米德"); student2.setAge(22); int rowCount = studentMapper2.updateByPrimaryKeySelective(student2); if (rowCount > 0) { sqlSession2.commit(); System.out.println("sqlSession2 更新student数据成功"); } // 使用sqlSession1进行第二次查询 student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第二次查询:" + new JSONObject(student)); // 使用sqlSession2进行第一次查询 student2 = studentMapper2.selectByPrimaryKey(1); System.out.println("sqlSession2 第一次查询:" + new JSONObject(student2)); // 关闭会话 sqlSession1.close(); sqlSession2.close(); }
控制台输出结果:
能够看到,设置成读已提交后,两个事务在数据更新后查询出来的数据是一致的了。至因而使用可重复读仍是读已提交,就取决于实际的业务需求了,若是但愿同一个事务的生命周期内,读取的数据是一致的,就使用可重复读级别。若是但愿两个不一样的事务之间查询出来的数据是一致的,那么就使用读已提交级别。
mybatis自身的缓存作的并不完美,不过除了使用mybatis自带的二级缓存, 你也可使用你本身实现的缓存或者其余第三方的缓存方案建立适配器来彻底覆盖缓存行为。因此它提供了使用自定义缓存的机会,咱们能够选择使用咱们喜欢的自定义缓存,下面将介绍一下,使用ehcache做为mybatis的自定义缓存的具体步骤。
首先,要想使用mybatis自定义缓存,就必须让自定义缓存类实现mybatis提供的Cache 接口(org.apache.ibatis.cache.Cache):
public interface Cache { // 获取缓存编号 String getId(); // 获取缓存对象的大小 int getSize(); // 保存key值缓存对象 void putObject(Object key, Object value); // 经过kEY获取值 Object getObject(Object key); // 缓存中是否有某个key boolean hasKey(Object key); // 获取缓存的读写锁 ReadWriteLock getReadWriteLock(); // 经过key删除缓存对象 Object removeObject(Object key); // 清空缓存 void clear(); }
咱们要使用ehcache作自定义缓存,就应该完成这个自定义缓存类,但mybatis的git上提供了相对于的适配包,咱们只须要下载便可,下面是适配包的maven依赖:
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
接着在相应的 mapper xml文件中配置相应的缓存实现类:
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
实现Cache接口的是EhcacheCache的父类AbstractEhcacheCache,咱们能够看一下它的源码:
package org.mybatis.caches.ehcache; import java.util.concurrent.locks.ReadWriteLock; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Ehcache; import net.sf.ehcache.Element; import org.apache.ibatis.cache.Cache; public abstract class AbstractEhcacheCache implements Cache { protected static CacheManager CACHE_MANAGER = CacheManager.create(); protected final String id; protected Ehcache cache; public AbstractEhcacheCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } else { this.id = id; } } public void clear() { this.cache.removeAll(); } public String getId() { return this.id; } public Object getObject(Object key) { Element cachedElement = this.cache.get(key); return cachedElement == null ? null : cachedElement.getObjectValue(); } public int getSize() { return this.cache.getSize(); } public void putObject(Object key, Object value) { this.cache.put(new Element(key, value)); } public Object removeObject(Object key) { Object obj = this.getObject(key); this.cache.remove(key); return obj; } public void unlock(Object key) { } public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj == null) { return false; } else if (!(obj instanceof Cache)) { return false; } else { Cache otherCache = (Cache)obj; return this.id.equals(otherCache.getId()); } } public int hashCode() { return this.id.hashCode(); } public ReadWriteLock getReadWriteLock() { return null; } public String toString() { return "EHCache {" + this.id + "}"; } public void setTimeToIdleSeconds(long timeToIdleSeconds) { this.cache.getCacheConfiguration().setTimeToIdleSeconds(timeToIdleSeconds); } public void setTimeToLiveSeconds(long timeToLiveSeconds) { this.cache.getCacheConfiguration().setTimeToLiveSeconds(timeToLiveSeconds); } public void setMaxEntriesLocalHeap(long maxEntriesLocalHeap) { this.cache.getCacheConfiguration().setMaxEntriesLocalHeap(maxEntriesLocalHeap); } public void setMaxEntriesLocalDisk(long maxEntriesLocalDisk) { this.cache.getCacheConfiguration().setMaxEntriesLocalDisk(maxEntriesLocalDisk); } public void setMemoryStoreEvictionPolicy(String memoryStoreEvictionPolicy) { this.cache.getCacheConfiguration().setMemoryStoreEvictionPolicy(memoryStoreEvictionPolicy); } }
接着咱们还须要在resources目录下,建立ehcache的配置文件:ehcache.xml,文件内容以下:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <!-- Mandatory Default Cache configuration. These settings will be applied to caches created programmtically using CacheManager.add(String cacheName) --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大个数。 eternal:对象是否永久有效,一但设置了,timeout将不起做用。 timeToIdleSeconds:设置对象在失效前的容许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前容许存活时间(单位:秒)。最大时间介于建立时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每一个Cache都应该有本身的一个缓冲区。 maxElementsOnDisk:硬盘最大缓存个数。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你能够设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> </ehcache>
以上就完成了自定义缓存的配置,接下来咱们测试一下缓存是否生效,测试代码以下:
@Test public void testMybatisCache2() throws IOException { String confPath = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(confPath); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); // 使用sqlSession1进行第一次查询 StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); Student student = studentMapper.selectByPrimaryKey(1); System.out.println("sqlSession1 第一次查询:" + new JSONObject(student)); sqlSession1.close(); // 使用sqlSession2进行第一次查询 StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); Student student2 = studentMapper2.selectByPrimaryKey(1); System.out.println("sqlSession2 第一次查询:" + new JSONObject(student2)); sqlSession2.close(); }
控制台输出结果:
能够看到,sqlsession2 查询数据的时候缓存命中率为0.5,而且也没有向数据库发送sql语句,那么就表明咱们配置的自定义缓存生效并能够成功缓存数据了。