日期 版本 作者 说明
2023-07-08 V-1.0 孔留锋 文档创建。

概要

简介

​ 项目基于RuoYi-Vue前后端分离版本3.8.1。现需要将版本升级到最新版本3.8.6。

升级内容

组件升级

​ druid、fastjson、pagehelper、oshi、kaptcha

实施

1.主POM文件修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<properties>
<druid.version>1.2.16</druid.version>
<kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.boot.version>1.4.6</pagehelper.boot.version>
<fastjson.version>2.0.34</fastjson.version>
<oshi.version>6.4.3</oshi.version>
</properties>
<dependencyManagement>
<!-- 验证码 -->
<dependency>
<groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
</dependencyManagement>

2.framework模块POM文件修改

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<!-- 验证码 -->
<dependency>
<groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId>
<exclusions>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

3.重启异常问题

1
2
3
4
5
6
21:54:29.546 [http-nio-8080-exec-100] DEBUG c.r.s.m.S.selectPage_mpCount - [debug,137] - ==>  Preparing: SELECT COUNT() FROM sys_user WHERE (del_flag = ?)
### Error querying database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') FROM sys_user WHERE (del_flag = '0')' at line 1
### The error may exist in com/ruoyi/system/mapper/SysUserMapper.java (best guess)
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT COUNT() FROM sys_user WHERE (del_flag = ?)

4.问题排查及解决

问题描述

​ Mybatis-plus 分页,count 时候没有传入count(1) 或count(*) ,注入的是count() ,导致语法错误。怀疑给升级的pagehelper版本有问题,pagehelper版本由1.4.1升级到1.4.6.

​ 版本退回1.4.1,发现果然没有报错。

最终解决方案

​ 版本继续升级到1.4.6,断点打在查询语句如下地方。

1
Page<SysUser> pageResutl = iSysUserService.page(page, query);

​ 跟踪发现,进入PaginationInnerInterceptor 类,autoCountSql方法,具体如下:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* 获取自动优化的 countSql
*
* @param optimizeCountSql 是否进行优化
* @param sql sql
* @return countSql
*/
protected String autoCountSql(boolean optimizeCountSql, String sql) {
if (!optimizeCountSql) {
return lowLevelCountSql(sql);
}
try {
Select select = (Select) CCJSqlParserUtil.parse(sql);
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
Distinct distinct = plainSelect.getDistinct();
GroupByElement groupBy = plainSelect.getGroupBy();
List<OrderByElement> orderBy = plainSelect.getOrderByElements();

if (CollectionUtils.isNotEmpty(orderBy)) {
boolean canClean = true;
if (groupBy != null) {
// 包含groupBy 不去除orderBy
canClean = false;
}
if (canClean) {
for (OrderByElement order : orderBy) {
// order by 里带参数,不去除order by
Expression expression = order.getExpression();
if (!(expression instanceof Column) && expression.toString().contains(StringPool.QUESTION_MARK)) {
canClean = false;
break;
}
}
}
if (canClean) {
plainSelect.setOrderByElements(null);
}
}
//#95 Github, selectItems contains #{} ${}, which will be translated to ?, and it may be in a function: power(#{myInt},2)
for (SelectItem item : plainSelect.getSelectItems()) {
if (item.toString().contains(StringPool.QUESTION_MARK)) {
return lowLevelCountSql(select.toString());
}
}
// 包含 distinct、groupBy不优化
if (distinct != null || null != groupBy) {
return lowLevelCountSql(select.toString());
}
// 包含 join 连表,进行判断是否移除 join 连表
if (optimizeJoin) {
List<Join> joins = plainSelect.getJoins();
if (CollectionUtils.isNotEmpty(joins)) {
boolean canRemoveJoin = true;
String whereS = Optional.ofNullable(plainSelect.getWhere()).map(Expression::toString).orElse(StringPool.EMPTY);
// 不区分大小写
whereS = whereS.toLowerCase();
for (Join join : joins) {
if (!join.isLeft()) {
canRemoveJoin = false;
break;
}
FromItem rightItem = join.getRightItem();
String str = "";
if (rightItem instanceof Table) {
Table table = (Table) rightItem;
str = Optional.ofNullable(table.getAlias()).map(Alias::getName).orElse(table.getName()) + StringPool.DOT;
} else if (rightItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) rightItem;
/* 如果 left join 是子查询,并且子查询里包含 ?(代表有入参) 或者 where 条件里包含使用 join 的表的字段作条件,就不移除 join */
if (subSelect.toString().contains(StringPool.QUESTION_MARK)) {
canRemoveJoin = false;
break;
}
str = subSelect.getAlias().getName() + StringPool.DOT;
}
// 不区分大小写
str = str.toLowerCase();
String onExpressionS = join.getOnExpression().toString();
/* 如果 join 里包含 ?(代表有入参) 或者 where 条件里包含使用 join 的表的字段作条件,就不移除 join */
if (onExpressionS.contains(StringPool.QUESTION_MARK) || whereS.contains(str)) {
canRemoveJoin = false;
break;
}
}
if (canRemoveJoin) {
plainSelect.setJoins(null);
}
}
}
// 优化 SQL
plainSelect.setSelectItems(COUNT_SELECT_ITEM);
return select.toString();
} catch (JSQLParserException e) {
// 无法优化使用原 SQL
logger.warn("optimize this sql to a count sql has exception, sql:\"" + sql + "\", exception:\n" + e.getCause());
} catch (Exception e) {
logger.warn("optimize this sql to a count sql has error, sql:\"" + sql + "\", exception:\n" + e);
}
return lowLevelCountSql(sql);
}

