1.MyBatis-Plus 入门
1.1. 什么是 MyBatis-Plus
MyBatis-Plus 是一个基于 MyBatis 的增强工具包,旨在简化 MyBatis 的使用,提供了许多便捷的功能,如自动生成 CRUD 代码、分页查询、代码生成器等。它通过注解和配置文件的方式,极大地减少了开发者的工作量,提高了开发效率。
1.2. MyBatis-Plus 的安装与配置
- 添加依赖:在项目的
pom.xml 文件中添加 MyBatis-Plus 的依赖。
1 2 3 4 5
| <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>
|
- 配置数据源:在
application.yml 或 application.properties 文件中配置数据库连接信息。
1 2 3 4 5 6
| spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 1234
|
- 在
mapper接口中继承 BaseMapper<T>,其中 T 是实体类。
1 2 3
| public interface UserMapper extends BaseMapper<User> { }
|
1.3. 常用注解
- 类名驼峰转下划线作为表名
- 字段名驼峰转下划线作为列名
- id 为主键,自动增长
- @TableName(value = “user”): 指定表名
- @TableId(type = IdType.AUTO): 指定主键生成策略(不指定默认雪花算法)
- IDType.AUTO: 自增
- IDType.INPUT: 手动输入
- IDType.ASSIGN_ID: 雪花算法
- @TableField(value = “age”): 指定表中字段信息
- 成员变量名和表中字段名不一致
- 成员变量是 is 开头的布尔类型(Boolean isMarried)
- 成员变量和数据库关键字冲突(如 order)
- 成员变量不需要映射到数据库中(@TableField(exist = false))
1.4 常用配置
1 2 3 4 5 6
| mybatis-plus: type-aliases-package: com.itheima.mp.domain.po global-config: db-config: id-type: auto mapper-locations: classpath*:/mapper/**/*.xml
|
2. MyBatis-Plus 核心功能
2.1 条件构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Test void testQueryWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<User>() .select("id", "username", "info", "balance") .like("username", "o") .ge("balance", 1000); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
@Test void testLambdaQueryWrapper() { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>() .select(User::getId, User::getUsername, User::getInfo, User::getBalance) .like(User::getUsername, "o") .ge(User::getBalance, 1000); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
@Test void testUpdateByQueryWrapper() { User user = new User(); user.setBalance(2000); QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack"); userMapper.update(user, wrapper); }
@Test void testUpdateWrapper() { UpdateWrapper<User> wrapper = new UpdateWrapper<User>() .setSql("balance = balance - 100") .in("id", List.of(1, 2, 3)); userMapper.update(null, wrapper); }
|
2.2 自定义 SQL
1 2 3 4 5 6 7 8 9 10 11
| @Test void testCustomSqlUpdate(){ List<Integer> ids = List.of(1, 2, 3); int amount = 200; QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids); userMapper.updateBalanceByIds(wrapper, amount); }
|
在 UserMapper 接口中定义自定义 SQL 方法:
1
| void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);
|
在对应的 XML 映射文件中编写 SQL 语句:
1 2 3
| <update id="updateBalanceByIds"> update user set balance = balance - #{amount} ${ew.customSqlSegment} </update>
|
2.3 Service 接口
自定义接口 Service,继承 IService,其中 T 是实体类
1 2 3 4
| public interface IUserService extends IService<User> {
}
|
在 Service 实现类中继承 ServiceImpl<Mapper, T>,其中 Mapper 是 Mapper 接口,T 是实体类
1 2 3 4
| @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
|
Swagger 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| knife4j: enable: true openapi: title: 用户管理接口文档 description: "用户管理接口文档" email: zhanghuyi@itcast.cn concat: 虎哥 url: https://www.itcast.cn version: v1.0.0 group: default: group-name: default api-rule: package api-rule-resources: - com.itheima.mp.controller
|
Controller 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Api(tags = "用户管理接口") @RequestMapping("/users") @RestController @RequiredArgsConstructor public class UserController {
private final IUserService userService;
@ApiOperation(value = "新增用户接口") @PostMapping public void saveUser(@RequestBody UserFormDTO userDTO){
User user = BeanUtil.copyProperties(userDTO, User.class); userService.save(user); }
@ApiOperation(value = "删除用户接口") @DeleteMapping("/{id}") public void deleteUserById(@ApiParam("用户ID") @PathVariable Long id){ userService.removeById(id); }
@ApiOperation(value = "根据id查询用户接口") @GetMapping("/{id}") public UserVO queryUserById(@ApiParam("用户ID") @PathVariable Long id){ User user = userService.getById(id); return BeanUtil.copyProperties(user, UserVO.class); }
@ApiOperation(value = "根据id批量查询用户接口") @GetMapping public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @PathVariable("ids") List<Long> ids){ List<User> users = userService.listByIds(ids); return BeanUtil.copyToList(users, UserVO.class); } }
|
2.4 Iservice 的 Lambda 查询
UserServiceImpl 中复杂条件的查询
1 2 3 4 5 6 7 8
| @Override public List<User> queryUsers(UserQuery query) { return lambdaQuery().like(query.getName() != null, User::getUsername, query.getName()) .eq(query.getStatus() != null, User::getStatus, query.getStatus()) .ge(query.getMinBalance() != null, User::getBalance, query.getMinBalance()) .le(query.getMaxBalance() != null, User::getBalance, query.getMaxBalance()) .list(); }
|
2.5 Iservice 的批量插入
开启 rewriteBatchedStatements=true 参数
1 2 3 4 5 6
| spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver username: root password: MySQL123
|
2.6 扩展功能
2.6.1 MyBatisPlus 插件
打开 tools-> Configdatabase

