java mybatis缓存机制_Mybatis深入浅出之缓存机制

一、简述

MyBatis是常见的Java数据库访问层框架之一。MyBatis为提高其数据库查询性能,提供了缓存机制(查询缓存),包括一级缓存和二级缓存。由于项目的业务场景多样化以及分布式构架系统的普及,Mybatis缓存造成一些脏数据场景也是偶有发生,本文结合Mybatis源码以及官网等相关资料,帮助更多的开发人员熟悉掌握Mybatis缓存机制。

二、一级缓存

Mybatis在一次Sqlsession数据库查询之后,会将查询结果以键值对形式存储到内存中,当前Sqlsession后续以相同sql查询时,会直接去查询内存缓存,避免数据库查询,提高查询性能。

应用场景:当前线程同一个SqlSession会话中执行多次相同sql查询。

1、流程图:

43fcf3db39e005bd8a626eef537f0f77.png

2、 时序图:

80cda44add67abfa9452024b57918fbc.png

3、一级缓存特点

作用粒度:同一个SqlSession对象sql查询。

默认设置:Mybatis默认开启一级缓存。

作用:同一Sqlsession对象执行多次相同sql查询,避免数据库访问,提高查询性能,节省数据库访问开销。

清理方式:增删查改commit()都清理一级缓存(包括查询commit)

关闭方式:配置中localCacheScope设置为:STATEMENT。  即: ,默认数值:SESSION,即开启一级缓存。

脏读解决方案:方案1->关闭一级缓存;方案2->开启二级缓存。

4、验证

一级缓存命中

1>测试demo

e4bca761d3dc7baebb7ea83a3d3aafdd.png

2>配置

cd95e5412c31e6b89ad1afda60bb6508.png

3>运行结果

第一次查询进行数据库连接查询,第二次第三次查询为命中缓存查询。

c73ff7c024e04c2d4394512f1bb80d8c.png

清理一级缓存

afc3eaddfb7ade5b4e9292724c7735ec.png

执行结果:第一次查询之后,进行了commit操作,会清理一级缓存。因此第二次查询时仍然需要连接数据库,发送查询sql至数据库执行sql查询。

bcdce9dbdb7e14437ebc8b68e632ae1c.png

关闭一级缓存

46955c2bf356f9f8dd030458301a3195.png

执行结果:关闭一级缓存,同一SqlSession多次sql查询不共享缓存,每次都需要发送sql至数据库查询。

03ba898950c577eefaa513a3cca8216b.png

三、二级缓存

一级缓存其共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存,其共享范围为Namespace。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。

适用场景:多线程执行相同namespace下查询语句,直接查询二级缓存,避免数据库连接查询,提高性能。

1、流程图

6fe34fefd9578b8baa0a150631e05fbc.png

二级缓存开启后,同一个namespace下的所有sql操作语句,都影响着同一个Cache,即二级缓存可以被多个SqlSession共享,可以视为一个全局的变量。

二级缓存依赖于一级缓存,因此关闭一级缓存,即使二级缓存开启也没有数据。(sqlSession.close()执行完,Mybatis会将一级缓存对象拷贝存储到二级缓存)

查询执行顺序:二级缓存 -> 一级缓存 -> 数据库。

更新执行顺序:数据库->一级缓存->二级缓存。

