Mybatis 默认开启一级缓存,其一级缓存是SqlSession级别的,sqlSession级别的缓存,意味着伴随着sqlSession的生死。
一级缓存的作用:当使用同一个sqlSession对数据库做相同的查询时,第一次查询的结果会放入缓存,在缓存中是以Map的形式存放的,当后面相同的查询到来时就会去缓存中取数据,而不再查询数据库。
注意:这里的后面相同的查询到来时就会去缓存中取数据是有条件的,条件就是在第一次查询后和后几次的相同查询之间没有增删改操作,否则缓存中的数据会被清除,后来的相同查询会因在缓存中找不到缓存结果而去数据库中查询。
mybatis认为:对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。
1. 传入的statementId。
2. 查询时要求的结果集中的结果范围。
3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )。
4. 传递给java.sql.Statement要设置的参数值。
测试一下不加事务时,两次相同的查询一级缓存的情况:
mapper
<select id="getItem" parameterType="java.lang.Long" resultType="com.lz.springboot.pojo.Item">
select * from tb_item WHERE id = #{id}
</select>
service
public Item getItem(long id){
itemMapper.getItem(id);
return itemMapper.getItem(id);
}
日志打印(截取了一部分):
可以明显的看出是对数据库操作了两次,也就是说mybatis的一级缓存没有起到作用,是什么原因呢,从标记处可以看到在第一次查询时create一个SqlSession,然后从数据库连接池中fetch jdbc的连接,然后去数据库中查询,紧接着一句closing no transational SqlSession 这里就是问题的所在也就是说每一次查询完数据库后,SqlSession被关闭,上面说过一级缓存是SqlSession级别的缓存,一级缓存伴随着SqlSession的关闭,也就失效了,第二次相同的查询到来时会重复上面的步骤。也可以看出在不加事务时,mybatis会认为每一次对数据库的查询都是一次回话,多次就是多个回话,多个SqlSession。
加事务时,两次相同的查询一级缓存的情况:
此时service
@Transactional
public Item getItem(long id){
itemMapper.getItem(id);
return itemMapper.getItem(id);
}
日志打印:
与不加事务时的日志有所不同,可以看出只对数据库操作了一次。一开始也是新建一个SqlSession,接下来一步是为sqlsession注册事务同步,然后再去查询数据库,然后接下来是最大不同,mybatis并没有将当前的sqlsession关闭,而是Releasing transactional SqlSession,减少SqlSession的引用次数(每次对数据库的操作都是使用当前SqlSession的引用,这里的减少次数就是使用完之后将此次的引用销毁),当第二次相同的查询的时候,从当前的事务中fetch SqlSession,然后去缓存中取数据,减少SqlSession的引用次数,然后提交事务,最后关闭SqlSession。在这里加上了事务后,两次的相同查询会被当做一次回话,两次查询也就会使用同一个SqlSession,缓存也就有效。
事务中,在两次相同查询之间有增删改操作时的情况
在原来的基础上添加更新操作
mapper 添加
<update id="updateById" parameterType="java.lang.Long">
update tb_item SET price=29900000 WHERE id=#{id}
</update>
service 添加
@Transactional
public Item getItem(long id){
itemMapper.getItem(id);
itemMapper.updateById(id);
return itemMapper.getItem(id);
}
日志打印:
可以就算是有事务,相同的查询也会对数据库操作两次,原因就是在两次相同的查询之间有更新操作,会导致SqlSession缓存中的数据被清空,当第二次查询到来时,缓存中没有缓存数据,所以又去查询了数据库。
此次和不加事务缓存失效是两种不同的情况:不加事务时是因为两次查询不是同一个SqlSession,更直接的说就是不是同一个缓存。这次是同一个缓存,但是中间的增删改操作到时缓存数据失效被清空。
由于没有能力分析源码,所以不能从源码的角度分析为什么这样。
个人认为在开发中即使是查询service也应该加入事务,尤其是对于一个service中存在多个查询操作,优点是当存在着相同的查询并且两次查询之间不存在增删改操作时,可以使用到缓存,即使是不相同的查询,也会减少SqlSession实例的创建和销毁,能够提升效率,减少时间的浪费。