大家好,我是大华。这篇文章介绍MyBatis-Plus!
什么是 MyBatis-Plus?
简单来说,MyBatis-Plus是MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,既能够简化开发,又能够提高效率!
我们就从最简单的开始吧!只需要几行代码就能搞定常见的 CRUD 操作。
如果你是新手,可以能快速的学习MyBatis-Plus,如果你是有经验的开发,也可以巩固一下相关的内容。
1. 添加依赖
xml
<!-- 在 pom.xml 中添加以下依赖 -->
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
<!-- MyBatis-Plus Starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Lombok 简化实体类开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<optional>true</optional>
</dependency>
</dependencies>
2. 配置数据源
yaml
# application.yml
spring:
datasource:
# 替换成你的数据库信息
url: jdbc:mysql://localhost:3306/mybatis_plus_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置
mybatis-plus:
configuration:
# 打印 SQL 日志,方便调试
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true # 开启驼峰命名自动转换
global-config:
db-config:
# 逻辑删除配置
logic-delete-field: deleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
id-type: auto # 主键ID自增
3. 实体类设计
java
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user") // 指定对应的数据库表名
public class User {
/**
* 用户ID - 主键
* @TableId 注解表示这是主键
* value = "id" 表示对应数据库字段名
* type = IdType.AUTO 表示主键自增
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
* 如果字段名与数据库列名一致(驼峰转下划线),可以省略 @TableField
*/
private String username;
/**
* 年龄
*/
private Integer age;
/**
* 邮箱
*/
private String email;
/**
* 创建时间
* @TableField 配置填充策略
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
* 插入和更新时都自动填充
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除字段
* 0-未删除 1-已删除
*/
@TableLogic
private Integer deleted;
}
4. Mapper 接口
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* UserMapper 接口
* 继承 BaseMapper 就自动拥有了 CRUD 方法
*
* 注意:BaseMapper<User> 中的 User 是实体类类型
*/
@Mapper // 一定要加上 @Mapper 注解,Spring 才能扫描到
public interface UserMapper extends BaseMapper<User> {
// 不需要写任何方法,基本的 CRUD 都已经有了!
/**
* 如果需要自定义SQL,可以这样写
*/
// @Select("SELECT * FROM user WHERE age > #{minAge}")
// List<User> selectUsersByMinAge(@Param("minAge") Integer minAge);
}
5. 服务层实现
java
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* UserService 实现类
*
* 继承 ServiceImpl 就自动拥有了更多的 CRUD 方法
* 参数说明:
* - UserMapper:你的 Mapper 接口
* - User:你的实体类
*/
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// 可以在这里添加自定义的业务方法
/**
* 自定义方法:根据用户名查询用户
*/
public User getUserByUsername(String username) {
// 使用 MP 的查询构造器
return lambdaQuery()
.eq(User::getUsername, username) // username = ?
.one(); // 查询一条记录
}
/**
* 自定义方法:根据邮箱后缀查询用户
*/
public List<User> getUsersByEmailSuffix(String emailSuffix) {
return lambdaQuery()
.likeRight(User::getEmail, emailSuffix) // email like 'emailSuffix%'
.list();
}
}
6. 新增操作
java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 新增用户
*/
@PostMapping
public String addUser(@RequestBody User user) {
// 方法1:使用 save 方法
boolean success = userService.save(user);
if (success) {
return "新增用户成功,用户ID:" + user.getId();
} else {
return "新增用户失败";
}
}
/**
* 批量新增用户
*/
@PostMapping("/batch")
public String addUsers(@RequestBody List<User> users) {
// 批量插入,每次插入100条
boolean success = userService.saveBatch(users, 100);
return success ? "批量新增成功" : "批量新增失败";
}
/**
* 新增或更新用户(存在则更新,不存在则新增)
*/
@PostMapping("/save-or-update")
public String saveOrUpdateUser(@RequestBody User user) {
boolean success = userService.saveOrUpdate(user);
return success ? "操作成功" : "操作失败";
}
}
7. 查询操作
java
/**
* 查询相关操作
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据ID查询用户
*/
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getById(id);
}
/**
* 查询所有用户
*/
@GetMapping
public List<User> getAllUsers() {
return userService.list();
}
/**
* 条件查询:查询年龄大于18的用户
*/
@GetMapping("/adults")
public List<User> getAdultUsers() {
return userService.lambdaQuery()
.gt(User::getAge, 18) // age > 18
.list();
}
/**
* 复杂条件查询:年龄在18-60之间,并且用户名包含"张"
*/
@GetMapping("/complex")
public List<User> getComplexUsers() {
return userService.lambdaQuery()
.between(User::getAge, 18, 60) // age between 18 and 60
.like(User::getUsername, "张") // username like '%张%'
.orderByDesc(User::getAge) // 按年龄降序
.list();
}
/**
* 分页查询
*/
@GetMapping("/page")
public Page<User> getUserPage(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size) {
// 创建分页对象
Page<User> page = new Page<>(current, size);
// 执行分页查询
return userService.page(page);
}
/**
* 带条件的分页查询
*/
@GetMapping("/page-with-condition")
public Page<User> getUserPageWithCondition(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword) {
Page<User> page = new Page<>(current, size);
return userService.lambdaQuery()
.like(StringUtils.isNotBlank(keyword), User::getUsername, keyword)
.page(page);
}
}
8. 更新操作
java
/**
* 更新操作
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据ID更新用户
*/
@PutMapping
public String updateUser(@RequestBody User user) {
if (user.getId() == null) {
return "用户ID不能为空";
}
// 方法1:根据ID更新所有字段
boolean success = userService.updateById(user);
return success ? "更新成功" : "更新失败";
}
/**
* 条件更新:将所有年龄小于18的用户年龄设置为18
*/
@PutMapping("/fix-age")
public String fixAge() {
boolean success = userService.lambdaUpdate()
.lt(User::getAge, 18) // age < 18
.set(User::getAge, 18) // 设置 age = 18
.update();
return success ? "年龄修复成功" : "年龄修复失败";
}
/**
* 部分字段更新
*/
@PatchMapping("/{id}")
public String partialUpdateUser(@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
// 移除不能更新的字段
updates.remove("id");
updates.remove("createTime");
boolean success = userService.updateById(id, updates);
return success ? "部分更新成功" : "部分更新失败";
}
}
9. 删除操作
java
/**
* 删除操作
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据ID删除用户(逻辑删除)
*/
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
// 逻辑删除,实际上执行的是 update 操作,将 deleted 字段设置为 1
boolean success = userService.removeById(id);
return success ? "删除成功" : "删除失败";
}
/**
* 批量删除
*/
@DeleteMapping("/batch")
public String batchDeleteUsers(@RequestBody List<Long> ids) {
boolean success = userService.removeByIds(ids);
return success ? "批量删除成功" : "批量删除失败";
}
/**
* 条件删除:删除所有年龄大于100的用户
*/
@DeleteMapping("/old")
public String deleteOldUsers() {
boolean success = userService.lambdaUpdate()
.gt(User::getAge, 100) // age > 100
.remove();
return success ? "删除老年用户成功" : "删除失败";
}
}
高级特性
10. 自动填充功能
java
/**
* 自动填充处理器
* 用于自动填充 createTime 和 updateTime
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时自动填充
*/
@Override
public void insertFill(MetaObject metaObject) {
// 填充创建时间
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 填充更新时间
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
/**
* 更新时自动填充
*/
@Override
public void updateFill(MetaObject metaObject) {
// 更新时只填充更新时间
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
11. 分页插件配置
java
/**
* MyBatis-Plus 配置类
*/
@Configuration
@MapperScan("com.yourpackage.mapper") // 指定Mapper接口的扫描路径
public class MybatisPlusConfig {
/**
* 分页插件配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(1000L); // 设置最大单页限制数量
paginationInterceptor.setOverflow(true); // 超出最大页数后回到第一页
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
12. 乐观锁插件配置
java
// 在 MybatisPlusConfig 中添加乐观锁插件
/**
* 乐观锁插件配置
*/
@Bean
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
// 实体类中配置乐观锁字段
@Version
private Integer version;
13. 谨慎使用场景
1. 超复杂 SQL 场景
- 涉及大量复杂联表查询
- 需要高度优化的自定义 SQL
- 复杂的存储过程调用
2. 性能要求极高的场景
- 高并发写入场景
- 需要精细控制 SQL 执行的场景
3. 遗留系统改造
- 已有复杂 MyBatis 配置的项目
- 数据库设计不符合 MP 规范
14. 不推荐使用场景
1. JPA 生态重度依赖项目
- 已经深度使用 Spring Data JPA
- 需要 JPA 的级联操作等特性
2. 非关系型数据库
- MongoDB、Redis 等 NoSQL 数据库
- 图数据库等特殊存储
最佳实践建议
15. 实体类设计规范
java
// 基础实体类
@Data
public class BaseEntity {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Integer deleted;
}
// 业务实体类
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user")
public class User extends BaseEntity {
private String username;
private Integer age;
private String email;
// 数据库字段名与实体类字段名不一致时使用
@TableField("phone_number")
private String phoneNumber;
// 不映射到数据库的字段
@TableField(exist = false)
private String temporaryCode;
}
16. 服务层封装建议
java
// 好的服务层封装
@Service
@Slf4j
public class UserService extends ServiceImpl<UserMapper, User> {
/**
* 业务方法:用户注册
*/
public boolean register(UserRegisterDTO registerDTO) {
// 1. 校验用户名是否已存在
boolean exists = lambdaQuery()
.eq(User::getUsername, registerDTO.getUsername())
.exists();
if (exists) {
throw new RuntimeException("用户名已存在");
}
// 2. DTO 转 Entity
User user = new User();
BeanUtils.copyProperties(registerDTO, user);
// 3. 保存用户
return save(user);
}
/**
* 安全的更新方法
*/
public boolean safeUpdate(User user) {
if (user == null || user.getId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
// 只更新非空字段
return lambdaUpdate()
.eq(User::getId, user.getId())
.set(user.getUsername() != null, User::getUsername, user.getUsername())
.set(user.getAge() != null, User::getAge, user.getAge())
.set(user.getEmail() != null, User::getEmail, user.getEmail())
.update();
}
}
17. 异常处理建议
java
/**
* 全局异常处理
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Result<?>> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage());
return ResponseEntity.badRequest().body(Result.error(e.getMessage()));
}
/**
* 处理数据异常
*/
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<Result<?>> handleDataAccessException(DataAccessException e) {
log.error("数据访问异常: {}", e.getMessage());
return ResponseEntity.status(500).body(Result.error("数据操作失败"));
}
}
/**
* 统一返回结果
*/
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("成功");
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
总结
工具是为人服务的,选择适合自己项目的技术栈才是最重要的。MyBatis-Plus 在大多数业务场景下都能显著提升开发效率,值得大家深入学习和使用。
本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!