一级缓存
-
与数据库同一次的会话期间查询到的数据会放到本地缓存中.
-
以后如果需要获取相同的数据直接从缓存中拿,没必要再去查询数据库.
-
示例:观察一级缓存
@Test
public void testFitstCache(){
EmployeeDAO employeeDAO = this.sqlSession.getMapper(EmployeeDAO.class);
Employee employee = employeeDAO.getEmpById(1);
System.out.println(employee);
//再次获取相同的数据
Employee temp = employeeDAO.getEmpById(1);
System.out.println(temp);
System.out.println(employee==temp);
}
- 打印结果
- 可以发现在取出第二次相同的数据与第一次取出的数据是同一个对象
一级缓存失效的四种情况
- 一级缓存也称之为本地缓存,是sqlSession级别的缓存,一级缓存是一直开启的.
- 当使用不同的sqlSession对象时,sqlSession对象之间的缓存不能够共享.
- 如果sqlSession相同,但是查询条件不同,则查询的也是不同的数据.
- 如果sqlSession相同,但是两次查询期间执行了增删改查操作,则会更新sqlSession中的缓存,那么第二次查询时会再次进行对数据库查询.
- 如果sqlSession相同,若手动清除了一级缓存,第二次查询会再次访问数据库进行查询.
- 示例:使用两个sqlSession查询同一数据
@Test
public void testFitstCache2(){
EmployeeDAO employeeDAO = this.sqlSession.getMapper(EmployeeDAO.class);
Employee employee = employeeDAO.getEmpById(1);
System.out.println(employee);
//再次获取相同的数据
Employee temp = sqlSession2.getMapper(EmployeeDAO.class).getEmpById(1);
System.out.println(temp);
System.out.println(employee==temp);
}
- MyBatis会进行两次访问数据库
二级缓存
- 二级缓存也称之为全局缓存,基于namspace级别的缓存.
- 当一个会话查询一个条数据,这个数据就会被放在当前的一级缓存中.如果当前会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容.
- 例如:一个sqlSession会话中既有 Employee对象也有一个Department对象,但是这是不同namespace中查询出来的数据,所以就会放在各自的缓存中
二级缓存使用细节
- 在全局配置文件中开启二级缓存,二级缓存默认是关闭的,必须手动开启
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 对于每一个nameSpace都有自己的二级缓存,在需要开启二级缓存的映射文件中使用<cache>标签开启nameSpace的二级缓存
<mapper namespace="mao.shu.dao.EmployeeDAO">
<cache></cache>
</mapper>
- <cache>标签中有许多的默认属性,如果什么都不写,则都使用默认的配置
- 因为缓存中可能需要用到序列化技术,所以POJO数据传输类需要实现Serializable接口
public class Department implements Serializable {
...
}
- 示例:测试二级缓存
- 需要注意的是,只有当一个sqlSession被关闭时,这个sqlSession的一级缓存内容才会被放到二级缓存中
@Test
public void testSecondCache(){
Employee employee = this.sqlSession.getMapper(EmployeeDAO.class).getEmpById(1);
System.out.println(employee);
//关闭第一个sqlSession,此时会将sqlSession中的缓存内容保存到二级缓存中
this.sqlSession.close();
//再次获取相同的数据
Employee temp = sqlSession2.getMapper(EmployeeDAO.class).getEmpById(1);
System.out.println(temp);
}
- 从结果中可以看到,虽然使用了两个sqlSession对象查询,但是只发了一条sql语句,而第一次查询时从缓存中寻你找对应的对象,结果是—“0.0”,表示没有找到.而第二次直接从缓存中查找到了对应的对象—“0.5”
缓存有关的设置及属性
- 在MyBatis全局配置文件中使用<setting>标签可以控制二级缓存是否启动,默认值为"false",这个设置不会影响一级缓存.
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
- <select>标签中的"userCache"属性,该属性控制进行查询时是否使用一级缓存,一级缓存默认是开启的.如果将"userCache"设置为"false"则不适用一级缓存
<select id="getEmpsByName" resultType="map" useCache="false">
SELECT id, ename, age, job
FROM employee
WHERE ename = #{ename}
</select>
- “flushCache”:在<insert>,<update>,<select>,<delete>标签中都会有这个属性,这个属性是用来控制,在执行完sql语句之后是否刷新一级缓存和二级缓存
- <insert>和<update>标签的flushCache默认为true,因为进行完数据变更之后,有可能会进行数据查询,为了保证数据的准确性,所以会删除所有缓存中的数据
<delete id="removeEmp" flushCache="true">
DELETE
FROM employee
WHERE id = #{id}
</delete>
- 在sqlSession对象中有一个clearCache()方法,该方法用于清除当前sqlSession对象的一级缓存
@Test
public void testSecondCache(){
Employee employee = this.sqlSession.getMapper(EmployeeDAO.class).getEmpById(1);
System.out.println(employee);
this.sqlSession.clearCache();
}
5.当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear
@Test
public void testSecondCache2() {
Employee employee = this.sqlSession.getMapper(EmployeeDAO.class).getEmpById(1);
employee.setEname("修改测试");
this.sqlSession.getMapper(EmployeeDAO.class).updateEmp(employee);
System.out.println(employee);
//再次获取相同的数据
Employee temp = this.sqlSession.getMapper(EmployeeDAO.class).getEmpById(1);
System.out.println(temp);
}
缓存原理示意图
- 当一个新的会话进入时,会先在二级缓存中寻找是否有对应的namespace的二级缓存,如果有则直接使用二级缓存中的内容.
- 如果没有在二级缓存中找到对应的内容,则会查看会话中的一级缓存是否有对应的内容,如果有则使用一级缓存中的内容,如果没有则会访问数据库中查找.
- 当访问完数据库之后会将查询出来的内容保存到会话的一级缓存中供下次查询使用,如果开启了二级缓存,则在会话关闭时会将一级缓存中的内容保存到二级缓存中.
第三方缓存整合
- MyBatis最主要的功能是对数据库的访问操作封装,所以对于缓存只是简单的使用了Map集合实现缓存功能,如果要使用其他第三方更加优秀专业的缓存技术,MyBatis也提供了一个Cache接口,来整合第三方缓存框架,
- 例如使用第三方ehcache缓存,只需导入ehcache开发包,然后实现Cache接口,将缓存保存到ehcache中即可
- MyBatis也考虑到了这一点,所以也提供了各大缓存框架的适配包,只需要将对应的适配包加入到项目之中,就不需要自己手动编写实现Cache接口了
- 在MyBatis软件首页:https://github.com/mybatis
- 提供了对各种框架整合的实现,其中就有ehcache
- 点击Ehcache,在页面下方会提供使用文档
- 文档中提供了适配器的使用说明
- 如果需要手工导入jar包则点击
- 接着下载对应的jar包即可
- 最后将两个jar包都导入到项目中
整合Ehcache
- 导入ehcache开发包
- 导入MyBatis整合ehcache的适配包
- 在映射文件中使用ehcache的缓存
- type为适配包中的实现Cache接口的子类
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
- 要想ehcache缓存起作用,则需要在类路径下添加一个"ehcache.xml"配置文件—转自尚硅谷
- ehcache.xsd文件需要下载,或则在IDEA的[settings]中进行配置 http://ehcache.org/ehcache.xsd
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/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(先进先出)
-->
- 示例:测试ehcache缓存
@Test
public void testFitstCache() {
EmployeeDAO employeeDAO = this.sqlSession.getMapper(EmployeeDAO.class);
Employee employee = employeeDAO.getEmpById(1);
System.out.println(employee);
//再次获取相同的数据
Employee temp = employeeDAO.getEmpById(1);
System.out.println(temp);
System.out.println(employee == temp);
}
- ehcache会根据你配置的本地磁盘保存路径,自动在本地磁盘中创建一个文件夹用于保存缓存对象