三款神器(MyBatis Plus + MyBatisX + MyBatis Plus Join);终于不用写数据库操作代码,一键生成直接调用

大家好,我是一航!

程序员每天的搬砖日常,可以说CURD占据了绝大部分的工作;因此,数据库的CURD也就占据了很大一部分的工作时间,不是在配置xml,就是在写sql的路上;

那有没有什么方式能否把这份苦力活给替代了呢?当然是有的,也就是今天介绍的2框框架+1个工具(MyBatis Plus + MyBatisX + MyBatis Plus Join);不写一行数据库操作代码,不加一行配置文件;一键生成代码基础的CURD联表查询API统统搞定;让我们可以安心将精力完全放在产品业务逻辑开发上。

开整!!!

目录说明

  • 框架、工具介绍
  • 数据库相关配置
  • MybatisX代码自动生成
  • MyBatis Plus使用
    • 结构说明
    • Service的CURD操作
    • 条件构造器
  • MyBatis Plus Join联表查询
  • 总结

框架、工具介绍

  • MyBatis Plus

    MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

    官网地址:https://mp.baomidou.com/

  • MyBatis Plus Join

    一款对MyBatis Plus 扩展的框架,在其基础上增加了联表查询相关的API;

    https://gitee.com/best_handsome/mybatis-plus-join

  • MyBatisX

    MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。用于一键生成ORM代码;该插件在本文中的主要目的是为了快速生成基于MyBatis Plus相关的代码;

接下来就要开始对框架和工具的实战运用了;

导入依赖

  • 必备

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3.4</version>
    </dependency>
    

    数据库连接依赖;大版本务必和自己的数据库版本一致

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    

    分页

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-extension</artifactId>
        <version>3.4.1</version>
    </dependency>
    

    联表查询

    <!--https://gitee.com/best_handsome/mybatis-plus-join-->
    <dependency>
        <groupId>com.github.yulichang</groupId>
        <artifactId>mybatis-plus-join</artifactId>
        <version>1.1.8</version>
    </dependency>
    
  • 辅助

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.9</version>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.73</version>
    </dependency>
    

