mybatis 缓存

在mybatis中提供了两种类型的缓存即 一级缓存和二级缓存,mybatis默认支持一级缓存所以不用配置。首先来看看一级缓存:

1、mybatis 中的一级缓存,我们平时如果没有特别写明的话我们是看不到的,但他默认是开启的所以说我们在不知不觉中使用着mybatis的一级缓存。

2、mybatis一级缓存的范围是 Sqlsession级别,如果是两个session,或关闭了session而又开启一个session 都是没法使用到一级缓存的,说来说去mybatis的一级缓存是SqlSession范围的而缓存的key是 sql + where 而且条件要一直的,保存在hashMap中的。

3、如果调用了session的clearcache() 方法或者close 或者执行了 增删改方法则会清除一级缓存。

下面我们写几个小demo 来看看一级缓存和二级缓存

一、一级缓存

1、映射文件


[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  4. <mapper namespace="com.mybatis1.userMap">  
  5. <resultMap  id="result" type="com.mybatis1.User">  
  6. <result property="name" column="name"/>  
  7. <result property="password" column="password"/>  
  8. </resultMap>  
  9. <span style="white-space:pre">    </span><select id="getUser" parameterType="java.lang.String" resultMap="result" useCache="true">  
  10. <span style="white-space:pre">    </span>select * from usert where name =#{name}  
  11. <span style="white-space:pre">    </span></select>  
  12. </mapper>  

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <select id="getUser" parameterType="java.lang.String" resultMap="result">  
  2. select * from usert where name =#{name}  
  3. </select>  

其中的useCache =“true” 就说明了是使用以及缓存,在mybatis 中默认是开启以及缓存的所以写不写都是在使用者一级缓存,所以上面的两种写法是一样的。


但是如果有如下配置则就关闭了以及缓存或者说没有了一级缓存

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <select id="getUser" parameterType="java.lang.String" resultMap="result"  flushCache="true">  
  2. select * from usert where name =#{name}  
  3. </select>  

其中的flushCache="true" 表示刷新缓存,所以每一次都会刷新其中的缓存,所以和没有换粗的功能是一样的,因为数据还是要从数据库服务上去查询。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <select id="getUser" parameterType="java.lang.String" resultMap="result"  useCache="false">  
  2. select * from usert where name =#{name}  
  3. </select>  
这种设置直接关闭了一级缓存。

2、一级缓存demo

在默认情况下,也就是使用一级缓存并且查询的条件相同的情况下进行测试

1、命中缓存,同一个session 同样的条件

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. import java.io.InputStream;  
  2. import org.apache.ibatis.session.SqlSession;  
  3. import org.apache.ibatis.session.SqlSessionFactory;  
  4. import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
  5.   
  6. /** 
  7.  *@author WHD 
  8.  *data 2016年2月28日 
  9.  */  
  10. public class Test1 {  
  11. public static void main(String[] args) {  
  12.     // 配置文件  
  13.     String path="config.xml";  
  14.     InputStream input= Test.class.getClassLoader().getResourceAsStream(path);  
  15.     SqlSessionFactoryBuilder  builder=  new SqlSessionFactoryBuilder();  
  16.     SqlSessionFactory  factory= builder.build(input);  
  17.     SqlSession  session=factory.openSession();  
  18.     try{  
  19.     User user=session.selectOne("com.mybatis1.userMap.getUser""whd");  
  20.     // 直接走的缓存不会再次从数据库中查询  
  21.     User user1= session.selectOne("com.mybatis1.userMap.getUser""whd");  
  22.     System.out.println("user"+user.toString());  
  23.     System.out.println("user1"+user1.toString());  
  24.     }finally{  
  25.         session.close();  
  26.     }  
  27. }  
  28. }  

执行结果如下:


当打印日志的时候你会发现,向数据库只发送了一次请求,后面的查询走的就是缓存即一级缓存起作用。

2、清除缓存,同一个session 同样的条件

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public class Test1 {  
  2. public static void main(String[] args) {  
  3.     // 配置文件  
  4.     String path="config.xml";  
  5.     InputStream input= Test.class.getClassLoader().getResourceAsStream(path);  
  6.     SqlSessionFactoryBuilder  builder=  new SqlSessionFactoryBuilder();  
  7.     SqlSessionFactory  factory= builder.build(input);  
  8.     SqlSession  session=factory.openSession();  
  9.     try{  
  10.     User user=session.selectOne("com.mybatis1.userMap.getUser""whd");  
  11.     //调用方法清除缓存  
  12.     session.clearCache();  
  13.     //当上面清除了缓存后下面的查询在缓存中没有只能走数据库查询,打印日志你会发现,查询直接走了数据库  
  14.     User user1= session.selectOne("com.mybatis1.userMap.getUser""whd");  
  15.     System.out.println("user"+user.toString());  
  16.     System.out.println("user1"+user1.toString());  
  17.     }finally{  
  18.         session.close();  
  19.     }  
  20. }  
  21. }  



在这种情况下,执行完第一个查询后调用方法清除了缓存,所以第二次查询的时候直接走了数据库。

3、多个session ,同样的条件不同的session

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public class Test1 {  
  2. public static void main(String[] args) {  
  3.     // 配置文件  
  4.     String path="config.xml";  
  5.     InputStream input= Test.class.getClassLoader().getResourceAsStream(path);  
  6.     SqlSessionFactoryBuilder  builder=  new SqlSessionFactoryBuilder();  
  7.     SqlSessionFactory  factory= builder.build(input);  
  8.     SqlSession  session=factory.openSession();  
  9.     SqlSession  session1=factory.openSession();  
  10.     try{  
  11.     User user=session.selectOne("com.mybatis1.userMap.getUser""whd");  
  12.     System.out.println("user"+user.toString());  
  13.     }finally{  
  14.         // 关闭session 释放资源  
  15.         session.close();  
  16.     }  
  17.     try{  
  18.         User user1= session1.selectOne("com.mybatis1.userMap.getUser""whd");  
  19.         System.out.println(user1.toString());  
  20.     }finally{  
  21.         // 关闭session1 释放资源  
  22.         session1.close();  
  23.     }  
  24. }  
  25. }  



我们发现这里有两个session ,而查询日志中也是向数据库查询了两次,所以一级缓存是在同一个session 中


总结:从小demo 我们也看到了,使用或者说能命中一级缓存的条件有如下:

1、使用的是同一个session

2、查询条件相同

3、在第二次查询之前没有调用clearCache() 方法清除缓存

4、在映射文件中没有设置flushCache ="true"

5、在映射文件中没有设置 useCache="false"

如果同时满足这些条件的话就会命中一级缓存,而如果有一项不满足则不会命中一级缓存的。

6、当我们执行了 insert,update ,delete 是会清空一级缓存。

我们知道了在什么样的情况下能命中一级缓存,那什么是一级缓存,一级缓存的实现原理又是怎样的那下面我们来看看


 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。

一级缓存的原理如下图,具体细节以及实现代码见之后的mybatis 源码学习篇章:



应用程序中创建SqlSession 实例时,同时也生成了一个数据结构HashMap.

当执行session.selectOne("namespace+id",param); 时 首相会到HashMap中去查找key 为这次查询的key,如果有就是返回值,也就是图中的1、2、3.1 这三个步骤。

注意: 这里的key 个人看法应该是: 命名空间值+id+sql语句+where条件 (没看源码不能确定,等看了源码再来解释)

如果Map 中找不到key 为这次key的key 那就查询数据库,在返回结果,并将结果添加到缓存中之后再返回结果给应用程序,也就是图中的3.2、3.3、3.4以及3.5.

这里的HashMap 是依附于SqlSession 对象的。如果SqlSession 对象销毁了则缓存也就没了。

如果调用了clearCache () 方法则会把HashMap 集合清空,同样如果insert ,update,delete 则同样会清空HashMap集合。


二、mybatis 二级缓存


mybatis 二级缓存相比一级缓存配置的属性比较多,而且可以使用自己定义的缓存,在使用二级缓存的时候要添加<cache> 标签,

mybatis二级缓存的范围是配置文件或者说缓存配置和缓存实例是绑定在sql 映射文件的命名空间的。

mybatis二级缓存有如下几个属性:eviction 缓存回收方式、flushInterval 缓存刷新时间、 size 存数据结果或列表的引用个数,注意这里不是缓存的大小而是引用的个数
readonly 是否只读,下面来看看二级缓存的属性

首先看看完整的二级缓存配置文件:

[html]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  4. <mapper namespace="com.mybatis1.userMap">  
  5. <cache/>  
  6. <resultMap  id="result" type="com.mybatis1.User">  
  7. <result property="name" column="name"/>  
  8. <result property="password" column="password"/>  
  9. </resultMap>  
  10.     <select id="getUser" parameterType="java.lang.String" resultMap="result">  
  11.     select * from usert where name =#{name}  
  12.     </select>  
  13. </mapper>  
其中的<cache> 就是二级缓存的配置,mybatis的二级缓存不像一级缓存,如果要使用的话要在映射配置文件中添加<cache> 这个标签,以及按照自己的需要添加相应的属性,如果只是<cache/>则使用默认的属性的值。

1、内存回收策略:eviction

mybatis 提供了如下几种回收策略:

LRU- 最近最少使用的:移除最长时间不被使用的对象。

FIFO-先进先出:按对象进入缓存的顺序来移除他们。

SOFT-软引用:移除基于垃圾回收器状态和软引用规则的对象。

WEAK-弱引用:更积极的移除基于垃圾收集器状态和引用规则的对象。

默认情况下使用的是LRU.

2、刷新间隔:flushInterval

可以设置为任何正整数,表示的是毫秒数,默认情况下是不设置,也就是只有在调用语句的时候刷新。

3、引用数目:size

可以设置为任何正整数,是缓存的对象数目默认 1024.

4、只读:readOnly

如果是只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改。这样就提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)
这样会慢一些,但是安全,因此默认是false. 

