一级缓存
一级缓存 是 SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结
构用于存储缓存数据。
不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不
同的sqlSession中的缓存是互相不能读取的。
一级缓存的工作原理:
用户发起查询请求,查找某条数据,sqlSession 先去缓存中查找,是否有该数据,如果有,读取;
如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。
但 sqlSession 执行commit,即增删改操作时会清空缓存。这么做的目的是避免脏读。
如果commit不清空缓存,会有以下场景:
A查询了某商品库存为10件,并将10件库存的数据存入缓存中,之后被客户买走了10件,数据被delete
了,但是下次查询这件商品时,并不从数据库中查询,而是从缓存中查询,就会出现错误。
那么为什么要提供二级缓存呢?
- 二级缓存是 mapper 级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个
SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的作用范围更大。 - 实际开发中,MyBatis 通常和 Spring 进行整合开发。每次查询之后都要进行关闭 sqlSession ,关闭之后数据被清空。所以 spring 整合之后,如果没有开启事务,一级缓存 是没有意义的。
二级缓存
二级缓存原理:
二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个Mapper的sql语句,多个 SqlSession
可以共用二级缓存,二级缓存是跨 SqlSession 的。
UserMapper有一个二级缓存区域(按 namespace 划分),每个 mapper 也有自己的二级缓存区域
(按namespace分)。
每一个 namespace 的 mapper 都有一个二级缓存区域,如果相同两个 mapper 的 namespace ,这两
个mapper执行sql查询到数据将存在相同的二级缓存区域中。
开启二级缓存
1,打开总开关
在MyBatis的全局配置文件中加入:
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2,在需要开启二级缓存的 mapper.xml 中加入 cache 标签 :
<cache/>
3,让使用二级缓存的 POJO 类实现 Serializable 接口
public class User implements Serializable {.... }
测试:
@Test public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findUserById(1);
System.out.println(user2); sqlSession2.close();
}
输出结果:
DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 103887628.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1 User [id=1, username=张三, sex=1, birthday=null, address=null]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@631330c]
DEBUG [main] - Returned connection 103887628 to pool.
DEBUG [main] - Cache Hit Ratio [com.iot.mybatis.mapper.UserMapper]: 0.5
User [id=1, username=张三, sex=1, birthday=null, address=null]
从打印的信息看出,两个sqlSession,去查询同一条数据,只发起一次select查询语句,第二次
直接从Cache中读取。
前面我们说到,Spring和MyBatis整合时, 每次查询之后都要进行关闭sqlSession,关闭之后数据被清
空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。
那么如果开启二级缓存,关闭 sqlsession 后,会把该 sqlsession 一级缓存中的数据添加到namespace
的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。
总结:
对于查询多、commit少且用户对查询结果实时性要求不高,此时采用 mybatis 二级缓存技术降低数据
库访问量,提高访问速度。
但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
二级缓存是建立在同一个 namespace下的,如果对表的操作查询可能有多个 namespace,那么得到的
数据就是错误的。
例如:
订单 和 订单详情 分别是 orderMapper、orderDetailMapper。
在查询订单详情(orderDetailMapper)时,我们需要把订单信息(orderMapper)也查询出来,那么
这个订单详情(orderDetailMapper)的信息被二级缓存在 orderDetailMapper 的 namespace中,这
个时候有人要修改订单的基本信息(orderMapper),那就是在 orderMapper 的 namespace 下修
改,他是不会影响到 orderDetailMapper 的缓存的,那么你再次查找订单详情时,拿到的是缓存的数
据,这个数据其实已经是过时的。
二级缓存的使用原则
1. 只能在一个命名空间下使用二级缓存
由于二级缓存中的数据是基于namespace的,即不同 namespace 中的数据互不干扰。
在多个namespace中存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一
致现象。
2. 在单表上使用二级缓存
如果一个表与其它表有关联关系,那么就非常有可能存在多个 namespace 对同一数据的操作。
而不同 namespace 中的数据相互干扰,所以就有可能出现多个 namespace 中的数据不一致现
象。
3. 查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓
存,对二级缓存的频繁刷新将降低系统性