2.2K Star 11.1K Fork 3.4K

GVPbaomidou / mybatis-plus

 / 详情

多租户 right join bug

已完成
创建于  
2021-10-28 11:15

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

3.4.3.4

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

right join on条件为非主键
right join 在on中添加了右表.tenant_id=xxx
right join是通过where将右表筛选之后的数据全部查询出来, on中的条件只是筛选左表的数据, 这里无法做到筛选条件

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

代码为你们提供的用例: https://gitee.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample-tenant
修改 src/main/java/com/baomidou/mybatisplus/samples/tenant/mapper/UserMapper.xml文件如下图所示
输入图片说明
执行junit单测 com.baomidou.mybatisplus.samples.tenant.TenantTest#testTenantFilter
生成的sql为

SELECT a.name AS addr_name, u.id, u.name FROM user_addr a RIGHT JOIN user u ON u.name = a.name AND u.tenant_id = 1

期望的sql应该是

SELECT a.name AS addr_name, u.id, u.name FROM user_addr a RIGHT JOIN user u ON u.name = a.name AND a.tenant_id = 1 where u.tenant_id=1

期望查询的数据是3条, 实际查询了5条

报错信息

评论 (28)

闵柯 创建了任务
闵柯 修改了描述
闵柯 修改了标题
闵柯 修改了描述
展开全部操作日志

734db3a
test无此问题

你的例子里面的sql也是有问题的

SELECT * FROM entity e right join entity1 e1 on e1.id = e.id

应该改写为

SELECT * FROM entity e right join entity1 e1 on e1.id = e.id and  e .tenant_id = 1 where  e1 .tenant_id = 1

但是mybatis-plus改写成

 SELECT * FROM entity e RIGHT JOIN entity1 e1 ON e1.id = e.id AND  e1 .tenant_id = 1 WHERE  e .tenant_id = 1

mybatis-plus改写的这个sql on 里面的e1.tenant_id=1并不能起到筛选的作用, 右表里面的所有的租户都会被筛选出来, 如果结果能对的原因是这里是id关联的, where中的条件会筛选出来正确的结果, 如果on中的条件是普通字段, 而且有不同租户有相同的值, 这个sql就有问题了

为什么是“期望查询的数据是3条, 实际查询了5条”?

本来就是5个用户,其中1个用户2个地址

from user_addr
right join user --right join 本意 就是拿所有用户

这并不是一个用户, 不同租户下面的名字相同的租户怎么能认为是同一个用户呢? 多租户场景下面, 所有的查询应该都是限定在当前租户下面, 而不能跨租户, 这个和right join, 没有关系吧

还有, 为什么一个问题还没有得出结论就打上无效的标签, 这是什么对待开源的态度啊

yuxiaobin 添加了
 
invalid
标签
闵柯 任务状态待办的 修改为已取消
你有医保你先上 任务状态已取消 修改为待办的
你有医保你先上 添加了
 
bug
标签
你有医保你先上 移除了
 
invalid
标签
你有医保你先上 关联分支设置为3.0
你有医保你先上 优先级设置为主要
你有医保你先上 优先级主要 修改为严重

经过验证,该问题确实是存在的。我们的处理方案是接下来可能会对所有使用租户的表进行降级处理。即:所有需要进行租户过滤的条件中,原表名称都会被替换为过滤租户后的子查询:

SELECT * FROM demo

中的 demo 将在需要进行租户过滤时被转换为:

SELECT * FROM (SELECT * FROM demo WHERE tenant_id = 1) as demo

我们认为租户本来可被视作一个前置的过滤条件,因此提前进行过滤;这种方式也可以避免处理各种 OUTER JOIN 的情况。

@闵柯 https://github.com/baomidou/mybatis-plus/pull/4035

@你有医保你先上 提前过滤,当表数据量大时,性能会有问题。
已在 github 提交了一个 pr 尝试修复,可以 review 下。

你的处理还是有点不对, 你在单测中的断言如下图所示
输入图片说明
处理的原始sql

SELECT * FROM with_as_1 e right join entity1 e1 on e1.id = e.id

多租户处理之后的sql

SELECT * FROM with_as_1 e RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 WHERE e1.tenant_id = 1

因为right join on中的条件不能筛选e1, 所以此处on中的e1.tenant_id = 1没有作用, 但是e并没有筛选条件,假如e中有其他租户的数据满足e1.id = e.id, 那么也会join。
所以期望的sql应该是

SELECT * FROM with_as_1 e RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 WHERE e1.tenant_id = 1

通过on中的e.tenant_id = 1筛选出e表到租户数据, where中的e1.tenant_id = 1筛选出e1表的数据

来份 sql 文件,我测试下

:sweat: 没有问题的,这个测试用例中 with_as_1 是忽略租户处理的表,所以不能加租户的关联条件。

不好意思, 没注意看到租户表的配置 :joy:
但是上面那个还是有问题, 如下是代码
测试代码
原始sql

SELECT * FROM entity e right join entity1 e1 on e1.id = e.id

多租户处理之后的sql

SELECT * FROM entity e RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 WHERE e.tenant_id = 1 AND e1.tenant_id = 1

