mybatis-plus如何禁用一级缓存的方法

2025-05-29 0 38

前言

用过mybatis-plus的朋友可能会知道,mybatis-plus提供了多租户插件的功能,这个功能可以让开发人员不用手动写租户语句,由该插件自动帮你加上租户语句。今天的素材来源就是取自业务开发人员使用多租户插件时,遇到的一个神奇的问题

问题重现

业务开发人员要实现根据手机号码更新租户的密码功能,其代码形如下

?

1

2

3

4

5

6

7

8

9

10

11

12
for(Tenant t : tenantList){

ApplicationChainContext.getCurrentContext().put(ApplicationChainContext.TENANT_ID,t.getId()+"");

Optional<SaasUser> user = this.findByUserPhone(req.getUserPhone());

user.ifPresent(u -> {

count.getAndSet(count.get() + 1);

LambdaUpdateWrapper<SaasUser> wrapper = new LambdaUpdateWrapper<>();

String md5Pwd = Md5Utils.hash(req.getNewUserPwd());

wrapper.eq(SaasUser::getId,user.get().getId());

wrapper.set(SaasUser::getUserPwd,md5Pwd);

this.update(wrapper);

});

}

从代码上看起来没啥问题,因为使用了多租户插件,当我们执行this.findByUserPhone(req.getUserPhone());就会自动带上租户的信息。但在执行的时候发现一个问题,如下图

mybatis-plus如何禁用一级缓存的方法

从图中我们可以发现,当查询不到用户信息时,后续的查询操作,都没有sql语句出现。这说明2点,sql语句要么被系统吃了,要么系统没有执行sql

问题分析

前面说了sql语句没有打印出来,说明sql要么没执行,要么就sql语句被系统吃了。到底是哪种,与其猜测,倒不如去官方找找问题的答案,很遗憾在mybatis-plus官网或者issue上并没找到答案,于是只好跟踪源码进行分析。最后发现mybatis-plus果然如他官网介绍的只做增强不做改变,他最终调用查询的逻辑,走的是原生mybatis的查询逻辑。其查询的核心代码如下

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());

if (this.closed) {

throw new ExecutorException("Executor was closed.");

} else {

if (this.queryStack == 0 && ms.isFlushCacheRequired()) {

this.clearLocalCache();

}

List list;

try {

++this.queryStack;

list = resultHandler == null ? (List)this.localCache.getObject(key) : null;

if (list != null) {

this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

} else {

list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

}

} finally {

--this.queryStack;

}

if (this.queryStack == 0) {

Iterator var8 = this.deferredLoads.iterator();

while(var8.hasNext()) {

BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();

deferredLoad.load();

}

this.deferredLoads.clear();

if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

this.clearLocalCache();

}

}

return list;

}

}

从代码我们可以得出一个重要的信息,如下

?

1

2

3

4

5

6
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;

if (list != null) {

this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

} else {

list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

}

当resultHandler为空 ,list的取值是this.localCache.getObject(key),即会走本地缓存,而不会进行数据库查询

问题破解

从源码可以得知,原生的mybatis默认会走本地缓存,即所谓的一级缓存,而mybatis-plus作为mybatis的增强版,其逻辑和mybatis原生逻辑是一样的。那如何禁用mybatis-plus的一级缓存呢,从源码分析,我们可以得知,当list为空时,则不会走缓存,而会查询数据。而list的缓存取值,来源于
this.localCache.getObject(key)。因此禁用缓存的逆向思维就是要么清空localCache,要么就是变更key,使this.localCache.getObject(key)取到的值为null。因此解决方案至少有以下两种

方案一:清空localCache

那要怎么清空?

通过源码我们可以看到清空localCache的地方有两处,一处是

?

1

2

3
if (queryStack == 0 && ms.isFlushCacheRequired()) {

clearLocalCache();

}

另外一处是

?

1

2

3

4
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

// issue #482

clearLocalCache();

}

因此我们可以通过变更configuration.getLocalCacheScope()为STATEMENT进行清空。可以通过在yml做如下配置