之后选择 Code Generator

2.6.2 Db 静态工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override public UserVO queryUserAndAdressesById(Long id) { User user = getById(id); if(user == null || user.getStatus() == 2){ throw new RuntimeException("用户状态异常"); } List<Address> list = Db.lambdaQuery(Address.class) .eq(Address::getUserId, id).list(); UserVO userVO = BeanUtil.copyProperties(user, UserVO.class); if(CollUtil.isNotEmpty(list)){ userVO.setAddresses(BeanUtil.copyToList(list, AddressVO.class)); } return userVO; }
|
2.6.3 逻辑删除
逻辑删除就是在数据库中不真正删除数据,而是通过一个标志位来表示数据是否被删除。这样可以保留数据的完整性,方便后续的数据恢复和审计。
MybatisPlus 生成的 SQL 语句才支持自动的逻辑删除,自定义 SQL 需要自己手动处理逻辑删除。
1 2 3 4 5 6
| mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0
|
2.6.4 枚举处理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Getter public enum UserStatus { NORMAL(1, "正常"), FROZEN(2, "冻结"), ; @EnumValue private final int value; @JsonValue private final String desc; UserStatus(int value, String desc){ this.value = value; this.desc = desc; } }
|
2.6.5 JSON 处理器
UserInfo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.itheima.mp.domain.po;
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
@Data @NoArgsConstructor @AllArgsConstructor(staticName = "of") public class UserInfo { private Integer age; private String intro; private String gender; }
|
1 2 3 4 5
| @TableName(value = "user", autoResultMap = true) public class User { @TableField(typeHandler = JacksonTypeHandler.class) private UserInfo info; }
|
2.7 插件功能
2.7.1 分页插件
配置分页插件
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class MybatisConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); paginationInnerInterceptor.setMaxLimit(1000L); interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; } }
|
查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Test void testPageQuery(){ int pageNo = 1, pageSize = 2; Page<User> page = Page.of(pageNo, pageSize); page.addOrder(new OrderItem("balance", true)); page.addOrder(new OrderItem("id", true)); Page<User> p = userService.page(page); long total = p.getTotal(); long pages = p.getPages(); System.out.println(total); System.out.println(pages); p.getRecords().forEach(System.out::println); }
|
pageDTO
1 2 3 4 5 6 7 8 9 10 11
| @Data @ApiModel(description = "分页结果") public class PageDTO<T> { @ApiModelProperty("总记录数") private Long total; @ApiModelProperty("总页数") private Long pages; @ApiModelProperty("集合") private List<T> list; }
|
UserServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Override public PageDTO<UserVO> queryUsersPage(UserQuery query) { Page<User> page = Page.of(query.getPageNo(), query.getPageSize()); if(StrUtil.isNotBlank(query.getSortBy())) { page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc())); } else{ page.addOrder(new OrderItem("update_time", false)); } Page<User> p = lambdaQuery().like(query.getName() != null, User::getUsername, query.getName()) .eq(query.getStatus() != null, User::getStatus, query.getStatus()) .page(page); PageDTO<UserVO> dto = new PageDTO<>(); dto.setTotal(p.getTotal()); dto.setPages(p.getPages()); List<User> records = p.getRecords(); if(CollUtil.isEmpty(records)){ dto.setList(Collections.emptyList()); return dto; } List<UserVO> vos = BeanUtil.copyToList(records, UserVO.class); dto.setList(vos); return dto; }
|
在刚才的代码中,从 PageQuery 到 MybatisPlus 的 Page 之间转换的过程还是比较麻烦的。
可以在 PageQuery 这个实体中定义一个工具方法,简化开发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Data public class PageQuery { @ApiModelProperty("页码") private Integer pageNo; @ApiModelProperty("每页记录数") private Integer pageSize; @ApiModelProperty("排序字段") private String sortBy; @ApiModelProperty("是否升序") private Boolean isAsc;
public <T> Page<T> toMpPage(OrderItem ... orders){ Page<T> p = Page.of(pageNo, pageSize); if (sortBy != null) { p.addOrder(new OrderItem(sortBy, isAsc)); return p; } if(orders != null){ p.addOrder(orders); } return p; }
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){ return this.toMpPage(new OrderItem(defaultSortBy, isAsc)); }
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() { return toMpPage("create_time", false); }
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() { return toMpPage("update_time", false); } }
|