Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?

リリース: 2023-08-23 14:25:16
転載
798 人が閲覧しました

#キャッシュはどこで機能しますか?

個人的には、mybatis の一次キャッシュと二次キャッシュはあまり良い設計ではないと思っています。不適切な使用は多くの問題を引き起こす可能性があるため、今日は具体的に何が問題なのかを見てみましょう。

前のセクションでは、Executor が StatementHandler を呼び出して SQL を実行し、前のものと後のものの間のリンクとして機能することを紹介しました。 Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?Executor の設計は典型的なデコレータ パターンであり、SimpleExecutor と ReuseExecutor は特定の実装クラスであり、CachingExecutor はデコレータ クラスです。

特定のコンポーネント実装クラスには親クラス BaseExecutor があり、この親クラスはテンプレート パターンの典型的なアプリケーションであることがわかります。一次キャッシュを操作する操作はすべてこのクラス内にあり、データベースを操作するための特定の機能は、サブクラスに実装させます。

「2 次キャッシュはデコレータ クラスです。2 次キャッシュがオンになると、CachingExecutor が特定の実装クラスを修飾するために使用されるため、クエリを実行するときは、最初にクエリを実行する必要があります。 2 次キャッシュを取得してから、1 次キャッシュをクエリします。"Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?"では、1 次キャッシュと 2 次キャッシュの違いは何ですか?"

一次キャッシュ

Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?
// BaseExecutor
protected PerpetualCache localCache;
ログイン後にコピー

一次キャッシュは、次のメンバー変数 localCache です。 BaseExecutor (HashMap の単純なカプセル化) なので、一次キャッシュのライフサイクルは SqlSession と同じです。SqlSession に詳しくない場合は、データベースのセッションである JDBC プログラミングの Connection と比較できます。

「一级缓存和二级缓存key的构建规则是一致的,都是一个CacheKey对象,因为Mybatis中涉及动态SQL等多方面的因素,缓存的key不能仅仅通过String来表示」

当执行sql的如下4个条件都相等时,CacheKey才会相等

  1. mappedStatment的id
  2. 指定查询结构集的范围
  3. 查询所使用SQL语句
  4. 用户传递给SQL语句的实际参数值

「当查询的时候先从缓存中查询,如果查询不到的话再从数据库中查询」

org.apache.ibatis.executor.BaseExecutor#queryMybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?当使用同一个SqlSession执行更新操作时,会先清空一级缓存。因此一级缓存中内容被使用的概率也很低Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?

一级缓存测试

「看到美团技术团队上关于一级缓存和二级缓存的一些测试写的挺不错的,就直接引用过来了」

原文地址: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,代码如下所示:

Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?执行结果:Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?我们可以看到,只有第一次真正查询了数据库,后续的查询使用了一级缓存。

「实验2」

增加了对数据库的修改操作,验证在一次数据库会话中,如果对数据库发生了修改操作,一级缓存是否会失效。

Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?执行结果:Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?我们可以看到,在修改操作后执行的相同查询,查询了数据库,一级缓存失效。

「实验3」

开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库,验证一级缓存只在数据库会话内部共享。

Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?输出如下Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?sqlSession1和sqlSession2读的时相同的数据,但是都查询了数据库,说明了「一级缓存只在数据库会话层面共享」

sqlSession2更新了id为1的学生的姓名,从凯伦改为了小岑,但sqlSession1之后的查询中,id为1的学生的名字还是凯伦,出现了脏数据,也证明了之前的设想,一级缓存只在数据库会话层面共享

「MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement,即进行如下配置」

<setting name="localCacheScope" value="STATEMENT"/>
ログイン後にコピー

原因也很简单,看BaseExecutor的query()方法,当配置成STATEMENT时,每次查询完都会清空缓存Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?「看到这你可能会想,我用mybatis后没设置这个参数啊,好像也没发生脏读的问题啊,其实是因为你和spring整合了」

当mybatis和spring整合后(整合的相关知识后面还有一节)

  1. 在未开启事务的情况之下,每次查询,spring都会关闭旧的sqlSession而创建新的sqlSession,因此此时的一级缓存是没有起作用的
  2. 在开启事务的情况之下,spring使用threadLocal获取当前线程绑定的同一个sqlSession,因此此时一级缓存是有效的,当事务执行完毕,会关闭sqlSession

「当mybatis和spring整合后,未开启事务的情况下,不会有任何问题,因为一级缓存没有生效。当开启事务的情况下,可能会有问题,由于一级缓存的存在,在事务内的查询隔离级别是可重复读,即使你数据库的隔离级别设置的是提交读」

二级缓存

Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?
// 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,就用缓存装饰器装饰一下具体组件实现类,从而让二级缓存生效Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?「2.mapper映射文件中」mapper映射文件中如果配置了中的任意一个标签,则表示开启了二级缓存功能,没有的话表示不开启

<cache type="" eviction="FIFO" size="512"></cache>
ログイン後にコピー

二级缓存的部分配置如上,type就是填写一个全类名,用来指定二级缓存的实现类,这个实现类需要实现Cache接口,默认是PerpetualCache(你可以利用这个属性将mybatis二级缓存和Redis,Memcached等缓存组件整合在一起)

org.apache.ibatis.builder.MapperBuilderAssistant#useNewCacheMybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?

这个eviction表示缓存清空策略,可填选项如下

选项解释装饰器类
LRU最近最少使用的:移除最长时间不被使用的对象LruCache
FIFO先进先出:按对象进入缓存的顺序来移除它们FifoCache
SOFT软引用:移除基于垃圾回收器状态和软引用规则的对象SoftCache
WEAK弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象WeakCache

デコレータ パターンの一般的な実装では、キャッシュ クリア戦略を変更してデコレータを変更します。 Mybatis の 1 次キャッシュと 2 次キャッシュの使用が推奨されないのはなぜですか?「3.