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默认没有开启二级缓存,需要Mybatis的配置文件的setting全局参数中配置开启二级缓存。
如何设置二级缓存 接下来测试MyBatis的二级缓存,所有代码和测试一级缓存的代码完全一样,只是需要在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接口中,命名空间就是接口的全限定名称。
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标签中所有这些行为都可以通过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 33 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
初始化数据库表 /MyTwoLevelCacheTest/src/domain/tb_user.sql 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 取消外键约束 SET FOREIGN_KEY_CHECKS = 0 ;# 创建数据库表 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' );# 回复外键检查 SET FOREIGN_KEY_CHECKS= 1 ;
数据库配置文件 db.properties:
/MyTwoLevelCacheTest/src/db.properties mysql5.x配置 1 2 3 4 driver =com.mysql.jdbc.Driver url =jdbc:mysql://localhost:3306/mybatis username =root password =root
/MyTwoLevelCacheTest/src/db.properties mysql8.x配置 1 2 3 4 driver =com.mysql.cj.jdbc.Driver url =jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf-8 username =root password =root
log4j.xml /MyTwoLevelCacheTest/src/log4j.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?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 /MyTwoLevelCacheTest/src/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 <?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 /MyTwoLevelCacheTest/src/domain/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 + "]" ; } }
工具类 /MyTwoLevelCacheTest/src/fractory/SqlSessionFactoryTools.java 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 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 SqlSessionFactoryTools { 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 /MyTwoLevelCacheTest/src/mapper/UserMapper.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 <?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接口 /MyTwoLevelCacheTest/src/mapper/UserMapper.java 1 2 3 4 5 6 7 8 package mapper;import domain.User;public interface UserMapper { User selectUserById (int id) ; void deleteUserById (int id) ; }
测试类 /MyTwoLevelCacheTest/src/test/TwoLevelCacheTest.java 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); } }
运行结果:
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语句。