Mybatis 缓存Cache

一、什么是缓存(Cache)

Cache是高速缓冲存储器 一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问。在Java中简单的理解缓存就是实现数据的拷贝,提高系统性能,减少数据库压力。

二、为什么要用缓存(Cache)

因为如果使用了缓存,在缓存中有需要的数据,就不用从数据库中获取转而从缓存中获取,这样就大大提高了系统性能。

三、一级缓存

Mybatis的一级缓存不需要任何配置,在每一个 SqlSession 当中都有一个一级缓存区,作用范围是 SqlSession 。
事务里面一个SqlSession

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@cfe17f6]
JDBC Connection [mpl.EConnection@12b4e6ec] will be managed by Spring

(一)原理

在这里插入图片描述
在这里插入图片描述

Mybatis一级缓存中,当第一次发起查询用户 ID 为1的用户信息,先去找缓存中是否有 ID 为1的用户信息,如果没有则从数据库查询用户信息,将用户信息存储到一级缓存中;如果 sqlSession 去执行插入、更新、删除(执行commit操作)就会清空SqlSession 中的一级缓存,这样做的目的为了让缓存存储的是最新的信息,避免脏读;当第二次发起查询用户 ID 为1的用户信息,先去找缓存中是否有 ID 为1的用户信息,如果缓存中有则直接从缓存中获取用户信息。
这里涉及到一个缓存命中率(Cache Hit Ratio),指的是在缓存中查询到的次数/总共在缓存中查询的次数。

(二)测试

@Test
public void testFirstCache() {
	SqlSession sqlsession = sqlSessionFactory.openSession();
	UserMapper mapper = sqlsession
			.getMapper(UserMapper.class);
	try {
		//第一次查询ID为1的用户信息
		User user = mapper.findUserById(1);
		System.out.println(user);
		
		//对用户信息进行修改后执行Commit操作
		user.setUsername("测试");
		mapper.updateUser(user);
		sqlsession.commit();
		
		//第二次查询ID为1的用户信息
		user = mapper.findUserById(1);
		System.out.println(user);
		
		//关闭 sqlsession
		sqlsession.close();
		
	} catch (Exception e) {
		e.printStackTrace();
	}
}

在第一次测试时注释代码user.setUsername(“测试”); mapper.updateUser(user); sqlsession.commit();,然后控制台输出的日志是:

DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 838411509.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE ID = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=张三, sex=null, birthday=null, address=null]
User [id=1, username=张三, sex=null, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
DEBUG [main] - Returned connection 838411509 to pool.
在将上述代码释放后,控制台日志是这样的:

DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 838411509.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE ID = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=张三, sex=null, birthday=null, address=null]
DEBUG [main] - ==>  Preparing: UPDATE USER SET USERNAME = ?, SEX = ?, BIRTHDAY = ?, ADDRESS = ? WHERE ID = ? 
DEBUG [main] - ==> Parameters: 测试(String), null, null, null, 1(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE ID = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=测试, sex=null, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@31f924f5]
DEBUG [main] - Returned connection 838411509 to pool.

可以看出在第二次查询时没有从数据库中查询,而是从缓存的读取的。

(三)应用
在正式环境下,我们要和Spring整合,而事务控制是在 service 中,一个 service 中会调用多个 Mapper 的方法,而在 service 方法开始时会启动一个 sqlSession ,然后执行 service 中的代码(可能会查询相同内容,这个时候就会用到一级缓存),最后会执行 sqlsession 的 close() 方法关闭。

四、二级缓存

Mybatis 的二级缓存需要手动开启才能启动,与一级缓存的最大区别就在于二级缓存的作用范围比一级缓存大,二级缓存是多个 sqlSession 可以共享一个 Mapper 的二级缓存区域,二级缓存作用的范围是 Mapper 中的同一个命名空间(namespace)的 statement 。在配置文件默认开启了二级缓存的情况下,如果每一个 namespace 都开启了二级缓存,则都对应有一个二级缓存区,同一个 namespace 共用一个二级缓存区。

(一)原理
在这里插入图片描述
在这里插入图片描述

在 Mybatis 开启二级缓存的情况下,当 sqlSession1 去查询用户 ID 为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中;当sqlSession2 去执行相同 Mapper 下的 statement ,执行 commit 提交,清空该 Mapper下的二级缓存区域的数据;当sqlSession3 去查询用户 ID 为1的用户信息,先去缓存中找是否存在数据,如果存在则直接从缓存中取出数据,不存在就去数据库查询读取。

(二)开启二级缓存

第一个需要配置的地方是配置文件SqlMapConfig.xml。(可以省略,因为它默认是开启的,配置的目的是方便维护)

<!-- 全局配置参数 -->
<settings>
	<!-- 开启二级缓存 -->
	<setting name="cacheEnabled" value="true"/>
</settings>

第二个是 Mapper 文件,在需要开启二级缓存的 statement 的命名空间(namespace)中配置标签<cache></cache>

<mapper namespace="com.mapper.UserMapper">
	<!-- 开启本 Mapper 下的 namespace 下的二级缓存 -->
	<cache></cache>
	
	<!-- 各种数据库操作 -->
</mapper>

(三)Cache参数

Cache标签有六个参数,其中:

