• MyBatis-plus超神用法--一文带你玩转MP


    前言

    MyBatis-Plus是一个基于MyBatis的增强工具,提供了很多便捷的功能和增强的功能,以下是一些MyBatis-Plus的超神用法:

    1. 通用Mapper:MyBatis-Plus提供了通用Mapper的功能,可以通过继承BaseMapper接口,实现简单的CRUD操作,无需编写SQL语句,减少重复的代码。

    2. 分页查询:MyBatis-Plus内置了分页查询的功能,可以很方便地进行分页查询操作。使用Page对象进行分页查询,可以通过设置当前页码和每页显示的数量来获取分页数据。

    3. 逻辑删除:MyBatis-Plus支持逻辑删除功能,通过在实体类的字段上添加@TableLogic注解,可以实现逻辑删除的功能。删除操作只是将逻辑删除字段置为特定值,而不是真正的删除数据。

    4. 自动填充:MyBatis-Plus提供了自动填充的功能,可以在插入或更新数据时自动填充某些字段的值,如创建时间和更新时间等。通过实现MetaObjectHandler接口,可以自定义填充的规则。

    5. 条件构造器:MyBatis-Plus提供了强大的条件构造器的功能,可以通过Lambda表达式来构造查询条件,避免手写SQL语句。可以使用eq、ne、gt、ge、lt、le等方法来构造各种条件。

    6. 乐观锁:MyBatis-Plus支持乐观锁的功能,通过在实体类的字段上添加@Version注解,可以实现乐观锁的功能。在更新数据时,会自动判断版本号是否一致,如果一致才会进行更新操作。

    7. 动态表名:MyBatis-Plus支持动态表名的功能,可以动态地切换表名进行查询操作。通过在Mapper接口的方法上添加@TableName注解,并在SQL语句中使用#tableName来引用动态表名。

    8. SQL注入器:MyBatis-Plus提供了SQL注入器的功能,可以自定义SQL注入的操作。可以自定义SQL注入的方法,并在Mapper接口中调用自定义的SQL注入方法。

    通过这些功能,可以大幅度提高开发效率,减少编写重复代码的工作量。使用MyBatis-Plus可以更加便捷地进行数据库操作,同时还能提供一些高级功能的支持。本文主要介绍 mp 搭配 Spring Boot 的使用。

    一、快速入门

    1.创建一个Spring Boot项目。
    2.导入依赖
    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0modelVersion>
    5. <parent>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-parentartifactId>
    8. <version>2.3.4.RELEASEversion>
    9. <relativePath/>
    10. parent>
    11. <groupId>com.examplegroupId>
    12. <artifactId>mybatis-plusartifactId>
    13. <version>0.0.1-SNAPSHOTversion>
    14. <name>mybatis-plusname>
    15. <properties>
    16. <java.version>1.8java.version>
    17. properties>
    18. <dependencies>
    19. <dependency>
    20. <groupId>org.springframework.bootgroupId>
    21. <artifactId>spring-boot-starterartifactId>
    22. dependency>
    23. <dependency>
    24. <groupId>org.springframework.bootgroupId>
    25. <artifactId>spring-boot-starter-testartifactId>
    26. <scope>testscope>
    27. dependency>
    28. <dependency>
    29. <groupId>org.springframework.bootgroupId>
    30. <artifactId>spring-boot-configuration-processorartifactId>
    31. dependency>
    32. <dependency>
    33. <groupId>com.baomidougroupId>
    34. <artifactId>mybatis-plus-boot-starterartifactId>
    35. <version>3.4.2version>
    36. dependency>
    37. <dependency>
    38. <groupId>mysqlgroupId>
    39. <artifactId>mysql-connector-javaartifactId>
    40. <scope>runtimescope>
    41. dependency>
    42. <dependency>
    43. <groupId>org.projectlombokgroupId>
    44. <artifactId>lombokartifactId>
    45. dependency>
    46. dependencies>
    47. <build>
    48. <plugins>
    49. <plugin>
    50. <groupId>org.springframework.bootgroupId>
    51. <artifactId>spring-boot-maven-pluginartifactId>
    52. plugin>
    53. plugins>
    54. build>
    55. project>
    3.配置数据库
    1. # application.yml
    2. spring:
    3. datasource:
    4. driver-class-name: com.mysql.cj.jdbc.Driver
    5. url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai
    6. username: root
    7. password: root
    8. mybatis-plus:
    9. configuration:
    10. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印
    4.创建一个实体类
    1. package com.example.mp.po;
    2. import lombok.Data;
    3. import java.time.LocalDateTime;
    4. @Data
    5. public class User {
    6. private Long id;
    7. private String name;
    8. private Integer age;
    9. private String email;
    10. private Long managerId;
    11. private LocalDateTime createTime;
    12. }
    5.创建一个mapper接口
    1. package com.example.mp.mappers;
    2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    3. import com.example.mp.po.User;
    4. public interface UserMapper extends BaseMapper { }
    6.在SpringBoot启动类上配置mapper接口的扫描路径
    1. package com.example.mp;
    2. import org.mybatis.spring.annotation.MapperScan;
    3. import org.springframework.boot.SpringApplication;
    4. import org.springframework.boot.autoconp.SpringBootApplication;
    5. @SpringBootApplication
    6. @MapperScan("com.example.mp.mappers")
    7. public class MybatisPlusApplication {
    8. public static void main(String[] args) {
    9. SpringApplication.run(MybatisPlusApplication.class, args);
    10. }
    11. }
    7.在数据库中创建表
    1. DROP TABLE IF EXISTS user;
    2. CREATE TABLE user (
    3. id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键id',
    4. name VARCHAR(30) DEFAULT NULL COMMENT '姓名',
    5. age INT(11) DEFAULT NULL COMMENT '年龄',
    6. email VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
    7. manager_id BIGINT(20) DEFAULT NULL COMMENT '直属上级id',
    8. create_time DATETIME DEFAULT NULL COMMENT '创建时间',
    9. CONSTRAINT manager_fk FOREIGN KEY(manager_id) REFERENCES user (id)
    10. ) ENGINE=INNODB CHARSET=UTF8;
    11. INSERT INTO user (id, name, age ,email, manager_id, create_time) VALUES
    12. (1, '大BOSS', 40, 'boss@baomidou.com', NULL, '2023-10-22 09:48:00'),
    13. (2, '李经理', 40, 'boss@baomidou.com', 1, '2023-10-22 09:48:00'),
    14. (3, '黄主管', 40, 'boss@baomidou.com', 2, '2023-10-22 09:48:00'),
    15. (4, '吴组长', 40, 'boss@baomidou.com', 2, '2023-10-22 09:48:00'),
    16. (5, '小菜', 40, 'boss@baomidou.com', 2, '2023-10-22 09:48:00')
    8.编写一个SpringBoot测试类
    1. package com.example.mp;
    2. import com.example.mp.mappers.UserMapper;
    3. import com.example.mp.po.User;
    4. import org.junit.Test;
    5. import org.junit.runner.RunWith;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.boot.test.context.SpringBootTest;
    8. import org.springframework.test.context.junit4.SpringRunner;
    9. import java.util.List;
    10. import static org.junit.Assert.*;
    11. @RunWith(SpringRunner.class)
    12. @SpringBootTest
    13. public class SampleTest {
    14. @Autowired
    15. private UserMapper mapper;
    16. @Test
    17. public void testSelect() {
    18. List list = mapper.selectList(null);
    19. assertEquals(5, list.size());
    20. list.forEach(System.out::println);
    21. }
    22. }

    至此,准备工作完成,数据库情况如下:

    项目目录如下:

    运行测试类

            可以看到,针对单表的基本CRUD操作,只需要创建好实体类,并创建一个继承自BaseMapper的接口即可,可谓非常简洁。并且,我们注意到,User类中的managerIdcreateTime属性,自动和数据库表中的manager_idcreate_time对应了起来,这是因为mp自动做了数据库下划线命名,到Java类的驼峰命名之间的转化。

    二、核心功能

    注解

    mp一共提供了8个注解,这些注解是用在Java的实体类上面的。

    @TableName

            注解在类上,指定类和数据库表的映射关系。实体类的类名(转成小写后)和数据库表名相同时,可以不指定该注解。

    @TableId

            注解在实体类的某一字段上,表示这个字段对应数据库表的主键。当主键名为id时(表中列名为id,实体类中字段名为id),无需使用该注解显式指定主键,mp会自动关联。若类的字段名和表的列名不一致,可用value属性指定表的列名。另,这个注解有个重要的属性type,用于指定主键策略。

    @TableField

            注解在某一字段上,指定Java实体类的字段和数据库表的列的映射关系。这个注解有如下几个应用场景。

    排除非表字段

            若Java实体类中某个字段,不对应表中的任何列,它只是用于保存一些额外的,或组装后的数据,则可以设置exist属性为false,这样在对实体对象进行插入时,会忽略这个字段。排除非表字段也可以通过其他方式完成,如使用static或transient关键字,但个人觉得不是很合理,不做赘述。

    字段验证策略

            通过insertStrategyupdateStrategywhereStrategy属性进行配置,可以控制在实体对象进行插入,更新,或作为WHERE条件时,对象中的字段要如何组装到SQL语句中。

    字段填充策略

            通过fill属性指定,字段为空时会进行自动填充。

         fill属性有三个可选值:
    1. FieldFill.DEFAULT:默认值,表示不进行自动填充。
    2. FieldFill.INSERT:表示在插入数据时进行自动填充。
    3. FieldFill.UPDATE:表示在更新数据时进行自动填充。

            例如,定义一个实体类User,其中有一个createTime字段和一个updateTime字段,需要在插入和更新数据时自动填充这两个字段的值。可以在对应的字段上添加@TableField注解,并指定fill属性为FieldFill.INSERT和FieldFill.UPDATE。

    1. public class User {
    2. private Long id;
    3. private String username;
    4. @TableField(fill = FieldFill.INSERT)
    5. private Date createTime;
    6. @TableField(fill = FieldFill.UPDATE)
    7. private Date updateTime;
    8. // 省略getter和setter方法
    9. }

     然后,在插入和更新数据时,MyBatis-Plus会自动填充createTime和updateTime字段的值。

    1. User user = new User();
    2. user.setUsername("test");
    3. userMapper.insert(user); // 在插入数据时,自动填充createTime字段的值
    4. user.setUsername("test2");
    5. userMapper.updateById(user); // 在更新数据时,自动填充updateTime字段的值

    通过这种方式,可以方便地进行自动填充操作,减少了手动填充的工作量。

    @Version

    标识乐观锁字段,用于实现乐观锁机制。

    @EnumValue

    标识枚举类字段对应的数据库存储值。

    @TableLogic

    标识逻辑删除字段。

    @KeySequence

    标识主键序列生成策略。

    @InterceptorIgnore

    用于标识方法是否忽略拦截器的拦截。

    CRUD接口

            mp封装了一些最基础的CRUD方法,只需要直接继承mp提供的接口,无需编写任何SQL,即可食用。mp提供了两套接口,分别是Mapper CRUD接口和Service CRUD接口。并且mp还提供了条件构造器Wrapper,可以方便地组装SQL语句中的WHERE条件。

    Mapper CRUD接口

            只需定义好实体类,然后创建一个接口,继承mp提供的BaseMapper,即可食用。mp会在mybatis启动时,自动解析实体类和表的映射关系,并注入带有通用CRUD方法的mapper。BaseMapper里提供的方法,部分列举如下:

    insert(T entity) 插入一条记录

    deleteById(Serializable id) 根据主键id删除一条记录

    delete(Wrapper wrapper) 根据条件构造器wrapper进行删除

    selectById(Serializable id) 根据主键id进行查找

    selectBatchIds(Collection idList) 根据主键id进行批量查找

    selectByMap(Map map) 根据map中指定的列名和列值进行等值匹配查找 selectMaps(Wrapper wrapper) 根据 wrapper 条件,查询记录,将查询结果封装为一个Map,Map的key为结果的列,value为值

    selectList(Wrapper wrapper) 根据条件构造器wrapper进行查询

    update(T entity, Wrapper wrapper) 根据条件构造器wrapper进行更新

    updateById(T entity)

    下面讲解几个比较特别的方法

    selectMaps

      BaseMapper接口还提供了一个selectMaps方法,这个方法会将查询结果封装为一个Map,Map的key为结果的列,value为值

    该方法的使用场景如下:

    只查部分列

             当某个表的列特别多,而SELECT的时候只需要选取个别列,查询出的结果也没必要封装成Java实体类对象时(只查部分列时,封装成实体后,实体对象中的很多属性会是null),则可以用selectMaps,获取到指定的列后,再自行进行处理即可

    1. @Test
    2. public void test3() {
    3. QueryWrapper wrapper = new QueryWrapper<>();
    4. wrapper.select("id","name","email").likeRight("name","黄");
    5. List> maps = userMapper.selectMaps(wrapper);
    6. maps.forEach(System.out::println);
    7. }
    进行数据统计
    1. // 按照直属上级进行分组,查询每组的平均年龄,最大年龄,最小年龄
    2. /**
    3. select avg(age) avg_age ,min(age) min_age, max(age) max_age from user group by manager_id having sum(age) < 500;
    4. **/
    5. @Test
    6. public void test3() {
    7. QueryWrapper wrapper = new QueryWrapper<>();
    8. wrapper.select("manager_id", "avg(age) avg_age", "min(age) min_age", "max(age) max_age")
    9. .groupBy("manager_id").having("sum(age) < {0}", 500);
    10. List> maps = userMapper.selectMaps(wrapper);
    11. maps.forEach(System.out::println);
    12. }

    selectObjs

            只会返回第一个字段(第一列)的值,其他字段会被舍弃

    1. @Test
    2. public void test3() {
    3. QueryWrapper wrapper = new QueryWrapper<>();
    4. wrapper.select("id", "name").like("name", "黄");
    5. List objects = userMapper.selectObjs(wrapper);
    6. objects.forEach(System.out::println);
    7. }
    8. 得到的结果,只封装了第一列的id

      selectCount

              查询满足条件的总数,注意,使用这个方法,不能调用QueryWrapperselect方法设置要查询的列了。这个方法会自动添加select count(1)

      1. @Test
      2. public void test3() {
      3. QueryWrapper wrapper = new QueryWrapper<>();
      4. wrapper.like("name", "黄");
      5. Integer count = userMapper.selectCount(wrapper);
      6. System.out.println(count);
      7. }

      Service CRUD 接口

              另外一套CRUD是Service层的,只需要编写一个接口,继承IService,并创建一个接口实现类,即可食用。(这个接口提供的CRUD方法,和Mapper接口提供的功能大同小异,比较明显的区别在于IService支持了更多的批量化操作,如saveBatch,saveOrUpdateBatch等方法。

      示例如下

      1.首先,新建一个接口,继承IService

      1. package com.example.mp.service;
      2. import com.baomidou.mybatisplus.extension.service.IService;
      3. import com.example.mp.po.User;
      4. public interface UserService extends IService {
      5. }

      2.创建这个接口的实现类,并继承ServiceImpl,最后打上@Service注解,注册到Spring容器中

      1. package com.example.mp.service.impl;
      2. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      3. import com.example.mp.mappers.UserMapper;
      4. import com.example.mp.po.User;
      5. import com.example.mp.service.UserService;
      6. import org.springframework.stereotype.Service;
      7. @Service
      8. public class UserServiceImpl extends ServiceImpl implements UserService { }

      3.测试代码

      1. package com.example.mp;
      2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
      3. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
      4. import com.example.mp.po.User;
      5. import com.example.mp.service.UserService;
      6. import org.junit.Test;
      7. import org.junit.runner.RunWith;
      8. import org.springframework.beans.factory.annotation.Autowired;
      9. import org.springframework.boot.test.context.SpringBootTest;
      10. import org.springframework.test.context.junit4.SpringRunner;
      11. @RunWith(SpringRunner.class)
      12. @SpringBootTest
      13. public class ServiceTest {
      14. @Autowired
      15. private UserService userService;
      16. @Test
      17. public void testGetOne() {
      18. LambdaQueryWrapper wrapper = Wrappers.lambdaQuery();
      19. wrapper.gt(User::getAge, 28);
      20. User one = userService.getOne(wrapper, false); // 第二参数指定为false,使得在查到了多行记录时,不抛出异常,而返回第一条记录
      21. System.out.println(one);
      22. }
      23. }

      4.结果

      另,IService也支持链式调用,代码写起来非常简洁,查询示例如下

      1. @Test
      2. public void testChain() {
      3. List list = userService.lambdaQuery()
      4. .gt(User::getAge, 39)
      5. .likeRight(User::getName, "王")
      6. .list();
      7. list.forEach(System.out::println);
      8. }

      更新示例如下

      1. @Test
      2. public void testChain() {
      3. userService.lambdaUpdate()
      4. .gt(User::getAge, 39)
      5. .likeRight(User::getName, "王")
      6. .set(User::getEmail, "w39@baomidou.com")
      7. .update();
      8. }

      删除示例如下

      1. @Test
      2. public void testChain() {
      3. userService.lambdaUpdate()
      4. .like(User::getName, "青蛙")
      5. .remove();
      6. }

      条件构造器

              mp让我觉得极其方便的一点在于其提供了强大的条件构造器Wrapper,可以非常方便的构造WHERE条件。条件构造器主要涉及到3个类,AbstractWrapperQueryWrapperUpdateWrapper,它们的类关系如下

                    

              在AbstractWrapper中提供了非常多的方法用于构建WHERE条件,而QueryWrapper针对SELECT语句,提供了select()方法,可自定义需要查询的列,而UpdateWrapper针对UPDATE语句,提供了set()方法,用于构造set语句。条件构造器也支持lambda表达式,写起来非常舒爽。

      下面对AbstractWrapper中用于构建SQL语句中的WHERE条件的方法进行部分列举

      • eq:equals,等于

      • allEq:all equals,全等于

      • ne:not equals,不等于

      • gt:greater than ,大于 >

      • ge:greater than or equals,大于等于

      • lt:less than,小于<

      • le:less than or equals,小于等于

      • between:相当于SQL中的BETWEEN

      • notBetween

      • like:模糊匹配。like("name","黄"),相当于SQL的name like '%黄%'

      • likeRight:模糊匹配右半边。likeRight("name","黄"),相当于SQL的name like '黄%'

      • likeLeft:模糊匹配左半边。likeLeft("name","黄"),相当于SQL的name like '%黄'

      • notLikenotLike("name","黄"),相当于SQL的name not like '%黄%'

      • isNull

      • isNotNull

      • in

      • and:SQL连接符AND

      • or:SQL连接符OR

      • apply:用于拼接SQL,该方法可用于数据库函数,并可以动态传参

       使用示例

      1. // 案例先展示需要完成的SQL语句,后展示Wrapper的写法
      2. // 1. 名字中包含佳,且年龄小于25
      3. // SELECT * FROM user WHERE name like '%佳%' AND age < 25
      4. QueryWrapper wrapper = new QueryWrapper<>();
      5. wrapper.like("name", "佳").lt("age", 25);
      6. List users = userMapper.selectList(wrapper);
      7. // 下面展示SQL时,仅展示WHERE条件;展示代码时, 仅展示Wrapper构建部分
      8. // 2. 姓名为黄姓,且年龄大于等于20,小于等于40,且email字段不为空
      9. // name like '黄%' AND age BETWEEN 20 AND 40 AND email is not null
      10. wrapper.likeRight("name","黄").between("age", 20, 40).isNotNull("email");
      11. // 3. 姓名为黄姓,或者年龄大于等于40,按照年龄降序排列,年龄相同则按照id升序排列
      12. // name like '黄%' OR age >= 40 order by age desc, id asc
      13. wrapper.likeRight("name","黄").or().ge("age",40).orderByDesc("age").orderByAsc("id");
      14. // 4.创建日期为2021年3月22日,并且直属上级的名字为李姓
      15. // date_format(create_time,'%Y-%m-%d') = '2021-03-22' AND manager_id IN (SELECT id FROM user WHERE name like '李%')
      16. wrapper.apply("date_format(create_time, '%Y-%m-%d') = {0}", "2021-03-22") // 建议采用{index}这种方式动态传参, 可防止SQL注入
      17. .inSql("manager_id", "SELECT id FROM user WHERE name like '李%'");
      18. // 上面的apply, 也可以直接使用下面这种方式做字符串拼接,但当这个日期是一个外部参数时,这种方式有SQL注入的风险
      19. wrapper.apply("date_format(create_time, '%Y-%m-%d') = '2021-03-22'");
      20. // 5. 名字为王姓,并且(年龄小于40,或者邮箱不为空)
      21. // name like '王%' AND (age < 40 OR email is not null)
      22. wrapper.likeRight("name", "王").and(q -> q.lt("age", 40).or().isNotNull("email"));
      23. // 6. 名字为王姓,或者(年龄小于40并且年龄大于20并且邮箱不为空)
      24. // name like '王%' OR (age < 40 AND age > 20 AND email is not null)
      25. wrapper.likeRight("name", "王").or(
      26. q -> q.lt("age",40)
      27. .gt("age",20)
      28. .isNotNull("email")
      29. );
      30. // 7. (年龄小于40或者邮箱不为空) 并且名字为王姓
      31. // (age < 40 OR email is not null) AND name like '王%'
      32. wrapper.nested(q -> q.lt("age", 40).or().isNotNull("email"))
      33. .likeRight("name", "王");
      34. // 8. 年龄为30,31,34,35
      35. // age IN (30,31,34,35)
      36. wrapper.in("age", Arrays.asList(30,31,34,35));
      37. // 或
      38. wrapper.inSql("age","30,31,34,35");
      39. // 9. 年龄为30,31,34,35, 返回满足条件的第一条记录
      40. // age IN (30,31,34,35) LIMIT 1
      41. wrapper.in("age", Arrays.asList(30,31,34,35)).last("LIMIT 1");
      42. // 10. 只选出id, name 列 (QueryWrapper 特有)
      43. // SELECT id, name FROM user;
      44. wrapper.select("id", "name");
      45. // 11. 选出id, name, age, email, 等同于排除 manager_id 和 create_time
      46. // 当列特别多, 而只需要排除个别列时, 采用上面的方式可能需要写很多个列, 可以采用重载的select方法,指定需要排除的列
      47. wrapper.select(User.class, info -> {
      48. String columnName = info.getColumn();
      49. return !"create_time".equals(columnName) && !"manager_id".equals(columnName);
      50. });
      Condition

              条件构造器的诸多方法中,均可以指定一个boolean类型的参数condition,用来决定该条件是否加入最后生成的WHERE语句中,比如

      1. String name = "黄"; // 假设name变量是一个外部传入的参数
      2. QueryWrapper wrapper = new QueryWrapper<>();
      3. wrapper.like(StringUtils.hasText(name), "name", name);
      4. // 仅当 StringUtils.hasText(name) 为 true 时, 会拼接这个like语句到WHERE中
      5. // 其实就是对下面代码的简化
      6. if (StringUtils.hasText(name)) {
      7. wrapper.like("name", name);
      8. }
      实体对象作为条件

              调用构造函数创建一个Wrapper对象时,可以传入一个实体对象。后续使用这个Wrapper时,会以实体对象中的非空属性,构建WHERE条件(默认构建等值匹配的WHERE条件,这个行为可以通过实体类里各个字段上的@TableField注解中的condition属性进行改变)

      1. @Test
      2. public void test3() {
      3. User user = new User();
      4. user.setName("黄主管");
      5. user.setAge(28);
      6. QueryWrapper wrapper = new QueryWrapper<>(user);
      7. List users = userMapper.selectList(wrapper);
      8. users.forEach(System.out::println);
      9. }

      执行结果如下。可以看到,是根据实体对象中的非空属性,进行了等值匹配查询

              若希望针对某些属性,改变等值匹配的行为,则可以在实体类中用@TableField注解进行配置,示例如下

      1. package com.example.mp.po;
      2. import com.baomidou.mybatisplus.annotation.SqlCondition;
      3. import com.baomidou.mybatisplus.annotation.TableField;
      4. import lombok.Data;
      5. import java.time.LocalDateTime;
      6. @Data
      7. public class User {
      8. private Long id;
      9. @TableField(condition = SqlCondition.LIKE) // 配置该字段使用like进行拼接
      10. private String name;
      11. private Integer age;
      12. private String email;
      13. private Long managerId;
      14. private LocalDateTime createTime;
      15. }

      运行下面的测试代码

      1. @Test
      2. public void test3() {
      3. User user = new User();
      4. user.setName("黄");
      5. QueryWrapper wrapper = new QueryWrapper<>(user);
      6. List users = userMapper.selectList(wrapper);
      7. users.forEach(System.out::println);
      8. }

      从下图得到的结果来看,对于实体对象中的name字段,采用了like进行拼接

      @TableField中配置的condition属性实则是一个字符串,SqlCondition类中预定义了一些字符串以供选择

      1. package com.baomidou.mybatisplus.annotation;
      2. public class SqlCondition {
      3. //下面的字符串中, %s 是占位符, 第一个 %s 是列名, 第二个 %s 是列的值
      4. public static final String EQUAL = "%s=#{%s}";
      5. public static final String NOT_EQUAL = "%s<>#{%s}";
      6. public static final String LIKE = "%s LIKE CONCAT('%%',#{%s},'%%')";
      7. public static final String LIKE_LEFT = "%s LIKE CONCAT('%%',#{%s})";
      8. public static final String LIKE_RIGHT = "%s LIKE CONCAT(#{%s},'%%')";
      9. }

       SqlCondition中提供的配置比较有限,当我们需要<>等拼接方式,则需要自己定义。比如

      1. package com.example.mp.po;
      2. import com.baomidou.mybatisplus.annotation.SqlCondition;
      3. import com.baomidou.mybatisplus.annotation.TableField;
      4. import lombok.Data;
      5. import java.time.LocalDateTime;
      6. @Data
      7. public class User {
      8. private Long id;
      9. @TableField(condition = SqlCondition.LIKE)
      10. private String name;
      11. @TableField(condition = "%s > #{%s}") // 这里相当于大于, 其中 > 是字符实体
      12. private Integer age;
      13. private String email;
      14. private Long managerId;
      15. private LocalDateTime createTime;
      16. }

      测试如下

      1. @Test
      2. public void test3() {
      3. User user = new User();
      4. user.setName("黄");
      5. user.setAge(30);
      6. QueryWrapper wrapper = new QueryWrapper<>(user);
      7. List users = userMapper.selectList(wrapper);
      8. users.forEach(System.out::println);
      9. }

      从下图得到的结果,可以看出,name属性是用like拼接的,而age属性是用>拼接的

      allEq方法

      allEq方法传入一个map,用来做等值匹配

      1. @Test
      2. public void test3() {
      3. QueryWrapper wrapper = new QueryWrapper<>();
      4. Map param = new HashMap<>();
      5. param.put("age", 40);
      6. param.put("name", "黄飞飞");
      7. wrapper.allEq(param);
      8. List users = userMapper.selectList(wrapper);
      9. users.forEach(System.out::println);
      10. }

      当allEq方法传入的Map中有value为null的元素时,默认会设置为is null

      1. @Test
      2. public void test3() {
      3. QueryWrapper wrapper = new QueryWrapper<>();
      4. Map param = new HashMap<>();
      5. param.put("age", 40);
      6. param.put("name", null);
      7. wrapper.allEq(param);
      8. List users = userMapper.selectList(wrapper);
      9. users.forEach(System.out::println);
      10. }

      若想忽略map中value为null的元素,可以在调用allEq时,设置参数boolean null2IsNullfalse

      1. @Test
      2. public void test3() {
      3. QueryWrapper wrapper = new QueryWrapper<>();
      4. Map param = new HashMap<>();
      5. param.put("age", 40);
      6. param.put("name", null);
      7. wrapper.allEq(param, false);
      8. List users = userMapper.selectList(wrapper);
      9. users.forEach(System.out::println);
      10. }

      若想要在执行allEq时,过滤掉Map中的某些元素,可以调用allEq的重载方法allEq(BiPredicate filter, Map params)

      1. @Test
      2. public void test3() {
      3. QueryWrapper wrapper = new QueryWrapper<>();
      4. Map param = new HashMap<>();
      5. param.put("age", 40);
      6. param.put("name", "黄飞飞");
      7. wrapper.allEq((k,v) -> !"name".equals(k), param); // 过滤掉map中key为name的元素
      8. List users = userMapper.selectList(wrapper);
      9. users.forEach(System.out::println);
      10. }
      lambda条件构造器

      lambda条件构造器,支持lambda表达式,可以不必像普通条件构造器一样,以字符串形式指定列名,它可以直接以实体类的方法引用来指定列。示例如下

      1. @Test
      2. public void testLambda() {
      3. LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
      4. wrapper.like(User::getName, "黄").lt(User::getAge, 30);
      5. List users = userMapper.selectList(wrapper);
      6. users.forEach(System.out::println);
      7. }

      像普通的条件构造器,列名是用字符串的形式指定,无法在编译期进行列名合法性的检查,这就不如lambda条件构造器来的优雅。

      另外,还有个链式lambda条件构造器,使用示例如下

      1. @Test
      2. public void testLambda() {
      3. LambdaQueryChainWrapper chainWrapper = new LambdaQueryChainWrapper<>(userMapper);
      4. List users = chainWrapper.like(User::getName, "黄").gt(User::getAge, 30).list();
      5. users.forEach(System.out::println);
      6. }

      更新操作

      上面介绍的都是查询操作,现在来讲更新和删除操作。

      BaseMapper中提供了2个更新方法

      • updateById(T entity)

      根据入参entityid(主键)进行更新,对于entity中非空的属性,会出现在UPDATE语句的SET后面,即entity中非空的属性,会被更新到数据库,示例如下

      1. @RunWith(SpringRunner.class)
      2. @SpringBootTest
      3. public class UpdateTest {
      4. @Autowired
      5. private UserMapper userMapper;
      6. @Test
      7. public void testUpdate() {
      8. User user = new User();
      9. user.setId(2L);
      10. user.setAge(18);
      11. userMapper.updateById(user);
      12. }
      13. }

      • update(T entity, Wrapper wrapper)

      根据实体entity和条件构造器wrapper进行更新,示例如下

      1. @Test
      2. public void testUpdate2() {
      3. User user = new User();
      4. user.setName("王三蛋");
      5. LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>();
      6. wrapper.between(User::getAge, 26,31).likeRight(User::getName,"吴");
      7. userMapper.update(user, wrapper);
      8. }

      额外演示一下,把实体对象传入Wrapper,即用实体对象构造WHERE条件的案例

      1. @Test
      2. public void testUpdate3() {
      3. User whereUser = new User();
      4. whereUser.setAge(40);
      5. whereUser.setName("王");
      6. LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(whereUser);
      7. User user = new User();
      8. user.setEmail("share@baomidou.com");
      9. user.setManagerId(10L);
      10. userMapper.update(user, wrapper);
      11. }

      注意到我们的User类中,对name属性和age属性进行了如下的设置

      1. @Data
      2. public class User {
      3. private Long id;
      4. @TableField(condition = SqlCondition.LIKE)
      5. private String name;
      6. @TableField(condition = "%s > #{%s}")
      7. private Integer age;
      8. private String email;
      9. private Long managerId;
      10. private LocalDateTime createTime;
      11. }

      执行结果

      再额外演示一下,链式lambda条件构造器的使用

      1. @Test
      2. public void testUpdate5() {
      3. LambdaUpdateChainWrapper wrapper = new LambdaUpdateChainWrapper<>(userMapper);
      4. wrapper.likeRight(User::getEmail, "share")
      5. .like(User::getName, "飞飞")
      6. .set(User::getEmail, "ff@baomidou.com")
      7. .update();
      8. }

      反思

      由于BaseMapper提供的2个更新方法都是传入一个实体对象去执行更新,这在需要更新的列比较多时还好,若想要更新的只有那么一列,或者两列,则创建一个实体对象就显得有点麻烦。针对这种情况,UpdateWrapper提供有set方法,可以手动拼接SQL中的SET语句,此时可以不必传入实体对象,示例如下

      1. @Test
      2. public void testUpdate4() {
      3. LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>();
      4. wrapper.likeRight(User::getEmail, "share").set(User::getManagerId, 9L);
      5. userMapper.update(null, wrapper);
      6. }

       

      删除操作

      BaseMapper一共提供了如下几个用于删除的方法

      • deleteById  根据主键id进行删除

      • deleteBatchIds  根据主键id进行批量删除

      • deleteByMap  根据Map进行删除(Map中的key为列名,value为值,根据列和值进行等值匹配)

      • delete(Wrapper wrapper)  根据条件构造器Wrapper进行删除

      与前面查询和更新的操作大同小异,不做赘述

      自定义SQL

      当mp提供的方法还不能满足需求时,则可以自定义SQL。

      原生mybatis

      示例如下

      • 注解方式

      1. public interface UserMapper extends BaseMapper {
      2. @Select("select * from user")
      3. List selectRaw();
      4. }
      • xml方式

        1. "com.example.mp.mappers.UserMapper">
        1. public interface UserMapper extends BaseMapper {
        2. List selectRaw();
        3. }

        使用xml时,若xml文件与mapper接口文件不在同一目录下,则需要在application.yml中配置mapper.xml的存放路径

        1. mybatis-plus:
        2. mapper-locations: /mappers/*

        若有多个地方存放mapper,则用数组形式进行配置

        1. mybatis-plus:
        2. mapper-locations:
        3. - /mappers/*
        4. - /com/example/mp/*

        测试代码如下

        1. @Test
        2. public void testCustomRawSql() {
        3. List users = userMapper.selectRaw();
        4. users.forEach(System.out::println);
        5. }

        结果

      mybatis-plus

      也可以使用mp提供的Wrapper条件构造器,来自定义SQL

      示例如下

      • 注解方式

       

      1. public interface UserMapper extends BaseMapper {
      2. // SQL中不写WHERE关键字,且固定使用${ew.customSqlSegment}
      3. @Select("select * from user ${ew.customSqlSegment}")
      4. List findAll(@Param(Constants.WRAPPER)Wrapper wrapper);
      5. }
      • xml方式

        1. public interface UserMapper extends BaseMapper {
        2. List findAll(Wrapper wrapper);
        3. }
        1. <mapper namespace="com.example.mp.mappers.UserMapper">
        2. <select id="findAll" resultType="com.example.mp.po.User">
        3. SELECT * FROM user ${ew.customSqlSegment}
        4. select>
        5. mapper>

        分页查询

        BaseMapper中提供了2个方法进行分页查询,分别是selectPageselectMapsPage,前者会将查询的结果封装成Java实体对象,后者会封装成Map。分页查询的示例如下

      1. 创建mp的分页拦截器,注册到Spring容器中

       

      1. @Configuration
      2. public class MybatisPlusConfig {
      3. /** 新版mp **/
      4. @Bean
      5. public MybatisPlusInterceptor mybatisPlusInterceptor() {
      6. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
      7. interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
      8. return interceptor;
      9. }
      10. /** 旧版mp 用 PaginationInterceptor **/
      11. }

      2. 执行分页查询

      1. @Test
      2. public void testPage() {
      3. LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
      4. wrapper.ge(User::getAge, 28);
      5. // 设置分页信息, 查第3页, 每页2条数据
      6. Page page = new Page<>(3, 2);
      7. // 执行分页查询
      8. Page userPage = userMapper.selectPage(page, wrapper);
      9. System.out.println("总记录数 = " + userPage.getTotal());
      10. System.out.println("总页数 = " + userPage.getPages());
      11. System.out.println("当前页码 = " + userPage.getCurrent());
      12. // 获取分页查询结果
      13. List records = userPage.getRecords();
      14. records.forEach(System.out::println);
      15. }

      3. 结果

      4. 其他

      注意到,分页查询总共发出了2次SQL,一次查总记录数,一次查具体数据。若希望不查总记录数,仅查分页结果。可以通过Page的重载构造函数,指定isSearchCountfalse即可

      public Page(long current, long size, boolean isSearchCount)

      在实际开发中,可能遇到多表联查的场景,此时BaseMapper中提供的单表分页查询的方法无法满足需求,需要自定义SQL,示例如下(使用单表查询的SQL进行演示,实际进行多表联查时,修改SQL语句即可)

      1. 在mapper接口中定义一个函数,接收一个Page对象为参数,并编写自定义SQL

      1. // 这里采用纯注解方式。当然,若SQL比较复杂,建议还是采用XML的方式
      2. @Select("SELECT * FROM user ${ew.customSqlSegment}")
      3. Page selectUserPage(Page page, @Param(Constants.WRAPPER) Wrapper wrapper);

      2. 执行查询

      1. @Test
      2. public void testPage2() {
      3. LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
      4. wrapper.ge(User::getAge, 28).likeRight(User::getName, "王");
      5. Page page = new Page<>(3,2);
      6. Page userPage = userMapper.selectUserPage(page, wrapper);
      7. System.out.println("总记录数 = " + userPage.getTotal());
      8. System.out.println("总页数 = " + userPage.getPages());
      9. userPage.getRecords().forEach(System.out::println);
      10. }

      3. 结果

    9. 相关阅读:
      基于ABP实现DDD--领域服务、应用服务和DTO实践
      nginx(六十四)proxy模块(五)接收上游响应
      Java8 Optional 的常见操作
      【设计模式】策略模式(行为型)⭐⭐
      通俗易懂地说一下 nft
      ARM功耗管理之功耗数据与功耗收益评估
      C++回顾录04-构造函数
      HTTP协议(上)
      腾讯云服务器可用区是什么意思?可用区选择方法
      windows和docker环境下springboot整合gdal3.x
    10. 原文地址:https://blog.csdn.net/lf21qp/article/details/134288137