image-20230708101913592

​ 执行 plainSelect.setSelectItems(COUNT_SELECT_ITEM);的时候获取的是空,进入SelectExpressionItem类,发现来源于jsqlparser 包,版本为4.5. 版本退回1.4.1,发现jsqlparser版本为4.2 。

​ 解决方案一,住pom文件中指定jsqlparser版本 ,版本降级。

1
2
3
4
5
6
7
8
9
10
11
<properties>
<jsqlparser>4.2</jsqlparser>
</properties>
<dependencyManagement>
<!-- 验证码 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>${jsqlparser}</version>
</dependency>
</dependencyManagement>

扩展与思考

扩展

​ 登录Maven 参考 https://central.sonatype.com/,查看最新版本jsqlparser。

image-20230708102735310

		尝试升级4.6版本,报错。 继续Maven版本查看。

​ mybatis-plus 版本为 3.4.2 执行 PaginationInnerInterceptor 注入 count 时候如下:

1
2
3
4
5
6
7
8
9
10
//系统启动初始化
protected static final List<SelectItem> COUNT_SELECT_ITEM = Collections.singletonList(defaultCountSelectItem());

private static SelectItem defaultCountSelectItem() {
//jsqlparser包中
Function function = new Function();
function.setName("COUNT");
function.setAllColumns(true);
return new SelectExpressionItem(function);
}

​ jsqlparser包中 Function 不同版本的toString 对 count 的处理方式,也就是说从4.3版本开始就已经支持了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//V4.2
public String toString() {
String params;
if (this.parameters == null && this.namedParameters == null) {
if (this.isAllColumns()) {
params = "(*)";
} else {
params = "()";
}
}
//....
return ans;
}
//V4.3-V4.6
public String toString() {
String params;
if (this.parameters == null && this.namedParameters == null) {
params = "()";
}
//....
return ans;
}

​ 查看系统中使用的mybatis-pulus3.4.2版本默认的支持

image-20230708105949569

​ 常看mybatis-plus 版本发布

image-20230708110203069

jsqlparser 4.3版本是2021年12月12日发布, 看mybatis-plus3.5.0版本,已经PaginationInnerInterceptor类

image-20230708110531021

image-20230708110710040

​ mybatis-plus升级支持了对最新的jsqlparser的升级。 还可以选择升级mybatis-plus版本,但风险点有点高,放弃。

mybatis-plus版本 jsqlparser默认版本
3.4.2 4.0
3.5.0 4.3

思考

​ 一些项目在做的时候相关组件不是最新版本,项目可能持续几年,后面会发现各个组件都已经升级。并且不同组件之间存在着 多组件 版本不一致问题。

​ 建议,大框架除非官方发布由重大问题的,项目系统稳定的情况下不建议升级,否则会出现不可预见问题。如springboot最新版本都干到3.1.1 了。 对于ruoyi框架本省 3.8.1对应springboot2.5.10,3.8.6对应springboot2.5.15, 因考虑因素太多升级也很慎重。

image-20230708111858153