三款神器(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))
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://lupf.cn/articles/2021/11/30/1638265998035.html