目录
一、一级缓存(本地缓存)
1、一级缓存
一级缓存是Session会话级别的缓存,位于表示一次数据库会话的SqlSession对象之中,又被称之为本地缓存。一级缓存是MyBatis内部实现的一个特性,用户不能配置,默认情况下自动支持的缓存,用户没有定制它的权利(不过这也不是绝对的,可以通过开发插件对它进行修改)
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
2、工作机制
由于MyBatis使用SqlSession对象表示一次数据库的会话,那么,对于会话级别的一级缓存也应该是在SqlSession中控制的。
实际上, MyBatis只是一个MyBatis对外的接口,SqlSession将它的工作交给了Executor执行器这个角色来完成,负责完成对数据库的各种操作。当创建了一个SqlSession对象时,MyBatis会为这个SqlSession对象创建一个新的Executor执行器,而缓存信息就被维护在这个Executor执行器中,MyBatis将缓存和对缓存相关的操作封装成了Cache接口中。
Executor接口的实现类BaseExecutor中拥有一个Cache接口的实现类PerpetualCache,则对于BaseExecutor对象而言,它将使用PerpetualCache对象维护缓存。
由于Session级别的一级缓存实际上就是使用PerpetualCache维护的,那么PerpetualCache是怎样实现的呢?PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v> 来实现的,没有其他的任何限制。
3、一级缓存示例:
测试代码:
@Test
public void testFirstLevelCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee emp = mapper.selectEmployee(102);
System.out.println(emp);
Employee emp1 = mapper.selectEmployee(102);
System.out.println(emp1);
System.out.println(emp == emp1);
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
日志输出:
2018/12/30 19:29:23 DEBUG - Opening JDBC Connection
2018/12/30 19:29:23 DEBUG - Checked out connection 936580213 from pool.
2018/12/30 19:29:23 DEBUG - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@37d31475]
2018/12/30 19:29:23 DEBUG - ==> Preparing: SELECT employee_id AS employeeId, first_name AS firstName, last_name AS lastName, email AS email, phone_number AS phoneNumber, hire_date AS hireDate, job_id AS jobId, salary AS salary, manager_id AS managerId, department_id AS departmentId FROM employee WHERE employee_id = ?
2018/12/30 19:29:23 DEBUG - ==> Parameters: 102(Integer)
2018/12/30 19:29:23 TRACE - <== Columns: employeeId, firstName, lastName, email, phoneNumber, hireDate, jobId, salary, managerId, departmentId
2018/12/30 19:29:23 TRACE - <== Row: 102, Lex, De Haan, LDEHAAN, 515.123.4569, 2001-01-13, AD_VP, 17000.00, 100, 90
2018/12/30 19:29:23 DEBUG - <== Total: 1
Employee [employeeId=102, firstName=Lex, lastName=De Haan, email=LDEHAAN, phoneNumber=515.123.4569, hireDate=Sat Jan 13 00:00:00 CST 2001, jobId=AD_VP, salary=17000.00, managerId=100, departmentId=90]
Employee [employeeId=102, firstName=Lex, lastName=De Haan, email=LDEHAAN, phoneNumber=515.123.4569, hireDate=Sat Jan 13 00:00:00 CST 2001, jobId=AD_VP, salary=17000.00, managerId=100, departmentId=90]
true
可以看出两次的查询,只发了一条sql,第二次的查询数据则是从缓存数据中获取。两个对象也是相同的。
4、一级缓存失效情况及其生命周期
(1)失效情况
1、SqlSession不同。
2、SqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
3、SqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
4、SqlSession相同,手动清除了一级缓存(缓存清空)
(2)生命周期
a. MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
b. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
c. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
d.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
参考及拓展:https://blog.csdn.net/luanlouis/article/details/41280959#t5
二、二级缓存(全局缓存)
二级缓存是Application应用级别的缓存,它的是生命周期很长,跟Application的声明周期一样,也就是说它的作用范围是整个Application应用。
1、工作机制
当开一个会话时,一个SqlSession对象会使用一个Executor对象来完成会话操作,MyBatis的二级缓存机制的关键就是对这个Executor对象做文章。如果用户配置了"cacheEnabled=true",那么MyBatis在为SqlSession对象创建Executor对象时,会对Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。CachingExecutor对于查询请求,会先判断该查询请求在Application级别的二级缓存中是否有缓存结果,如果有查询结果,则直接返回缓存结果;如果缓存中没有,再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后在返回给用户。
CachingExecutor是Executor的装饰者,以增强Executor的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式。
2、二级缓存的划分
MyBatis并不是简单地对整个Application就只有一个Cache缓存对象,它将缓存划分的更细,即是Mapper级别的,即每一个Mapper都可以拥有一个Cache对象
(1)为每一个Mapper分配一个Cache缓存对象(使用<cache>节点配置);
(2)多个Mapper共用一个Cache缓存对象(使用<cache-ref>节点配置);
注意:
查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。
而新的会话进来,会先到二级缓存进行查找数据,再去一级缓存中查找数据,最后是到数据库中查询数据。
3、使用二级缓存
(1)设置步骤:
1、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
2、mapper.xml中配置使用二级缓存:在Mapper文件中配置标签:<cache></cache>
cache标签的属性:
属性 | 说明 | 备注 |
---|---|---|
eviction | 缓存的回收策略 | • LRU – 最近最少使用的:移除最长时间不被使用的对象。 • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 • 默认的是 LRU。 |
flushInterval | 缓存刷新间隔 | 缓存多长时间清空一次,默认不清空,设置一个毫秒值 |
readOnly | 是否只读 | • true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快 • false:非只读:mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢 |
size | 缓存存放多少元素; | |
type | 指定自定义缓存的全类名 |
3、POJO需要实现序列化接口
(2)测试
注意:第一个sqlSession1用完需要close,查询的数据才会写入二级缓存,否则还是停留在一级缓存。
@Test
public void testSecondLevelCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
Employee emp1 = mapper1.selectEmployee(102);
System.out.println(emp1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
Employee emp2 = mapper2.selectEmployee(102);
System.out.println(emp2);
} catch (Exception e) {
e.printStackTrace();
}
}
输出日志:
(3)其他配置
1、每个select标签都有useCache="true"。若值为 false:不使用缓存(一级缓存依然使用,二级缓存不使用),默认为true
2、每个增删改标签的flushCache="true"。insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。 设置statement配置中的flushCache=”true” 属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。一级缓存就清空了;二级也会被清除;、
3、sqlSession.clearCache();只是清楚当前session的一级缓存
4、全局配置文件中的属性:localCacheScope,配置SESSION,则启用一级缓存,STATEMENT:可以禁用一级缓存。一般不作配置。
参考及拓展:https://blog.csdn.net/luanlouis/article/details/41408341
三、第三方缓存框架——ehcache
MyBatis缓存做的并不专业,用的是map。mybatis中默认自带的二级缓存有个弊端,即无法实现分布式缓存,什么意思呢?就是说缓存的数据在本地的服务器上,假设现在有两个服务器A和B,用户访问的时候访问了A服务器,查询后的缓存就会放在A服务器上,假设现在有个用户访问的是B服务器,那么,他在B服务器上就无法获取刚刚那个缓存。
mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。mybatis也默认实现了一个,但是这个缓存的实现无法实现分布式缓存,所以我们要自己来实现。ehcache分布式缓存就可以,mybatis提供了一个针对cache接口的ehcache实现类,这个类在mybatis和ehcache的整合包中。
ehcache是一个分布式缓存框架,EhCache 是一个纯Java的进程内缓存框架,是一种广泛使用的开源Java分布式缓存,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
1、整合步骤
进入MyBatis的github代码库(https://github.com/mybatis),找到ehcache-cache项目。
查看该项目的文档:http://www.mybatis.org/ehcache-cache/
(1)引入maven依赖:
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
(2)在Mapper文件进行配置:
指定自定义缓存的type:org.mybatis.caches.ehcache.EhcacheCache
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
(3)需要ehcache.xml文件:
<?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="10000"
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(先进先出)
-->
2、测试
直接测试上方的二级缓存例子,查看日志输出:日志中会打印关于ehcache的初始化和缓存信息。
2018/12/30 22:41:51 DEBUG [org.apache.ibatis.logging.LogFactory] - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 495792375.
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Returned connection 495792375 to pool.
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.ConfigurationFactory] - Configuring ehcache from ehcache.xml found in the classpath: file:/E:/workspace-mars/MyBatisTest/target/classes/ehcache.xml
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.ConfigurationFactory] - Configuring ehcache from URL: file:/E:/workspace-mars/MyBatisTest/target/classes/ehcache.xml
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.ConfigurationFactory] - Configuring ehcache from InputStream
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.BeanHandler] - Ignoring ehcache attribute xmlns:xsi
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.BeanHandler] - Ignoring ehcache attribute xsi:noNamespaceSchemaLocation
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.DiskStoreConfiguration] - Disk Store Path: D:\cache\ehcache
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.CacheManager] - Creating new CacheManager with default config
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.util.PropertyUtil] - propertiesString is null.
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.ConfigurationHelper] - No CacheManagerEventListenerFactory class specified. Skipping...
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.Cache] - No BootstrapCacheLoaderFactory class specified. Skipping...
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.Cache] - CacheWriter factory not configured. Skipping...
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.ConfigurationHelper] - No CacheExceptionHandlerFactory class specified. Skipping...
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.store.MemoryStore] - Initialized net.sf.ehcache.store.MemoryStore for com.starfall.mybaits.dao.EmployeeMapper
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.DiskStorePathManager] - Using diskstore path D:\cache\ehcache
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.DiskStorePathManager] - Holding exclusive lock on D:\cache\ehcache\.ehcache-diskstore.lock
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.store.disk.DiskStorageFactory] - Failed to delete file com%002estarfall%002emybaits%002edao%002e%0045mployee%004dapper.index
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.store.disk.DiskStorageFactory] - Matching data file missing (or empty) for index file. Deleting index file D:\cache\ehcache\com%002estarfall%002emybaits%002edao%002e%0045mployee%004dapper.index
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.store.disk.DiskStorageFactory] - Failed to delete file com%002estarfall%002emybaits%002edao%002e%0045mployee%004dapper.index
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.Cache] - Initialised cache: com.starfall.mybaits.dao.EmployeeMapper
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.config.ConfigurationHelper] - CacheDecoratorFactory not configured for defaultCache. Skipping for 'com.starfall.mybaits.dao.EmployeeMapper'.
2018/12/30 22:41:52 DEBUG [com.starfall.mybaits.dao.EmployeeMapper] - Cache Hit Ratio [com.starfall.mybaits.dao.EmployeeMapper]: 0.0
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Opening JDBC Connection
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Checked out connection 495792375 from pool.
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1d8d30f7]
2018/12/30 22:41:52 DEBUG [com.starfall.mybaits.dao.EmployeeMapper.selectEmployee] - ==> Preparing: SELECT employee_id AS employeeId, first_name AS firstName, last_name AS lastName, email AS email, phone_number AS phoneNumber, hire_date AS hireDate, job_id AS jobId, salary AS salary, manager_id AS managerId, department_id AS departmentId FROM employee WHERE employee_id = ?
2018/12/30 22:41:52 DEBUG [com.starfall.mybaits.dao.EmployeeMapper.selectEmployee] - ==> Parameters: 102(Integer)
2018/12/30 22:41:52 TRACE [com.starfall.mybaits.dao.EmployeeMapper.selectEmployee] - <== Columns: employeeId, firstName, lastName, email, phoneNumber, hireDate, jobId, salary, managerId, departmentId
2018/12/30 22:41:52 TRACE [com.starfall.mybaits.dao.EmployeeMapper.selectEmployee] - <== Row: 102, Lex, De Haan, LDEHAAN, 515.123.4569, 2001-01-13, AD_VP, 17000.00, 100, 90
2018/12/30 22:41:52 DEBUG [com.starfall.mybaits.dao.EmployeeMapper.selectEmployee] - <== Total: 1
Employee [employeeId=102, firstName=Lex, lastName=De Haan, email=LDEHAAN, phoneNumber=515.123.4569, hireDate=Sat Jan 13 00:00:00 CST 2001, jobId=AD_VP, salary=17000.00, managerId=100, departmentId=90]
2018/12/30 22:41:52 DEBUG [net.sf.ehcache.store.disk.Segment] - put added 0 on heap
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1d8d30f7]
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1d8d30f7]
2018/12/30 22:41:52 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Returned connection 495792375 to pool.
2018/12/30 22:41:52 DEBUG [com.starfall.mybaits.dao.EmployeeMapper] - Cache Hit Ratio [com.starfall.mybaits.dao.EmployeeMapper]: 0.5
Employee [employeeId=102, firstName=Lex, lastName=De Haan, email=LDEHAAN, phoneNumber=515.123.4569, hireDate=Sat Jan 13 00:00:00 CST 2001, jobId=AD_VP, salary=17000.00, managerId=100, departmentId=90]