10.5 MyBatis缓存机制 10.5.2二级缓存(mapper级别) 二级缓存是mapper级别的缓存。使用二级缓存时,多个SqlSession使用同一个mapper的SQL语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储的。相比一级缓存SqlSession,二级缓存的范围更大,多个SqlSession可以共享二级缓存中的数据 ,二级缓存是跨SqlSession的。SqlSession共享的,其作用域是mapper的同一个namespace。不同的SqlSession两次执行相同的namespace下的SQL语句,且向SQL中传递的参数也相同,即最终执行相同的SQL语句时 ,当第一个SqlSession调用close()方法关闭一级缓存时,第一次从数据库中査询到的数据会被保存到二级缓存 ,第二次查询时会从二级缓存中获取数据,不再去底层数据库查询,从而提高査询效率.MyBatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
如何设置二级缓存 接下来测试MyBatis的二级缓存,所有代码和测试一级缓存的代码完全一样,只是需要在配置文件中开启二级缓存.
在mybatis-config.xml中设置开启二级缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"   "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration >     ......     <settings >                   <setting               name ="cacheEnabled"              value ="true" />     </settings >      ...... </configuration > 
setting标签的cacheEnabled属性的value为true时表示在此配置文件下开启二级缓存,该属性默认为false。
MyBatis的二级缓存是和命名空间绑定的,即二级缓存需要配置在Mapper.xml映射文件或者Mapper接口中。
在映射文件中,命名空间就是XML根节点mapper的namespace属性。 
在Mapper接口中,命名空间就是接口的全限定名称。 
 
