首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Mybatis-Plus 实战使用与最佳实践

Mybatis-Plus 实战使用与最佳实践

原创
作者头像
小焱
发布2025-11-04 08:57:33
发布2025-11-04 08:57:33
2000
举报
文章被收录于专栏:Java开发Java开发

MyBatis-Plus 实战使用与最佳实践

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,在 MyBatis 基础上只做增强不做改变,旨在简化开发、提高效率。本文从实战使用最佳实践两方面,结合实际开发场景,带你全面掌握 MP 的核心用法。

一、环境准备

1. 依赖引入(Maven)

以 Spring Boot 项目为例,核心依赖如下(需配合 MyBatis、数据库驱动):

代码语言:xml
复制
<!-- MyBatis-Plus 核心依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version> <!-- 推荐使用最新稳定版 -->
</dependency>

<!-- 数据库驱动(以 MySQL 8.0 为例) -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- 可选:代码生成器(高效生成实体、Mapper 等) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId> <!-- 模板引擎,也可选用 Velocity -->
</dependency>

2. 配置文件(application.yml)

核心配置:数据源、MyBatis-Plus 基础配置

代码语言:yaml
复制
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  # Mapper.xml 文件路径(如果使用 XML 编写复杂 SQL)
  mapper-locations: classpath:mapper/*.xml
  # 实体类别名包路径(简化 XML 中类名引用)
  type-aliases-package: com.example.mpdemo.entity
  configuration:
    # 开启驼峰命名自动转换(数据库字段下划线 -> Java 驼峰)
    map-underscore-to-camel-case: true
    # 打印 SQL(开发环境开启,生产环境关闭)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 全局逻辑删除字段名(统一逻辑删除字段,如 is_deleted)
      logic-delete-field: isDeleted
      # 逻辑删除:1=已删除,0=未删除
      logic-delete-value: 1
      logic-not-delete-value: 0

3. 启动类注解

添加 @MapperScan 扫描 Mapper 接口所在包:

代码语言:java
复制
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.mpdemo.mapper") // Mapper 接口包路径
public class MpDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MpDemoApplication.class, args);
    }
}

二、核心实战使用

1. 实体类(Entity)配置

MP 通过注解映射数据库表/字段,核心注解:

  • @TableName:映射数据库表名(表名与类名一致可省略)
  • @TableId:主键字段(必选),指定主键策略
  • @TableField:普通字段(字段名与属性名一致可省略)
  • @TableLogic:逻辑删除字段(配合全局配置或单独指定)

示例:

代码语言:java
复制
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data // Lombok 简化 getter/setter(可选)
@TableName("t_user") // 数据库表名:t_user
public class User {
    // 主键:自增策略(MySQL 自增字段)
    @TableId(type = IdType.AUTO)
    private Long id;

    // 普通字段:数据库字段名 user_name(驼峰自动转换)
    private String userName;

    private Integer age;

    // 逻辑删除字段(配合全局配置,也可单独指定 value)
    @TableLogic
    private Integer isDeleted;

    // 自动填充:创建时间(插入时填充)
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    // 自动填充:更新时间(插入/更新时填充)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
自动填充处理器(可选)

如果实体类有 createTime/updateTime 等需要自动填充的字段,需实现 MetaObjectHandler

代码语言:java
复制
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 插入时填充
    @Override
    public void insertFill(MetaObject metaObject) {
        strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    // 更新时填充
    @Override
    public void updateFill(MetaObject metaObject) {
        strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

2. Mapper 接口(核心)

MP 提供 BaseMapper 接口,包含 CRUD 基础方法,Mapper 接口只需继承 BaseMapper<实体类> 即可直接使用,无需编写 XML。

基础 Mapper 示例
代码语言:java
复制
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends BaseMapper<User> {
    // 无需编写任何方法,直接使用 BaseMapper 中的 CRUD 方法
}
BaseMapper 核心方法(常用)

方法

功能描述

示例

insert(T entity)

新增一条记录

userMapper.insert(new User("张三", 20))

deleteById(Serializable id)

根据主键删除

userMapper.deleteById(1L)

updateById(T entity)

根据主键更新

user.setAge(21); userMapper.updateById(user)

selectById(Serializable id)

根据主键查询

User user = userMapper.selectById(1L)

selectList(Wrapper<T> queryWrapper)

条件查询列表

见下文条件构造器示例

selectPage(Page<T> page, Wrapper<T> queryWrapper)

分页查询

见下文分页示例

3. 条件构造器(QueryWrapper/LambdaQueryWrapper)

MP 提供强大的条件构造器,支持动态 SQL 拼接,无需手动写 WHERE 条件。推荐使用 LambdaQueryWrapper(类型安全,避免字段名写错)

示例:条件查询
代码语言:java
复制
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.mpdemo.entity.User;
import com.example.mpdemo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    // 条件查询:查询年龄 > 18 且用户名包含 "张" 的用户
    public List<User> queryUser() {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .gt(User::getAge, 18) // age > 18
                .like(User::getUserName, "张"); // user_name LIKE '%张%'
        return userMapper.selectList(queryWrapper);
    }

    // 动态条件:根据传入参数拼接条件(如用户名可为空)
    public List<User> queryUserByCondition(String userName, Integer age) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .like(userName != null, User::getUserName, userName) // 条件成立才拼接
                .ge(age != null, User::getAge, age); // age >= 传入值
        return userMapper.selectList(queryWrapper);
    }
}
常用条件方法

方法

功能描述

示例

eq

等于(=)

eq(User::getId, 1L)

ne

不等于(!=)

ne(User::getAge, 20)

gt

大于(>)

gt(User::getAge, 18)

ge

大于等于(>=)

ge(User::getAge, 18)

lt

小于(<)

lt(User::getAge, 30)

le

小于等于(<=)

le(User::getAge, 30)

like

模糊查询(LIKE)

like(User::getUserName, "张")

likeLeft

左模糊(LIKE '%值')

likeLeft(User::getUserName, "张")

in

IN 查询

in(User::getId, 1L, 2L, 3L)

isNull

字段为 NULL

isNull(User::getUserName)

orderByAsc

升序排序

orderByAsc(User::getCreateTime)

orderByDesc

降序排序

orderByDesc(User::getCreateTime)

4. 分页查询

MP 内置分页插件,无需额外配置(3.5+ 版本自动注入),直接使用 Page 类即可实现分页。

分页示例
代码语言:java
复制
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.mpdemo.entity.User;
import com.example.mpdemo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    // 分页查询:第 1 页,每页 10 条,条件:年龄 > 18
    public IPage<User> queryUserByPage(Integer pageNum, Integer pageSize) {
        // 1. 构建分页对象(pageNum:页码,pageSize:每页条数)
        Page<User> page = new Page<>(pageNum, pageSize);
        
        // 2. 构建查询条件
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .gt(User::getAge, 18);
        
        // 3. 分页查询(返回 IPage,包含总条数、当前页数据等)
        return userMapper.selectPage(page, queryWrapper);
    }
}
分页结果说明(IPage)

方法

功能描述

getTotal()

总记录数

getPages()

总页数

getCurrent()

当前页码

getSize()

每页条数

getRecords()

当前页数据列表

hasNext()

是否有下一页

hasPrevious()

是否有上一页

5. 自定义 SQL(XML/注解)

对于复杂 SQL(如多表联查、分组统计),MP 支持保留 MyBatis 原生写法,可通过 XML 或注解实现。

示例 1:XML 自定义 SQL
  1. 在 Mapper 接口添加方法:import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mpdemo.entity.User; import org.apache.ibatis.annotations.Param; import java.util.List; public interface UserMapper extends BaseMapper<User> { // 自定义 SQL:查询指定年龄段的用户数 Integer countByAgeRange(@Param("minAge") Integer minAge, @Param("maxAge") Integer maxAge); // 多表联查(示例:用户与订单关联) List<UserOrderVO> selectUserWithOrder(@Param("userId") Long userId); }
  2. 编写 XML(resources/mapper/UserMapper.xml):<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mpdemo.mapper.UserMapper"> <!-- 自定义统计 SQL --> <select id="countByAgeRange" resultType="java.lang.Integer"> SELECT COUNT(*) FROM t_user WHERE age BETWEEN #{minAge} AND #{maxAge} AND is_deleted = 0 <!-- 逻辑删除过滤 --> </select> <!-- 多表联查 SQL --> <select id="selectUserWithOrder" resultType="com.example.mpdemo.vo.UserOrderVO"> SELECT u.id, u.user_name, o.order_no, o.amount FROM t_user u LEFT JOIN t_order o ON u.id = o.user_id WHERE u.id = #{userId} AND u.is_deleted = 0 AND o.is_deleted = 0 </select> </mapper>
示例 2:注解自定义 SQL(简单 SQL 推荐)
代码语言:java
复制
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface UserMapper extends BaseMapper<User> {
    // 注解方式编写简单 SQL
    @Select("SELECT * FROM t_user WHERE age = #{age} AND is_deleted = 0")
    List<User> selectByAge(@Param("age") Integer age);
}

6. 代码生成器(高效开发)

MP 代码生成器可自动生成 实体类、Mapper 接口、Service、Controller,减少重复编码。

生成器示例(3.5+ 版本)
代码语言:java
复制
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;

public class CodeGenerator {
    public static void main(String[] args) {
        // 数据库连接配置
        String url = "jdbc:mysql://localhost:3306/mp_demo?useSSL=false&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "123456";

        // 代码生成配置
        FastAutoGenerator.create(url, username, password)
                // 全局配置
                .globalConfig(builder -> {
                    builder.author("your-name") // 作者名
                            .outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出目录
                            .disableOpenDir() // 生成后不打开文件夹
                            .commentDate("yyyy-MM-dd"); // 注释日期格式
                })
                // 包配置(生成的类所在包)
                .packageConfig(builder -> {
                    builder.parent("com.example.mpdemo") // 父包名
                            .entity("entity") // 实体类包名
                            .mapper("mapper") // Mapper 接口包名
                            .service("service") // Service 包名
                            .controller("controller") // Controller 包名
                            .xml("mapper") // Mapper XML 包名(resources/mapper)
                            .pathInfo(Collections.singletonMap(OutputFile.xml, 
                                    System.getProperty("user.dir") + "/src/main/resources/mapper"));
                })
                // 策略配置
                .strategyConfig(builder -> {
                    builder.addInclude("t_user", "t_order") // 要生成的数据库表名(多个表用逗号分隔)
                            .addTablePrefix("t_") // 表前缀(生成实体类时去掉前缀,如 t_user -> User)
                            // 实体类策略
                            .entityBuilder()
                            .enableLombok() // 启用 Lombok
                            .enableTableFieldAnnotation() // 生成字段注解(@TableField)
                            .logicDeleteFieldName("is_deleted") // 逻辑删除字段
                            .addTableFills(
                                    // 自动填充配置
                                    com.baomidou.mybatisplus.generator.fill.ColumnFill.INSERT, "create_time"
                            )
                            .addTableFills(
                                    com.baomidou.mybatisplus.generator.fill.ColumnFill.INSERT_UPDATE, "update_time"
                            )
                            // Mapper 策略
                            .mapperBuilder()
                            .enableBaseResultMap() // 启用 BaseResultMap(XML 中生成结果映射)
                            .enableBaseColumnList() // 启用 BaseColumnList(XML 中生成字段列表)
                            // Service 策略
                            .serviceBuilder()
                            .formatServiceFileName("%sService") // Service 接口名格式(如 UserService)
                            .formatServiceImplFileName("%sServiceImpl") // Service 实现类名格式
                            // Controller 策略
                            .controllerBuilder()
                            .enableRestStyle(); // 启用 REST 风格(@RestController)
                })
                // 模板引擎(Freemarker)
                .templateEngine(new FreemarkerTemplateEngine())
                // 执行生成
                .execute();
    }
}

三、最佳实践

1. 数据库设计与实体映射

  • 表名/字段命名规范:数据库表名用 t_ 前缀(如 t_user),字段名用下划线命名(如 user_name),实体类用驼峰命名(userName),配合 map-underscore-to-camel-case: true 自动转换。
  • 主键策略选择
    • MySQL 自增字段:用 IdType.AUTO(需数据库字段设置自增)。
    • 分布式场景:用 IdType.ASSIGN_ID(MP 内置雪花算法,生成 19 位 Long 型 ID,无需数据库配置)。
  • 逻辑删除:统一使用逻辑删除(is_deleted 字段),避免物理删除数据,通过全局配置简化注解。

2. 条件构造器使用规范

  • 优先使用 LambdaQueryWrapper:避免硬编码字段名(如 user_name),减少字段名修改导致的 Bug。
  • 动态条件拼接:使用带条件判断的方法(如 like(condition, column, value)),避免拼接无效 SQL。
  • 复杂条件拆分:如果条件过多,拆分到多个方法或使用 and/or 嵌套,提高可读性:// 嵌套条件:(age > 18 AND user_name LIKE '%张%') OR (age < 10 AND user_name LIKE '%李%') LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>() .and(w -> w.gt(User::getAge, 18).like(User::getUserName, "张")) .or(w -> w.lt(User::getAge, 10).like(User::getUserName, "李"));

3. 性能优化

  • 避免查询所有字段:使用 select(字段...) 指定需要的字段,减少数据传输:LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>() .select(User::getId, User::getUserName) // 只查询 id 和 user_name .gt(User::getAge, 18);
  • 分页查询必用索引:分页查询的 WHERE 条件和 ORDER BY 字段必须建索引,避免全表扫描(如 agecreate_time 字段建索引)。
  • 批量操作使用 saveBatch/updateBatchById:MP 提供批量插入/更新方法,内部优化了 JDBC 批处理,比循环单条操作效率高:// 批量插入(推荐批量大小 1000 以内) List<User> userList = new ArrayList<>(); // ... 添加数据 userMapper.saveBatch(userList, 500); // 每 500 条提交一次
  • 避免 selectList 无限制查询:如果数据量较大,必须使用分页查询,防止内存溢出。

4. 异常处理与日志

  • 生产环境关闭 SQL 打印log-impl 配置只在开发环境开启,生产环境关闭,避免日志泄露敏感信息。
  • 捕获 MP 异常:MP 提供 MybatisPlusException 及其子类,可针对性捕获数据库操作异常:try { userMapper.insert(user); } catch (MybatisPlusException e) { log.error("新增用户失败:{}", e.getMessage(), e); throw new BusinessException("用户创建失败"); // 业务异常 }

5. 扩展性与维护性

  • 自定义 Mapper 继承 BaseMapper:不要直接在 Mapper 接口写大量方法,复杂 SQL 抽离到 XML 或单独的 Mapper 方法。
  • Service 层封装业务逻辑:不要在 Controller 中直接调用 Mapper,通过 Service 层封装 CRUD 和业务逻辑,提高代码复用性。
  • 使用 @Param 注解:多参数查询时,用 @Param 指定参数名,避免 XML 中参数绑定错误。

6. 版本与插件选择

  • 使用稳定版本:优先选择 3.5+ 版本,修复了大量 Bug,且支持 Lambda 条件构造器、自动分页等特性。
  • 可选插件
    • 分页插件:3.5+ 自动注入,无需额外配置。
    • 乐观锁插件:处理并发更新,实体类添加 @Version 注解(数据库字段需为 int 类型):@Version private Integer version; // 乐观锁版本号字段
    • 性能分析插件:开发环境用于监控 SQL 执行时间(3.5+ 需手动配置)。

四、常见问题排查

  1. 字段映射错误:检查实体类字段名与数据库字段名是否一致,是否开启驼峰转换,是否添加 @TableField 注解。
  2. 主键自增失败:确认数据库字段已设置自增,实体类 @TableId 类型为 IdType.AUTO
  3. 逻辑删除不生效:检查全局配置是否正确,实体类是否添加 @TableLogic 注解,SQL 是否自动拼接 is_deleted = 0
  4. 分页查询总条数为 0:确认分页对象 Page 的页码和每页条数是否正确,查询条件是否过滤了所有数据。
  5. LambdaQueryWrapper 报红:检查实体类是否有 getter 方法(Lombok @Data 可自动生成),MP 版本是否为 3.5+。

五、总结

MyBatis-Plus 核心价值是简化开发、提高效率,通过 BaseMapper、条件构造器、分页插件等特性,减少重复编码。实战中需遵循数据库设计规范、合理使用 Lambda 条件构造器、封装业务逻辑,同时注意性能优化和代码维护性。掌握以上内容,可覆盖 90% 以上的业务场景,大幅提升开发效率。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MyBatis-Plus 实战使用与最佳实践
    • 一、环境准备
      • 1. 依赖引入(Maven)
      • 2. 配置文件(application.yml)
      • 3. 启动类注解
    • 二、核心实战使用
      • 1. 实体类(Entity)配置
      • 2. Mapper 接口(核心)
      • 3. 条件构造器(QueryWrapper/LambdaQueryWrapper)
      • 4. 分页查询
      • 5. 自定义 SQL(XML/注解)
      • 6. 代码生成器(高效开发)
    • 三、最佳实践
      • 1. 数据库设计与实体映射
      • 2. 条件构造器使用规范
      • 3. 性能优化
      • 4. 异常处理与日志
      • 5. 扩展性与维护性
      • 6. 版本与插件选择
    • 四、常见问题排查
    • 五、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档