Skip to content

MyBatis-Plus

导航目录

1. MyBatis-Plus 简介

1.1 什么是 MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

愿景:我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。

1.2 核心特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅通过少量配置即可实现单表大部分 CRUD 操作
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器)
  • 支持 ActiveRecord 模式:实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成各层代码
  • 内置分页插件:基于 MyBatis 物理分页,支持多种数据库(MySQL, Oracle, PostgreSQL等)
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用
  • 内置全局拦截插件:提供全表 delete、update 操作智能分析阻断,预防误操作

2. 快速开始

2.1 环境要求

  • JDK 8+
  • Maven 3.0+
  • MyBatis
  • Spring Boot 2.x/3.x(可选)

2.2 引入依赖

Maven:

xml
<dependencies>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.6</version>
    </dependency>

    <!-- 代码生成器 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.6</version>
    </dependency>

    <!-- 模板引擎 -->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>2.3</version>
    </dependency>
</dependencies>

Gradle:

gradle
dependencies {
    implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.6'
    implementation 'com.baomidou:mybatis-plus-generator:3.5.6'
    implementation 'org.apache.velocity:velocity-engine-core:2.3'
}

2.3 配置

application.yml:

yaml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: root

mybatis-plus:
  configuration:
    # 日志实现
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 驼峰下划线转换
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      # 主键类型
      id-type: auto
      # 逻辑删除字段名
      logic-delete-field: deleted
      # 逻辑删除值
      logic-delete-value: 1
      # 逻辑未删除值
      logic-not-delete-value: 0

2.4 启动类配置

java
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3. 核心功能

3.1 核心注解

MyBatis-Plus 的注解是实体类与数据库表之间的“映射桥梁”,核心解决“实体属性 ↔ 数据库字段”的匹配问题。

3.1.1 @TableName

用于标识实体类对应的数据库表名。

属性类型说明
valueString数据库表名
schemaString数据库 schema
keepGlobalPrefixboolean是否保持全局表前缀(默认 false)

3.1.2 @TableId

用于标记主键属性,指定主键生成策略。

属性类型说明
valueString主键字段名
typeIdType主键生成策略

主键策略 IdType:

描述适用场景
AUTO数据库 ID 自增依赖数据库自增策略
NONE无状态未设置主键类型
INPUT手动输入插入前自行 set 主键值
ASSIGN_ID雪花算法分布式系统,Long 或 String 类型
ASSIGN_UUIDUUID32位无中划线字符串

3.1.3 @TableField

用于标记非主键属性,解决属性名与字段名不一致等问题。

属性类型说明
valueString数据库字段名
existboolean是否为数据库表字段(默认 true)
fillFieldFill字段自动填充策略(如插入、更新时自动赋值)
selectboolean是否参与查询(默认 true)

3.1.4 实体类示例

java
@Data
@TableName("user")  // 指定表名
public class User {

    @TableId(type = IdType.AUTO)  // 主键策略
    private Long id;

    @TableField("user_name") // 字段映射
    private String name;

    private Integer age;

    @TableField(fill = FieldFill.INSERT)  // 自动填充
    private LocalDateTime createTime;

    @Version  // 乐观锁
    private Integer version;

    @TableLogic  // 逻辑删除
    private Integer deleted;

    @TableField(exist = false) // 排除非表字段
    private String remark;
}

3.2 Mapper 接口

java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface UserMapper extends BaseMapper<User> {
    // 自定义方法
    List<User> selectAllByName(String name);

    // 注解方式
    @Select("SELECT * FROM user WHERE age > #{age}")
    List<User> selectByAge(@Param("age") Integer age);
}

3.3 Service 接口

java
import com.baomidou.mybatisplus.extension.service.IService;

public interface UserService extends IService<User> {
    // 自定义业务方法
    List<User> customMethod(String name);
}
java
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    public List<User> customMethod(String name) {
        // 自定义实现
        return baseMapper.selectAllByName(name);
    }
}

4. CRUD 接口

MyBatis-Plus 提供了通用 Mapper 和通用 Service,极大简化了 CRUD 操作。

