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
语句。