0%

10.5 MyBatis缓存机制 10.5.2二级缓存(mapper级别)

10.5 MyBatis缓存机制 10.5.2二级缓存(mapper级别)

二级缓存是mapper级别的缓存。使用二级缓存时,多个SqlSession使用同一个mapperSQL语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用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">
<!-- XML 配置文件包含对 MyBatis 系统的核心设置 -->
<configuration>
......
<settings>
<!-- 开启二级缓存 -->
<setting
name="cacheEnabled"
value="true"/>
</settings>
......
</configuration>

setting标签的cacheEnabled属性的valuetrue时表示在此配置文件下开启二级缓存,该属性默认为false

MyBatis的二级缓存是和命名空间绑定的,即二级缓存需要配置在Mapper.xml映射文件或者Mapper接口中。

  • 在映射文件中,命名空间就是XML根节点mappernamespace属性。
  • 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">
<!-- namespace指用户自定义的命名空间。 -->
<mapper namespace="org.fkit.mapper.UserMapper">
<cache/>
</mapper>

默认的二级缓存作用

默认的二级缓存会有如下作用:

  • 映射语句文件中的所有SELECT语句将会被缓存。
  • 映射语句文件中的所有INSERTUPDATEDELETE语句会刷新缓存。
  • 缓存会使用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">
<!-- namespace指用户自定义的命名空间。 -->
<mapper namespace="org.fkit.mapper.UserMapper">
<!-- 开启二级缓存
回收策略为先进先出
自动刷新时间60s
最多缓存512个引用对象
只读
-->
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
</mapper>

以上配置创建了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,而且返回的对象被认为是只读的.

cache标签的属性

cache标签用来开启当前mappernamespace下的二级缓存。该标签的属性设置如下:

  • flushInterval属性。刷新间隔。可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况下是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
  • size属性。缓存数目。可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
  • readonly属性。只读。该属性可以被设置为truefalse
    • 设置为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" >
<!-- 请在mybatis-config.xml中配置如下设置 -->
<!-- <settings> -->
<!-- <setting -->
<!-- name="logImpl" -->
<!-- value="log4j"/> -->
<!-- </settings> -->
<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">
<!-- 该配置文件包含对 MyBatis 系统的核心设置 -->
<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()
{
// TODO Auto-generated constructor stub
}
// 此处省略getter和setter方法,请自己补上
@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">
<!-- namespace指用户自定义的命名空间。 -->
<mapper namespace="mapper.UserMapper">
<!-- ############## 下面的代码放在mapper.xml文件中 ############### -->
<!-- flushInterval:刷新时间间隔,默认刷新,只在执行SQL语句时刷新 -->
<!-- size:缓存数目,默认1024 -->
<!-- readOnly:是否只读,true为只读,默认false -->
<!-- eviction:回收策略,默认LRU(最近最少使用), -->
<!-- FIFO(先进先出), -->
<!-- SORT(软引用:移除基于垃圾回收器的状态和软引用规则) -->
<!-- WEAK(弱引用,更积极的移除基于垃圾回收器的窗台和弱引用规则的对象) -->
<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 {
//按id查询
User selectUserById(Integer id);
//按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();
// 获取mapper接口的代理对象
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开头的语句,这行日志后面输出的值为当前执行方法的缓存命中率。在第一次査询id1User对象时,执行了一条select语句,接下来调用SqlSessionclose()方法,该方法会关闭SqlSession一级缓存,同时会将査询数据保存到二级缓存中。
当第二次获取id1User对象时,重新获得的一级缓存SqlSession中并没有缓存任何对象,但是因为启用了二级缓存,当MyBatis在一级缓存中没有找到id1User对象时,会去二级缓存中查找,所以不会再次执行select语句。

原文链接: 10.5 MyBatis缓存机制 10.5.2二级缓存(mapper级别)