Mybatis-Plus的一次PR经历-识别表名

前言

在公司迭代一套动态数据源插件的时候,发现了Mybatis-Plus的一个小BUG,事件经过是这样的,由于业务需要,我们需要开发一套类似于数据中心的一个服务,整合了公司以前两套老平台,且平台下有对各组织进行了水平分库。组织大概有10-20个左右,需要根据所有组织或者当前组织聚合各个数据库中的数据,于是就要开发一个动态数据源的插件。

本文相关ISSUE

本文相关PR


问题描述

在使用动态表的时候,com.baomidou.mybatisplus.core.toolkit.TableNameParser#accept,该方法不支持
ON DUPLICATE KEY UPDATE语句的,该方法默认把ON DUPLICATE KEY UPDATE后面的第一个字段名作为表名使用。

问题描述1

问题描述2

如上图中new_brand_code显然不是一张表名,但是他判断为了表名,导致我们动态数据源工程在该列名上加上了数据库名。

问题修复

判断了mysql的特殊语句ON_DUPLICATE_KEY_UPDATE,如果判断到token为DUPLICATE到时候索引自增2直接跳过update,防止在下面循环中将update后跟的字段名作为表名。

修复代码

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
/**
* 接受一个新的访问者,并访问当前 SQL 的表名称
* <p>
* 现在我们改成了访问者模式,不在对以前的 SQL 做改动
* 同时,你可以方便的获得表名位置的索引
*
* @param visitor 访问者
*/
public void accept(TableNameVisitor visitor) {
int index = 0;
String first = tokens.get(index).getValue();
if (isOracleSpecialDelete(first, tokens, index)) {
visitNameToken(tokens.get(index + 1), visitor);
} else if (isCreateIndex(first, tokens, index)) {
visitNameToken(tokens.get(index + 4), visitor);
} else {
while (hasMoreTokens(tokens, index)) {
String current = tokens.get(index++).getValue();
if (isFromToken(current)) {
processFromToken(tokens, index, visitor);
} else if (isOnDuplicateKeyUpdate(current, index)) {
index = skipDuplicateKeyUpdateIndex(index);
} else if (concerned.contains(current.toLowerCase())) {
if (hasMoreTokens(tokens, index)) {
SqlToken next = tokens.get(index++);
visitNameToken(next, visitor);
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @param current 当前token
* @param index 索引
* @return 判断是否是mysql的特殊语法 on duplicate key update
*/
private boolean isOnDuplicateKeyUpdate(String current, int index) {
if (KEYWORD_DUPLICATE.equals(current.toLowerCase())) {
if (hasMoreTokens(tokens, index++)) {
String next = tokens.get(index).getValue();
return KEYWORD_UPDATE.equals(next.toLowerCase());
}
}
return false;
}
1
2
3
4
private int skipDuplicateKeyUpdateIndex(int index) {
// on duplicate key update为mysql的固定写法,直接跳过即可。
return index + 2;
}

测试用例

测试用例

修复前:
修复前

修复后:
修复后

很显然cf_procedure这个字段不能作为表名。修复后改字段不再被判断为表名。