4.1 BaseMapper vs IService 对比

维度BaseMapperIService
定位数据访问层(DAO)接口业务逻辑层(Service)接口
底层依赖直接对接 MyBatis 生成 SQL基于 BaseMapper 封装,提供高级能力
方法命名贴近 SQL(insert/delete/select)贴近业务(save/remove/get)
功能侧重基础单表 CRUD补充批量操作、分页增强等

4.2 Mapper CRUD

继承 BaseMapper<T> 即可拥有单表 CRUD 能力。

java
@Autowired
private UserMapper userMapper;

// 插入(自动回填ID)
User user = new User();
user.setName("张三");
int result = userMapper.insert(user);

// 根据 ID 删除
userMapper.deleteById(1L);

// 根据 ID 更新(仅更新非 null 字段)
User updateUser = new User();
updateUser.setId(1L);
updateUser.setName("李四");
userMapper.updateById(updateUser);

// 根据 ID 查询
User user = userMapper.selectById(1L);

// 查询列表
List<User> users = userMapper.selectList(new QueryWrapper<User>().gt("age", 18));

4.3 Service CRUD

继承 ServiceImpl<M, T> 并实现自定义 Service 接口(继承 IService<T>)。

java
@Autowired
private UserService userService;

// 批量插入(默认批次 1000)
userService.saveBatch(userList);

// 保存或更新(根据 ID 判断)
userService.saveOrUpdate(user);

// 链式查询
List<User> list = userService.lambdaQuery()
    .like(User::getName, "张")
    .gt(User::getAge, 18)
    .list();

// 链式更新
userService.lambdaUpdate()
    .set(User::getAge, 20)
    .eq(User::getId, 1L)
    .update();

5. 条件构造器 (Wrapper)

条件构造器是 MyBatis-Plus 的灵魂,用于在 Java 层动态生成 SQL 的 WHERE 条件。

5.1 Wrapper 家族体系

类名说明
QueryWrapper支持查询(SELECT)和删除(DELETE)条件
UpdateWrapper支持更新(UPDATE)条件,可手动设置 set 字段
LambdaQueryWrapper推荐。基于 Lambda 表达式,具有类型检查,防止字段名写错
LambdaUpdateWrapper推荐。基于 Lambda 的更新构造器

5.2 常用方法

方法说明示例
eq等于 (=)eq("name", "张三")
ne不等于 (<>)ne("age", 20)
gt / ge大于 (>) / 大于等于 (>=)gt("age", 18)
lt / le小于 (<) / 小于等于 (<=)lt("age", 30)
like模糊查询 (%值%)like("name", "张")
between范围查询 (BETWEEN)between("age", 20, 30)
isNull为空 (IS NULL)isNull("email")
in集合查询 (IN)in("id", 1, 2, 3)
orderByDesc倒序排序orderByDesc("create_time")

5.3 动态条件控制 (Condition)

所有 Wrapper 方法都支持第一个参数 boolean condition,用于控制该条件是否拼接。

java
String name = "张三";
Integer age = null;

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(name), User::getName, name)
       .gt(age != null, User::getAge, age);

5.4 复杂条件拼接

java
// 嵌套查询:WHERE (name = '张三' OR age > 20) AND status = 1
wrapper.and(i -> i.eq(User::getName, "张三").or().gt(User::getAge, 20))
       .eq(User::getStatus, 1);

5.5 代码示例

5.5.1 QueryWrapper 示例

java
// 基本查询
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 等于条件
queryWrapper.eq("name", "张三");
// 不等于条件
queryWrapper.ne("age", 20);
// 大于条件
queryWrapper.gt("age", 18);
// 小于等于条件
queryWrapper.le("salary", 5000);
// 模糊查询
queryWrapper.like("name", "张");
// 左模糊
queryWrapper.likeLeft("name", "三");
// 右模糊
queryWrapper.likeRight("name", "张");
// 范围查询
queryWrapper.between("age", 18, 30);
// 空值查询
queryWrapper.isNull("email");
// 非空查询
queryWrapper.isNotNull("phone");
// 集合查询
queryWrapper.in("id", 1, 2, 3, 4, 5);
// 排序
queryWrapper.orderByDesc("create_time").orderByAsc("id");
// 分组
queryWrapper.groupBy("department_id");
// 聚合
queryWrapper.select("department_id", "COUNT(*) as user_count");