设置属性后的配置文件:

[html]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  4. <mapper namespace="com.mybatis1.userMap">  
  5. <cache  
  6. eviction="FIFO"  
  7. flushInterval="60000"  
  8. size="1024"  
  9. readOnly="true"  
  10. />  
  11. <resultMap  id="result" type="com.mybatis1.User">  
  12. <result property="name" column="name"/>  
  13. <result property="password" column="password"/>  
  14. </resultMap>  
  15.     <select id="getUser" parameterType="java.lang.String" resultMap="result">  
  16.     select * from usert where name =#{name}  
  17.     </select>  
  18. </mapper>  


设置了二级缓存后,在看看两个session 查寻的过程和结果,代码如下:


[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package com.mybatis1;  
  2.   
  3. import java.io.InputStream;  
  4. import org.apache.ibatis.session.SqlSession;  
  5. import org.apache.ibatis.session.SqlSessionFactory;  
  6. import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
  7.   
  8. /** 
  9.  *@author WHD 
  10.  *data 2016年2月28日 
  11.  */  
  12. public class Test1 {  
  13. public static void main(String[] args) {  
  14.     // 配置文件  
  15.     String path="config.xml";  
  16.     InputStream input= Test.class.getClassLoader().getResourceAsStream(path);  
  17.     SqlSessionFactoryBuilder  builder=  new SqlSessionFactoryBuilder();  
  18.     SqlSessionFactory  factory= builder.build(input);  
  19.     // 这样写默认不会自动提交,如果设置为自动提交则二级缓存不起作用  
  20.     SqlSession  session=factory.openSession();  
  21.     SqlSession  session1=factory.openSession();  
  22.     try{  
  23.     User user=session.selectOne("com.mybatis1.userMap.getUser""whd");  
  24.     // 这里必须要提交一次,而且不能使用自动提交不然二级缓存不起作用  
  25.     session.commit();  
  26.     User user2= session1.selectOne("com.mybatis1.userMap.getUser""whd");  
  27.     System.out.println("user"+user.toString());  
  28.     System.out.println("user2"+user2.toString());  
  29.     }finally{  
  30.         // 关闭session 释放资源  
  31.         session.close();  
  32.         session1.close();  
  33.     }  
  34. }  
  35. }  

查询过程日志,结果如下:



从查询的过程和结果我们看到,虽然使用了两个session 对象但是只向数据库服务发送了一次查询请求,而第二次走了缓存。


进行了insert 、update、delete 后缓存会被刷新清空,下面看看在执行了insert 后在进行查询的情况如何:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package com.mybatis1;  
  2.   
  3. import java.io.InputStream;  
  4. import org.apache.ibatis.session.SqlSession;  
  5. import org.apache.ibatis.session.SqlSessionFactory;  
  6. import org.apache.ibatis.session.SqlSessionFactoryBuilder;  
  7.   
  8. /** 
  9.  * @author WHD data 2016年2月28日 
  10.  */  
  11. public class Test1 {  
  12.     public static void main(String[] args) {  
  13.         // 配置文件  
  14.         String path = "config.xml";  
  15.         InputStream input = Test.class.getClassLoader().getResourceAsStream(  
  16.                 path);  
  17.         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();  
  18.         SqlSessionFactory factory = builder.build(input);  
  19.         // 这样写默认不会自动提交,如果设置为自动提交则二级缓存不起作用  
  20.         SqlSession session = factory.openSession();  
  21.         SqlSession session1 = factory.openSession();  
  22.         try {  
  23.             User user = session  
  24.                     .selectOne("com.mybatis1.userMap.getUser""whd");  
  25.             // 这里必须要提交一次,而且不能使用自动提交不然二级缓存不起作用  
  26.             session.commit();  
  27.             // 添加addUser  
  28.             User users = new User();  
  29.             users.setName("www");  
  30.             users.setPassword("1234");  
  31.             session.insert("com.mybatis1.userMap.addUser", users);  
  32.             // 提交  
  33.             session.commit();  
  34.             User user2 = session1.selectOne("com.mybatis1.userMap.getUser",  
  35.                     "whd");  
  36.             System.out.println("user" + user.toString());  
  37.             System.out.println("user2" + user2.toString());  
  38.         } finally {  
  39.             // 关闭session 释放资源  
  40.             session.close();  
  41.             session1.close();  
  42.         }  
  43.     }  
  44. }  

执行过程以及结果:


从执行的结果中我们发下,两次查询都都走了数据库查询,也就是缓存没有命中,这是为什么那,因为在insert、update、delete时会把缓存清空,所以会直接查询数据库。


总结:

1、mybatis 的二级缓存是配置文件范围的或者说命名空间范围的。

2、映射文件中的所有select 语句都会被缓存。

3、映射文件中的insert、update、delete 语句会刷新清空缓存。

4、缓存回收方式 默认使用LRU最近最少方式。

5、如果指定了刷新时间间隔则会在指定的时间刷新清空缓存。

7、默认情况下二级缓存存储1024引用对象。

上面的二级缓存都是用了mybatis 默认的缓存即HashMap的方式,我们知道mybatis 提供了接口我们可以实现接口来自定义自己的缓存,配置文件具体格式如下:

<cache type=”com.domain.something.MyCustomCache”/>  其中的type就是实现接口类。而要实现mybatis 的接口如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public interface Cache {  
  2. String getId();  
  3. int getSize();  
  4. void putObject(Object key, Object value);  
  5. Object getObject(Object key);  
  6. boolean hasKey(Object key);  
  7. Object removeObject(Object key);  
  8. void clear();  
  9. ReadWriteLock getReadWriteLock();  
  10. }  

怎么实现这个接口以及怎么自定义接口在下篇文章中我们在实现。

到这里我们知道了mybatis 一级缓、二级缓存的使用,但我们不知道他们的实现原理以及他们的实现,我们带着这些问题继续学习,一级二级缓存实现原理会在之后的文章中进行分析。


 如有写的不对还请大家指正 共同学习 谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值