mybatis-plus icon indicating copy to clipboard operation
mybatis-plus copied to clipboard

BaseMultiTableInnerInterceptor 处理多表时,会错误地把where条件附加到`left join`的on联表条件中

Open GeTOUO opened this issue 1 year ago • 6 comments

当前使用版本(必填,否则不予处理)

V3.5.3.1

该问题是如何引起的?(确定最新版也有问题再提!!!)

BaseMultiTableInnerInterceptor 在执行 processPlainSelect(final PlainSelect plainSelect, final String whereSegment) 方法时逻辑有漏洞

重现步骤(如果有就写完整)

  1. 下列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 (?)
  1. 期望拦截器处理上述查询时,仅对AUTH_SUBJECT表追加where条件,但实际处理中,条件会被追加到on语句中。

报错信息

不会报错,但此处设计是有问题的。

  1. 是否因为在 processPlainSelect 方法中的 处理 join 环境,应该考虑 左连接或右连接特殊处理一下?不能把数据筛选条件和联表条件做and。

GeTOUO avatar Mar 10 '23 03:03 GeTOUO

PR

huayanYu avatar Mar 12 '23 00:03 huayanYu

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

不对吗?

miemieYaho avatar Aug 02 '23 09:08 miemieYaho

租户和权限过滤的需求还是不一样吧


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

不对吗?

GeTOUO avatar Aug 02 '23 13:08 GeTOUO

我也遇到了这个问题 后续是怎么解决的

HillCheuang avatar Sep 01 '23 03:09 HillCheuang

目前我的解决方案是如下: 复制把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条件的数据权限注释掉了

HillCheuang avatar Sep 06 '23 02:09 HillCheuang

租户和权限过滤的需求还是不一样吧

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 = ?) 

HillCheuang avatar Nov 22 '23 08:11 HillCheuang