개인적으로 마이바티스의 1단계 캐시와 2단계 캐시는 별로 좋은 디자인이 아니라고 생각합니다. 저는 기본적으로 직장에서 1단계 캐시와 2단계 캐시를 사용하지 않기 때문입니다. 잘못 사용하면 많은 문제가 발생할 수 있으니 오늘은 어떤 문제가 있는지 살펴볼까요?
이전 섹션에서는 Executor가 SQL을 실행하기 위해 StateHandler를 호출한다고 소개했는데, 이는 이전과 다음을 연결하는 역할을 합니다. Executor의 디자인은 전형적인 데코레이터 패턴입니다. SimpleExecutor와 ReuseExecutor는 구체적인 구현 클래스인 반면 CachingExecutor는 데코레이터 클래스입니다.
특정 컴포넌트 구현 클래스에는 상위 클래스인 BaseExecutor가 있는 것을 볼 수 있는데, 이 상위 클래스는 템플릿 패턴의 일반적인 응용 프로그램이며, 1단계 캐시를 운영하는 작업은 모두 이 클래스에 있으며, 데이터베이스를 운영하는 것은 서브클래스의 몫입니다.
"두 번째 수준 캐시는 데코레이터 클래스입니다. 두 번째 수준 캐시가 활성화되면 CachingExecutor를 사용하여 특정 구현 클래스를 장식하므로 쿼리할 때 두 번째 수준 캐시를 먼저 쿼리한 다음 1단계 캐시" "1단계 캐시와 2단계 캐시의 차이점은 무엇인가요?"
// BaseExecutor protected PerpetualCache localCache;
첫번째- 레벨 캐시는 BaseExecutor의 멤버 변수 localCache(HashMap 간단한 캡슐화용)이므로 첫 번째 레벨 캐시의 수명 주기는 SqlSession과 동일합니다. SqlSession에 익숙하지 않은 경우 JDBC 프로그래밍의 Connection과 비교할 수 있습니다. , 이는 데이터베이스의 세션입니다.
「一级缓存和二级缓存key的构建规则是一致的,都是一个CacheKey对象,因为Mybatis中涉及动态SQL等多方面的因素,缓存的key不能仅仅通过String来表示」
当执行sql的如下4个条件都相等时,CacheKey才会相等
「当查询的时候先从缓存中查询,如果查询不到的话再从数据库中查询」
org.apache.ibatis.executor.BaseExecutor#query当使用同一个SqlSession执行更新操作时,会先清空一级缓存。因此一级缓存中内容被使用的概率也很低
「看到美团技术团队上关于一级缓存和二级缓存的一些测试写的挺不错的,就直接引用过来了」
原文地址:https://tech.meituan.com/2018/01/19/mybatis-cache.html 测试代码github地址:https://github.com/kailuncen/mybatis-cache-demo
接下来通过实验,了解MyBatis一级缓存的效果,每个单元测试后都请恢复被修改的数据。
首先是创建示例表student,创建对应的POJO类和增改的方法,具体可以在entity包和mapper包中查看。
CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(200) COLLATE utf8_bin DEFAULT NULL, `age` tinyint(3) unsigned DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
在以下实验中,id为1的学生名称是凯伦
「实验1」
开启一级缓存,范围为会话级别,调用三次getStudentById,代码如下所示:
执行结果:我们可以看到,只有第一次真正查询了数据库,后续的查询使用了一级缓存。
「实验2」
增加了对数据库的修改操作,验证在一次数据库会话中,如果对数据库发生了修改操作,一级缓存是否会失效。
执行结果:我们可以看到,在修改操作后执行的相同查询,查询了数据库,一级缓存失效。
「实验3」
开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库,验证一级缓存只在数据库会话内部共享。
输出如下sqlSession1和sqlSession2读的时相同的数据,但是都查询了数据库,说明了「一级缓存只在数据库会话层面共享」
sqlSession2更新了id为1的学生的姓名,从凯伦改为了小岑,但sqlSession1之后的查询中,id为1的学生的名字还是凯伦,出现了脏数据,也证明了之前的设想,一级缓存只在数据库会话层面共享
「MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement,即进行如下配置」
<setting name="localCacheScope" value="STATEMENT"/>
原因也很简单,看BaseExecutor的query()方法,当配置成STATEMENT时,每次查询完都会清空缓存「看到这你可能会想,我用mybatis后没设置这个参数啊,好像也没发生脏读的问题啊,其实是因为你和spring整合了」
当mybatis和spring整合后(整合的相关知识后面还有一节)
「当mybatis和spring整合后,未开启事务的情况下,不会有任何问题,因为一级缓存没有生效。当开启事务的情况下,可能会有问题,由于一级缓存的存在,在事务内的查询隔离级别是可重复读,即使你数据库的隔离级别设置的是提交读」
// Configuration protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
「而二级缓存是Configuration对象的成员变量,因此二级缓存的生命周期是整个应用级别的。并且是基于namespace构建的,一个namesapce构建一个缓存」
「二级缓存不像一级缓存那样查询完直接放入一级缓存,而是要等事务提交时才会将查询出来的数据放到二级缓存中。」
因为如果事务1查出来直接放到二级缓存,此时事务2从二级缓存中拿到了事务1缓存的数据,但是事务1回滚了,此时事务2不就发生了脏读了吗?
「二级缓存的相关配置有如下3个」
「1.mybatis-config.xml」
<settings> <setting name="cacheEnabled" value="true"/> </settings>
这个是二级缓存的总开关,只有当该配置项设置为true时,后面两项的配置才会有效果
从Configuration类的newExecutor方法可以看到,当cacheEnabled为true,就用缓存装饰器装饰一下具体组件实现类,从而让二级缓存生效「2.mapper映射文件中」mapper映射文件中如果配置了62aecd17e676a41d3547c3bf97bb07b0和c9f60baecdceda902422ce5608e73ae9中的任意一个标签,则表示开启了二级缓存功能,没有的话表示不开启
<cache type="" eviction="FIFO" size="512"></cache>
二级缓存的部分配置如上,type就是填写一个全类名,用来指定二级缓存的实现类,这个实现类需要实现Cache接口,默认是PerpetualCache(你可以利用这个属性将mybatis二级缓存和Redis,Memcached等缓存组件整合在一起)
org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache
这个eviction表示缓存清空策略,可填选项如下
选项 | 解释 | 装饰器类 |
---|---|---|
LRU | 最近最少使用的:移除最长时间不被使用的对象 | LruCache |
FIFO | 先进先出:按对象进入缓存的顺序来移除它们 | FifoCache |
SOFT | 软引用:移除基于垃圾回收器状态和软引用规则的对象 | SoftCache |
WEAK | 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象 | WeakCache |
데코레이터 패턴의 일반적인 구현, 캐시 지우기 전략 변경은 데코레이터를 변경하는 것입니다. 『3.
이 속성은 쿼리에 의해 생성된 결과를 2차 캐시에 저장해야 하는지 여부를 나타냅니다. 이 구성은 true입니다. 2차 캐시를 문장 레벨로 나누어
2차 캐시는 네임스페이스, 즉 하나의 매퍼 매핑 파일을 기준으로 구현됩니다. 하나의 캐시를 사용합니다
이 실험에서는 ID가 1인 학생의 이름을 가리켜 초기화했습니다.
「실험 1」
트랜잭션을 제출하지 않고 sqlSession1이 데이터를 쿼리한 후 동일한 sqlSession2 쿼리가 캐시에서 데이터를 가져오는지 테스트합니다. 실행 결과:sqlsession이 commit() 메서드를 호출하지 않는 경우 2단계 캐시가 역할을 하지 않는 것을 확인할 수 있습니다.
「실험 2」
트랜잭션을 제출할 때 sqlSession1이 데이터를 쿼리한 후 동일한 sqlSession2 쿼리가 캐시에서 데이터를 가져오는지 여부를 테스트합니다. 그림에서 볼 수 있듯이 sqlsession2의 쿼리는 캐시를 사용하며, 캐시 적중률은 0.5입니다.
「실험 3」
업데이트 작업이 네임스페이스 아래의 두 번째 수준 캐시를 새로 고치는지 테스트합니다.
sqlSession3이 데이터베이스를 업데이트하고 트랜잭션을 제출한 후 sqlsession2의 StudentMapper 네임스페이스에 있는 쿼리가 캐시가 아닌 데이터베이스로 이동하는 것을 볼 수 있습니다.
「실험 4」
验证MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况。
getStudentByIdWithClassInfo的定义如下
通常我们会为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace的,多表查询语句所在的namspace无法感应到其他namespace中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。
执行结果:
在这个实验中,我们引入了两张新的表,一张class,一张classroom。class中保存了班级的id和班级名,classroom中保存了班级id和学生id。我们在StudentMapper中增加了一个查询方法getStudentByIdWithClassInfo,用于查询学生所在的班级,涉及到多表查询。在ClassMapper中添加了updateClassName,根据班级id更新班级名的操作。
当sqlsession1的studentmapper查询数据后,二级缓存生效。保存在StudentMapper的namespace下的cache中。当sqlSession3的classMapper的updateClassName方法对class表进行更新时,updateClassName不属于StudentMapper的namespace,所以StudentMapper下的cache没有感应到变化,没有刷新缓存。当StudentMapper中同样的查询再次发起时,从缓存中读取了脏数据。
「实验5」
为了解决实验4的问题呢,可以使用Cache ref,让ClassMapper引用StudenMapper命名空间,这样两个映射文件对应的SQL操作都使用的是同一块缓存了。
mapper文件中的配置如下
<cache-ref namespace="mapper.StudentMapper"/>
执行结果:
不过这样做的后果是,缓存的粒度变粗了,多个Mapper namespace下的所有操作都会对缓存使用造成影响。
mybatis의 1단계 캐시와 2단계 캐시는 모두 로컬 기반이므로 분산 환경에서는 더티 읽기가 필연적으로 발생합니다.
2차 캐시는 Cache 인터페이스를 구현하여 캐시를 중앙에서 관리하고 더티 읽기를 방지할 수 있지만 일정 개발 비용이 들고 다중 테이블 쿼리에 사용할 경우 부적절하게 사용하면 더티 데이터가 나타날 가능성이 매우 높습니다
위 내용은 Mybatis의 1단계 캐시와 2단계 캐시를 사용하지 않는 것이 왜 권장되지 않습니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!