数据库配置

  • 数据库表

    一张简单的用户数据表

    CREATE TABLE `user_info`  (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
      `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
      `source` tinyint(4) NULL DEFAULT NULL COMMENT '来源',
      PRIMARY KEY (`id`) USING BTREE,
      INDEX `source_id`(`source`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 1008 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
    
  • SpringBoot数据库配置

    spring:
      application:
        name: ehang-mybatis-plus
      #数据库连接相关配置
      datasource:
        url: jdbc:mysql://192.168.1.237:3306/ehang?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
        username: root
        password: 123456
        #阿里巴巴的druid的mysql连接池
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
    
  • 启动类配置Dao扫描

    其中basePackages路径,请根据个人的实际情况填写;

    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @MapperScan(basePackages = {
          
          "com.ehang.springboot.mybatisplus.generator.**.mapper"})
    

MybatisX

MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。一键生成ORM代码;结合MyBatis Plus,生成的代码就已经具备了数据库增删改查的基本功能,直接去开发业务功能就好了;

插件使用步骤如下:

  • 安装插件

    安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

  • 配置数据源

  • 自动生成ORM代码

    • 第一步

      选中表(支持多选),右键选择“MybatisX-Generator

    • 配置基础信息

    • 属性、方法配置

    • 生成后的效果

MyBatis Plus使用

官网:https://mp.baomidou.com/

官方示例:https://github.com/baomidou/mybatis-plus-samples

结构说明

上面介绍的工具(MyBatisX)已经帮我们基于MyBatis Plus3生成好了数据库操作的基础CURD代码,先一起来简单看一下有那些内容

  • demain

    用于接收数据库数据的Java实体类

  • UserInfoMapper.xml

    指明Java实体类与数据库表之间的映射关系

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.ehang.springboot.mybatisplus.generator.user.mapper.UserInfoMapper">
    
        <resultMap id="BaseResultMap" type="com.ehang.springboot.mybatisplus.generator.user.demain.UserInfo">
                <id property="id" column="id" jdbcType="INTEGER"/>
                <result property="userName" column="user_name" jdbcType="VARCHAR"/>
                <result property="age" column="age" jdbcType="INTEGER"/>
                <result property="source" column="source" jdbcType="TINYINT"/>
        </resultMap>
    
        <sql id="Base_Column_List">
            id,user_name,age,
            source
        </sql>
    </mapper>
    
  • mapper

    数据库操作的Mapper,继承了MyBatis Plus的BaseMapper

    public interface UserInfoMapper extends BaseMapper<UserInfo> {
          
          
    }
    

    BaseMapper帮我们做了大量的数据库基础操作,详情如下:

    public interface BaseMapper<T> extends Mapper<T> {
          
          
        int insert(T entity);
    
        int deleteById(Serializable id);
    
        int deleteByMap(@Param("cm") Map<String, Object> columnMap);
    
        int delete(@Param("ew") Wrapper<T> queryWrapper);
    
        int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    
        int updateById(@Param("et") T entity);
    
        int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
    
        T selectById(Serializable id);
    
        List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    
        List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
    
        T selectOne(@Param("ew") Wrapper<T> queryWrapper);
    
        Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
    
        List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
    
        List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
    
        List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
    
        <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
    
        <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
    }
    
  • Service

    service层的基础接口,继承了MyBatis Plus的IService,定义了众多基础的Service接口,由于内容较多,这里就不贴出来了,可以自行查看IService接口的定义;

    如果自动生成的接口无法满足业务需求的时候,也可以在这里定义接口,来满足个性化的需要。

    public interface UserInfoService extends IService<UserInfo> {
          
          
    }
    
  • ServiceImpl

    继承了MyBatis Plus 的ServiceImpl,ServiceImpl基于BaseMapper实现了IService定义的基础的接口

    public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo>
        implements UserInfoService{
          
          
    }
    

到此,三个简单的类,CURD相关的Service层、Dao层功能就已经全部有了;如果是非特殊情况,这些API已经足够我们去做业务功能开发了。

Service的CURD功能

基本的结构了解清楚之后,就一起来看看,IService到底帮我们提供了那些API,这些API又要如何去使用;

API列表

API 功能 描述
save 添加、保存 支持单条和批量
saveOrUpdate 添加或者修改 主键不存在就添加,否则就基于主键修改
remove 删除数据 条件删除、主键删除、批量删除
update 修改 支持单条修改、批量修改
get 查询单条记录
list 批量查询 批量查询
page 分页查询 需要分页插件的支持
count 记录数 查询总数、满足条件的记录数
chain 流式调用 让API调用更加方便简单

save

插入功能

  • API列表

    // 插入一条记录(选择字段,策略插入)
    boolean save(T entity);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList);
    // 插入(批量) batchSize指明单批次最大数据量,批量插入数量较大时,推荐使用这个
    boolean saveBatch(Collection<T> entityList, int batchSize);
    
  • 代码

    // 单个插入
    @Test
    void save() {
          
          
        UserInfo userInfo = new UserInfo(null, "张三", 10, (byte) 1);
        boolean save = userInfoService.save(userInfo);
        log.info("单条添加的结果:{}", save);
    }
    
    // 批量插入
    @Test
    void saveBatch() {
          
          
        UserInfo lisi = new UserInfo(null, "李四", 10, (byte) 1);
        UserInfo wangwu = new UserInfo(null, "王五", 10, (byte) 1);
        List<UserInfo> userInfos = new ArrayList<>();
        userInfos.add(lisi);
        userInfos.add(wangwu);
        boolean saveBatch = userInfoService.saveBatch(userInfos, 10);
        log.info("批量添加的结果:{}", saveBatch);
    }
    
  • 测试结果

SaveOrUpdate

插入,如果数据存在则修改

  • API列表

    // TableId 注解存在更新记录,否插入一条记录
    boolean saveOrUpdate(T entity);
    // 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
    boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
    
  • 测试代码

    @Test
    void saveOrUpdate() {
          
          
        // 单个修改
        UserInfo userInfo = new UserInfo(1004, "张三(改)", 20, (byte) 1);
        boolean saveOrUpdate = userInfoService.saveOrUpdate(userInfo);
        log.info("单条插入(或修改)的结果:{}", saveOrUpdate);
    
        // 根据条件修改
        LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(UserInfo::getSource, 1);
        boolean saveOrUpdateByWrapper = userInfoService.saveOrUpdate(userInfo, updateWrapper);
        log.info("单条插入(或根据条件修改)的结果:{}", saveOrUpdateByWrapper);
    
        // 批量插入
        UserInfo lisi = new UserInfo(1005, "李四(改)", 10, (byte) 1);
        UserInfo wangwu = new UserInfo(1006, "王五(改)", 10, (byte) 1);
        List<UserInfo> userInfos = new ArrayList<>();
        userInfos.add(lisi);
        userInfos.add(wangwu);
        boolean saveBatch = userInfoService.saveOrUpdateBatch(userInfos, 10);
        log.info("批量插入(或修改)的结果:{}", saveBatch);
    }
    
  • 执行结果

remove

删除数据

  • API列表

    // 根据 entity 条件,删除记录
    boolean remove(Wrapper<T> queryWrapper);
    // 根据 ID 删除
    boolean removeById(Serializable id);
    // 根据 columnMap 条件,删除记录
    boolean removeByMap(Map<String, Object> columnMap);
    // 删除(根据ID 批量删除)
    boolean removeByIds(Collection<? extends Serializable> idList);
    
  • 测试代码

        @Test
        void remove() {
          
          
            // 根据条件删除
            LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(UserInfo::getUserName, "张三");
            boolean remove = userInfoService.remove(queryWrapper);
            log.info("根据条件删除用户数据:{}", remove);
        }
    
        @Test
        void removeById() {
          
          
            // 根据主键id删除
            boolean removeById = userInfoService.removeById(1006);
            log.info("根据主键ID删除用户数据:{}", removeById);
    
        }
    
        @Test
        void removeByMap() {
          
          
            // 根据列的值删除
            Map<String, Object> cms = new HashMap();
            cms.put("user_name", "李四");
            cms.put("source", 1);
            boolean removeByMap = userInfoService.removeByMap(cms);
    
            log.info("根据字段值删除用户数据:{}", removeByMap);
        }
    
        @Test
        void removeByIds() {
          
          
            // 根据主键id批量删除
            List<Integer> ids = Arrays.asList(new Integer[]{
          
          1004, 1005, 1006});
            boolean removeByIds = userInfoService.removeByIds(ids);
            log.info("根据主键ids批量删除用户数据:", removeByIds);
        }
    
  • 测试结果

update

修改数据

  • API列表

    // 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
    boolean update(Wrapper<T> updateWrapper);
    // 根据 whereWrapper 条件,更新记录
    boolean update(T updateEntity, Wrapper<T> whereWrapper);
    // 根据 ID 选择修改
    boolean updateById(T entity);
    // 根据ID 批量更新
    boolean updateBatchById(Collection<T> entityList);
    // 根据ID 批量更新
    boolean updateBatchById(Collection<T> entityList, int batchSize);
    
  • 测试代码

    @SpringBootTest
    @Slf4j
    public class UpdateTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        public void update() {
          
          
            // 不建议使用,有
            // 以下的setSql和set选一个即可,务必要设置条件 否则有全部修改的风险
            //updateWrapper.setSql("user_name = '张三'");
            LambdaUpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<UserInfo>()
                    .lambda()
                    .set(UserInfo::getUserName, "一行Java(改1)")
                    .eq(UserInfo::getId, 1);
            boolean update = userInfoService.update(updateWrapper);
            log.info("根据UpdateWrapper修改(不推荐使用):{}", update);
        }
    
        @Test
        public void update2() {
          
          
            // 将符合UpdateWrapper全部修改为entity的值
            LambdaUpdateWrapper<UserInfo> updateWrapper1 = new UpdateWrapper<UserInfo>()
                    .lambda()
                    .eq(UserInfo::getUserName, "一行Java(改1)");
            UserInfo wangwu = new UserInfo(1, "一行Java(改2)", 10, (byte) 1);
            boolean update = userInfoService.update(wangwu, updateWrapper1);
            log.info("根据UpdateWrapper修改为指定对象:{}", update);
        }
    
        // 根据对象ID进行修改
        @Test
        public void updateById() {
          
          
            UserInfo wangwu = new UserInfo(1, "一行Java(改2)", 10, (byte) 1);
            boolean update = userInfoService.updateById(wangwu);
            log.info("根据对象ID修改:{}", update);
        }
    
        // 根据ID批量修改数据
        @Test
        public void updateBatchById() {
          
          
            UserInfo u1 = new UserInfo(1, "一行Java 1", 10, (byte) 1);
            UserInfo u2 = new UserInfo(2, "一行Java 2", 20, (byte) 1);
            UserInfo u3 = new UserInfo(3, "一行Java 3", 30, (byte) 1);
            List<UserInfo> us = new ArrayList<>();
            us.add(u1);
            us.add(u2);
            us.add(u3);
            boolean update = userInfoService.updateBatchById(us);
            log.info("根据对象ID批量修改:{}", update);
        }
    
        // 根据ID批量修改数据,每个批次的数量由后面的batchSize指定
        @Test
        public void updateBatchById2() {
          
          
            UserInfo u1 = new UserInfo(1, "一行Java 1", 10, (byte) 1);
            UserInfo u2 = new UserInfo(2, "一行Java 2", 20, (byte) 1);
            UserInfo u3 = new UserInfo(3, "一行Java 3", 30, (byte) 1);
            List<UserInfo> us = new ArrayList<>();
            us.add(u1);
            us.add(u2);
            us.add(u3);
            boolean update = userInfoService.updateBatchById(us, 2);
            log.info("根据对象ID批量修改:{}", update);
        }
    }
    
  • 测试结果

Get

获取单条记录

  • API列表

    // 根据 ID 查询
    T getById(Serializable id);
    // 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
    T getOne(Wrapper<T> queryWrapper);
    // 根据 Wrapper,查询一条记录
    T getOne(Wrapper<T> queryWrapper, boolean throwEx);
    // 根据 Wrapper,查询一条记录
    Map<String, Object> getMap(Wrapper<T> queryWrapper);
    // 根据 Wrapper,查询一条记录
    <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    
  • 测试代码

    @SpringBootTest
    @Slf4j
    public class GetTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void getById() {
          
          
            UserInfo userInfo = userInfoService.getById(1);
            log.info("根据ID查询用户信息:{}", userInfo);
        }
    
        // 查询一条数据,如果根据条件查询出了多条,则会报错
        @Test
        void getOne() {
          
          
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .eq(UserInfo::getId, 1);
            UserInfo userInfo = userInfoService.getOne(lambdaQueryWrapper);
            log.info("根据ID查询单用户信息:{}", userInfo);
        }
    
        // 查询单条数据,如果返回多条数据则去取第一条返回
        @Test
        void getOne2() {
          
          
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .eq(UserInfo::getUserName, "一行Java 1")
                    .orderByDesc(UserInfo::getId);
            UserInfo userInfo = userInfoService.getOne(lambdaQueryWrapper, false);
            log.info("根据ID查询单用户信息:{}", userInfo);
        }
    
        // 查询单条数据 以Map的方式返回
        @Test
        void getMap() {
          
          
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .eq(UserInfo::getId, 1);
            // String为数据库列名  Object为值
            Map<String, Object> map = userInfoService.getMap(lambdaQueryWrapper);
            log.info("根据ID查询单用户信息:{}", map);
        }
    
        // 查询返回结果的第一列
        @Test
        void getObj() {
          
          
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .eq(UserInfo::getUserName, "一行Java 1")
                    .select(UserInfo::getUserName);
    
            String obj = userInfoService.getObj(lambdaQueryWrapper, (u) -> u.toString());
            log.info("getObj:{}", obj);
        }
    }
    

    执行结果

List

批量查询

  • API列表

    // 查询所有
    List<T> list();
    // 查询列表
    List<T> list(Wrapper<T> queryWrapper);
    // 查询(根据ID 批量查询)
    Collection<T> listByIds(Collection<? extends Serializable> idList);
    // 查询(根据 columnMap 条件)
    Collection<T> listByMap(Map<String, Object> columnMap);
    // 查询所有列表
    List<Map<String, Object>> listMaps();
    // 查询列表
    List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
    // 查询全部记录
    List<Object> listObjs();
    // 查询全部记录
    <V> List<V> listObjs(Function<? super Object, V> mapper);
    // 根据 Wrapper 条件,查询全部记录
    List<Object> listObjs(Wrapper<T> queryWrapper);
    // 根据 Wrapper 条件,查询全部记录
    <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    
  • 测试代码

    package com.ehang.springboot.mybatisplus;
    
    import com.alibaba.fastjson.JSON;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.ehang.springboot.mybatisplus.generator.user.demain.UserInfo;
    import com.ehang.springboot.mybatisplus.generator.user.service.UserInfoService;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.Map;
    
    @SpringBootTest
    @Slf4j
    public class PageTest {
          
          
    
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void page() {
          
          
            // 分页查询;结果以对象方式返回
            Page<UserInfo> page = userInfoService.page(new Page<UserInfo>(2, 5));
            log.info("page:{}", page);
        }
    
        @Test
        void pageByWrapper() {
          
          
            // 带查询条件的分页查询; 结果以对象方式返回
            // 查询条件是id大于10
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .ge(UserInfo::getId, 10);
            Page<UserInfo> page = userInfoService.page(new Page<UserInfo>(2, 5), lambdaQueryWrapper);
            log.info(":{}", page);
        }
    
        @Test
        void pageMaps() {
          
          
            // 分页查询;以Map的方式返回
            Page<Map<String, Object>> page = userInfoService.pageMaps(new Page(2, 5));
            log.info("pageMaps:{}", JSON.toJSONString(page));
        }
    
        @Test
        void pageMapsByWrapper() {
          
          
            // 带查询条件的分页查询,结果以Map方式返回
            // 查询条件是id大于10
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .ge(UserInfo::getId, 10);
            Page<Map<String, Object>> page = userInfoService.pageMaps(new Page(2, 5), lambdaQueryWrapper);
            log.info("pageMapsByWrapper:{}", JSON.toJSONString(page));
        }
    }
    

    执行结果

page

分页查询

  • 分页插件

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-extension</artifactId>
        <version>3.4.1</version>
    </dependency>
    
  • 分页插件配置

        // 新版
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
          
          
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return interceptor;
        }
    
    //    // 旧版
    //    @Bean
    //    public PaginationInterceptor paginationInterceptor() {
          
          
    //        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
    //        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
    //        // paginationInterceptor.setOverflow(false);
    //        // 设置最大单页限制数量,默认 500 条,-1 不受限制
    //        // paginationInterceptor.setLimit(500);
    //        // 开启 count 的 join 优化,只针对部分 left join
    //        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
    //        return paginationInterceptor;
    //    }
    
  • API列表

    // 无条件分页查询
    IPage<T> page(IPage<T> page);
    // 条件分页查询
    IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
    // 无条件分页查询
    IPage<Map<String, Object>> pageMaps(IPage<T> page);
    // 条件分页查询
    IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
    
  • 测试代码

    @SpringBootTest
    @Slf4j
    public class PageTest {
          
          
    
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void page() {
          
          
            // 分页查询;结果以对象方式返回
            Page<UserInfo> page = userInfoService.page(new Page<UserInfo>(2, 5));
            log.info("page:{}", page);
        }
    
        @Test
        void pageByWrapper() {
          
          
            // 带查询条件的分页查询; 结果以对象方式返回
            // 查询条件是id大于10
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .ge(UserInfo::getId, 10);
            Page<UserInfo> page = userInfoService.page(new Page<UserInfo>(2, 5), lambdaQueryWrapper);
            log.info("pageByWrapper:{}", page);
        }
    
        @Test
        void pageMaps() {
          
          
            // 分页查询;以Map的方式返回
            Page<Map<String, Object>> page = userInfoService.pageMaps(new Page(2, 5));
            log.info("pageMaps:{}", JSON.toJSONString(page));
        }
    
        @Test
        void pageMapsByWrapper() {
          
          
            // 带查询条件的分页查询,结果以Map方式返回
            // 查询条件是id大于10
            LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new QueryWrapper<UserInfo>()
                    .lambda()
                    .ge(UserInfo::getId, 10);
            Page<Map<String, Object>> page = userInfoService.pageMaps(new Page(2, 5), lambdaQueryWrapper);
            log.info("pageMapsByWrapper:{}", JSON.toJSONString(page));
        }
    }
    

Count

查询记录数

  • API列表

    // 查询总记录数
    int count();
    // 根据 Wrapper 条件,查询总记录数
    int count(Wrapper<T> queryWrapper);
    
  • 测试代码

    @SpringBootTest
    @Slf4j
    public class CountTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void count() {
          
          
            int count = userInfoService.count();
            log.info("总数:{}", count);
        }
    
        @Test
        void countByWrapper() {
          
          
            int count = userInfoService.count(new QueryWrapper<UserInfo>()
                    .lambda()
                    .ge(UserInfo::getId, 100));
            log.info("按条件查询总数:{}", count);
        }
    }
    

Chain(重要)

service的链式操作,这个是实际使用中会用的比较频繁的API,让我们在写代码时,调用API的操作更加的优雅;

  • API列表

    // 链式查询 普通
    QueryChainWrapper<T> query();
    // 链式查询 lambda 式。注意:不支持 Kotlin
    LambdaQueryChainWrapper<T> lambdaQuery(); 
    
    // 链式更改 普通
    UpdateChainWrapper<T> update();
    // 链式更改 lambda 式。注意:不支持 Kotlin 
    LambdaUpdateChainWrapper<T> lambdaUpdate();
    
  • 测试代码

    @SpringBootTest
    @Slf4j
    public class ChainTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void chainQuery() {
          
          
            List<UserInfo> userInfos = userInfoService
                    .query()
                    .eq("user_name", "一行Java 1")
                    .list();
            log.info("流式查询:{}", JSON.toJSONString(userInfos));
        }
    
        @Test
        void chainLambdaQuery() {
          
          
            List<UserInfo> userInfos = userInfoService
                    .lambdaQuery()
                    .eq(UserInfo::getUserName, "一行Java 1")
                    .list();
            log.info("流式查询:{}", JSON.toJSONString(userInfos));
        }
    }
    

Service相关的API基本已经演示完毕了,在示例代码中,也见到了一些常用的条件构造器,比如eqge等,但条件构造器远不止这么一点点;MyBatis Plus 给所有的条件构造都提供了详细的API支持

条件构造器

构造器详细列表

下面通过一张表格,来完整的看一下所有条件构造器的方法;

关键字 作用 示例 等价SQL
allEq 匹配所有字段全部eq .query().allEq({id:1,user_name:“老王”,age:null}).list() WHERE id =1 AND user_neme=“老王” AND age IS NULL
eq 等于(==) .lambdaQuery().eq(UserInfo::getId, 1) WHERE id = 1
ne 不等于(<>) .lambdaQuery().ne( UserInfo::getId, 1) WHERE id <> 1
gt 大于(>) .lambdaQuery().gt( UserInfo::getId, 1) WHERE id > 1
ge 大于等于(>=) .lambdaQuery().ge( UserInfo::getId, 1) WHERE id >= 1
lt 小于(<) .lambdaQuery().lt( UserInfo::getId, 1) WHERE id < 1
le 小于等于(<=) .lambdaQuery().le( UserInfo::getId, 1) WHERE id <= 1
between 指定区间内 .lambdaQuery().between( UserInfo::getId, 1,10) WHERE (id BETWEEN 1 AND 10)
notBetween 指定区间外 .lambdaQuery().notBetween( UserInfo::getId, 5,100) WHERE (id NOT BETWEEN 5 AND 100)
like 字符串匹配 .lambdaQuery().like( UserInfo::getUserName, “一行Java”) WHERE (user_name LIKE “%一行Java%”)
notLike 字符串不匹配 .lambdaQuery().notLike( UserInfo::getUserName, “一行Java”) WHERE (user_name NOT LIKE “%一行Java%”)
likeLeft 字符串左匹配 .lambdaQuery().likeLeft( UserInfo::getUserName, “一行Java”) WHERE (user_name LIKE “%一行Java”)
likeRight 字符串右匹配 .lambdaQuery().likeRight( UserInfo::getUserName, “一行Java”) WHERE (user_name LIKE “一行Java%”)
isNull 等于null .lambdaQuery().isNull(UserInfo::getUserName) WHERE (user_name IS NULL)
isNotNull 不等于null .lambdaQuery().isNotNull( UserInfo::getUserName) WHERE (user_name IS NOT NULL)
in 包含 .lambdaQuery().in(UserInfo::getId, 1, 2, 3) WHERE (id IN (1, 2, 3))
notIn 不包含 .lambdaQuery().notIn(UserInfo::getId, 1, 2, 3) WHERE (id NOT IN (1, 2, 3))
inSql sql方式包含 .lambdaQuery().inSql(UserInfo::getId, “1, 2, 3”) WHERE (id IN (1, 2, 3))
notInSql sql方式不包含 .lambdaQuery().notInSql(UserInfo::getId, “1, 2, 3”) WHERE (id NOT IN (1, 2, 3))
groupBy 分组 .select(“source,count(id) as sum”).groupBy(“source”).having(“count(id) > {0}”, 35); GROUP BY source HAVING count(id) > 35
orderByAsc 升序 .lambdaQuery().orderByAsc(UserInfo::getSource) ORDER BY source ASC
orderByDesc 降序 .lambdaQuery().orderByDesc(UserInfo::getSource) ORDER BY source DESC
orderBy 排序 .lambdaQuery().orderBy(true, true, UserInfo::getSource) ORDER BY source ASC
having having子句 .select(“source,count(id) as sum”).groupBy(“source”).having(“count(id) > {0}”, 35); GROUP BY source HAVING count(id) > 35
func 自定义Consumer .lambdaQuery().func(i -> {if (true) {i.eq(UserInfo::getId, 10);} else {i.eq(UserInfo::getId, 100); }}) WHERE (id = 10)
or 多条件满足一个 .lambdaQuery()…le(UserInfo::getId, 10) .or(i -> .eq(UserInfo::getUserName, “张三”).ge(UserInfo::getId, 1005)) WHERE (id <= 10 OR (user_name = “张三” AND id >= 1005))
and 多条件同时满足 .lambdaQuery().le(UserInfo::getId, 10) .and(i -> i.eq(UserInfo::getUserName, “一行Java 1”)) WHERE (id <= 10 AND (user_name = “一行Java 1”))
nested 指定条件用()嵌套 .lambdaQuery().ge(UserInfo::getId, 10).nested(i -> i.eq(UserInfo::getUserName, “张三”).or(m -> m.ge(UserInfo::getId, 1005))) WHERE (id >= 10 AND (user_name = “张三” OR (id >= 1005)))
apply 拼接sql .lambdaQuery().apply(“id < {0}”, 20) WHERE (id < 20)
last 拼接语句在sql最后 .lambdaQuery().apply(“id < {0}”, 20).last(“limit 1”) WHERE (id < ?) limit 1
exists 子句存在数据 .lambdaQuery().exists(“select id from user_info where id > 1000”) WHERE (EXISTS (select id from user_info where id > 1000))
notExists 子句不存在数据 .lambdaQuery().notExists(“select id from user_info where id > 10000”) WHERE (NOT EXISTS (select id from user_info where id > 10000))

通过上面的表格,再结合示例代码以及等价SQL就能很清晰的看出各个条件构造器的功能了;

下面拧几个不好理解或者需要注意的构造器,专门说一下

allEq

  • 参数

    • condition

      所有条件是否生效,默认是true;设置为false之后,设置的所有的条件都不会生效

    • params

      Map参数;设置需要匹配的字段和对应的值

    • filter

      用于设置需要过滤的字段

    • null2IsNull

      是否忽略null值;默认是true,如果有需要匹配的字段是null,则会添加 is null的查询条件;如果设置为false,将会自动剔除所有值null的字段校验

  • 测试代码

    /**
     * AllEq
     */
    @SpringBootTest
    @Slf4j
    public class AllEqTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        Map<String, Object> params = new HashMap();
    
        @BeforeEach
        public void init() {
          
          
            params.put("user_name", "一行Java 1");
            params.put("id", null);
        }
    
        @Test
        void allEq() {
          
          
            List<UserInfo> list = userInfoService.query().allEq(params).list();
            log.info("{}", JSON.toJSONString(list));
            // 等价sql: SELECT id,user_name,age,source FROM user_info WHERE (user_name = "一行Java 1" AND id IS NULL)
        }
    
        @Test
        void allEqConditionFalse() {
          
          
            //-----------condition参数------------
            // 表示是否才上查询条件,如果等于false 将不会添加任何查询条件
            List<UserInfo> list = userInfoService.query().allEq(false, params, true).list();
            log.info("{}", JSON.toJSONString(list));
            // 等价sql: SELECT id,user_name,age,source FROM user_info
        }
    
        @Test
        void allEqNull2IsNull() {
          
          
            //-----------null2IsNull演示------------
            // null2IsNull = false;会自动踢出null值条件
            List<UserInfo> list = userInfoService.query().allEq(params, false).list();
            // 等价sql: SELECT id,user_name,age,source FROM user_info WHERE (user_name = "一行Java 1")
            log.info("{}", JSON.toJSONString(list));
        }
    
        @Test
        void allEqFilter() {
          
          
            //---------filter延时----------
            // filter字段,表示要忽略的字段
            // 以下是忽略key为id的条件
            List<UserInfo> list = userInfoService.query().allEq((k, v) -> !k.equals("id"), params).list();
            // 等价sql:SELECT id,user_name,age,source FROM user_info WHERE (user_name = "一行Java 1")
            log.info("{}", JSON.toJSONString(list));
        }
    }
    

groupBy and having

跟组跟having筛选

  • 示例代码

    @SpringBootTest
    @Slf4j
    public class GroupByAndHavingTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void groupByAndHaving() {
          
          
            QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
            userInfoQueryWrapper.select("source,count(id) as sum")
                    .groupBy("source")
                    .having("count(id) > {0}", 35);
            List<Map<String, Object>> maps = userInfoService.listMaps(userInfoQueryWrapper);
    
            // 等价sql:SELECT source,count(id) as sum FROM user_info GROUP BY source HAVING count(id) > 35
        }
    }
    

func

用于设置条件子句

实际的业务场景下,可能存在不同的业务条件下导致的sql执行条件也有所不同;那么就可以通过func子句来进行设置

  • 测试代码

    @SpringBootTest
    @Slf4j
    public class FuncTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void func() {
          
          
            Boolean condition = true;
            List<UserInfo> userInfos = userInfoService.lambdaQuery()
                    .func(i -> {
          
          
                        if (condition) {
          
          
                            i.eq(UserInfo::getId, 10);
                        } else {
          
          
                            i.eq(UserInfo::getId, 100);
                        }
                    }).list();
            log.info("userInfos:{}", userInfos);
            //func(i -> {if (true) {i.eq(UserInfo::getId, 10);} else {i.eq(UserInfo::getId, 100); }})
        }
    }
    
  • 执行结果

    • Boolean condition = true;

    • Boolean condition = false;

or 、 and

or:多条件满足一个即可

and:多条件同时满足

  • 示例代码

    @SpringBootTest
    @Slf4j
    public class OrAndTest {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void or() {
          
          
            List<UserInfo> userInfos = userInfoService.lambdaQuery()
                    .le(UserInfo::getId, 10)
                    .or(i -> i.eq(UserInfo::getUserName, "张三").ge(UserInfo::getId, 1005))
                    .list();
            log.info("userInfo:{}", userInfos);
            // 等价sql:SELECT id,user_name,age,source FROM user_info WHERE (id <= 10 OR (user_name = "张三" AND id >= 1005))
        }
    
        @Test
        void and() {
          
          
            List<UserInfo> userInfos = userInfoService.lambdaQuery()
                    .le(UserInfo::getId, 10)
                    .and(i -> i.eq(UserInfo::getUserName, "一行Java 1")).list();
            log.info("userInfo:{}", userInfos);
            // 等价sql:SELECT id,user_name,age,source FROM user_info WHERE (id <= 10 AND (user_name = "一行Java 1"))
        }
    }
    
    • or

    • and

nested、apply、last

  • nested

    嵌套;

    比如当条件中存在and和or组合的时候,就需要对or的多个条件进行嵌套,防止与and之间产生错误的组合关系

  • apply

    拼接sql;有些特殊个性化场景下,很难用api去定义一些操作;比如,需要对时间继续格式化之后作为查询条件,此时就需要借助一段简单的sql拼接来完成效果

    apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")`--->`date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
    
  • last

    在sql的末尾带上指定的语句;比如last("limit 1"),就会在sql语句的末尾加上limit 1

  • API列表

    // nested
    nested(Consumer<Param> consumer)
    nested(boolean condition, Consumer<Param> consumer)
    
    // apply
    apply(String applySql, Object... params)
    apply(boolean condition, String applySql, Object... params)
    
    // last
    last(String lastSql)
    last(boolean condition, String lastSql)
    
  • 示例代码

    @SpringBootTest
    @Slf4j
    public class Nested_Apply_Limit_Test {
          
          
        @Autowired
        UserInfoService userInfoService;
    
        @Test
        void nested() {
          
          
            List<UserInfo> userInfos = userInfoService.lambdaQuery()
                    .ge(UserInfo::getId, 10)
                    .nested(
                            i -> i.eq(UserInfo::getUserName, "张三").or(m -> m.ge(UserInfo::getId, 1005))
                    )
                    .list();
            log.info("userInfo:{}", userInfos);
            // 等价sql:SELECT id,user_name,age,source FROM user_info WHERE (id <= 10 OR (user_name = "张三" AND id >= 1005))
        }
    
        @Test
        void apply() {
          
          
            List<UserInfo> userInfos = userInfoService.lambdaQuery()
                    .apply("id < {0}", 20)
                    .list();
            log.info("userInfo:{}", userInfos);
            // 等价sql:SELECT id,user_name,age,source FROM user_info WHERE (id < 20)
        }
    
        @Test
        void last() {
          
          
            List<UserInfo> userInfos = userInfoService.lambdaQuery()
                    .last("limit 1")
                    .list();
            log.info("userInfo:{}", userInfos);
            // 等价sql:SELECT id,user_name,age,source FROM user_info limit 1
        }
    }
    

有了这些API条件构造器,是不是一行数据库操作的代码都没有写,基础的CURD统统都能搞定了;

但是,实际的业务并不只是基础的CURD,有没有发现,联表查询MyBatis Plus并没有支持,但是关联查询在业务开发中,又会经常用到,如果单纯基于MyBatis Plus,要实现联表,就只能自己写配置,写SQL去实现了,这就违背了本文的初衷了;

那有没有一款框架能帮助我们去封装联表查询呢?那就是下面要介绍的一款框架MyBatis Plus Join

MyBatis Plus Join

MyBatis Plus Join一款专门解决MyBatis Plus 关联查询问题的扩展框架,他并不一款全新的框架,而是基于MyBatis Plus功能的增强,所以MyBatis Plus的所有功能MyBatis Plus Join同样拥有;框架的使用方式和MyBatis Plus一样简单,几行代码就能实现联表查询的功能

官方仓库:https://gitee.com/best_handsome/mybatis-plus-join

准备工作

为了方便做联表测试,这里预先准备三张表(学校表、班级表、学生表),用来做关联查询,sql如下:

DROP TABLE IF EXISTS `school_info`;
CREATE TABLE `school_info`  (
  `id` int(11) NOT NULL,
  `school_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '学校名称',
  `school_addr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '学校地址',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `school_info` VALUES (1, 'XXX小学', 'xx区xx街道80号');

-- ----------------------------------------------------------------

CREATE TABLE `class_info`  (
  `id` int(11) NOT NULL,
  `class_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称',
  `class_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `school_id` int(11) NOT NULL COMMENT '隶属的学校',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `class_info` VALUES (1, '一年级1班', NULL, 1);
INSERT INTO `class_info` VALUES (2, '一年级2班', NULL, 1);

-- ----------------------------------------------------------------

CREATE TABLE `student_info`  (
  `id` int(11) NOT NULL,
  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  `class_id` int(11) NULL DEFAULT NULL,
  `school_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `student_info` VALUES (1, '张三', 7, 1, 1);
INSERT INTO `student_info` VALUES (2, '李四', 7, 2, 1);
INSERT INTO `student_info` VALUES (3, '王五', 8, 1, 1);
INSERT INTO `student_info` VALUES (4, '赵六', 8, 1, 1);

基础MyBatis Plus代码生成

参考MyBatisX的插件使用,这里就不重复了

MyBatis Plus Join 核心类说明

  • MPJBaseMapper

    扩展了MyBatis Plus的 BaseMapper 接口

    public interface MPJBaseMapper<T> extends BaseMapper<T> {
          
          
        Integer selectJoinCount(@Param("ew") MPJBaseJoin var1);
    
        <DTO> DTO selectJoinOne(@Param("resultTypeClass_Eg1sG") Class<DTO> var1, @Param("ew") MPJBaseJoin var2);
    
        Map<String, Object> selectJoinMap(@Param("ew") MPJBaseJoin var1);
    
        <DTO> List<DTO> selectJoinList(@Param("resultTypeClass_Eg1sG") Class<DTO> var1, @Param("ew") MPJBaseJoin var2);
    
        List<Map<String, Object>> selectJoinMaps(@Param("ew") MPJBaseJoin var1);
    
        <DTO, P extends IPage<?>> IPage<DTO> selectJoinPage(P var1, @Param("resultTypeClass_Eg1sG") Class<DTO> var2, @Param("ew") MPJBaseJoin var3);
    
        <P extends IPage<?>> IPage<Map<String, Object>> selectJoinMapsPage(P var1, @Param("ew") MPJBaseJoin var2);
    }
    
  • MPJBaseService

    扩展了MyBatis Plus的 IService 接口

    public interface MPJBaseService<T> extends IService<T> {
          
          
        Integer selectJoinCount(MPJBaseJoin var1);
    
        <DTO> DTO selectJoinOne(Class<DTO> var1, MPJBaseJoin var2);
    
        <DTO> List<DTO> selectJoinList(Class<DTO> var1, MPJBaseJoin var2);
    
        <DTO, P extends IPage<?>> IPage<DTO> selectJoinListPage(P var1, Class<DTO> var2, MPJBaseJoin var3);
    
        Map<String, Object> selectJoinMap(MPJBaseJoin var1);
    
        List<Map<String, Object>> selectJoinMaps(MPJBaseJoin var1);
    
        <P extends IPage<Map<String, Object>>> IPage<Map<String, Object>> selectJoinMapsPage(P var1, MPJBaseJoin var2);
    }
    
  • MPJBaseServiceImpl

    扩展了MyBatis Plus的 ServiceImpl 接口实现

    public class MPJBaseServiceImpl<M extends MPJBaseMapper<T>, T> extends ServiceImpl<M, T> implements MPJBaseService<T> {
          
          
    ...
    }
    

基础代码调整

简单的三处调整,就能完成整合工作

  • 将mapper改为继承MPJBaseMapper (必选)

    修改前

    public interface StudentInfoMapper extends BaseMapper<StudentInfo> {
          
          
    }
    

    修改后

    public interface StudentInfoMapper extends MPJBaseMapper<StudentInfo> {
          
          
    }
    
  • 将service改为继承MPJBaseService (可选)

    修改前

    public interface StudentInfoService extends BaseService<StudentInfo> {
          
          
    }
    

    修改后

    public interface StudentInfoService extends MPJBaseService<StudentInfo> {
          
          
    }
    
  • 将serviceImpl改为继承MPJBaseServiceImpl (可选)

    修改前

    @Service
    public class StudentInfoServiceImpl extends BaseServiceImpl<StudentInfoMapper, StudentInfo>
        implements StudentInfoService{
          
          
    }
    

    修改后

    @Service
    public class StudentInfoServiceImpl extends MPJBaseServiceImpl<StudentInfoMapper, StudentInfo>
        implements StudentInfoService{
          
          
    }
    

联表测试

测试需求:查询学生所处的班级及学校

DTO定义

用于联表查询后接收数据的实体类

@Data
public class StudentInfoDTO {
    
    
	// 学生id
    private Integer id;

    // 性名
    private String name;

    // 年龄
    private Integer age;

    // 班级名称
    private String className;

    // 学校名称
    private String schoolName;

    // 学校地址 用于测试别名
    private String scAddr;
}

单记录联表查询

@Autowired
StudentInfoService sutdentInfoService;

/**
 * 联表查询单个
 */
@Test
public void selectJoinOne() {
    
    
    StudentInfoDTO studentInfoDTO = sutdentInfoService.selectJoinOne(StudentInfoDTO.class,
            new MPJLambdaWrapper<StudentInfo>()
                    .selectAll(StudentInfo.class)
                    .select(SchoolInfo::getSchoolName)
                    .selectAs(SchoolInfo::getSchoolAddr, StudentInfoDTO::getScAddr)
                    .select(ClassInfo::getClassName)
                    .leftJoin(SchoolInfo.class, SchoolInfo::getId, StudentInfo::getSchoolId)
                    .leftJoin(ClassInfo.class, ClassInfo::getId, StudentInfo::getClassId)
                    .eq(StudentInfo::getId, 1));
    log.info("selectJoinOne:{}", JSON.toJSONString(studentInfoDTO));
}

简单说明

  • StudentInfoDTO.class

    表示resultType,用于接收联表查询之后的数据库返回

  • selectAll

    指明查询实体对应的所有字段

  • select

    指定查询列,同一个select只能指明单个表的列,所以多表关联时需要使用多个select去指明不同表的列

  • selectAs

    重命名,表现在sql层面是会给字段加上as(别名);主要用在数据库字段名也实体对象的名称不一致的情况;

  • leftJoin、rightJoin、innerJoin

    左链接、右连接、等值连接;不懂这三种连接方式的,可参考:SQL中 inner join、left join、right join、full join 到底怎么选?详解来了

    • 参数一:参与联表的对象
    • 参数二:on关联的指定,此属性必须是第一个对象中的值
    • 参数三:参与连表的ON的另一个实体类属性
  • 条件构造器

    联表后可能会存在各种筛选条件,可以根据上面对条件构造器的介绍,指明所需要的筛选条件,比如上面.eq(StudentInfo::getId, 1)),就是用来指明ID为1的学生信息。

  • 表名

    默认主表别名是t,其他的表别名以先后调用的顺序使用t1,t2,t3…

    需要直接apply语句的时候,就得知道对应的表面是什么再进行添加,所以不到万不得已的时候,不建议直接追加语句。