List<User> users = userMapper.selectList(queryWrapper);

5.5.2 LambdaQueryWrapper 示例(推荐)

java
// 基本查询
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
// 等于条件
lambdaWrapper.eq(User::getName, "张三");
// 不等于条件
lambdaWrapper.ne(User::getAge, 20);
// 大于条件
lambdaWrapper.gt(User::getAge, 18);
// 小于等于条件
lambdaWrapper.le(User::getSalary, 5000);
// 模糊查询
lambdaWrapper.like(User::getName, "张");
// 范围查询
lambdaWrapper.between(User::getAge, 18, 30);
// 空值查询
lambdaWrapper.isNull(User::getEmail);
// 集合查询
lambdaWrapper.in(User::getId, 1, 2, 3, 4, 5);
// 排序
lambdaWrapper.orderByDesc(User::getCreateTime).orderByAsc(User::getId);

List<User> users = userMapper.selectList(lambdaWrapper);

5.5.3 UpdateWrapper 示例

java
// 更新操作
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 设置更新字段
updateWrapper.set("name", "李四")
             .set("age", 25)
             .set("salary", 6000);
// 条件
updateWrapper.eq("id", 1L);

int result = userMapper.update(null, updateWrapper);

5.5.4 LambdaUpdateWrapper 示例(推荐)

java
// 更新操作
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
// 设置更新字段
lambdaUpdateWrapper.set(User::getName, "李四")
                  .set(User::getAge, 25)
                  .set(User::getSalary, 6000);
// 条件
lambdaUpdateWrapper.eq(User::getId, 1L);

int result = userMapper.update(null, lambdaUpdateWrapper);

5.5.5 动态条件构建

java
// 动态条件构建
String name = "张三";
Integer age = 20;
String email = "";

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 动态添加条件
wrapper.eq(StringUtils.hasText(name), User::getName, name)
       .gt(age != null, User::getAge, age)
       .isNotNull(StringUtils.hasText(email), User::getEmail);

List<User> users = userMapper.selectList(wrapper);

5.5.6 复杂逻辑组合

java
// 复杂逻辑组合:(name = '张三' OR name = '李四') AND (age > 18 AND status = 1)
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.and(i -> i.eq(User::getName, "张三").or().eq(User::getName, "李四"))
       .and(i -> i.gt(User::getAge, 18).eq(User::getStatus, 1));

List<User> users = userMapper.selectList(wrapper);

5.5.7 实际业务场景示例

场景1:根据条件查询用户列表

java
public List<User> queryUsers(UserQueryDTO queryDTO) {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

    // 用户名模糊查询
    if (StringUtils.hasText(queryDTO.getUserName())) {
        wrapper.like(User::getName, queryDTO.getUserName());
    }

    // 年龄范围查询
    if (queryDTO.getMinAge() != null) {
        wrapper.ge(User::getAge, queryDTO.getMinAge());
    }
    if (queryDTO.getMaxAge() != null) {
        wrapper.le(User::getAge, queryDTO.getMaxAge());
    }

    // 状态查询
    if (queryDTO.getStatus() != null) {
        wrapper.eq(User::getStatus, queryDTO.getStatus());
    }

    // 部门查询
    if (queryDTO.getDepartmentId() != null) {
        wrapper.eq(User::getDepartmentId, queryDTO.getDepartmentId());
    }

    // 排序
    wrapper.orderByDesc(User::getCreateTime);

    return userMapper.selectList(wrapper);
}

场景2:批量更新用户状态

java
public boolean updateUserStatus(List<Long> userIds, Integer status) {
    LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
    wrapper.set(User::getStatus, status)
           .in(User::getId, userIds);

    return userMapper.update(null, wrapper) > 0;
}

场景3:根据条件删除用户

