MyBatis Plus + 两款神器,彻底解放双手,从此告别加班!爽!

2022-09-03 10:42:47 浏览数 (1)

大家好,我是一航!

后端程序员每天的搬砖日常,可以说绝大部分工作都与CURD有关;自然,数据库的CURD操作也就占据了主要的工作时间;不是在配置xml,就是在写sql的路上,但这一切又都是些技术含量不高的体力活;

那有没有什么方式能把这份苦力活给干掉呢?

答案是有的,也就是今天介绍的2框框架 1个工具(MyBatis Plus MyBatisX MyBatis Plus Join);不写一行数据库操作代码,不加一行配置文件;一键生成代码基础的CURD联表查询API统统搞定;让我们可以安心将精力完全放在产品业务逻辑开发上,准点下班不是梦!

本文是一个手把手的详解教程,如果没有足够的时间,可以先点赞、收藏,用的时候再拿出来翻一翻。

开整!!!

示例源码地址:https://github.com/vehang/ehang-spring-boot/tree/main/spring-boot-010-mysql-mybatis-plus 所有测试用例全部在Test目录下

本文目录:

1框架、工具介绍

  • 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相关的代码;

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

2导入依赖

必备

代码语言:javascript复制
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3.4</version>
</dependency>

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

代码语言:javascript复制
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>

分页

代码语言:javascript复制
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-extension</artifactId>
    <version>3.4.1</version>
</dependency>

联表查询

代码语言:javascript复制
<!--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>

辅助

代码语言:javascript复制
<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>

3数据库配置

数据库表

一张简单的用户数据表,在你本地库中执行以下脚本创建;

代码语言:javascript复制
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数据库配置

请把以下的IP数据库名以及用户密码换成你本地对应的配置;

代码语言:javascript复制
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+8&useSSL=false
    username: root
    password: 123456
    #阿里巴巴的druid的mysql连接池
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver

启动类配置Dao扫描

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

代码语言:javascript复制
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan(basePackages = {"com.ehang.springboot.mybatisplus.generator.**.mapper"})

4MybatisX

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

插件使用步骤如下:

  • 安装插件 安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。
  • 配置数据源
  • 自动生成ORM代码
    • 第一步 选中表(支持多选),右键选择“MybatisX-Generator
    • 配置基础信息
    • 属性、方法配置
    • 生成后的效果

5MyBatis Plus使用

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

结构说明

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

demain

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

UserInfoMapper.xml

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

代码语言:javascript复制
<?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

代码语言:javascript复制
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

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

代码语言:javascript复制
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接口的定义;

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

代码语言:javascript复制
public interface UserInfoService extends IService<UserInfo> {
}

ServiceImpl

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

代码语言:javascript复制
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列表

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

代码

代码语言:javascript复制
// 单个插入
@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列表

代码语言:javascript复制
// 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);

测试代码

代码语言:javascript复制
@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列表

代码语言:javascript复制
// 根据 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);

测试代码

代码语言:javascript复制
    @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列表

代码语言:javascript复制
// 根据 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);

测试代码

代码语言:javascript复制
@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列表

代码语言:javascript复制
// 根据 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);

测试代码

代码语言:javascript复制
@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列表

代码语言:javascript复制
// 查询所有
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);

测试代码

代码语言:javascript复制
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

分页查询

分页插件

代码语言:javascript复制
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-extension</artifactId>
    <version>3.4.1</version>
</dependency>

分页插件配置

代码语言:javascript复制
    // 新版
    @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列表

代码语言:javascript复制
// 无条件分页查询
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);

测试代码

代码语言:javascript复制
@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列表

代码语言:javascript复制
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

测试代码

代码语言:javascript复制
@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列表

代码语言:javascript复制
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery(); 

// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin 
LambdaUpdateChainWrapper<T> lambdaUpdate();

测试代码

代码语言:javascript复制
@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之后,设置的所有的条件都不会生效
  • filter 用于设置需要过滤的字段

测试代码

代码语言:javascript复制
/**
 * 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筛选

示例代码

代码语言:javascript复制
@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子句来进行设置

测试代码

代码语言:javascript复制
@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;
or 、 and

or:多条件满足一个即可

and:多条件同时满足

示例代码

代码语言:javascript复制
@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
nested、apply、last

nested

嵌套;

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

apply

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

代码语言:javascript复制
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列表

代码语言:javascript复制
// 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)

示例代码

代码语言:javascript复制
@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

6MyBatis 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如下:

代码语言:javascript复制
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 接口

代码语言:javascript复制
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 接口

代码语言:javascript复制
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 接口实现

代码语言:javascript复制
public class MPJBaseServiceImpl<M extends MPJBaseMapper<T>, T> extends ServiceImpl<M, T> implements MPJBaseService<T> {
...
}

基础代码调整

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

将mapper改为继承MPJBaseMapper (必选)

修改前

代码语言:javascript复制
public interface StudentInfoMapper extends BaseMapper<StudentInfo> {
}

修改后

代码语言:javascript复制
public interface StudentInfoMapper extends MPJBaseMapper<StudentInfo> {
}

将service改为继承MPJBaseService (可选)

修改前

代码语言:javascript复制
public interface StudentInfoService extends BaseService<StudentInfo> {
}

修改后

代码语言:javascript复制
public interface StudentInfoService extends MPJBaseService<StudentInfo> {
}

将serviceImpl改为继承MPJBaseServiceImpl (可选)

修改前

代码语言:javascript复制
@Service
public class StudentInfoServiceImpl extends BaseServiceImpl<StudentInfoMapper, StudentInfo>
    implements StudentInfoService{
}

修改后

代码语言:javascript复制
@Service
public class StudentInfoServiceImpl extends MPJBaseServiceImpl<StudentInfoMapper, StudentInfo>
    implements StudentInfoService{
}

联表测试

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

DTO定义

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

代码语言:javascript复制
@Data
public class StudentInfoDTO {
 // 学生id
    private Integer id;

    // 性名
    private String name;

    // 年龄
    private Integer age;

    // 班级名称
    private String className;

    // 学校名称
    private String schoolName;

    // 学校地址 用于测试别名
    private String scAddr;
}
单记录联表查询
代码语言:javascript复制
@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

代码语言:javascript复制
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 = ?)

执行结果

联表查多条
代码语言:javascript复制
@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

代码语言:javascript复制
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)

执行结果

联表分页查询
代码语言:javascript复制
@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

代码语言:javascript复制
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

执行结果

7测试源码

https://github.com/vehang/ehang-spring-boot/tree/main/spring-boot-010-mysql-mybatis-plus 所有测试用例全部在Test目录下

8总结

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

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

0 人点赞