public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throwsSQLException {

BoundSql boundSql=ms.getBoundSql(parameterObject);

CacheKey key=createCacheKey(ms, parameterObject, rowBounds, boundSql);returnquery(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

}public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throwsSQLException {

Cache cache=ms.getCache();if (cache != null) {

flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {

ensureNoOutParams(ms, parameterObject, boundSql);

@SuppressWarnings("unchecked")

List list = (List) tcm.getObject(cache, key);if (list == null) {

list= delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

tcm.putObject(cache, key, list);//issue #578 and #116

}returnlist;

}

}

2、开启二级缓存

步骤一:配置setting,即:

步骤二:指定namespace配置开启,即:

publicExecutor newExecutor(Transaction transaction, ExecutorType executorType) {

executorType= executorType == null ?defaultExecutorType : executorType;

executorType= executorType == null ?ExecutorType.SIMPLE : executorType;

Executor executor;if (ExecutorType.BATCH ==executorType) {

executor= new BatchExecutor(this, transaction);

}else if (ExecutorType.REUSE ==executorType) {

executor= new ReuseExecutor(this, transaction);

}else{

executor= new SimpleExecutor(this, transaction);

}if(cacheEnabled) {

executor= newCachingExecutor(executor);

}

executor=(Executor) interceptorChain.pluginAll(executor);returnexecutor;

}

2b4a14eda950bc851c216d70eeb83d17.png

f0f02bfc665a3703b3152783cd00f8fc.png

cad5dfa977d3cbb499e64a601797a149.png

执行结果:sqlSession2查询命中二级缓存。命中率为:1/2=0.5。

b575bd12f069d4f857fb912e4f6b09a2.png

注意:

1、二级缓存依赖于一级缓存写入,不关闭sqlSession1,不会写入当前namespace的二级缓存,造成二级缓存失效。

2、Mybatis二级缓存对象存储在硬盘中,因此需要namespace下实体对象序列化,如果不序列话运行会报错:

Exception in thread "main" org.apache.ibatis.cache.CacheException:

Error serializing object. Cause: java.io.NotSerializableException: ****。

解决方案:namespace对应的实体类实现序列化接口

public class TruckInfo implements Serializable {}

序列化:将数据从内存存储到硬盘。(注意:类的属性为实例或者有父类都需要序列化,即为级联序列化,避免数据部分丢失)

反序列化:将数据从硬盘存储到内存。

3、二级缓存特点

Mybatis默认关闭二级缓存。

作用粒度:相同namespace下所有sql语句共享缓存,即:支持不同sqlSession共享缓存。

数据写入时机:sqlSession.close(),即:sql会话关闭后,统一将当前namespace下的一级缓存写入二级缓存。节约I/O,提高I/O性能。

二级缓存清理:与清理一级缓存相同,执行commit()方法,但是仅仅针对增删改commit,查询commit无法触发清理。

关闭单个查询语句二级缓存方式:xml sql语句设置关闭useCache="false"。

1>二级缓存清理:

8d001f67c67c2200296998a86a2300e8.png

1f2ef7b6d65133d90079ddc7687e3f26.png

2>关闭单个sql查询二级缓存

cb4e04397397a900e3883e89c7fcec46.png

49b1837fd59c0c3ae46a069fc03cd4df.png

1b83a9fa4e6c091b1e0992e1e2a0e2aa.png

关闭了二级缓存查询,即使存储了缓存也不起作用。

4、二级缓存特殊场景

多个sql.xml文件中namespace相同,则这些xml产生的Mapper对象仍然共享同一个namespace二级缓存。

两张表TA,其namespaceA、TB其namespaceB,A表的部分sqlA不规范编写到namespaceB中,则该部分sqlA的二级缓存存储在namespaceB中,当使用namespaceA二级缓存时可能造成脏数据,因为部分更新缓存存储在namespaceB中。

解决方案:将namespaceA引用关联到namespaceB中,保证两个映射文件对应的SQL操作都使用的是同一块缓存。

增加缓存作用域与缓存粒度,多个Mapper namespace下的所有sql操作都会对缓存使用造成影响。

应对分布式部署项目,一个应用多个实例机器造成脏数据(二级缓存)。

解决方案:<1>禁用二级缓存,<2>使用分布式缓存中间件。

5、二级缓存中间件

当前有不少第三方二级缓存中间件,比如:memcache、ehcache等,根据Mybatis规范要求,整合第三方二级缓存中间件,必须实现org.apache.ibatis.cache接口。

缓存中间件的一个优点在于:可以设置二级缓存为内存缓存,减少了I/O消耗,提高了查询性能。

四、缓存比较

对比项

一级缓存

二级缓存

作用粒度

sqlSession

nameSpace

开启方式

默认开启

配置开启

存储方式

内存

硬盘

清理时机

增删改查commit()

增删改commit()

存储时机

执行完sql

sqlSession.close()

关闭方式

设置statement模式

不启用->全局关闭

设置useCache="false"->单个sql查询关闭

脏数据场景

多线程并发场景,不同线程不同sqlSession更新数据

分布式多实例机器部署

namespace不规范编写其他表sql

五、验证代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值