MyBatis有一级缓存和二级缓存。一级缓存默认开启,二级缓存默认关闭。
一级缓存基于SqlSession,只要是同一个SqlSession,执行相同的SQL语句会返回缓存中的结果,从而减少数据库访问次数。需要注意的是,如果使用了延迟加载,则会出现无法使用一级缓存的情况,即使查询同一个对象也会多次查库。为避免这种情况,要谨慎使用延迟加载,比如学生
对象中有班级
属性,大部分学生
的班级
是相同的,这时就不应该对学生
中的班级
属性使用延迟加载技术。
二级缓存在大部分情况下都不好用。理论上MyBatis的二级缓存只能用在单表上,即一个不和任何表相关联的表,而大部分表都不可能没有关联。
二级缓存的机制是,一个namespace使用一个缓存;同一个namespace下,缓存所有select的查询结果;一旦namespace下有update或delete语句被执行,即清空该namespace下的所有缓存。
这个特征导致了有关联的表不能使用二级缓存。比如上面的例子,学生
表关联了班级
表,这种情况就不能用二级缓存。因为当班级
表修改时,只会清空班级
表的缓存,学生
表的缓存还保留着老的班级
表数据。
此时如果想让缓存可用,则必须让学生
表和班级
表使用同一个缓存,修改班级
表时,同时清空班级
表和学生
表的缓存。使用自定义缓存可以做到这点。
使用了二级缓存的对象,不能再使用延迟加载。因为被缓存的对象需要进行序列化,在读取缓存时,碰到延迟加载的属性就会报错。
如果使用本地缓存,可以将缓存设置成readonly,这样可以不进行序列化。但如果使用redis集群缓存,则必须进行序列化。
所以使用延迟加载的对象,就不要再对属性使用延迟加载技术。
需要缓存的对象必须实现Serializable
接口,如果实体类继承了某个有属性的父类,那么这个父类也必须实现Serializable
接口,否则相应字段无法被缓存。
另外还需注意,必须显式设置serialVersionUID
的值。如果显式设置serialVersionUID
的值,虚拟机会自动计算一个值。实体类一旦有改动,这个自动计算的值就会变化,导致反序列化失败。同样,有父类的,父类也需指定serialVersionUID
的值。
使用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"/>