mybatis-plus
mybatis-plus copied to clipboard
BaseMultiTableInnerInterceptor 处理多表时,会错误地把where条件附加到`left join`的on联表条件中
当前使用版本(必填,否则不予处理)
V3.5.3.1
该问题是如何引起的?(确定最新版也有问题再提!!!)
BaseMultiTableInnerInterceptor
在执行 processPlainSelect(final PlainSelect plainSelect, final String whereSegment)
方法时逻辑有漏洞
重现步骤(如果有就写完整)
- 下列sql是一个联表查询用户及其对应的角色已经授予了哪些数据权限范围,通过拦截器,会在执行该sql时,同样进行数据权限的过滤,过滤的逻辑是AUTH_SUBJECT 表的数据内容,但是条件的执行时,过滤的条件语句被错误地添加到了on的联表语句中。如果这是一个 join连接这没有问题,但对于
LEFT JOIN
来说会左表的数据会超出范围最终导致结果集返回了过多的数据。
SELECT
SUB.id AS subject_id,
SUB.NAME AS subject_name,
SRREL.ROLE_ID AS role_id,
SCOPE.ATTR_TYPE AS scope_type,
SCOPE.ATTR_ID AS scope_id,
...
FROM
AUTH_SUBJECT SUB
LEFT JOIN AUTH_SUBJECT_ROLE_REL SRREL ON SUB.ID = SRREL.SUBJECT_ID
LEFT JOIN AUTH_SUBJECT_ROLE_REL_SCOPE_CONFIG SCOPE ON SRREL.ID = SCOPE.SUBJECT_ROLE_REL_ID
WHERE SUB.ID IN (?)
- 期望拦截器处理上述查询时,仅对AUTH_SUBJECT表追加where条件,但实际处理中,条件会被追加到on语句中。
报错信息
不会报错,但此处设计是有问题的。
- 是否因为在
processPlainSelect
方法中的 处理 join 环境,应该考虑 左连接或右连接特殊处理一下?不能把数据筛选条件和联表条件做and。
PR
SELECT * FROM entity e
left join entity1 e1 on e1.id = e.id
WHERE e.id = ?
变为
SELECT * FROM entity e
LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1
WHERE (e.id = ?) AND e.tenant_id = 1
不对吗?
租户和权限过滤的需求还是不一样吧
SELECT * FROM entity e left join entity1 e1 on e1.id = e.id WHERE e.id = ?
变为
SELECT * FROM entity e LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 WHERE (e.id = ?) AND e.tenant_id = 1
不对吗?
我也遇到了这个问题 后续是怎么解决的
目前我的解决方案是如下: 复制把BaseMultiTableInnerInterceptor的大部分方法复制出来(因为processJoins方法是私有化的方法导致不能直接重写) 然后主要是重写了processJoins 具体代码如下:
private List<Table> processJoins(List<Table> mainTables, List<Join> joins, final String whereSegment) {
// join 表达式中最终的主表
Table mainTable = null;
// 当前 join 的左表
Table leftTable = null;
if (mainTables.size() == 1) {
mainTable = mainTables.get(0);
leftTable = mainTable;
}
//对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
Deque<List<Table>> onTableDeque = new LinkedList<>();
for (Join join : joins) {
// 处理 on 表达式
FromItem joinItem = join.getRightItem();
// 获取当前 join 的表,subJoint 可以看作是一张表
List<Table> joinTables = null;
if (joinItem instanceof Table) {
joinTables = new ArrayList<>();
joinTables.add((Table) joinItem);
} else if (joinItem instanceof SubJoin) {
joinTables = processSubJoin((SubJoin) joinItem, whereSegment);
}
if (joinTables != null) {
// 如果是隐式内连接
if (join.isSimple()) {
mainTables.addAll(joinTables);
continue;
}
// 当前表是否忽略
Table joinTable = joinTables.get(0);
List<Table> onTables = null;
// 如果不要忽略,且是右连接,则记录下当前表
if (join.isRight()) {
mainTable = joinTable;
mainTables.clear();
if (leftTable != null) {
onTables = Collections.singletonList(leftTable);
}
} else if (join.isInner()) {
if (mainTable == null) {
onTables = Collections.singletonList(joinTable);
} else {
onTables = Arrays.asList(mainTable, joinTable);
}
// mainTable = null;
// mainTables.clear();
} else if(join.isLeft()){
//左链接
onTables = Collections.singletonList(joinTable);
}else{
onTables = Collections.singletonList(joinTable);
}
if (CollectionUtils.isNotEmpty(onTables)) {
onTables.forEach(table -> {
if (table != null && !mainTables.contains(table)) {
mainTables.add(table);
}
});
}
if (mainTable != null && !mainTables.contains(mainTable)) {
mainTables.add(mainTable);
}
// 获取 join 尾缀的 on 表达式列表
Collection<Expression> originOnExpressions = join.getOnExpressions();
// 正常 join on 表达式只有一个,立刻处理
if (originOnExpressions.size() == 1 && onTables != null) {
// List<Expression> onExpressions = new LinkedList<>();
// onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables, whereSegment));
// join.setOnExpressions(onExpressions);
leftTable = mainTable == null ? joinTable : mainTable;
continue;
}
// 表名压栈,忽略的表压入 null,以便后续不处理
onTableDeque.push(onTables);
// 尾缀多个 on 表达式的时候统一处理
if (originOnExpressions.size() > 1) {
Collection<Expression> onExpressions = new LinkedList<>();
for (Expression originOnExpression : originOnExpressions) {
List<Table> currentTableList = onTableDeque.poll();
if (CollectionUtils.isEmpty(currentTableList)) {
onExpressions.add(originOnExpression);
} else {
onExpressions.add(builderExpression(originOnExpression, currentTableList, whereSegment));
}
}
join.setOnExpressions(onExpressions);
}
leftTable = joinTable;
} else {
processOtherFromItem(joinItem, whereSegment);
leftTable = null;
}
}
return mainTables;
}
这个时候的mainTables就包含所有的 join 的table 和驱动表
private List<Table> processSubJoin(SubJoin subJoin, final String whereSegment) {
List<Table> mainTables = new ArrayList<>();
if (subJoin.getJoinList() != null) {
List<Table> list = processFromItem(subJoin.getLeft(), whereSegment);
mainTables.addAll(list);
mainTables = processJoins(mainTables, subJoin.getJoinList(), whereSegment);
}
return mainTables;
}
不过值得注意的是 我把拼接on条件的数据权限注释掉了
租户和权限过滤的需求还是不一样吧
SELECT * FROM entity e left join entity1 e1 on e1.id = e.id WHERE e.id = ?
变为
SELECT * FROM entity e LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 WHERE (e.id = ?) AND e.tenant_id = 1
不对吗?
实际上运行效果是 将where条件拼到了left join 里面 但是没有拼到where条件里面
SELECT * FROM entity e
LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1
WHERE (e.id = ?)