使用示例

1)数据库及表准备

sql 语句:

  1. use test;
  2. CREATE TABLE `student` (
  3. `id` int(0) NOT NULL AUTO_INCREMENT,
  4. `dept_id` int(0) NULL DEFAULT NULL,
  5. `name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  6. `remark` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  7. PRIMARY KEY (`id`) USING BTREE
  8. ) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
  9. -- ----------------------------
  10. -- Records of student
  11. -- ----------------------------
  12. INSERT INTO `student` VALUES (1, 1, '小菜', '关注小菜不迷路!');
  13. INSERT INTO `student` VALUES (2, 2, '小明', '好好学习,天天向上!');

2)pom 依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-test</artifactId>
  4. </dependency>
  5. <!--lombok-->
  6. <dependency>
  7. <groupId>org.projectlombok</groupId>
  8. <artifactId>lombok</artifactId>
  9. <version>1.16.16</version>
  10. </dependency>
  11. <!--MP插件-->
  12. <dependency>
  13. <groupId>com.baomidou</groupId>
  14. <artifactId>mybatis-plus-boot-starter</artifactId>
  15. <version>3.2.0</version>
  16. </dependency>
  17. <!--Mysql-->
  18. <dependency>
  19. <groupId>mysql</groupId>
  20. <artifactId>mysql-connector-java</artifactId>
  21. <version>8.0.21</version>
  22. </dependency>
  23. <!-- 连接池 -->
  24. <dependency>
  25. <groupId>com.alibaba</groupId>
  26. <artifactId>druid</artifactId>
  27. <version>1.2.1</version>
  28. </dependency>
  29. <!--JUNIT-->
  30. <dependency>
  31. <groupId>junit</groupId>
  32. <artifactId>junit</artifactId>
  33. <version>4.13.1</version>
  34. </dependency>

3)配置文件

  1. spring:
  2. datasource:
  3. url: jdbc:mysql://localhost:3306/test
  4. username: root
  5. password: 123456
  6. driver-class-name: com.mysql.cj.jdbc.Driver

4)实体类

  1. @Data
  2. @Builder
  3. @TableName("student")
  4. public class User {
  5. @TableId(type = IdType.AUTO)
  6. private Integer id;
  7. private Integer deptId;
  8. private String name;
  9. private String remark;
  10. }

5)Mapper

  1. public interface UserMapper extends BaseMapper<User> {}

6)测试类

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class MapperTest {
  4. @Autowired
  5. private UserMapper userMapper;
  6. @Test
  7. public void getAll() {
  8. List<User> users = userMapper.selectList(null);
  9. users.forEach(System.out::println);
  10. }
  11. }
  12. /** OUTPUT:
  13. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!)
  14. User(id=2, deptId=1, name=小明, remark=好好学习,天天向上!)
  15. **/
  1. (SpringRunner{ UserMapper userMapper; { List<User> users = userMapper.selectList(); users.forEach(System.out::println); }}

总结:
在以上的结果,我们可以看到已经打印出了数据库中的全部数据(两条)。
其中并没有看到平时我们需要写的 mapper.xml 文件,只是用到了 usermapper 中的 selectList() 方法,而 UserMapper 继承了 BaseMapper 这个接口,这个接口便是 MybatisPlus 提供给我们的,我们再来看下这个接口给我们提供了哪些方法。

2. CRUD 基操

1)insert

  1. @Test
  2. public void insert() {
  3. //这里使用了 lombok 中的建造者模式构建对象
  4. User user = User.builder().deptId(1).name("小华").remark("小华爱学习").build();
  5. int insertFlag = userMapper.insert(user);
  6. log.info("插入影响行数,{} | 小华的ID: {}", insertFlag, user.getId());
  7. }
  8. /** OUTPUT:
  9. 插入影响行数,1 | 小华的ID: 8
  10. **/

可以看到我们不仅插入了数据,而且还获取到了插入数据的ID ,但是值得注意的是这里的 ID 虽然是自增的,但并非是 MP 默认的 ID生成策略 ,而是我们在实体类中指定的
MyBatis-Plus 基础使用 - 图1
通过 @TableId 来标记主键 ID, MP 中支持的主键生成策略有以下几种

  • AUTO —> 主键自增
  • NONE —> 未设置主键类型
  • INPUT —> 用户输入 ID
  • ID_WORKER —> 全局唯一ID (idWorker)
  • UUID —> 全局唯一ID (UUID)
  • ID_WORKER_STR —> 字符串全局唯一ID (idWorker 的字符串表示)

通过 @TableField 来标记其他字段的。
@TableField 相关值

  • value
    • 用于解决字段名不一致问题和驼峰命名,比如实体类中属性名为 remark ,但是表中的字段名为 describe ,这个时候就可以使用 @TableField(value="describe") 来进行转换。
    • 驼峰转换如果在全局中有配置驼峰命名,这个地方可不写。
  • exist
    • 用于在数据表中不存在的字段,我们可以使用 @TableField(exist = false) 来进行标记
  • condition
    • 用在预处理 WHERE 实体条件自定义运算规则,比如我配置了 @TableField(condition = SqlCondition.LIKE),输出 SQL 为:select 表 where name LIKE CONCAT('%',值,'%')
    • 其中 SqlCondition 值如下:
    • image.png
  • update
    • 用在预处理 set 字段自定义注入,比如我配置了 @TableField(update = "%s+1"),其中 %s 会填充字段,输出 SQL 为:update 表名 set 字段 = 字段+1 where 条件
  • select
    • 用于是否查询时约束,如果我们有个字段 remarktext 类型的,查询的时候不想查询该字段,那么就可以使用 @TableField(select = false) 来约束查询的时候不查询该字段

      2)update

      MybatisPlus 的更新操作存在两种: ```java int updateById(Param(“et”) T entity);

int update(@Param(“et”) T entity, @Param(“ew”) Wrapper updateWrapper);

  1. <a name="KQoSF"></a>
  2. #### 根据 ID 更新
  3. ```java
  4. @Test
  5. public void update() {
  6. User user = User.builder().id(3).name("小华").remark("小华爱玩游戏").build();
  7. userMapper.updateById(user);
  8. }
  9. /** 更新结果:
  10. User(id=3, deptId=1, name=小华, remark=小华爱玩游戏)
  11. **/

根据条件更新(使用 UpdateWrapper)

  1. @Test
  2. public void update() {
  3. UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
  4. updateWrapper.eq("name","小华").set("remark","小华爱下棋");
  5. userMapper.update(null, updateWrapper);
  6. }
  7. /** 更新结果:
  8. User(id=3, deptId=1, name=小华, remark=小华爱下棋)
  9. **/

我们也可以将要更新的条件放进 user 对象 里面:

  1. @Test
  2. public void update() {
  3. UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
  4. updateWrapper.eq("name","小华");
  5. User user = User.builder().remark("小华爱游泳").build();
  6. userMapper.update(user, updateWrapper);
  7. }
  8. /** 更新结果:
  9. User(id=3, deptId=1, name=小华, remark=小华爱游泳)
  10. **/

3)delete

MybatisPlus 中删除的方式相对于更新多,总共有四种:

  1. int deleteById(Serializable id);
  2. int deleteByMap(@Param("cm") Map<String, Object> columnMap);
  3. int delete(@Param("ew") Wrapper<T> wrapper);
  4. int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);

根据 ID 删除

  1. @Test
  2. public void deleteById() {
  3. userMapper.deleteById(3);
  4. }
  5. /** SQL语句:
  6. DELETE FROM student WHERE id = 3;
  7. **/

根据 Map 删除

  1. @Test
  2. public void deleteByMap() {
  3. HashMap<String, Object> columnMap = new HashMap<>();
  4. columnMap.put("name","小华");
  5. columnMap.put("remark","小华爱游泳");
  6. userMapper.deleteByMap(columnMap);
  7. }
  8. /** SQL语句:
  9. DELETE FROM student WHRE name = '小华' AND remark = '小华爱游泳';
  10. **/

根据 Wrapper 删除(使用 UpdateWrapper 或 QueryWrapper)

  1. @Test
  2. public void delete() {
  3. UpdateWrapper<User> wrapper = new UpdateWrapper<>();
  4. wrapper.eq("remark","小华爱下棋");
  5. userMapper.delete(wrapper);
  6. }
  7. /** SQL语句:
  8. DELETE FROM student WHRE remark = '小华爱下棋';
  9. **/

根据 Wrapper 删除还有另外一种方式,直接将实体类放入 Wrapper 中包装:

  1. @Test
  2. public void delete() {
  3. User user = User.builder().remark("小华爱下棋").build();
  4. UpdateWrapper<User> wrapper = new UpdateWrapper<>(user);
  5. userMapper.delete(wrapper);
  6. }
  7. /** SQL语句:
  8. DELETE FROM student WHRE remark = '小华爱下棋';
  9. **/

根据 ID 批量删除

  1. @Test
  2. public void deleteBatchIds() {
  3. List<Integer> idList = new ArrayList<>();
  4. idList.add(4);
  5. idList.add(7);
  6. userMapper.deleteBatchIds(idList);
  7. }
  8. /** SQL语句:
  9. DELETE FROM student WHERE id In (4,7)
  10. **/

4)select (使用 QueryWrapper )

查询操作在我们开发中是最经常用到的,也是重中之重。MybatisPlus 中支持查询的方法也比较多,如下:

  1. T selectById(Serializable id);
  2. List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
  3. List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
  4. T selectOne(@Param("ew") Wrapper<T> queryWrapper);
  5. Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
  6. List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
  7. List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
  8. List<Object> selectObjs(@aram("ew") Wrapper<T> queryWrapper);
  9. IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
  10. IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);

可以看到总共有 10 个方法,我们接下来一个一个测试

查询所有

  1. @Test
  2. public void selectList() {
  3. List<User> users = userMapper.selectList(null);
  4. users.forEach(System.out::println);
  5. }
  6. /**
  7. OUTPUT:
  8. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!)
  9. User(id=2, deptId=1, name=小明, remark=好好学习,天天向上!)
  10. SQL语句:
  11. SELECT id, dept_id, name, remark FROM student;
  12. **/

查询数量

  1. @Test
  2. public void selectCount() {
  3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  4. queryWrapper.like("name","小");
  5. System.out.println(userMapper.selectCount(queryWrapper));
  6. }
  7. /**
  8. OUTPUT:
  9. 2
  10. SQL语句:
  11. SELECT COUNT( 1 ) FROM student WHERE (name LIKE '%小%');
  12. **/

根据 ID 查询

  1. @Test
  2. public void selectById() {
  3. User user = userMapper.selectById(1);
  4. System.out.println(user);
  5. }
  6. /**
  7. OUTPUT:
  8. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!)
  9. SQL语句:
  10. SELECT id, dept_id, name, remark FROM student WHERE ID = 1;
  11. **/

根据 ID 批量查询

  1. @Test
  2. public void selectBatchIds() {
  3. List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2));
  4. users.forEach(System.out::println);
  5. }
  6. /**
  7. OUTPUT:
  8. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!)
  9. User(id=2, deptId=1, name=小明, remark=好好学习,天天向上!)
  10. SQL语句:
  11. SELECT id, dept_id, name, remark FROM student WHERE ID IN (1, 2);
  12. **/

根据条件查询单条

  1. @Test
  2. public void selectOne() {
  3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  4. queryWrapper.eq("name","小菜");
  5. User user = userMapper.selectOne(queryWrapper);
  6. System.out.println(user);
  7. }
  8. /**
  9. OUTPUT:
  10. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!)
  11. SQL语句:
  12. SELECT id, name, dept_id, remark FROM student WHERE (name = '小菜');
  13. **/

根据条件查询多条

通过 map 传递参数,不是通过 LIKE 查询,而是通过 **=** 查询

  1. @Test
  2. public void selectByMap() {
  3. HashMap<String, Object> columnMap = new HashMap<>();
  4. columnMap.put("name","小");
  5. List<User> users = userMapper.selectByMap(columnMap);
  6. users.forEach(System.out::println);
  7. }
  8. /**
  9. OUTPUT:
  10. null
  11. SQL语句:
  12. SELECT id, name, dept_id, remark FROM student WHERE name = '小';
  13. **/

如果我们没有新建实体类进行结果封装,我们还可以用 Map 来接收结果集:

  1. @Test
  2. public void selectMaps() {
  3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  4. queryWrapper.like("name","小");
  5. List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
  6. maps.forEach(System.out::println);
  7. }
  8. /**
  9. OUTPUT:
  10. {name=小菜, remark=关注小菜不迷路!, id=1, dept_id=1}
  11. {name=小明, remark=好好学习,天天向上!, id=2, dept_id=1}
  12. SQL语句:
  13. SELECT id, name, dept_id, remark FROM student WHERE (name LIKE '%小%');
  14. **/

也可以用 Object 对象来接收结果集:

  1. @Test
  2. public void selectObjs() {
  3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  4. queryWrapper.like("name", "小");
  5. List<Object> objects = userMapper.selectObjs(queryWrapper);
  6. }
  7. /**
  8. OUTPUT:
  9. {name=小菜, remark=关注小菜不迷路!, id=1, dept_id=1}
  10. {name=小明, remark=好好学习,天天向上!, id=2, dept_id=1}
  11. SQL语句:
  12. SELECT id, name, dept_id, remark FROM student WHERE (name LIKE '%小%');
  13. **/

分页查询

  1. @Test
  2. public void selectPage() {
  3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  4. queryWrapper.like("name", "小");
  5. Page<User> page = new Page<>(1, 1);
  6. IPage<User> userIPage = userMapper.selectPage(page, queryWrapper);
  7. System.out.println("数据总数:" + userIPage.getTotal());
  8. System.out.println("总页数:" + userIPage.getPages());
  9. System.out.println("当前页:" + userIPage.getCurrent());
  10. System.out.println("页大小:" + userIPage.getSize());
  11. userIPage.getRecords().forEach(System.out::println);
  12. }
  13. /**
  14. OUTPUT:
  15. 数据总数:2
  16. 总页数:2
  17. 当前页:1
  18. 页大小:1
  19. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!)
  20. SQL语句:
  21. SELECT id, name, dept_id, remark
  22. FROM student
  23. WHERE (name LIKE '%小%')
  24. LIMIT 0,1;
  25. **/

3. 条件构造器(Wrapper)

CRUD 的基本操作中,我们想要通过条件查询都是通过 Wrapper 类进行封装的,上面只是简单的用到 eqlike 操作。事实上这个类十分强大,我们在下面会详细进行介绍
Wrapper.png
非 lambda 的 Wrapper

  • **UpdateWrapper**:用于更新的条件构造器
  • **QueryWrapper**:用于筛选的条件构造器

MyBatis-Plus 基础使用 - 图4

1)allEq

全部 eq 或个别 isNull

  1. allEq(Map<R, V> params)
  2. allEq(Map<R, V> params, boolean null2IsNull)
  3. allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
  4. allEq(BiPredicate<R, V> filter, Map<R, V> params)
  5. allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
  6. allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)

参数说明:

param: key 为数据库字段名,value 为字段值 nullsIsNull: 为 true 则在 map 的 value 为 null 时调用 isNull 方法,为 false 时则忽略 value 为 null 时不调用 isNull 方法 filter: 过滤函数,判断是否允许字段传入比对条件中

使用示例:

  • allEq(Map<R, V> params)

    1. @Test
    2. public void testAllEq() {
    3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    4. Map<String,Object> params = new HashMap<>();
    5. params.put("name","小菜");
    6. params.put("dept_id",1);
    7. params.put("remark",null);
    8. queryWrapper.allEq(params); //会调用 isNull 方法
    9. userMapper.selectList(queryWrapper);
    10. }
    11. /**
    12. 结果:
    13. {}
    14. SQL语句:
    15. SELECT id,name,dept_id,remark
    16. FROM student
    17. WHERE (name = '小菜' AND dept_id = 1 AND remark IS NULL);
    18. **/
  • allEq(Map<R, V> params, boolean null2IsNull)

    1. @Test
    2. public void testAllEq() {
    3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    4. Map<String,Object> params = new HashMap<>();
    5. params.put("name","小菜");
    6. params.put("dept_id",1);
    7. params.put("remark",null);
    8. queryWrapper.allEq(params, false); //不会调用 isNull 方法
    9. userMapper.selectList(queryWrapper);
    10. }
    11. /**
    12. 结果:
    13. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!)
    14. SQL语句:
    15. SELECT id,name,dept_id,remark
    16. FROM student
    17. WHERE (name = '小菜' AND dept_id = 1);
    18. **/
  • allEq(boolean condition, Map<R, V> params, boolean null2IsNull)

    1. @Test
    2. public void testAllEq() {
    3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    4. Map<String,Object> params = new HashMap<>();
    5. params.put("name","小菜");
    6. params.put("dept_id",1);
    7. params.put("remark",null);
    8. queryWrapper.allEq(false,params,false); //不会带入条件进行查询
    9. userMapper.selectList(queryWrapper);
    10. }
    11. /**
    12. 结果:
    13. {name=小菜, remark=关注小菜不迷路!, id=1, dept_id=1}
    14. {name=小明, remark=好好学习,天天向上!, id=2, dept_id=1}
    15. SQL语句:
    16. SELECT id,name,dept_id,remark
    17. FROM student;
    18. **/
  • allEq(BiPredicate<R, V> filter, Map<R, V> params)

    1. @Test
    2. public void testAllEq() {
    3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    4. Map<String, Object> params = new HashMap<>();
    5. params.put("name", "小菜");
    6. params.put("dept_id", 1);
    7. params.put("remark", null);
    8. //只有 key 中含有 “m” 才会用作条件判断
    9. queryWrapper.allEq((k, v) -> (k.contains("m")), params);
    10. userMapper.selectList(queryWrapper);
    11. }
    12. /**
    13. 结果:
    14. 0
    15. SQL语句:
    16. SELECT id,name,dept_id,remark
    17. FROM student
    18. WHERE (name = '小菜' AND remark IS NULL);
    19. **/

    2)比较操作

  • eq:=

  • ne:!=
  • gt:>
  • ge:>=
  • lt:<
  • le:<=
  • between:between ... and ...
  • notBetween:not between ... and ...
  • in:in(.., .., ..)
  • notIn:not in(.., .., ..)

    3)模糊查询

  • like:like("name","小菜") --> name like "%小菜%"

  • notLike:notLike("name","小菜") --> name not like "%小菜%"
  • likeLeft:like("name","小菜") --> name like "%小菜"
  • likeRight:like("name","小菜") --> name like "小菜%"

    4)排序

  • orderBy:

    1. orderBy(boolean condition, boolean isAsc, R... columns)
    2. orderBy(true, true, "id", "name") --> order by id ASC, name ASC
  • orderByAsc:

    1. orderByAsc("id","name") --> order by id ASC, name ASC
  • orderByDesc:

    1. orderByDesc("id","name) --> order by id Desc, name Desc

    5)逻辑查询

  • or:

    • 拼接:主动调用 or 表示紧接着下一个方法不是用 and 连接! (不调用 or 则默认为使用 and 连接), eq("id",1).or().eq("name","老王")
    • 嵌套:or(i -> i.eq("name", "李白").ne("status", "活着"))
  • and:
    • 嵌套:and(i -> i.eq("name", "李白").ne("status", "活着"))
  • 复杂 and 和 or 联用
    1. SELECT
    2. a.*
    3. FROM
    4. user_info a
    5. WHERE
    6. 1 = 1
    7. AND a.id <> 1
    8. AND ( (a.`name` = 'jack' AND a.category = 1) OR (a.phone = '13888888888' OR a.category = 2) )
    1. LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
    2. // AND a.id <> 1
    3. queryWrapper.ne(UserInfo::getId, "1");
    4. // AND ( (a.`name` = 'jack' AND a.category = 1) OR (a.phone = '13888888888' OR a.category = 2) )
    5. queryWrapper.and(i -> (i.and(j -> j.eq(UserInfo::getName, "jack").eq(UserInfo::getCategory, 1))).or(j -> j.eq(UserInfo::getPhone, "13888888888").eq(UserInfo::getCategory, 2)));
    6. // 查询结果
    7. List<UserInfo> list = userInfoMapper.selectList(queryWrapper);

    6)select

    在MP查询中,默认查询所有的字段,如果有需要也可以通过 select() 方法进行指定字段,如 select("id", "name")

    lambdaWrapper

    用 Lambda 表达式写的 CRUD 代码非常简洁,真正做到零配置,不需要在 xml 或用注解(@Select)写大量原生 SQL 代码
    业务上常如下:
    1. List<UserEntity> userList = userMapper.selectList(Wrappers.<UserEntity>lambdaQuery().eq(UserEntity::getSex, 0L).like(UserEntity::getUserName, "dun"));
    2. userList.forEach(u -> System.out.println("like全包含关键字查询::" + u.getUserName()));

    lambda 表达式的理论基础

    Java中的 lambda 表达式实质上是一个匿名方法,但该方法并非独立执行,而是用于实现由函数式接口定义的唯一抽象方法
    使用 lambda 表达式时,会创建实现了函数式接口的一个匿名类实例,如 Java8 中的线程 Runnable 类实现了函数接口:@FunctionalInterface
    1. new Thread(new Runnable() {
    2. @Override
    3. public void run() {
    4. System.out.println("xxxx");
    5. }
    6. }).start();
    如果用 lambda 会非常简洁,一行代码搞定。
    1. new Thread(()-> System.out.println("xxx")).start();
    所以在某些场景下使用 lambda 表达式真的能减少 java 中一些冗长的代码,增加代码的优雅性。

    AbstractWrapper 条件构造器说明

  1. 出现的第一个入参 boolean condition 表示该条件是否加入最后生成的 sql 中,例如:
    • query.like(StringUtils.isNotBlank(name), Entity::getName, name).eq(age!=null && age >= 0, Entity::getAge, age)
  2. 代码块内的多个方法均为从上往下补全个别 boolean 类型的入参,默认为 true
  3. 出现的泛型 Param 均为 Wrapper 的子类实例(均具有 AbstractWrapper 的所有方法)
  4. 方法在入参中出现的 R 为泛型,在普通 wrapper 中是 String ,在 LambdaWrapper 中是函数
    • 例:Entity::getId,Entity 为实体类,getId 为字段 id 的 getMethod
  5. 方法入参中的 R column 均表示数据库字段,当 R 具体类型为 String 时则为数据库字段名(字段名是数据库关键字的自己用转义符包裹),而不是实体类数据字段名,另当 R 具体类型为 SFunction 时项目 runtime 不支持 eclipse 自家的编译器
  6. 使用普通 wrapper,入参为 Map 和 List 的均以 json 形式表现
  7. 使用中如果入参的 Map 或者 List为空,则不会加入最后生成的 sql 中

    RPC 特别注意

    不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输
    Wrapper 很重 传输 Wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场) 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作

    lambda 构建复杂的查询条件构造器:LambdaQueryWrapper

    LambdaQueryWrapper 四种不同的 lambda 构造方法
  • 方式一 使用 QueryWrapper 的成员方法方法 lambda 构建 LambdaQueryWrapper ```java LambdaQueryWrapper lambda = new QueryWrapper().lambda();
  1. - 方式二 直接 new LambdaQueryWrapper
  2. ```java
  3. LambdaQueryWrapper<UserEntity> lambda = new LambdaQueryWrapper<>();
  • 方式三 使用 Wrappers 的静态方法 lambdaQuery 构建 LambdaQueryWrapper 推荐

    1. LambdaQueryWrapper<UserEntity> lambda = Wrappers.lambdaQuery();
  • 方式四:链式查询

    1. List<UserEntity> users = new LambdaQueryChainWrapper<UserEntity>(userMapper)
    2. .like(User::getName, "雨").ge(User::getAge, 20).list();

    笔者推荐使用 Wrappers 的静态方法 lambdaQuery 构建 LambdaQueryWrapper 条件构造器

    更新条件构造器:LambdaUpdateWrapper

    1. @Test
    2. public void testLambdaUpdate() {
    3. LambdaUpdateWrapper<UserEntity> luw = Wrappers.lambdaUpdate();
    4. luw.set(UserEntity::getUserName, "dunzung01")
    5. .set(UserEntity::getSex, 1);
    6. luw.eq(UserEntity::getUserId, 1);
    7. userMapper.update(null, luw);
    8. }

    Debug 调试

    为了 Debug 调试方便,需要在 application.yml 启动文件开启 Mybatis-Plus SQL 执行语句全栈打印

    1. #mybatis
    2. mybatis-plus:
    3. configuration:
    4. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

    查询条件构造器使用示例

  1. 等值查询:eq ```java @Test public void testLambdaQueryOfEq() { //eq查询 //相当于 select * from sys_user where user_id = 1 LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(UserEntity::getUserId, 1L); UserEntity user = userMapper.selectOne(lqw); System.out.println(“eq查询::” + user.getUserName()); }
  1. eq 查询等价于原生 sql 的等值查询。
  2. ```java
  3. select * from sys_user where user_id = 1
  1. 范围查询 :in

    1. @Test
    2. public void testLambdaQueryOfIn() {
    3. List<Long> ids = Arrays.asList(1L, 2L);
    4. LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    5. lqw.in(UserEntity::getUserId, ids);
    6. List<UserEntity> userList = userMapper.selectList(lqw);
    7. userList.forEach(u -> System.out.println("in查询::" + u.getUserName()));
    8. }

    in 查询等价于原生 sql 的 in 查询

    1. select * from sys_user where user_id in (1,2)
  2. 通配符模糊查询:like

    1. @Test
    2. public void testLambdaQueryOfLikeAll() {
    3. LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    4. lqw.eq(UserEntity::getSex, 0L)
    5. .like(UserEntity::getUserName, "dun");
    6. List<UserEntity> userList = userMapper.selectList(lqw);
    7. userList.forEach(u -> System.out.println("like全包含关键字查询::" + u.getUserName()));
    8. }

    like 查询等价于原生 sql 的 like 全通配符模糊查询。

    1. select * from sys_user where sex = 0 and user_name like '%dun%'
  3. 右通配符模糊查询:likeRight

    1. @Test
    2. public void testLambdaQueryOfLikeRight() {
    3. LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    4. lqw.eq(UserEntity::getSex, 0L)
    5. .likeRight(UserEntity::getUserName, "dun");
    6. List<UserEntity> userList = userMapper.selectList(lqw);
    7. userList.forEach(u -> System.out.println("like Right含关键字查询::" + u.getUserName()));
    8. }

    likeRight 查询相当于原生 sql 的 like 右通配符模糊查询。

    1. select * from sys_user where sex = 0 and user_name like 'dun%'
  4. 左通配符模糊查询:likeLeft

    1. @Test
    2. public void testLambdaQueryOfLikeLeft() {
    3. LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    4. lqw.eq(UserEntity::getSex, 0L)
    5. .likeLeft(UserEntity::getUserName, "zung");
    6. List<UserEntity> userList = userMapper.selectList(lqw);
    7. userList.forEach(u -> System.out.println("like Left含关键字查询::" + u.getUserName()));
    8. }

    likeLeft 查询相当于原生 sql 的 like 左通配符模糊查询。

    1. select * from sys_user where sex = 0 and user_name like '%zung'
  5. 条件判断查询

条件判断查询类似于 Mybatis 的 if 标签,第一个入参 boolean condition 表示该条件是否加入最后生成的 sql 中。

  1. @Test
  2. public void testLambdaQueryOfBoolCondition() {
  3. UserEntity condition = UserEntity.builder()
  4. .sex(1)
  5. .build();
  6. //eq 或 like 条件判断查询
  7. LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
  8. lqw.eq(condition.getSex() != null, UserEntity::getSex, 0L)
  9. // 满足 bool 判断,是否进查询按字段 userName 查询
  10. .like(condition.getUserName() != null, UserEntity::getUserName, "dun");
  11. List<UserEntity> userList = userMapper.selectList(lqw);
  12. userList.forEach(u -> System.out.println("like查询::" + u.getUserName()));
  13. }
  1. 利用 or 和 and 构建复杂的查询条件

    1. @Test
    2. public void testLambdaQueryOfOr_And() {
    3. LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    4. lqw.eq(UserEntity::getSex, 0L)
    5. .and(wrapper->wrapper.eq(UserEntity::getUserName,"dunzung")
    6. .or().ge(UserEntity::getAge, 50));
    7. List<UserEntity> userList = userMapper.selectList(lqw);
    8. userList.forEach(u -> System.out.println("like查询::" + u.getUserName()));
    9. }

    上面实例查询等价于原生 sql 查询

    1. select * from sys_user where sex = 0 and (use_name = 'dunzung' or age >=50)
  2. 善于利用分页利器 PageHelpler

    1. @Test
    2. public void testLambdaPage() {
    3. //PageHelper分页查询
    4. //相当于 select * from sys_user limit 0,2
    5. int pageNumber = 0;
    6. int pageSize = 2;
    7. PageHelper.startPage(pageNumber + 1, pageSize);
    8. LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
    9. lqw.orderByAsc(UserEntity::getAge)
    10. .orderByDesc(UserEntity::getMobile);
    11. List<UserEntity> userList = userMapper.selectList(lqw);
    12. userList.forEach(u -> System.out.println("page分页查询::" + u.getUserName()));
    13. }

    上面实例查询等价于原生 sql 分页查询:

    1. select * from sys_user order by age desc,mobile desc limit 0,2

    另外,Mybatis-Plus 自带分页组件,BaseMapper 接口提供两种分页方法来实现物理分页。

  • 第一个返回实体对象允许 null
  • 第二个人返回 map 对象多用于在指定放回字段时使用,避免为指定字段 null 值出现

    1. IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
    2. IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);

    注意,Mybatis-Plus 自带分页组件时,需要配置 PaginationInterceptor 分页插件。

    1. @Bean
    2. public PaginationInterceptor paginationInterceptor() {
    3. return new PaginationInterceptor();
    4. }

    Mybatis-Plus lambda 表达式的优势与劣势

    通过上面丰富的举例详解以及剖析 lambda 底层实现原理,想必大家会问:” lambda 表达式似乎只支持单表操作?”
    据我对 Mybatis-Plus 官网的了解,目前确实是这样。依笔者实际运用经验来看,其实程序员大部分开发的功能基本上都是针对单表操作的,Lambda 表达式的优势在于帮助开发者减少在 XML 编写大量重复的 CRUD 代码,这点是非常重要的 nice 的。很显然,Lambda 表达式对于提高程序员的开发效率是不言而喻的,我想这点也是我作为程序员非常喜欢 Mybatis-Plus 的一个重要原因。
    但是,如果涉及对于多表之间的关联查询,lambda 表达式就显得力不从心了,因为 Mybatis-Plus 并没有提供类似于 join 查询的条件构造器。
    lambda 表达式优点:

  • 单表操作,代码非常简洁,真正做到零配置,如不需要在 xml 或用注解(@Select)写大量原生 SQL 代码

  • 并行计算
  • 预测代表未来的编程趋势

lambda 表达式缺点:

  • 单表操作,对于多表关联查询支持不好
  • 调试困难
  • 底层逻辑复杂

    4. 配置讲解

    1)基本配置

  • configLocation

用于指明 MyBatis 配置文件的位置,如果我们有 MyBatis 的配置文件,需将配置文件的路径配置到 configLocation
SpringBoot:

  1. mybatis-plus.config-location = classpath:mybatis-config.xml
  1. <bean id="sqlSessionFactory"
  2. class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
  3. <property name="configLocation" value="classpath:mybatis-config.xml"/>
  4. </bean
  • mapperLocations

用于指明 Mapper 所对应的 XML 的文件位置,我们在 通用 CRUD 中用到的 Mapper 是直接继承 MP 提供的 **BaseMapper**,我们也可以自定义方法,然后在 XML 文件中自定义 SQL ,而这时我们需要告诉 Mapper 所对应 XML 文件的位置
SpringBoot:

  1. mybatis-plus.mapper-locations = classpath*:mybatis/*.xml

SpringMVC:

  1. <bean id="sqlSessionFactory"
  2. class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
  3. <property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
  4. </bean>
  • typeAliasesPackage

用于 MyBatis 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使用类名,而不用使用全限定的类名
SpringBoot:

  1. mybatis-plus.type-aliases-package = cbuc.life.bean

SpringMVC:

  1. <bean id="sqlSessionFactory"
  2. class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
  3. <property name="typeAliasesPackage"
  4. value="com.baomidou.mybatisplus.samples.quickstart.entity"/>
  5. </bean>

2)进阶配置

  • mapUnderScoreToCamelCase

是否开启自动驼峰命名规则映射,这个配置的默认值是 true ,但是这个属性在 MyBatis 中的默认值是 false ,所以在我们平时的开发中都会将这个配置开启。

  1. #关闭自动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
  2. mybatis-plus.configuration.map-underscore-to-camel-case = false
  • cacheEnabled

全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true

  1. mybatis-plus.configuration.cache-enabled = false

3)DB 策略配置

  • idType

全局默认主键类型,设置后,即可省略实体对象中的 @TableId(type = IdType.AUTO) 配置。该配置的默认值为 ID_WORKER
SpringBoot:

  1. mybatis-plus.global-config.db-config.id-type = auto

SpringMVC:

  1. <bean id="sqlSessionFactory"
  2. class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
  3. <property name="dataSource" ref="dataSource"/>
  4. <property name="globalConfig">
  5. <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
  6. <property name="dbConfig">
  7. <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
  8. <property name="idType" value="AUTO"/>
  9. </bean>
  10. </property>
  11. </bean>
  12. </property>
  13. </bean>
  • tablePrefix

表名前缀,全局配置后可省略@TableName()配置。该配置的默认值为 null
SpringBoot:

  1. mybatis-plus.global-config.db-config.table-prefix = yq_

SpringMVC:

  1. <bean id="sqlSessionFactory"
  2. class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
  3. <property name="dataSource" ref="dataSource"/>
  4. <property name="globalConfig">
  5. <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
  6. <property name="dbConfig">
  7. <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
  8. <property name="idType" value="AUTO"/>
  9. <property name="tablePrefix" value="yq_"/>
  10. </bean>
  11. </property>
  12. </bean>
  13. </property>
  14. </bean>

5. 其他扩展

1)自动填充

有时候我们在插入或更新数据的时候,希望有些字段可以自动填充。比如我们平时数据表里面会有个 插入时间 或者 更新时间 这种字段,我们会默认以当前时间填充,在 MP 中我们也可以进行配置。
首先我们需要借助 @TableField(fill = FieldFill.INSERT) 这个注解,在插入时进行填充。

  1. @TableField(fill = FieldFill.INSERT)
  2. private String remark;

其中自动填充的模式如下:

  1. public enum FieldFill {
  2. /**
  3. * 默认不处理
  4. */
  5. DEFAULT,
  6. /**
  7. * 插入时填充字段
  8. */
  9. INSERT,
  10. /**
  11. * 更新时填充字段
  12. */
  13. UPDATE,
  14. /**
  15. * 插入和更新时填充字段
  16. */
  17. INSERT_UPDATE
  18. }

然后我们再编写自定义的填充处理模式:

  1. @Component
  2. public class MyMetaObjectHandler implements MetaObjectHandler {
  3. @Override
  4. public void insertFill(MetaObject metaObject) {
  5. Object remark = getFieldValByName("remark", metaObject);
  6. if (null == remark) {
  7. setFieldValByName("remark", "好好学习", metaObject);
  8. }
  9. }
  10. @Override
  11. public void updateFill(MetaObject metaObject) {
  12. //自定义更新时填充
  13. }
  14. }

测试:

  1. @Test
  2. public void testObjectHandler() {
  3. User user = User.builder().deptId(1).name("小明").build();
  4. userMapper.insert(user);
  5. }
  6. /**
  7. SQL语句:
  8. INSERT INTO student ( name, dept_id, remark )
  9. VALUES ( '小明', 1, '好好学习' );
  10. **/

可以看到插入时,已经自动将我们填充的字段合并进去。

2)逻辑删除

在开发中,很多时候我们删除数据并不需要真正意义上的物理删除,而是使用逻辑删除,这样子查询的时候需要状态条件,确保被标记的数据不被查询到。MP 当然也支持这样的功能。
我们需要先为 student 表添加一个字段 status 来声明数据是否被删除,0 表示被删除,1表示未删除 ,然后也需要在实体类上增加这个属性:

  1. @TableLogic
  2. private Integer status;

application.yaml 中配置:

  1. mybatis-plus:
  2. global-config:
  3. db-config:
  4. logic-delete-value: 0
  5. logic-not-delete-value: 1

测试:

  1. @Test
  2. public void testLogicDelete() {
  3. userMapper.deleteById(1);
  4. }
  5. /**
  6. SQL语句:
  7. UPDATE student SET status=0
  8. WHERE id=1 AND status=1;
  9. **/

可以看出这段 SQL 并没有真正删除,而是进行了逻辑删除,只是更新了删除标识

3)通用枚举

如果有性别之类的字段,我们通常会用 01 来表示,但是查出来我们得进行值转换,这个时候我们就可以使用枚举来解决这个问题:
首先为 student 表添加一个 sex 字段来表示性别,0 表示女性,1 表示男性 ,然后定义一个枚举类:

  1. public enum SexEnum implements IEnum<Integer> {
  2. MAN(1, "男"),
  3. WOMEN(0, "女");
  4. private int code;
  5. private String value;
  6. SexEnum(int code, String value) {
  7. this.code = code;
  8. this.value = value;
  9. }
  10. @Override
  11. public Integer getValue() {
  12. return this.code;
  13. }
  14. //注意要重写此方法,不然会将值转换成 ‘MAN’,而不是 ‘男’
  15. @Override
  16. public String toString() {
  17. return this.value;
  18. }
  19. }

然后在实体类中添加对应属性:

  1. private SexEnum sex;

application.yaml 中配置:

  1. mybatis-plus:
  2. type-enums-package: cbuc.life.enums

测试:

  1. @Test
  2. public void selectOne() {
  3. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  4. queryWrapper.eq("name", "小菜");
  5. User user = userMapper.selectOne(queryWrapper);
  6. System.out.println(user);
  7. }
  8. /**
  9. 输出结果:
  10. User(id=1, deptId=1, name=小菜, remark=关注小菜不迷路!, status=1, sex=男)
  11. SQL语句:
  12. SELECT id,sex,name,dept_id,remark,status
  13. FROM student
  14. WHERE status=1 AND (name = '小菜');
  15. **/

用 Lambda 表达式写的 CRUD 代码非常简洁,真正做到零配置,不需要在 xml 或用注解(@Select)写大量原生 SQL 代码。