• Mybatis-Plus之连表查询


    一、序言

    Mybatis-Plus在设计之初是为了扩展而不是替代Mybatis,所以对于连表查询官方并没有给出解决方法,还是依托Mybatis通过XML配置文件中写SQL语句的方式。但是在多数据源适配上,还是想要消除掉XML以屏蔽不同数据库类型的查询(新增加一个数据库,不需要新增加一个XML配置)。

    最后采用第三方开源工具Mybatis-Plus-Join实现连表查询,开源地址:https://github.com/yulichang/mybatis-plus-join,支持一对一、一对多的操作。

    二、具体实现

    引入依赖

    <dependency>
        <groupId>com.github.yulichang</groupId>
        <artifactId>mybatis-plus-join</artifactId>
        <version>1.2.4</version>
        <exclusions>
            <exclusion>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    之前已经分享过关于Mybatis-Plus代码分层和改造,所以修改XwMapper继承MPJBaseMapper,整体对于原由的结构没有影响。Mybatis-Plus-Join提供MPJLambdaWrapperMPJQueryWrapper实现连表查询。MPJLambdaWrapper支持Lambda表达式查询。

    使用示例:

    List<UserDTO> list = baseMapper.selectJoinList(UserDTO.class,
        new MPJLambdaWrapper<UserDO>()
                .selectAll(UserDO.class)
                .select(UserAddressDO::getTel)
                .selectAs(UserAddressDO::getAddress, UserDTO::getUserAddress)
                .select(AreaDO::getProvince, AreaDO::getCity)
                .leftJoin(UserAddressDO.class, UserAddressDO::getUserId, UserDO::getId)
                .leftJoin(AreaDO.class, AreaDO::getId, UserAddressDO::getAreaId)
                .eq(UserDO::getId, 1)
                .like(UserAddressDO::getTel, "1")
                .gt(UserDO::getId, 5));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    对应的SQL语句:

    SELECT t.id, t.name, t.sex, t.head_img, t1.tel, t1.address AS userAddress, t2.province, t2.city 
    FROM user t
    LEFT JOIN user_address t1 ON t1.user_id = t.id 
    LEFT JOIN area t2 ON t2.id = t1.area_id 
    WHERE ( t.id = ? AND t1.tel LIKE ? AND t.id > ?)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对应字段说明:

    字段含义
    UserDTO查询返回的结果,对应ResultType
    UserDO查询对应的Entity
    selectAll查询指定实体类的全部字段
    select查询指定实体的指定字段
    leftJoin第一个参数参与连表的实体类class;
    第二个参数连表的ON字段,这个属性必须是第一个参数实体类的属性;
    第三个参数: 参与连表的ON的另一个实体类属性

    默认主表别名是t,其他的表别名以先后调用的顺序使用t1、t2、3;同时也支持分页查询。

    IPage<UserDTO> list = baseMapper.selectJoinPage(new Page<>(2, 10), UserDTO.class,
        new MPJLambdaWrapper<UserDO>()
                .selectAll(UserDO.class)
                .select(UserAddressDO::getTel)
                .selectAs(UserAddressDO::getAddress, UserDTO::getUserAddress)
                .select(AreaDO::getProvince, AreaDO::getCity)
                .leftJoin(UserAddressDO.class, UserAddressDO::getUserId, UserDO::getId)
                .leftJoin(AreaDO.class, AreaDO::getId, UserAddressDO::getAreaId)
                .eq(UserDO::getId, 1)
                .like(UserAddressDO::getTel, "1")
                .gt(UserDO::getId, 5));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    对应的SQL语句:

    SELECT t.id, t.name, t.sex, t.head_img, t1.tel, t1.address AS userAddress, t2.province, t2.city 
    FROM user t
    LEFT JOIN user_address t1 ON t1.user_id = t.id 
    LEFT JOIN area t2 ON t2.id = t1.area_id 
    WHERE ( t.id = ? AND t1.tel LIKE ? AND t.id > ?)
    LIMIT 2,10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    条件查询,可以查询主表以及关联表的所有字段,调用Mybatis-Plus原生的方法,不会有SQL注入风险,但是上面的字段适合查询结果映射成单一属性的对象,当对象中包含对象或者对象中包含集合的时候,上面的方式就不适合了。

    Mybatis-Plus-Join提供了@EntityMapping@FieldMapping 注解用来解决非单一属性对象连表查询的处理。通过在Entity中配置好对应的映射关系,再通过baseMapper调用Deep结尾的方法即可处理,例如:查询集合使用baseMapper#selectListDeep、分页查询baseMapper#selectPageDeep。

    举个栗子:

    @TableName("user")
    public class UserDO {
    
        @TableId
        private Integer id;
        private Integer pid;
    
        /**
         * 查询上级 一对一
         */
        @TableField(exist = false)
        @EntityMapping(thisField = "pid", joinField = "id")
        private UserDO pUser;
    
        /**
         * 查询下级 一对多
         */
        @TableField(exist = false)
        @EntityMapping(thisField = "id", joinField = "pid")
        private List<UserDO> childUser;
    
        /**
         * 带条件的查询下级 一对多
         */
        @TableField(exist = false)
        @EntityMapping(thisField = "id", joinField = "pid",
                condition = {
                        @MPJMappingCondition(column = "sex", value = "0"),//sex = '0' 默认条件是等于
                        @MPJMappingCondition(column = "name", value = "张三", keyWord = SqlKeyword.LIKE)//name like '%a%'
                },
                apply = @MPJMappingApply(value = "id between 1 and 20"))//拼接sql 同 wrapper.apply()
        private List<UserDO> childUserCondition;
    
        /**
         * 查询地址 (一对多)
         */
        @TableField(exist = false)
        @EntityMapping(thisField = "id", joinField = "userId")
        private List<UserAddressDO> addressList;
    
        /**
         * 绑定字段 (一对多)
         */
        @TableField(exist = false)
        @FieldMapping(tag = UserDO.class, thisField = "id", joinField = "pid", select = "id")
        private List<Integer> childIds;
    }
    
    • 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

    使用示例:

    @Test
    void test1() {
        UserDO deep = userMapper.selectByIdDeep(2);
        System.out.println(deep);
    }
    
    @Test
    void test2() {
        List<UserDO> list = userMapper.selectListDeep(Wrappers.emptyWrapper());
        list.forEach(System.out::println);
    }
    
    @Test
    void test3() {
        Page<UserDO> page = userMapper.selectPageDeep(new Page<>(2, 2), Wrappers.emptyWrapper());
        page.getRecords().forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    当查询映射的是实体,则使用@EntityMapping,而单一字段结果映射则使用 @FieldMapping,使用文档也给出了详细的说明,具体可以查看:MAPPING.md

    三、结尾

    Mybatis-Plus-Join连表查询对于单一属性对象的连表查询能很好的支持,查看源码是通过Mybatis-Plus,抽离成模板,通过自定义SQL注入器的方式实现。而对于非单一属性对象的查询是提供了两个注解,但底层是分成多条SQL语句采用IN查询得到结果后再聚合。

    所以原先一条语句被分成了两条甚至多条语句去处理,中间多了网络开销。在1000w数据量下,对比过使用Mybatis-Plus-Join注解的方式和使用XML的方式,Mybatis-Plus-Join注解的方式在查询效率上会慢一倍,所以对于QPS要求比较高的系统来说,对于非单一属性对象的查询使用注解的这种方式并非是最优的。

  • 相关阅读:
    SHELL编程基础2
    【深度学习21天学习挑战赛】8、卷积神经网络(认识Xception模型):动物识别
    springboot+nodejs+vue工程师售后服务评价管理系统
    springboot就业信息管理系统springboot32
    5、设计模式之工厂模式
    JUC笔记
    高教社杯数模竞赛特辑论文篇-2023年E题:基于小波变换和背包模型的小浪底水库最优监测方案研究(附获奖论文及Python代码实现)
    Gateway服务网关
    检测图像的圆形 检测直线 Hough变换检测直线 圆形检测 圆心半径检测 -matlab
    洛谷 P6268 [SHOI2002]舞会(二分图最大独立集)
  • 原文地址:https://blog.csdn.net/hsf15768615284/article/details/126566934