全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-10-04_MybatisPlus Sql Inject魔法

您的位置:首页 >> 新闻 >> 行业资讯

MybatisPlus Sql Inject魔法 ?本篇只讲应用,喜欢原理的可以不用看只保证Mysql可用,没有兼容其余数据库的方言代码笔者线上环境已用,非纸上谈兵?背景介绍笔者日常的工作有些业务会遇到唯一索引约束。注意到: 业务功能相似,要求当唯一键存在时根据唯一键更新,否则直接插入「Mysql DUPLICATE KEY UPDATE」天然支持上述功能由此引发笔者做出我司公共组件,用来提高开发效率。 常见做法保证业务安全,还要保证平稳更新与写入,遇到上述需求一般方案是使用分布式锁。步骤如下: 根据唯一索引查询,如果数据存在直接更新,完成写入数据不存在则加分布锁根据唯一索引查询,如果数据存在直接更新,反之插入数据,完成写入解锁简化代码如下: publicvoidduplicateKeyUpdate(NeedInsertModel model){ NeedInsertModel dbModel = findByUnique(model); /* 这里没考虑此刻恰好数据被别的线程删除的场景 */ if(Objects.nonNull(dbModel)) { updateByUnique(model); return; } String lockKey = lockKey(); RLocklock= redisClient.getLock(lockKey); lock.lock(); try{ dbModel = findByUnique(model); if(Objects.nonNull(dbModel)) { updateByUnique(model); }else { insert(model); } }finally{ lock.unlock(); }}可以看到为了达成「当唯一键存在时根据唯一键更新,否则直接插入」这一简单目的,在多线程,多进程场景下会产生很多套版代码(没什么不好,只是笔者懒惰成性)。 Sql Inject新思路我想到MybatisPlus的源码中这么写道: 只要继承该接口,就自动具备基础的CRUD功能,这是因为框架帮你生成了代理对象。可是问题是Mybatis Plus怎么知道要生成哪些代理方法,其中的代理逻辑又是哪里定义的呢?后来查资料发现,Mybatis Plus定义了一系列的「com.baomidou.mybatisplus.core.injector.AbstractMethod」对象来定制具体的逻辑,也就是生成SQL的逻辑。每一个实现类对应BaseMapper的一个方法。 选取最简单SelectById分析,其余的原理相同,其实就是拼接SQL语句。 看到这里,笔者当时就想,我直接按照官方的规范定制一个「AbstractMethod」的实现不就可以一劳永逸嘛。 经过研究代码如下: importcom.baomidou.mybatisplus.annotation.IdType;importcom.baomidou.mybatisplus.core.injector.AbstractMethod;importcom.baomidou.mybatisplus.core.metadata.TableFieldInfo;importcom.baomidou.mybatisplus.core.metadata.TableInfo;importcom.baomidou.mybatisplus.core.metadata.TableInfoHelper;importcom.baomidou.mybatisplus.core.toolkit.StringUtils;importcom.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;importorg.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;importorg.apache.ibatis.executor.keygen.KeyGenerator;importorg.apache.ibatis.executor.keygen.NoKeyGenerator;importorg.apache.ibatis.mapping.MappedStatement;importorg.apache.ibatis.mapping.SqlSource; importjava.util.Objects;importjava.util.function.Function;importjava.util.function.Predicate;importjava.util.stream.Collectors; /***@authorRaphael*@since2025/7/15 19:49*/publicclassDuplicateInserterextendsAbstractMethod{ /** * 创建时间应该不要被更新 */ privatestaticfinalStringCREATE_TIME="create_time"; /** * 新注入的方法名 */ privatestaticfinalStringMETHOD_NAME="duplicateUpdate"; /** * 更新字段集的sql片段 */ privatestaticfinalStringSEGMENT=" = VALUES("; /** * SQL模板:INSERT INTO 表名 字段集合 VALUES 值集合 ON DUPLICATE KEY UPDATE 更新字段集 */ privatestaticfinalStringFORMAT="script"+ "\nINSERT INTO %s %s VALUES %s ON DUPLICATE KEY UPDATE %s\n"+ "/script"; publicDuplicateInserter(){ super(METHOD_NAME); } @Override publicMappedStatementinjectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo){ StringinsertColumns=tableInfo.getAllInsertSqlColumnMaybeIf(EMPTY); StringinsertValues=tableInfo.getAllInsertSqlPropertyMaybeIf(EMPTY); StringinsertColumnsTrim=SqlScriptUtils.convertTrim( insertColumns, LEFT_BRACKET, RIGHT_BRACKET, null, COMMA StringinsertValuesTrim=SqlScriptUtils.convertTrim( insertValues, LEFT_BRACKET, RIGHT_BRACKET, null, COMMA StringkeyProperty=tableInfo.getKeyProperty(), keyColumn = tableInfo.getKeyColumn(); /* 过滤掉主键和create_time字段 */ PredicateTableFieldInfo needConcat = field - ( !Objects.equals(field.getColumn(), keyColumn) && !Objects.equals(field.getColumn(), CREATE_TIME) /* 构建 "column = VALUES(column)" 形式的字符串 */ FunctionTableFieldInfo, String stringFunc = field - field.getColumn() + SEGMENT + field.getColumn() + RIGHT_BRACKET; /* 构建 ON DUPLICATE KEY UPDATE 后面的 SET 子句 */ StringupdateSet=tableInfo .getFieldList() .stream() .filter(needConcat) .map(stringFunc) .collect(Collectors.joining(COMMA)); KeyGeneratorkeyGenerator=NoKeyGenerator.INSTANCE; /* 表包含主键处理逻辑,如果不包含主键当普通字段处理 */ if(StringUtils.isNotBlank(tableInfo.getKeyProperty())) { if(tableInfo.getIdType() == IdType.AUTO) { /* 自增主键 */ keyGenerator = Jdbc3KeyGenerator.INSTANCE; }elseif(null!= tableInfo.getKeySequence()) { keyGenerator = TableInfoHelper.genKeyGenerator(this.methodName, tableInfo, builderAssistant); } } Stringsql=String.format(FORMAT, tableInfo.getTableName(), insertColumnsTrim, insertValuesTrim, updateSet); SqlSourcesqlSource=languageDriver.createSqlSource(configuration, sql, modelClass); returnthis.addInsertMappedStatement( mapperClass, modelClass, METHOD_NAME, sqlSource, keyGenerator, keyProperty, keyColumn } }然而并没有什么作用,因为你没有将自己的「AbstractMethod」定制实现注册到Mybatis Plus框架,我们需要找到一个切口。 publicclass?StrengthenSqlInjectorextendsDefaultSqlInjector{ @Override publicListAbstractMethodgetMethodList(Class mapperClass, TableInfo tableInfo) { ListAbstractMethod methodList =super.getMethodList(mapperClass, tableInfo); /* ?? ?? ?? :注册自己的定制实现 */ methodList.add(newDuplicateInserter()); returnmethodList; } }还需要替换MybatisPlus自带的DefaultSqlInjector @Configuration@EnableTransactionManagementpublicclassMybatisPlusAutoConfigurationimplementsMybatisPlusPropertiesCustomizer{ @Override publicvoidcustomize(MybatisPlusProperties properties) { properties.getGlobalConfig() .setSqlInjector(newStrengthenSqlInjector()); } }如此便可完成了。你问我怎么使用?仅仅只需要在业务Mapper中声明一下duplicateUpdate即可,框架会自动帮你生成支持「DUPLICATE KEY UPDATE」语法的SQL。 @MapperpublicinterfaceBusinessMapper extendsBaseMapperBusinessModel { voidduplicateUpdate(BusinessModel model); }温馨提示?? ?? ??「接下来这段话非常重要」 因为我们是替换MybatisPlus自带的DefaultSqlInjector,注意这里是替换逻辑,因此假设你的系统中有多个MybatisPlusPropertiesCustomizer的Bean那么只会有最后一个生效,因此如果你有多个自定义实现最好全部都放置在一起,只有一个StrengthenSqlInjector最好。 AI编程资讯AI编码专区指南: https://aicoding.juejin.cn/aicoding 点击“阅读译文“了解详情~ 阅读原文

上一篇:2018-06-25_洞察需求,传递价值——好莱坞如何持续运营市场增量 下一篇:2018-06-01_CVPR2018 | 摆好Pose却没管理好面部表情?腾讯优图Facelet-Bank人脸处理技术了解一下

TAG标签:

23
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价