如果left join的时候, 如果有entity表不满足on条件会出现,会出现有entity1表字段,但是entity表字段为空的情况
在where中加入e.tenant_id=1会导致这些数据被排除,所以在where中不能含有e.tenant_id=1这个条件
例如entity表数据如下

id tenant_id
1 1
2 1
entity1表数据如下
id tenant_id
---- -----------
1 1
2 1
3 1
多租户处理之后的sql
SELECT * FROM entity e RIGHT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 WHERE e.tenant_id = 1 AND e1.tenant_id = 1;

查询结果输入图片说明
将过滤放到表中的sql

SELECT * FROM (select * from entity where tenant_id=1) e RIGHT JOIN (select * from entity1 where tenant_id=1) e1 ON e1.id = e.id;

的查询结果输入图片说明
最后是期望sql应该是将entity表的筛选条件放到on中, entity1表的筛选条件放到where中
如下

SELECT * FROM entity e RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 WHERE e1.tenant_id = 1;

查询结果输入图片说明

不好意思, 我也没有对应的sql

看错了, 是有问题,我回头再改一版

github 代码已更新

你这个有点复杂, 我没有深入查看核心代码, 这个需要其他熟悉的人去review一下

抱歉啊,最近加班比较多,还没来得及看,我看下其他人能否测试下~

你有医保你先上 任务状态待办的 修改为进行中
你有医保你先上 计划截止日期设置为2021-11-17
你有医保你先上 计划开始日期设置为2021-11-16
你有医保你先上 计划截止日期2021-11-17 修改为2021-11-25
你有医保你先上 计划截止日期2021-11-25 修改为2021-11-26
你有医保你先上 计划截止日期2021-11-26 修改为2021-11-21

希望你们整出连表查询的方法来。写SQL是有一点折磨了。 :sob:

青苗 任务状态进行中 修改为待办的

@Hccake 貌似outer join处理有点问题

目前只适配了 隐式内连接、显示内连接、左(外)连接、右(外)连接,其他 join 类型没有处理,你可以留下你需要处理的 sql,我回头补下

测试案例,需要在postgresql中测试, mysql不支持full outer join

CREATE TABLE People
(
    ID        bigint NOT NULL,
    TENANT_ID int4         DEFAULT NULL,
    NAME      varchar(255) DEFAULT NULL,
    PRIMARY KEY (ID)
);
CREATE TABLE Cars
(
    ID        bigint NOT NULL,
    TENANT_ID int4         DEFAULT NULL,
    NAME      varchar(255) DEFAULT NULL,
    PRICE     int          DEFAULT NULL,
    people_id bigint       DEFAULT NULL,
    PRIMARY KEY (ID),
    FOREIGN KEY (people_id) REFERENCES People (ID)
);

insert into people (id, tenant_id, name)
values (1, 1, 'Audi');
insert into people (id, tenant_id, name)
values (2, 1, 'Mercedes');
insert into people (id, tenant_id, name)
values (3, 2, 'Skoda');


INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (1, 1, 'Audi', 52642, 1);
INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (2, 2, 'Mercedes', 57127, 1);
INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (3, 1, 'Skoda', 9000, 1);
INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (4, 1, 'Volvo', 29000, null);
INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (5, 2, 'Bentley', 350000, 1);
INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (6, 2, 'Citroen', 21000, 1);
INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (7, 1, 'Hummer', 41400, 1);
INSERT INTO Cars (ID, TENANT_ID, NAME, PRICE, people_id)
VALUES (8, 1, 'Volkswagen', 21600, 1);
-- 正确但是效率可能不太好的sql
select t1.*, t2.*
from (select * from cars where TENANT_ID = 1) t1
         full outer join (select * from people where People.TENANT_ID = 1) t2 on (t1.people_id = t2.ID);
-- 原始sql
select c.*, p.*
from cars c
         full outer join people p on c.people_id = p.id;
-- tenant插件处理的sql
SELECT c.*, p.*
FROM cars c
         FULL OUTER JOIN people p ON c.people_id = p.id
WHERE c.tenant_id = 1;

感觉full outer join需要写成子查询的方式

测试了下,full outer join 不好处理,只能重构为子查询,改写起来非常复杂。

目前可以把 full outer join 的原始语句改写为

select * from 
(select * from cars) t1
full outer join 
(select * from people) t2 
on t1.people_id = t2.ID;

这种形式,插件处理后会变为

SELECT * FROM 
(SELECT * FROM cars WHERE cars.tenant_id = 1) t1 
FULL OUTER JOIN 
(SELECT * FROM people WHERE people.tenant_id = 1) t2 
ON t1.people_id = t2.ID

复杂多表查询 自动加上租户过滤条件的确不太靠谱,坑太多

发现这种处理方式有点像一个伪需求,可能在数据源或数据库层处理更为合适

请测试 使用新版本 3.5.3

青苗 任务状态待办的 修改为已完成

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(8)
969595 miemieyaho 1699515566 1325729 h825944942 1578948977 1438397 hccake 1622643582 985875 duwenkang 1578937589 12260 jobob 1695284587
Java
1
https://gitee.com/baomidou/mybatis-plus.git
git@gitee.com:baomidou/mybatis-plus.git
baomidou
mybatis-plus
mybatis-plus

搜索帮助