?

1

2

3
mybatis-plus:

configuration:

local-cache-scope: statement

方案二:变更localcache的key,使this.localCache.getObject(key)取到的值为null

我们先看下key的构成

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {

if (this.closed) {

throw new ExecutorException("Executor was closed.");

} else {

CacheKey cacheKey = new CacheKey();

cacheKey.update(ms.getId());

cacheKey.update(rowBounds.getOffset());

cacheKey.update(rowBounds.getLimit());

cacheKey.update(boundSql.getSql());

List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

Iterator var8 = parameterMappings.iterator();

while(var8.hasNext()) {

ParameterMapping parameterMapping = (ParameterMapping)var8.next();

if (parameterMapping.getMode() != ParameterMode.OUT) {

String propertyName = parameterMapping.getProperty();

Object value;

if (boundSql.hasAdditionalParameter(propertyName)) {

value = boundSql.getAdditionalParameter(propertyName);

} else if (parameterObject == null) {

value = null;

} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {

value = parameterObject;

} else {

MetaObject metaObject = this.configuration.newMetaObject(parameterObject);

value = metaObject.getValue(propertyName);

}

cacheKey.update(value);

}

}

if (this.configuration.getEnvironment() != null) {

cacheKey.update(this.configuration.getEnvironment().getId());

}

return cacheKey;

}

}

从源码可以看出key是由statementId+原生sql+value(查询出来的对象)+ sqlsession.hashcode组成。

因此变更key,我们可以从原生sql入手。看到这边可能有朋友会说,租户id正常不都不一样吗,既然mybatis-plus通过多租户插件,其产生的sql语句不都不一样吗,进而产生的key不就都不一样了吗。如果跟踪源码就会发现其原生的sql是没有加上租户信息的。因此我们可以取巧在查询的sql语句中添加一个随机数,形如下

?

1

2

3

4

5

6

7
public Optional<SaasUser> findByUserPhone(String userPhone) {

LambdaQueryWrapper<SaasUser> wrapper = new LambdaQueryWrapper<>();

wrapper.eq(SaasUser::getUserPhone,userPhone);

wrapper.apply("{0} = {0}",UUID.randomUUID().toString());

SaasUser saasUser = getBaseMapper().selectOne(wrapper);

return Optional.ofNullable(saasUser);

}

即在原先的查询代码语句多加

?

1
wrapper.apply("{0} = {0}",UUID.randomUUID().toString());

此时sql语句如下

Preparing: SELECT id, user_code, user_name, main_accout_flag, user_pwd, user_phone, admin_flag, user_status, last_login_time, login_ip, pwd_update_time, tenant_id, create_date, created_by, created_by_id, last_updated_by, last_updated_by_id, last_update_date, object_version_number, delete_flag FROM saas_user WHERE delete_flag = 0 AND (user_phone = ? AND ? = ?) AND tenant_id = 424210194470490118
==> Parameters: 111111(String), edcda7fe-ee43-481a-90f7-8b41cb51a3d1(String), edcda7fe-ee43-481a-90f7-8b41cb51a3d1(String)

这样每次产生的sql就会不一样,导致取到不一样key,进而使this.localCache.getObject(key)为空,这样就可以让mybatis每次都进行数据库查询,从而达到禁用一级缓存的目的

总结

方案一的配置是基于全局配置,方案二是基于局部配置。就个人而言,是比较推荐方案二,即通过添加随机值的方式。因为mybatis配置一级缓存的意义,本身就是出于提供性能考虑。不过方案要站在业务的视角进行考虑,为了确保功能能正确运行,有时候牺牲一些性能也无伤大雅

到此这篇关于mybatis-plus如何禁用一级缓存的方法的文章就介绍到这了,更多相关mybatis-plus 禁用一级缓存内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!

原文链接:https://segmentfault.com/a/1190000039515272

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 mybatis-plus如何禁用一级缓存的方法 https://www.kuaiidc.com/108176.html

相关文章

发表评论
暂无评论