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

  |   0 评论   |   0 浏览

 如遇图片加载失败,可尝试使用手机流量访问

大家好,我是一航!

程序员每天的搬砖日常,可以说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))
inSqlsql方式包含.lambdaQuery().inSql(UserInfo::getId, "1, 2, 3")WHERE (id IN (1, 2, 3))
notInSqlsql方式不包含.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
havinghaving子句.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文档以及各种条件构造器,灵活变通,即可完成各种想要的效果;

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




标题:三款神器(MyBatis Plus + MyBatisX + MyBatis Plus Join);终于不用写数据库操作代码,一键生成直接调用
作者:码霸霸
地址:https://blog.lupf.cn/articles/2021/11/30/1638265998035.html