参数名说明
type指定缓存(cache)接口的实现类型,当需要和ehcache整合时更改该参数值即可。
flushInterval刷新间隔。可被设置为任意的正整数,单位毫秒。默认不设置。
size引用数目。可被设置为任意正整数,缓存的对象数目等于运行环境的可用内存资源数目。默认是1024。
readOnly只读,true或false。只读的缓存会给所有的调用者返回缓存对象的相同实例。默认是false。
eviction缓存收回策略。LRU(最近最少使用的),FIFO(先进先出),SOFT( 软引用),WEAK( 弱引用)。默认是 LRU。

在 Mapper 中,在select中可设置useCache="false"来禁用缓存,默认是开启的;在insert、update、delete中设置flushCache="true"来清空缓存(刷新缓存),默认是会清空缓存。

(四)POJO类实现序列化

使 POJO 类实现Serializable 接口来实现序列化

public class User implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private int id;				//id
	private String username;	//用户名
	private String sex;			//性别
	private Date birthday;		//生日
	private String address;		//地址
	....
}

(五)测试

@Test
public void testSecondCache() {
	SqlSession sqlsession1 = sqlSessionFactory.openSession();
	UserMapper mapper1 = sqlsession1
			.getMapper(UserMapper.class);
	SqlSession sqlsession2 = sqlSessionFactory.openSession();
	UserMapper mapper2 = sqlsession2
			.getMapper(UserMapper.class);
	SqlSession sqlsession3 = sqlSessionFactory.openSession();
	UserMapper mapper3 = sqlsession3
			.getMapper(UserMapper.class);
	try {
		//sqlsession1查询ID为1的用户信息
		User user1 = mapper1.findUserById(1);
		System.out.println(user1);
		
		//关闭 sqlsession1
		sqlsession1.close();
		
		//sqlsession2查询ID为1的用户信息
		User user2 = mapper2.findUserById(1);
		//对用户信息进行修改后执行Commit操作
		user2.setUsername("张三");
		mapper2.updateUser(user2);
		sqlsession2.commit();
		
		//关闭 sqlsession2
		sqlsession2.close();
		
		
		//sqlsession3查询ID为1的用户信息
		User user3 = mapper3.findUserById(1);
		System.out.println(user3);
		
		//关闭 sqlsession3
		sqlsession3.close();
		
	} catch (Exception e) {
		e.printStackTrace();
	}
}

第一次测试先将关于 sqlsession2 的代码去掉,然后开始测试,在控制台打印出的日志如下:

DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Cache Hit Ratio [com.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 673068808.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE ID = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=张三, sex=null, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Returned connection 673068808 to pool.
DEBUG [main] - Cache Hit Ratio [com.mapper.UserMapper]: 0.5
User [id=1, username=张三, sex=null, birthday=null, address=null]

可以看出在 sqlsession1 进行查询 ID 为1的用户信息时,缓存命中率为 0 ,说明先是访问的缓存,读取缓存中是否有 ID 为1的用户数据,然后发现缓存中没有就去数据库里查出了用户信息;然后 sqlsession3 来查询 ID 为1的用户信息,发现缓存里有,就直接从缓存中读取了。
然后将关于 sqlsession3 的代码补上,进行第二次测试,控制台打印出的日志如下:

DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Cache Hit Ratio [com.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 673068808.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE ID = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=张三, sex=null, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Returned connection 673068808 to pool.
DEBUG [main] - Cache Hit Ratio [com.mapper.UserMapper]: 0.5
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 673068808 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - ==>  Preparing: UPDATE USER SET USERNAME = ?, SEX = ?, BIRTHDAY = ?, ADDRESS = ? WHERE ID = ? 
DEBUG [main] - ==> Parameters: 测试(String), null, null, null, 1(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Returned connection 673068808 to pool.
DEBUG [main] - Cache Hit Ratio [com.mapper.UserMapper]: 0.3333333333333333
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 673068808 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE ID = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=测试, sex=null, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
DEBUG [main] - Returned connection 673068808 to pool.

这说明在 sqlsession2 执行commit操作后将缓存的数据进行了清空,因为最后一次查询是从数据库中查询的。

(六)应用

二级缓存一般应用在对于访问多的查询请求且对查询结果的实时性要求不高的,此时可采用 Mybatis 二级缓存技术降低数据库访问量,提高访问速度。例如:耗时比较高的统计分析的sql。

参考:

https://blog.csdn.net/qq_24598601/article/details/83046468#t1 (Mybatis 缓存Cache)

https://tech.meituan.com/2018/01/19/mybatis-cache.html (聊聊MyBatis缓存机制)

https://tech.meituan.com/2018/01/19/mybatis-cache.html

http://c.biancheng.net/view/4304.htmlhttps://www.jianshu.com/p/eb16b7bfdf75

https://www.cnblogs.com/jian0110/p/9452592.html

https://www.cnblogs.com/jian0110/p/9387941.html

http://c.biancheng.net/view/4304.html (MyBatis 的工作原理)

https://blog.csdn.net/u013412772/article/details/73648537 (MyBatis常用对象SqlSessionFactory和SqlSession介绍和运用)

https://mybatis.org/mybatis-3/zh/logging.html (打印日志)

https://blog.csdn.net/u012373815/article/details/47069223 (mybatis的缓存机制(一级缓存二级缓存和刷新缓存)和mybatis整合ehcache)

https://www.cnblogs.com/wuzhenzhao/p/11103043.html

https://blog.csdn.net/qq_15006743/article/details/82464914