MyBatis-Plus 让你开发效率翻倍!新手也能5分钟上手!

大家好,我是大华。这篇文章介绍MyBatis-Plus

什么是 MyBatis-Plus?

简单来说,MyBatis-PlusMyBatis的增强工具,在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 在大多数业务场景下都能显著提升开发效率,值得大家深入学习和使用。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《这20条SQL优化方案,让你的数据库查询速度提升10倍》

《MySQL 为什么不推荐用雪花ID 和 UUID 做主键?》

《用html写了个超好用的网页主题切换插件》

《还在用 WebSocket 做实时通信?SSE 可能更简单》

相关推荐
绝无仅有2 小时前
某东互联网大厂的Redis面试知识点分析
后端·面试·架构
武子康2 小时前
Java-167 Neo4j CQL 实战:CREATE/MATCH 与关系建模速通 案例实测
java·开发语言·数据库·python·sql·nosql·neo4j
乌暮2 小时前
JavaEE入门--计算机是怎么工作的
java·后端·java-ee
前端世界2 小时前
ASP.NET 实战:用 CSS 选择器打造一个可搜索、响应式的书籍管理系统
css·后端·asp.net
Z3r4y2 小时前
【代码审计】RuoYi-4.2 五处安全问题分析
java·web安全·代码审计·若依4.2·ruoyi-4.2
代码栈上的思考2 小时前
Spring MVC 中 @RequestMapping 路径映射与请求处理全流程
java·spring·mvc
WZTTMoon3 小时前
Spring MVC 核心工作原理:DispatcherServlet 全流程深度解析
java·spring·mvc
Java水解3 小时前
MySQL 正则表达式:REGEXP 和 RLIKE 操作符详解
后端·mysql
金銀銅鐵3 小时前
[Java] 用 Swing 生成一个最大公约数计算器(展示计算过程)
java·后端·数学