java
public boolean deleteUsers(DeleteUserDTO deleteDTO) {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

    // 按状态删除
    if (deleteDTO.getStatus() != null) {
        wrapper.eq(User::getStatus, deleteDTO.getStatus());
    }

    // 按创建时间删除
    if (deleteDTO.getCreateTimeBefore() != null) {
        wrapper.lt(User::getCreateTime, deleteDTO.getCreateTimeBefore());
    }

    return userMapper.delete(wrapper) > 0;
}

6. 分页查询

6.1 配置分页插件

java
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

        return interceptor;
    }
}

6.2 分页查询使用

java
// 方式一:使用Page对象
Page<User> page = new Page<>(1, 10); // 当前页,每页大小
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18);
Page<User> result = userMapper.selectPage(page, wrapper);

// 获取分页信息
System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("当前页:" + result.getCurrent());
System.out.println("每页大小:" + result.getSize());
List<User> records = result.getRecords();

// 方式二:使用Service
Page<User> page = new Page<>(1, 10);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18);
Page<User> result = userService.page(page, wrapper);

// 不查询总记录数(性能优化)
Page<User> page = new Page<>(1, 10, false);

7. 高级功能

7.1 自动填充

java
@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());
    }
}

7.2 乐观锁

java
// 实体类中添加版本字段
public class User {
    @Version
    private Integer version;
}

// 使用乐观锁
User user = userMapper.selectById(1L);
user.setName("新名字");
user.setEmail("new@example.com");
// 此时version会自动+1
userMapper.updateById(user);

7.3 逻辑删除

概念对比

  • 物理删除:直接从数据库执行 DELETE 操作,数据彻底丢失,难以恢复。
  • 逻辑删除:执行 UPDATE 操作将状态位设为“已删除”,数据依然存在,但在查询时会自动过滤。

实现步骤

  1. 数据库字段:增加一个标识字段(如 deleted,默认值 0)。
  2. 实体类属性:在对应属性上添加 @TableLogic 注解。
  3. 配置文件(可选):自定义全局逻辑删除的值。
yaml
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除字段名
      logic-delete-value: 1 # 删除状态
      logic-not-delete-value: 0 # 未删除状态

效果

  • 执行 deleteById 时,底层变为 UPDATE user SET deleted=1 WHERE id=? AND deleted=0
  • 执行 selectList 时,底层自动加上 WHERE deleted=0

7.4 枚举处理

java
// 定义枚举
public enum GenderEnum {
    MALE(1, "男"),
    FEMALE(2, "女");

    private final Integer code;
    private final String desc;

    GenderEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    // getter...
}

// 实体类中使用枚举
public class User {
    @EnumValue  // 标记数据库存储的值
    private GenderEnum gender;
}

// 配置枚举处理器
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // ... 其他配置
        return interceptor;
    }

    @Bean
    public MybatisConfiguration mybatisConfiguration() {
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
        return configuration;
    }
}

8. 代码生成器

8.1 快速代码生成

java
public class CodeGenerator {

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator generator = new AutoGenerator();

        // 全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        globalConfig.setOutputDir(projectPath + "/src/main/java");
        globalConfig.setAuthor("Your Name");
        globalConfig.setOpen(false);
        globalConfig.setSwagger2(true); // 实体属性 Swagger2 注解
        generator.setGlobalConfig(globalConfig);

        // 数据源配置
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus");
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("root");
        generator.setDataSource(dataSourceConfig);

        // 包配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setModuleName("system");
        packageConfig.setParent("com.example");
        packageConfig.setEntity("entity");
        packageConfig.setMapper("mapper");
        packageConfig.setService("service");
        packageConfig.setServiceImpl("service.impl");
        packageConfig.setController("controller");
        generator.setPackageInfo(packageConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setInclude("user", "role"); // 表名
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("t_"); // 表前缀
        generator.setStrategy(strategy);

        // 执行生成
        generator.execute();
    }
}

8.2 自定义模板

java
// 自定义模板配置
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setEntity("templates/entity.java");
templateConfig.setMapper("templates/mapper.java");
templateConfig.setService("templates/service.java");
templateConfig.setServiceImpl("templates/serviceImpl.java");
templateConfig.setController("templates/controller.java");
generator.setTemplate(templateConfig);

