mybatis缓存机制(查询缓存)

本文详细解析MyBatis的一级和二级缓存机制,包括缓存的实现原理、测试方法及配置参数,帮助理解如何有效利用缓存提高数据库查询效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mybatis缓存机制(查询缓存)主要是减轻数据压力,提供数据库性能。

mybatis提供一级缓存和二级缓存

(1)一级缓存是sqlsession对象级别,在操作数据库是需要构造sqlsession对象,此对象中有个HashMap用于存储缓存数据,多个sqlsession间互不影响

mybatis默认支持一级缓存不需要配置。实现原理:

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。

如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

(1.1)一级缓存的测试:

mybatis默认支持一级缓存,不需要在配置文件去配置。
 

//测试一级缓存
@Test
public void testCache1() throws Exception{
	SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	
	//下边查询使用一个SqlSession
	//第一次发起请求,查询id为1的用户
	User user1 = userMapper.findUserById(1);
	System.out.println(user1.getUsername());

	//第二次发起请求,查询id为1的用户
	User user2 = userMapper.findUserById(1);
	System.out.println(user2.getUsername());
	
	sqlSession.close();

测试结果和输出日志:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 19140890.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@124111a] //关闭自动提交
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三
张三  //没有sql语句执行,说明是从一级缓存中读取
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@124111a]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@124111a]
DEBUG [main] - Returned connection 19140890 to pool.

可以看到第二次取user2的时候没有发出sql查询,说明调用了一级缓存

接下来测试看看在中间执行了增删改查的时候会不会清空一级缓存

//测试一级缓存
@Test
public void testCache1() throws Exception{
	SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	
	//下边查询使用一个SqlSession
	//第一次发起请求,查询id为1的用户
	User user1 = userMapper.findUserById(1);
	System.out.println(user1.getUsername());
	
	/*如果sqlSession去执行commit操作(执行插入、更新、删除),
	 * 清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
	 */
	
	//更新user1的信息
	user1.setUsername("测试用户22");
	userMapper.updateUser(user1);
	//执行commit操作去清空缓存
	sqlSession.commit();
	
	//第二次发起请求,查询id为1的用户
	User user2 = userMapper.findUserById(1);
	System.out.println(user2.getUsername());
	
	sqlSession.close();
	
}

 测试结果和输出日志:

DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 6778431.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@676e3f]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三
DEBUG [main] - ==>  Preparing: update user set username=?,birthday=?,sex=?,address=? where id=? 
DEBUG [main] - ==> Parameters: 测试用户2(String), 2015-06-07(Date), 男(String), 河南焦作(String), 1(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.Connection@676e3f] //提交后清空一级缓存,所以下面有执行sql语句查询
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
测试用户2
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@676e3f]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@676e3f]
DEBUG [main] - Returned connection 6778431 to pool.

发现第二次取数据时发出了sql请求,说明执行增删改查的时候会清空一级缓存 

(2)二级缓存
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)

//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭,一级缓存清除
}

如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为service方法结束,sqlSession就关闭,一级缓存就清空。这个时候就需要使用二级缓存

(2)二级缓存是mapper级别的,多个sqlsession间共享二级缓存。

(2.1)二级缓存需要手动配置开启,首先在全局配置文件mybatis-config.xml中开启二级缓存

<setting name="cacheEnabled"value="true"/>

(2.2)在mapper.xml中开启二缓存,mapper.xml下的sql执行完成会存储到它的缓存区,如:

<cache></cache>

(2.3)让使用二级缓存的POJO类实现Serializable接口

测试二级缓存

//测试二级缓存
@Test
public void testCache2() throws Exception{
	SqlSession sqlSession1 = sqlSessionFactory.openSession();
	SqlSession sqlSession2 = sqlSessionFactory.openSession();
	SqlSession sqlSession3 = sqlSessionFactory.openSession();
	
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);//创建代理对象
	//下边查询使用一个SqlSession
	//第一次发起请求,查询id为1的用户
	User user1 = userMapper1.findUserById(1);
	System.out.println(user1.getUsername());
	
	sqlSession1.close();//不关闭SqlSession无法写进二级缓存区域中
	
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);//创建代理对象
	//第二次发起请求,查询id为1的用户
	User user2 = userMapper2.findUserById(1);
	System.out.println(user2.getUsername());
	sqlSession2.close();
	
	
}

测试结果和输出日志: 

DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 26255574.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@190a0d6]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@190a0d6]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@190a0d6]
DEBUG [main] - Returned connection 26255574 to pool.
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5
张三

我们可以发现,日志输出中有一句我们之前没有见过,是:
Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0
这句的意思是缓存命中率为0.0,说明第一次是在缓存中找,可是没找到
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5
这句的意思是缓存命中率为0.5,说明第一次是在缓存中找,可是没找到,第二次找到了。

这个测试证明了二级缓存的存在

下面证明第三方sqlSession执行增删改会清空缓存的事实:

//测试二级缓存
@Test
public void testCache2() throws Exception{
	SqlSession sqlSession1 = sqlSessionFactory.openSession();
	SqlSession sqlSession2 = sqlSessionFactory.openSession();
	SqlSession sqlSession3 = sqlSessionFactory.openSession();
	
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);//创建代理对象
	//下边查询使用一个SqlSession
	//第一次发起请求,查询id为1的用户
	User user1 = userMapper1.findUserById(1);
	System.out.println(user1.getUsername());
	//不关闭SqlSession无法写进二级缓存区域中
	sqlSession1.close();
	
	UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);//创建代理对象
	User user=userMapper3.findUserById(1);
	user.setUsername("张明明");
	userMapper3.updateUser(user);
	
	sqlSession3.commit();
	sqlSession3.close();//执行提交,并关闭SqlSession,清空UserMapper二级缓存
	
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);//创建代理对象
	//第二次发起请求,查询id为1的用户
	User user2 = userMapper2.findUserById(1);
	System.out.println(user2.getUsername());
	sqlSession2.close();
	
	
}

输出结果和日志信息:

DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 189219.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Returned connection 189219 to pool.
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 189219 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - ==>  Preparing: update user set username=?,birthday=?,sex=?,address=? where id=? 
DEBUG [main] - ==> Parameters: 张明明(String), 2015-06-07(Date), 男(String), 河南焦作(String), 1(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Returned connection 189219 to pool.
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.3333333333333333
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 189219 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - ==>  Preparing: SELECT * FROM USER WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张明明
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Returned connection 189219 to pool.

发现第二次再去查1号用户的时候,又再一次查询了数据库,查到了最新的数据"张明明"。

二级缓存远远没有那么简单,他还有一些配置的参数。 

根据以上,想要使用二级缓存时需要想好两个问题:

1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读

2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。

使用二级缓存带来了数据的实时性问题,因为容易发生脏读。

注意:开启缓存的弊端是数据没有实时性,当数据库中的数据一旦修改,查询的数据还是缓存中的数据没有实时性,对于某些需要实时性显示数据的接口我们可以设置useCache="false",设置该属性后,该接口每次查询出来都是去执行sql查询出实时性数据。

设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。

<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">  
总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
②.清空缓存
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">  
总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。

注意:

(1)当为select语句时:

flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。

useCache默认为true,表示会将本条语句的结果进行二级缓存。

(2)当为insert、update、delete语句时:

flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。

useCache属性在该情况下没有。

当为select语句的时候,如果没有去配置flushCache、useCache,那么默认是启用缓存的,所以,如果有必要,那么就需要人工修改配置

3.对应的pojo实现序列化(implements Serializable)
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值