MyBatis二级缓存

2022-11-24 12:01 阅读

MyBatis有一级缓存和二级缓存。一级缓存默认开启,二级缓存默认关闭。

一级缓存基于SqlSession,只要是同一个SqlSession,执行相同的SQL语句会返回缓存中的结果,从而减少数据库访问次数。需要注意的是,如果使用了延迟加载,则会出现无法使用一级缓存的情况,即使查询同一个对象也会多次查库。为避免这种情况,要谨慎使用延迟加载,比如学生对象中有班级属性,大部分学生班级是相同的,这时就不应该对学生中的班级属性使用延迟加载技术。

二级缓存在大部分情况下都不好用。理论上MyBatis的二级缓存只能用在单表上,即一个不和任何表相关联的表,而大部分表都不可能没有关联。

二级缓存机制

二级缓存的机制是,一个namespace使用一个缓存;同一个namespace下,缓存所有select的查询结果;一旦namespace下有update或delete语句被执行,即清空该namespace下的所有缓存。

这个特征导致了有关联的表不能使用二级缓存。比如上面的例子,学生表关联了班级表,这种情况就不能用二级缓存。因为当班级表修改时,只会清空班级表的缓存,学生表的缓存还保留着老的班级表数据。

此时如果想让缓存可用,则必须让学生表和班级表使用同一个缓存,修改班级表时,同时清空班级表和学生表的缓存。使用自定义缓存可以做到这点。

二级缓存与延迟加载

使用了二级缓存的对象,不能再使用延迟加载。因为被缓存的对象需要进行序列化,在读取缓存时,碰到延迟加载的属性就会报错。

如果使用本地缓存,可以将缓存设置成readonly,这样可以不进行序列化。但如果使用redis集群缓存,则必须进行序列化。

所以使用延迟加载的对象,就不要再对属性使用延迟加载技术。

二级缓存与序列化

需要缓存的对象必须实现Serializable接口,如果实体类继承了某个有属性的父类,那么这个父类也必须实现Serializable接口,否则相应字段无法被缓存。

另外还需注意,必须显式设置serialVersionUID的值。如果显式设置serialVersionUID的值,虚拟机会自动计算一个值。实体类一旦有改动,这个自动计算的值就会变化,导致反序列化失败。同样,有父类的,父类也需指定serialVersionUID的值。

二级缓存与Devtools

使用SpringBoot进行开发,并使用了Devtools,会出现ClassCastException异常。这是因为DevTools会改变ClassLoader,导致反序列化时,出现类型不匹配的情况。

使用MyBatis自带的二级缓存实现,可以新建/src/main/resource/META-INF/spring-devtools.propertis,加上以下内容:

restart.include.mybatis=/mybatis-[\\w-\\.]+\\.jar

如果是自行实现的缓存,则使用以下方法进行反序列化:

public static <T> T deserialize(final byte[] bytes) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    try (ObjectInputStream in = new ConfigurableObjectInputStream(new ByteArrayInputStream(bytes), classLoader)) {
        @SuppressWarnings("unchecked") final T obj = (T) in.readObject();
        return obj;
    } catch (final ClassNotFoundException | IOException ex) {
        throw new RuntimeException(ex);
    }
}

开启与关闭

mapper.xml中设置<cache/>即可开启该namespace的二级缓存。

<mapper namespace="...">
  <cache/>
  ...
</mapper>

在每个方法中,也可以设置缓存的使用情况。默认设置如下:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

application.yaml中可以统一关闭二级缓存。

# MyBatis是否开启二级缓存。默认:true
#mybatis.configuration.cache-enabled: false

自定义缓存

只需要实现MyBatis的Cache接口,即可在二级缓存中使用自定义缓存。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

并在mapper.xml中指定自己实现的二级缓存:

<cache type="com.domain.something.MyCustomCache"/>

使用原则

  • 只在读取远大于写入的表中使用二级缓存。
  • 相互关联的表必须使用同一个缓存,以免脏读。
  • 设置缓存时间,时间不要太长,如10分钟。以免直接修改数据库数据后,必须长时间等待生效,甚至重启应用。
QQ咨询
电话
微信
微信扫码咨询