等价SQL

SELECT 
	t.id,
	t.name,
	t.age,
	t.class_id,
	t.school_id,
	t1.school_name,
	t1.school_addr AS scAddr,
	t2.class_name
FROM 
	student_info t
	LEFT JOIN school_info t1 ON (t1.id = t.school_id)
	LEFT JOIN class_info t2 ON (t2.id = t.class_id)
WHERE (t.id = ?)

执行结果

联表查多条

@Autowired
StudentInfoService sutdentInfoService;

/**
 * 联表查询批量
 */
@Test
public void selectJoinList() {
    
    
    List<StudentInfoDTO> studentInfoDTOS = sutdentInfoService.selectJoinList(StudentInfoDTO.class,
            new MPJLambdaWrapper<StudentInfo>()
                    .selectAll(StudentInfo.class)
                    .select(SchoolInfo::getSchoolName)
                    .selectAs(SchoolInfo::getSchoolAddr, StudentInfoDTO::getScAddr)
                    .select(ClassInfo::getClassName)
                    .leftJoin(SchoolInfo.class, SchoolInfo::getId, StudentInfo::getSchoolId)
                    .leftJoin(ClassInfo.class, ClassInfo::getId, StudentInfo::getClassId)
            //.eq(StudentInfo::getId, 1)
    );
    log.info("selectJoinList:{}", JSON.toJSONString(studentInfoDTOS));
}

