- MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存。
- 一级缓存和二级缓存。
1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
2、二级缓存需要手动开启和配置,是基于namespace级别的缓存。
3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
- 一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
- 一级缓存是一直开启的,时不能被关闭的, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;
- 在mybatis3.1之后, 可以配置本地缓存的作用域.。
在 mybatis.xml 中配置
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
- key:hashCode+查询的SqlId+编写的sql查询语句+参数
- 一级缓存失效的四种情况(发送多次sql查询)
1、不同的SqlSession对应不同的一级缓存
2、同一个SqlSession但是查询条件不同(当前一级缓存中还没有这个数据)
3、同一个SqlSession两次查询期间执行了任何一次增删改 *** 作(这次增删改可能对当前数据有影响)
4、同一个SqlSession两次查询期间手动清空了缓存
案例一:一级缓存体验
EmployeeMapper接口
Employee getEmpById(Integer id);
select id, last_name lastName, email, gender from tbl_employee where id = #{id}
测试代码:
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee emp1 = mapper.getEmpById(1); System.out.println(emp1); Employee emp2 = mapper.getEmpById(1); System.out.println(emp2); System.out.println(emp1==emp2); sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); }finally { sqlSession.close(); }
结果:
DEBUG 10-17 16:16:19,519 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:16:19,593 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:16:19,629 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} true Process finished with exit code 0
可以看出,第二次查询,并没有再次发送sql语句查询,而是将第一次查询的结果复制给第二次。这就是mybatis的一级缓存机制。
一级缓存失效情况1:不同的SqlSession
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee emp1 = mapper.getEmpById(1); System.out.println(emp1); SqlSession sqlSession2 = sqlSessionFactory.openSession(); EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class); Employee emp2 = mapper2.getEmpById(1); System.out.println(emp2); System.out.println(emp1==emp2); sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); }finally { sqlSession.close(); }
结果:
DEBUG 10-17 16:29:15,730 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:29:15,821 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:29:15,880 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} DEBUG 10-17 16:29:15,965 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:29:15,965 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:29:15,974 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} false Process finished with exit code
一级缓存失效情况2:同一个SqlSession但是查询条件不同
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee emp1 = mapper.getEmpById(1); System.out.println(emp1); Employee emp2 = mapper.getEmpById(3); System.out.println(emp2); System.out.println(emp1==emp2); sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); }finally { sqlSession.close(); }
结果:
DEBUG 10-17 16:32:05,436 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:32:05,515 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:32:05,567 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} DEBUG 10-17 16:32:05,568 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:32:05,569 ==> Parameters: 3(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:32:05,571 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=3, lastname='jerry', email='jerry@qq.com', gender='0', dept=null} false
一级缓存失效情况3:sqlSession相同,两次查询之间执行了增删改 *** 作
接口中添加方法
void addEmp(Employee emp);
insert into tbl_employee(last_name,email,gender,d_id) values(#{lastname},#{email},#{gender},#{dept.id})
测试代码
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee emp1 = mapper.getEmpById(1); System.out.println(emp1); mapper.addEmp(new Employee(null,"维曦","weixi@qq.com","1",new Dept(1004,"客服部"))); Employee emp2 = mapper.getEmpById(1); System.out.println(emp2); System.out.println(emp1==emp2); sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); }finally { sqlSession.close(); }
结果:
DEBUG 10-17 16:44:51,694 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:44:51,744 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:44:51,779 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} DEBUG 10-17 16:44:51,781 ==> Preparing: insert into tbl_employee(last_name,email,gender,d_id) values(?,?,?,?) (baseJdbcLogger.java:145) DEBUG 10-17 16:44:51,783 ==> Parameters: 维曦(String), weixi@qq.com(String), 1(String), 1004(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:44:51,786 <== Updates: 1 (baseJdbcLogger.java:145) DEBUG 10-17 16:44:51,786 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:44:51,787 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:44:51,789 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} false Process finished with exit code 0
一级缓存失效情况4:sqlSession相同,手动清除了一级缓存
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee emp1 = mapper.getEmpById(1); System.out.println(emp1); sqlSession.clearCache(); Employee emp2 = mapper.getEmpById(1); System.out.println(emp2); System.out.println(emp1==emp2); sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); }finally { sqlSession.close(); }
结果:
DEBUG 10-17 16:47:24,448 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:47:24,498 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:47:24,544 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} DEBUG 10-17 16:47:24,545 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 16:47:24,546 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 16:47:24,547 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} false Process finished with exit code 0二级缓存
- 二级缓存(second level cache),全局作用域缓存,基于namespace级别的缓存:一个namespace对应一个二级缓存
- 二级缓存默认不开启,需要手动配置
- MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效
- 工作机制:
1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
3、sqlSession===EmployeeMapper==>Employee
4、不同namespace查出的数据会放在自己对应的缓存中(map)
5、效果:数据会从二级缓存中获取。一般查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中 - 使用步骤
1、全局配置文件中开启二级缓存
2、需要使用二级缓存的映射文件处使用cache配置缓存
3、注意:POJO需要实现Serializable接口
- 缓存相关属性
- eviction缓存回收策略:
• LRU – 最近最少使用的:移除最长时间不被使用的对象。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
• 默认的是 LRU。- flushInterval刷新间隔,单位毫秒,缓存多长时间清空一次,默认不清空缓存仅仅调用语句时刷新
- size引用数目,正整数
• 代表缓存最多可以存储多少个对象,太大容易导致内存溢出- readOnly只读,true/false
• true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。不安全,速度快
• false:读写缓存;会返回缓存对象的拷贝(通过序列化)。安全,速度慢,因此默认是 false。- type指定自定义缓存的全类名;实现Cache接口即可;
- 缓存有关设置
• 1、全局setting的cacheEnable:配置二级缓存的开关。一级缓存一直是打开的。
• 2、select标签的useCache属性: 配置这个select是否使用二级缓存。一级缓存一直是使用的
• 3、sql标签的flushCache属性:增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
• 4、sqlSession.clearCache(): 只是用来清除一级缓存。
• 5、localCacheScope:本地缓存作用域:(一级缓存SESSION);当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存;
• 6、当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D *** 作后,默认该作用域下所有 select 中的缓存将被clear。
实 *** 示例
全局变量设置开启缓存
在映射文件中添加设置二级缓存
(这里演示,部分设置属性,其他的采用默认的)
实体类实现可序列化接口
public class Employee implements Serializable { private static final long serialVersionUID = 93066158432748273l;
测试代码
try { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); SqlSession sqlSession2 = sqlSessionFactory.openSession(); EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class); System.out.println(mapper.getEmpById(1)); sqlSession.close(); System.out.println(mapper2.getEmpById(1)); sqlSession2.close(); } catch (Exception e) { e.printStackTrace(); }finally { sqlSession.close(); }
运行结果:
DEBUG 10-17 18:24:45,121 Cache Hit Ratio [com.xiaoqiu.dao.EmployeeMapper]: 0.0 (LoggingCache.java:62) DEBUG 10-17 18:24:45,131 ==> Preparing: select id, last_name lastName, email, gender from tbl_employee where id = ? (baseJdbcLogger.java:145) DEBUG 10-17 18:24:45,176 ==> Parameters: 1(Integer) (baseJdbcLogger.java:145) DEBUG 10-17 18:24:45,215 <== Total: 1 (baseJdbcLogger.java:145) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} DEBUG 10-17 18:24:45,232 Cache Hit Ratio [com.xiaoqiu.dao.EmployeeMapper]: 0.5 (LoggingCache.java:62) Employee{id=1, lastname='张三', email='zhangsan@qq.com', gender='1', dept=null} Process finished with exit code 0缓存机制图 第三方缓存整合
- EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
- MyBatis定义了Cache接口方便我们进行自定义扩展。
- 步骤:
1、导入ehcache包,以及整合包,日志包
ehcache-core-2.6.8.jar
mybatis-ehcache-1.0.3.jar
slf4j-api-1.6.1.jar
slf4j-log4j12-1.6.2.jar
2、编写ehcache.xml配置文件
属性说明:
diskStore:指定数据在磁盘中的存储位置。
defaultCache:当借助CacheManager.add(“demoCache”)创建Cache时,EhCache便会采用指定的的管理策略以下属性是必须的:
maxElementsInMemory:在内存中缓存的element的最大数目
maxElementsOnDisk: 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal: 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk: 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘以下属性是可选的:
timeToIdleSeconds : 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds: 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
diskPersistent : 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds : 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy :当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
3、配置cache标签
参照缓存:若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)