9. 多数据源配置

9.1 依赖引入

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.6.1</version>
</dependency>

9.2 配置多数据源

yaml
spring:
  datasource:
    dynamic:
      primary: master # 设置默认的数据源或者数据源组
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master_db
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          url: jdbc:mysql://localhost:3306/slave_db
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver

9.3 使用注解切换数据源

java
@Service
public class UserService {

    @DS("master") // 使用主数据源
    public void addUser(User user) {
        userMapper.insert(user);
    }

    @DS("slave") // 使用从数据源
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}

10. 性能优化

10.1 SQL 性能分析插件

java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

    // SQL分析插件
    interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());

    return interceptor;
}

10.2 分页优化

java
// 使用不查询总记录数的分页(大数据量时)
Page<User> page = new Page<>(1, 10, false);

// 自定义分页SQL(复杂查询时)
@Select("SELECT * FROM user ${ew.customSqlSegment}")
List<User> selectPageCustom(@Param(Constants.WRAPPER) Wrapper<User> wrapper, Page<User> page);

10.3 查询优化

java
// 只查询需要的字段
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "name", "email");

// 使用Lambda避免字段名硬编码
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.select(User::getId, User::getName, User::getEmail);

11. 常见问题

11.1 字段映射问题

java
// 解决数据库字段名与实体类属性名不一致
public class User {
    @TableField("user_name")  // 数据库字段名
    private String name;

    // 忽略非表字段
    @TableField(exist = false)
    private String tempField;
}

11.2 主键策略问题

java
public class User {
    // 雪花算法ID
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    // 数据库自增
    @TableId(type = IdType.AUTO)
    private Long id;
}

11.3 批量操作性能

java
// 批量插入
List<User> userList = new ArrayList<>();
// ... 添加数据
boolean success = userService.saveBatch(userList, 1000); // 每批1000条

// 批量更新
boolean success = userService.updateBatchById(userList, 1000);

12. 最佳实践

12.1 项目结构

src/main/java/com/example/
├── entity/          # 实体类
├── mapper/          # Mapper接口
├── service/         # Service接口
│   └── impl/       # Service实现
├── controller/      # 控制器
└── config/         # 配置类

12.2 统一返回结果

java
@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("success");
        result.setData(data);
        return result;
    }
}

12.3 全局异常处理

java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result<String> handleException(Exception e) {
        return Result.error(500, e.getMessage());
    }
}

13. 扩展功能

13.1 自定义注入方法

java
public interface MyBaseMapper<T> extends BaseMapper<T> {

    int alwaysDeleteSome();

    int deleteAll();
}

public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new DeleteAll());
        methodList.add(new AlwaysDeleteSome());
        return methodList;
    }
}

13.2 多租户支持

java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

    // 多租户插件
    TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
    tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
        @Override
        public Expression getTenantId() {
            // 从上下文中获取租户ID
            return new LongValue(1L);
        }

        @Override
        public String getTenantIdColumn() {
            return "tenant_id";
        }

        @Override
        public boolean ignoreTable(String tableName) {
            // 忽略不需要租户隔离的表
            return "tenant".equals(tableName);
        }
    });
    interceptor.addInnerInterceptor(tenantInterceptor);

    return interceptor;
}

14. Db 静态工具类 (MyBatis-Plus 3.5.3+)

Db 类是 MyBatis-Plus 提供的一个静态方法工具类,旨在简化 Service 间的循环依赖问题,让开发者在非 Service 环境(如拦截器、异步任务)中也能方便地调用 CRUD。

14.1 核心优势

  • 无循环依赖:无需在 Service 中注入其他 Service,直接通过 Db 调用,有效解决“你依赖我,我依赖你”的死锁问题。
  • 静态调用:直接 Db.getById(id, User.class),代码更加整洁。
  • 性能一致:底层依然调用 ServiceImpl,性能与普通注入无异。

14.2 常用方法

java
// 根据 ID 查询
User user = Db.getById(1L, User.class);