在Mapper.xml映射文件中加入cache标签 在Mapper.xml中写入cache标签可以开启默认的二级缓存,代码如下:
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper  namespace ="org.fkit.mapper.UserMapper" >     <cache />  </mapper > 
默认的二级缓存作用 默认的二级缓存会有如下作用:
映射语句文件中的所有SELECT语句将会被缓存。 
映射语句文件中的所有INSERT、UPDATE、DELETE语句会刷新缓存。 
缓存会使用Least Recently Used(LRU 最近最少使用)策略来收回。 
根据时间表(如no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新。 
缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024个引用。 
缓存会被视为read/write(可读/可写)的,这意味着对象检索不是共享的 ,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。 
 
cache标签中所有这些行为都可以通过cache标签的属性来进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper  namespace ="org.fkit.mapper.UserMapper" >          <cache           eviction ="LRU"          flushInterval ="60000"          size ="512"          readOnly ="true" /> </mapper > 
以上配置创建了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,而且返回的对象被认为是只读的.
cache标签的属性 cache标签用来开启当前mapper的namespace下的二级缓存。该标签的属性设置如下:
flushInterval属性。刷新间隔。可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况下是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。size属性。缓存数目。可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。readonly属性。只读。该属性可以被设置为true或false。
设置为true表示只读,只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改。这提供了很重要的性能优势。 
可读写的缓存会返回缓存对象的拷贝(通过序列化)。这种方式会慢一些,但是安全,因此默认是false表示不是只读. 
 
eviction属性。收回策略,默认为LRU。有如下几种:
LRU。最近最少使用的策略,移除最长时间不被使用的对象。FIFO。先进先出策略,按对象进入缓存的顺序来移除它们.SOFT。软引用策略,移除基于垃圾回收器状态和软引用规则的对象.WEAK。弱引用策略,更积极地移除基于垃圾收集器状态和弱引用规则的对象. 
 
使用二级缓存的的Java对象必须可序列化 使用二级缓存时,与查询结果映射的Java对象必须实现java.io.Serializable接口的序列化和反序列化操作,如果存在父类,其成员都需要实现序列化接口。实现序列化接口是为了对缓存数据进行序列化和反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存,有可能是硬盘或者远程服务器.
实例 项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 E:\workspace_web2\MyTwoLevelCacheTest ├─src │ ├─db.properties │ ├─domain │ │ ├─tb_user.sql │ │ └─User.java │ ├─fractory │ │ └─SqlSessionFratoryTools.java │ ├─log4j.xml │ ├─mapper │ │ ├─UserMapper.java │ │ └─UserMapper.xml │ ├─mybatis-config.xml │ └─test │   └─TwoLevelCacheTest.java └─WebContent   └─WEB-INF     └─lib       ├─ant-1.9.6.jar       ├─ant-launcher-1.9.6.jar       ├─asm-5.2.jar       ├─cglib-3.2.5.jar       ├─commons-logging-1.2.jar       ├─javassist-3.22.0-CR2.jar       ├─log4j-1.2.17.jar       ├─log4j-api-2.3.jar       ├─log4j-core-2.3.jar       ├─mybatis-3.4.5.jar       ├─mysql-connector-java-5.1.44-bin.jar       ├─ognl-3.1.15.jar       ├─slf4j-api-1.7.25.jar       └─slf4j-log4j12-1.7.25.jar 
初始化数据库表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 DROP  TABLE  IF  EXISTS  `tb_user` ;CREATE  TABLE  `tb_user`  (  `id`  int (11 ) NOT  NULL  AUTO_INCREMENT,   `name`  varchar (18 ) DEFAULT  NULL ,   `sex`  char (2 ) DEFAULT  NULL ,   `age`  int (11 ) DEFAULT  NULL ,   PRIMARY KEY  (`id` ) ) ENGINE =InnoDB  DEFAULT  CHARSET =utf8mb4; INSERT  INTO  `tb_user`  VALUES  ('1' , '小明' , '男' , '21' );INSERT  INTO  `tb_user`  VALUES  ('2' , '小王' , '男' , '22' );INSERT  INTO  `tb_user`  VALUES  ('3' , '小丽' , '女' , '18' );INSERT  INTO  `tb_user`  VALUES  ('4' , '小芳' , '女' , '18' );INSERT  INTO  `tb_user`  VALUES  ('5' , '小王' , '男' , '22' );
数据库配置文件 db.properties:
1 2 3 4 driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis username=root password=root 
log4j.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration      PUBLIC "-//LOG4J//DTD LOG4J//EN"      "https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd" > <log4j:configuration >     <appender           name ="STDOUT"          class ="org.apache.log4j.ConsoleAppender" >         <layout  class ="org.apache.log4j.PatternLayout" >              <param                   name ="ConversionPattern"                  value ="%5p [%t] %m%n" />         </layout >      </appender >      <logger  name ="mapper.UserMapper" >          <level  value ="DEBUG" />      </logger >      <root >          <level  value ="ERROR" />          <appender-ref  ref ="STDOUT" />      </root >  </log4j:configuration > 
mybatis-config.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"   "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration >     <properties  resource ="db.properties" />      <settings >                   <setting               name ="logImpl"              value ="log4j" />                  <setting               name ="cacheEnabled"              value ="true" />     </settings >      <environments  default ="mysql" >          <environment  id ="mysql" >              <transactionManager  type ="JDBC" />              <dataSource  type ="pooled" >                  <property                       name ="driver"                      value ="${driver}" />                 <property                       name ="url"                      value ="${url}" />                 <property                       name ="username"                      value ="${username}" />                 <property                       name ="password"                      value ="${password}" />             </dataSource >          </environment >      </environments >      <mappers >          <mapper  resource ="mapper/UserMapper.xml" />      </mappers >  </configuration > 
User.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  domain;public  class  User      private  Integer id;     private  String name;     private  String sex;     private  Integer age;     public  User ()       {             }          @Override      public  String toString ()       {        return  "User [id="  + id + ", name="  + name + ", sex="  + sex + ", age="  + age + "]" ;     } } 
工具类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package  fractory;import  java.io.IOException;import  java.io.InputStream;import  org.apache.ibatis.io.Resources;import  org.apache.ibatis.session.SqlSession;import  org.apache.ibatis.session.SqlSessionFactory;import  org.apache.ibatis.session.SqlSessionFactoryBuilder;public  class  SqlSessionFratoryTools      private  static  SqlSessionFactory sqlSessionFactory = null ;     static      {         try          {             InputStream mybatisConfigXML = Resources.getResourceAsStream("mybatis-config.xml" );             sqlSessionFactory = new  SqlSessionFactoryBuilder().build(mybatisConfigXML);         } catch  (IOException e)         {             e.printStackTrace();         }     }     public  static  SqlSession getSqlSession ()       {        return  sqlSessionFactory.openSession();     } } 
mapper.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper  namespace ="mapper.UserMapper" >                                             <cache           flushInterval ="60000"          size ="512"          readOnly ="true"          eviction ="LRU" />     <select           id ="selectUserById"          parameterType ="int"          resultType ="domain.User" >  select * from tb_user where id=#{id}    </select >      <delete           id ="deleteUserById"          parameterType ="int" >  delete from tb_user where id=#{id}    </delete >  </mapper > 
mapper接口 1 2 3 4 5 6 7 8 package  mapper;import  domain.User;public  interface  UserMapper           User selectUserById (Integer id)  ;          void  deleteUserById (Integer id)  } 
测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package  test;import  org.apache.ibatis.session.SqlSession;import  domain.User;import  fractory.SqlSessionFratoryTools;import  mapper.UserMapper;public  class  TwoLevelCacheTest      public  static  void  main (String[] args)       {                 SqlSession sqlSession = SqlSessionFratoryTools.getSqlSession();                  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);                  User user = userMapper.selectUserById(1 );         System.out.println(user);         System.out.println("--------------------------------------------" );                  sqlSession.close();                  sqlSession = SqlSessionFratoryTools.getSqlSession();                  userMapper = sqlSession.getMapper(UserMapper.class);                  user = userMapper.selectUserById(1 );         System.out.println(user);     } } 
运行结果:
1 2 3 4 5 6 7 8 DEBUG [main] Cache Hit Ratio [mapper.UserMapper]: 0 .0  DEBUG [main] ==>  Preparing: select * from tb_user where id=?  DEBUG [main] ==> Parameters: 1 (Integer) DEBUG [main] <==      Total: 1  User [id=1 , name=小明, sex=男, age=21 ] -------------------------------------------- DEBUG [main] Cache Hit Ratio [mapper.UserMapper]: 0 .5  User [id=1 , name=小明, sex=男, age=21 ] 
代码详解 仔细观察MyBatis的执行结果,日志中有几条以Cache Hit Ratio开头的语句,这行日志后面输出的值为当前执行方法的缓存命中率。在第一次査询id为1的User对象时,执行了一条select语句,接下来调用SqlSession的close()方法,该方法会关闭SqlSession一级缓存,同时会将査询数据保存到二级缓存中。id为1的User对象时,重新获得的一级缓存SqlSession中并没有缓存任何对象,但是因为启用了二级缓存,当MyBatis在一级缓存中没有找到id为1的User对象时,会去二级缓存中查找,所以不会再次执行select语句。
原文链接: 10.5 MyBatis缓存机制 10.5.2二级缓存(mapper级别)