等价SQL

SELECT 
	t.id,
	t.name,
	t.age,
	t.class_id,
	t.school_id,
	t1.school_name,
	t1.school_addr AS scAddr,
	t2.class_name
FROM 
	student_info t
	LEFT JOIN school_info t1 ON (t1.id = t.school_id)
	LEFT JOIN class_info t2 ON (t2.id = t.class_id)

执行结果

联表分页查询

@Autowired
StudentInfoService sutdentInfoService;

/**
 * 分页查询
 */
@Test
public void selectJoinPage() {
    
    
    IPage<StudentInfoDTO> studentInfoDTOIPage = sutdentInfoService.selectJoinListPage(new Page<>(1, 2), StudentInfoDTO.class,
            new MPJLambdaWrapper<StudentInfo>()
                    .selectAll(StudentInfo.class)
                    .select(SchoolInfo::getSchoolName)
                    .selectAs(SchoolInfo::getSchoolAddr, StudentInfoDTO::getScAddr)
                    .select(ClassInfo::getClassName)
                    .leftJoin(SchoolInfo.class, SchoolInfo::getId, StudentInfo::getSchoolId)
                    .leftJoin(ClassInfo.class, ClassInfo::getId, StudentInfo::getClassId)
                    .orderByAsc(StudentInfo::getId)
    );
    log.info("selectJoinPage:{}", JSON.toJSONString(studentInfoDTOIPage));
}

等价SQL

SELECT 
	t.id,
	t.name,
	t.age,
	t.class_id,
	t.school_id,
	t1.school_name,
	t1.school_addr AS scAddr,
	t2.class_name
FROM 
	student_info t
	LEFT JOIN school_info t1 ON (t1.id = t.school_id)
	LEFT JOIN class_info t2 ON (t2.id = t.class_id)
ORDER BY 
	t.id ASC 
LIMIT 2

执行结果

总结

好了,MyBatis Plus + MyBatisX + MyBatis Plus Join的详细使用教程就讲解完了;再回头看,是不是发现业务功能开发一下子变的简单多了;

本文也只是介绍了大部分常用的内容,并没有列举出两款框架的所有东西;知道怎么使用之后,更多的使用细节,可以结合API文档以及各种条件构造器,灵活变通,即可完成各种想要的效果;

码字不易,如果觉得好用,帮忙点个赞,点个再看呗!感激涕零…

猜你喜欢

转载自blog.csdn.net/lupengfei1009/article/details/121689443