// 根据条件查询列表
List<User> users = Db.lambdaQuery(User.class)
    .like(User::getUsername, "o")
    .ge(User::getBalance, 1000)
    .list();

// 批量插入
List<User> userList = Arrays.asList(new User(), new User());
Db.saveBatch(userList);

// 更新数据
Db.lambdaUpdate(User.class)
    .set(User::getBalance, 2000)
    .eq(User::getUsername, "Rose")
    .update();

3. 实战案例:查询用户及收货地址

3.1 需求描述

改造根据 id 查询用户的接口,在查询用户的同时返回用户的收货地址列表。

3.2 实现步骤

步骤 1:导入 AddressVO

AddressVO.java 复制到 com.itheima.mp.domain.vo 包下:

java
package com.itheima.mp.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "收货地址VO")
public class AddressVO {
    @ApiModelProperty("地址id")
    private Long id;

    @ApiModelProperty("用户id")
    private Long userId;

    @ApiModelProperty("省")
    private String province;

    @ApiModelProperty("市")
    private String city;

    @ApiModelProperty("区/县")
    private String town;

    @ApiModelProperty("详细地址")
    private String street;

    @ApiModelProperty("联系人")
    private String contact;

    @ApiModelProperty("手机号")
    private String mobile;

    @ApiModelProperty("是否是默认地址")
    private Boolean isDefault;
}
步骤 2:改造 UserVO

UserVO 中添加地址列表属性:

java
package com.itheima.mp.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.List;

@Data
@ApiModel(description = "用户VO")
public class UserVO {
    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private Integer status;

    @ApiModelProperty("账户余额")
    private Integer balance;

    @ApiModelProperty("收货地址列表")
    private List<AddressVO> addresses;
}
步骤 3:修改 UserController
java
@RestController
@RequestMapping("/users")
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
public class UserController {

    private final IUserService userService;

    /**
     * 根据用户id查询用户
     * @param id 用户id
     */
    @ApiOperation("根据用户id查询用户")
    @GetMapping("/{id}")
    public UserVO queryById(@PathVariable("id") Long id) {
        return userService.queryUserAndAddressById(id);
    }
}
步骤 4:新增 IUserService 接口方法
java
public interface IUserService extends IService<User> {

    /**
     * 根据用户id查询用户和地址
     * @param id 用户id
     * @return userVO
     */
    UserVO queryUserAndAddressById(Long id);
}
步骤 5:实现 UserServiceImpl

使用 Db 工具类查询收货地址,避免循环依赖:

java
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public UserVO queryUserAndAddressById(Long id) {
        // 查询用户
        User user = getById(id);
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);

        // 使用Db工具类查询用户的收货地址列表,避免循环依赖
        List<Address> addressList = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id)
                .list();
        userVO.setAddresses(BeanUtil.copyToList(addressList, AddressVO.class));

        return userVO;
    }
}

4. 扩展练习:批量查询用户及地址

需求:改造根据用户 id 批量查询用户并返回用户的收货地址列表

java
@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
    // 1. 查询用户列表
    List<User> userList = this.listByIds(ids);
    List<UserVO> userVOS = BeanUtil.copyToList(userList, UserVO.class);

    // 2. 使用Db工具类查询地址列表,并按用户id分组
    List<Address> addressList = Db.lambdaQuery(Address.class)
            .in(Address::getUserId, ids)
            .list();
    Map<Long, List<AddressVO>> addressVoMap = BeanUtil.copyToList(addressList, AddressVO.class)
            .stream().collect(Collectors.groupingBy(AddressVO::getUserId));

    // 3. 将地址列表设置到对应的用户VO中
    for (UserVO userVO : userVOS) {
        userVO.setAddresses(addressVoMap.get(userVO.getId()));
    }
    return userVOS;
}

5. 注意事项

  1. Mapper 要求:使用 Db 工具类时,必须有对应实体类的 Mapper
  2. 事务管理Db 操作默认在事务中执行
  3. 性能考虑:对于复杂查询,建议使用自定义 Mapper 方法
  4. 代码简洁性:简单的单表操作使用 Db,复杂业务逻辑使用 Service