Mybatis缓存
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。
1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
一级缓存(local cache)
**一级缓存, 即本地缓存, 作用域默认为sqlSession。**当Session flush或close 后,该Session中的所有Cache将被清空。 本地缓存不能被关闭, 但可以调用clearCache() 来清空本地缓存, 或者改变缓存的作用域。在mybatis3.1之后, 可以配置本地缓存的作用域,在mybatis.xml 中配置 。同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中,key:hashCode+查询的SqlId+编写的sql查询语句+参数
UserMapper
public interface UserMapper {
public User findById(int id);
}
UserMapper.xml
<?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="com.itheima.dao.UserMapper">
<select id="findById" parameterType="int" resultType="user">
select * from user where id= #{id}
</select>
</mapper>
User.java
package com.itheima.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User {
private Integer id;
private String username;
private String password;
private String birthday;
/* 省略 get set方法 以及toString方法*/
}
SqlMapper.xml
<?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>
<typeAliases>
<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!--使用package扫描时,包名必须一致,类名和映射文件不区分大小写 Spring整合环境下必须一致-->
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
测试类
@Test
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findById(1);
System.out.println(user1);
User user2 = userMapper.findById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession.close();
}
日志显示两次查询,只查询了一次数据库
16:17:46,559 DEBUG ResolverUtil:256 - Checking to see if class com.itheima.dao.UserMapper matches criteria [is assignable to Object]
16:17:46,856 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.0
16:17:46,868 DEBUG JdbcTransaction:137 - Opening JDBC Connection
16:17:47,496 DEBUG PooledDataSource:406 - Created connection 599491651.
16:17:47,497 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
16:17:47,504 DEBUG findById:159 - ==> Preparing: select * from user where id= ?
16:17:47,551 DEBUG findById:159 - > Parameters: 1(Integer)
16:17:47,611 DEBUG findById:159 - < Total: 1
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
16:17:47,612 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.0
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
true
16:17:47,624 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
16:17:47,625 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
16:17:47,625 DEBUG PooledDataSource:363 - Returned connection 599491651 to pool.
一级缓存失效的四种情况
(未 close SqlSession情况下)
1、不同的SqlSession对应不同的一级缓存
@Test
public void test3() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.findById(1);
System.out.println(user1);
User user2 = userMapper2.findById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession1.close();
sqlSession2.close();
}
2、同一个SqlSession但是查询条件不同
@Test
public void test2() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user1 = userMapper.findById(1);
System.out.println(user1);
User user2 = userMapper.findById(2);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession.close();
}
3、同一个SqlSession两次查询期间执行了任何一次增删改操作
@Test
public void test4() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findById(1);
System.out.println(user1);
User user=new User();
user.setId(3);
user.setPassword("6789");
userMapper1.updataById(user);
User user2 = userMapper1.findById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession1.close();
}
4. 清除本地缓存
@Test
public void test4() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findById(1);
System.out.println(user1);
sqlSession1.clearCache();
User user2 = userMapper1.findById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession1.close();
}
二级缓存
二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存。
工作机制:
1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
2、如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容;
不同namespace查出的数据会放在自己对应的缓存中(map),查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。
使用方式:
1)、开启全局二级缓存配置:
sqlMapper.xml
<setting name="cacheEnabled" value="true"/>
2)、去mapper.xml中配置使用二级缓存: 只有在哪个mapper下面配置下面的,才会用到二级缓存,否则即使开启二级全局缓存,二级缓存也不生效
(放在接口对应的xml文件里, 比如查User对象 就放在UserMapper.xml) <cache/>
3)POJO需要实现序列化接口 (实现 serialize接口)
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的
readOnly设置为false 实体类必须实现Serializable序列化接口,否则报错
Caused by: java.io.NotSerializableException: com.itheima.domain.User1
原因:通过序列化 返回缓存对象的拷贝
cache标签可以配置的属性:
eviction:缓存的回收策略:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
flushInterval:缓存刷新间隔 缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。速度快安全
false:非只读:通过序列化 返回缓存对象的拷贝。安全,速度慢 默认值是 false。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
二级缓存代码示例
1)核心配置文件里配置 <setting name="cacheEnabled" value="true"/>
2)public class Person implements Serializable{...}
3)mapper映射文件加入<cache></cache>
sqlMapper.xml
<?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>
<typeAliases>
<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!--使用package扫描时,包名必须一致,类名和映射文件不区分大小写 Spring整合环境下必须一致-->
<package name="com.itheima.dao"></package>
</mappers>
</configuration>
User.java
package com.itheima.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private String birthday;
省略 set get方法 以及tostring方法
}
UserMapper.xml
<?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="com.itheima.dao.UserMapper">
<cache/>
<select id="findById" parameterType="int" resultType="user">
select * from user where id= #{id}
</select>
</mapper>
解释:sqlSession1查询结果之后,关闭sqlSession1,会将结果写入二级缓存,然后sqlSession2查询会从二级缓存中查询,不从数据查询数据了。下面日志可以证明:
@Test
public void testn() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findById(1);
System.out.println(user1);
sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findById(1);
System.out.println(user2);
System.out.println(user1==user2);
}
16:45:19,189 DEBUG ResolverUtil:256 - Checking to see if class com.itheima.dao.UserMapper matches criteria [is assignable to Object]
16:45:19,416 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.0
16:45:19,428 DEBUG JdbcTransaction:137 - Opening JDBC Connection
16:45:19,979 DEBUG PooledDataSource:406 - Created connection 599491651.
16:45:19,980 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
16:45:19,983 DEBUG findById:159 - ==> Preparing: select * from user where id= ?
16:45:20,018 DEBUG findById:159 - > Parameters: 1(Integer)
16:45:20,060 DEBUG findById:159 - < Total: 1
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
16:45:20,078 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
16:45:20,079 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
16:45:20,079 DEBUG PooledDataSource:363 - Returned connection 599491651 to pool.
16:45:20,082 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.5
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
false
注意:只有一级缓存关闭的情况下二级缓存才会生效,下面演示中一级缓存没有关闭,二级缓存没有起作用,注意sqlSession1.close()的位置
@Test
public void testn() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findById(1);
System.out.println(user1);
//关闭一级缓存 二级缓存生效
//sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession1.close();
sqlSession2.close();
}
查询了两次数据库
16:47:44,213 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.0
16:47:44,225 DEBUG JdbcTransaction:137 - Opening JDBC Connection
16:47:44,658 DEBUG PooledDataSource:406 - Created connection 599491651.
16:47:44,659 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
16:47:44,662 DEBUG findById:159 - ==> Preparing: select * from user where id= ?
16:47:44,698 DEBUG findById:159 - > Parameters: 1(Integer)
16:47:44,752 DEBUG findById:159 - < Total: 1
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
16:47:44,754 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.0
16:47:44,754 DEBUG JdbcTransaction:137 - Opening JDBC Connection
16:47:44,762 DEBUG PooledDataSource:406 - Created connection 1427889191.
16:47:44,762 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@551bdc27]
16:47:44,763 DEBUG findById:159 - ==> Preparing: select * from user where id= ?
16:47:44,763 DEBUG findById:159 - > Parameters: 1(Integer)
16:47:44,764 DEBUG findById:159 - < Total: 1
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
false
验证二级缓存 存储对象
@Test
public void testn() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapper.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = userMapper1.findById(1);
System.out.println(user1);
User1 user3 = userMapper1.findById1(1);
System.out.println(user3);
//关闭sqlSession1 关闭一级缓存
// 这个时候 数据就应该存到二级缓存 但是因为配置文件里 二级缓存的size大小只给了1 所以只存进去了 User的对象
// User1的对象就没存进去 也就是说下面查User用了缓存 查User1没有用缓存
sqlSession1.close();
// 验证二级缓存
User user2 = userMapper2.findById(1);
System.out.println(user2);
User1 user4 = userMapper2.findById1(1);
System.out.println(user4);
// System.out.println(user1==user2);
}
User使用了缓存,User1没有使用缓存
21:03:42,674 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
21:03:42,677 DEBUG findById:159 - ==> Preparing: select * from user where id= ?
21:03:42,722 DEBUG findById:159 - > Parameters: 1(Integer)
21:03:42,742 DEBUG findById:159 - < Total: 1
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
21:03:42,743 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.0
21:03:42,743 DEBUG findById1:159 - ==> Preparing: select username from user where id= ?
21:03:42,743 DEBUG findById1:159 - > Parameters: 1(Integer)
21:03:42,745 DEBUG findById1:159 - < Total: 1
User1{username=‘lucy’}
21:03:42,753 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
21:03:42,754 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
21:03:42,754 DEBUG PooledDataSource:363 - Returned connection 599491651 to pool.
21:03:42,756 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.3333333333333333
User{id=1, username=‘lucy’, password=‘123’, birthday=‘2018-12-12’, birthday=null, names=null}
21:03:42,756 DEBUG UserMapper:62 - Cache Hit Ratio [com.itheima.dao.UserMapper]: 0.25
21:03:42,757 DEBUG JdbcTransaction:137 - Opening JDBC Connection
21:03:42,757 DEBUG PooledDataSource:398 - Checked out connection 599491651 from pool.
21:03:42,758 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@23bb8443]
21:03:42,764 DEBUG findById1:159 - ==> Preparing: select username from user where id= ?
21:03:42,765 DEBUG findById1:159 - > Parameters: 1(Integer)
21:03:42,766 DEBUG findById1:159 - < Total: 1
User1{username=‘lucy’}
UserMapper.xml
<cache
eviction="FIFO"
flushInterval="60000"
size="1"
readOnly="false"
/>
User1
import java.io.Serializable;
public class User1 implements Serializable {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "User1{" +
"username='" + username + '\'' +
'}';
}
}