深入解析MyBatis Plus:从基础到高级实战

一、MyBatis Plus概述与核心价值

1.1 MyBatis Plus是什么

MyBatis Plus(简称MP)是一款基于MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,简化开发、提高效率。它提供了通用的CRUD操作、条件构造器、分页插件、代码生成器等诸多功能,使开发者能够更专注于业务逻辑的实现而非重复的数据库操作代码编写。

MP的核心特点包括:

  • 无侵入性:只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小:启动即会自动注入基本CRUD操作,性能基本无损耗
  • 强大的CRUD操作:内置通用Mapper、Service,仅需少量配置即可实现大部分单表CRUD操作
  • 支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件
  • 支持主键自动生成:支持多达4种主键策略
  • 内置分页插件:基于MyBatis物理分页,开发者无需关心具体实现
  • 内置性能分析插件:可输出SQL语句以及其执行时间

1.2 MyBatis Plus与MyBatis的关系

下表展示了MyBatis Plus与原生MyBatis的主要区别:

特性MyBatisMyBatis Plus
CRUD操作需手动编写SQL或使用Generator内置通用Mapper,自动生成基础CRUD
条件构造需在XML中编写复杂条件提供Wrapper条件构造器
分页功能需手动实现或使用第三方插件内置分页插件,简单配置即可使用
代码生成需依赖MyBatis Generator内置更强大的代码生成器
乐观锁需自行实现内置乐观锁机制
SQL注入防护需自行注意内置SQL注入剥离器

1.3 MyBatis Plus的核心组件

MyBatis Plus的核心架构由以下几个主要组件构成:

BaseMapper
+insert(T entity) : int
+deleteById(Serializable id) : int
+updateById(T entity) : int
+selectById(Serializable id) : T
+selectBatchIds(Collection idList) : List
+selectByMap(Map columnMap) : List
+selectOne(Wrapper queryWrapper) : T
«abstract»
Wrapper
+eq(String column, Object val) : Children
+ne(String column, Object val) : Children
+gt(String column, Object val) : Children
+ge(String column, Object val) : Children
+lt(String column, Object val) : Children
+le(String column, Object val) : Children
AbstractWrapper
+allEq(Map params) : Children
+like(String column, String val) : Children
+in(String column, Collection valueList) : Children
QueryWrapper
+select(String... columns) : Children
UpdateWrapper
+set(String column, Object val) : Children
LambdaQueryWrapper
+eq(SFunction column, R val) : LambdaQueryWrapper
UserMapper

二、MyBatis Plus环境搭建与基础配置

2.1 项目初始化与依赖引入

以Spring Boot项目为例,首先需要在pom.xml中添加MyBatis Plus的依赖:

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- MyBatis Plus Starter -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
    <!-- 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Lombok简化代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2.2 数据库配置

在application.yml中配置数据库连接和MyBatis Plus相关属性:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mp_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

mybatis-plus:
  configuration:
    # 日志实现
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启驼峰命名转换
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      # 主键类型 AUTO:数据库ID自增 NONE:未设置 INPUT:用户输入 ASSIGN_ID:雪花算法 ASSIGN_UUID:UUID
      id-type: assign_id
      # 逻辑删除字段名
      logic-delete-field: isDeleted
      # 逻辑删除值
      logic-delete-value: 1
      # 逻辑未删除值
      logic-not-delete-value: 0

2.3 实体类与Mapper创建

2.3.1 实体类示例
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;

@Data
@TableName("t_user")  // 指定表名,如果表名与类名一致(忽略大小写)可省略
public class User {
    /**
     * 主键
     * @TableId 主键注解
     * value: 数据库字段名(如果属性名与字段名一致可省略)
     * type: 主键生成策略
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    
    /**
     * 用户名
     */
    private String username;
    
    /**
     * 密码
     */
    private String password;
    
    /**
     * 年龄
     */
    private Integer age;
    
    /**
     * 邮箱
     */
    private String email;
    
    /**
     * 创建时间
     * @TableField 字段注解
     * fill: 字段自动填充策略
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
    /**
     * 逻辑删除标志(0:未删除 1:已删除)
     */
    @TableLogic
    private Integer isDeleted;
}
2.3.2 Mapper接口示例
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;

// 继承BaseMapper并指定泛型为User
public interface UserMapper extends BaseMapper<User> {
    // 已经包含了基本的CRUD方法,无需手动编写
    // 可以在此添加自定义的SQL方法
}

2.4 自动填充功能实现

实现MetaObjectHandler接口来处理自动填充:

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    /**
     * 插入时的填充策略
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
    }
    
    /**
     * 更新时的填充策略
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
}

三、MyBatis Plus基础CRUD操作

3.1 插入操作

3.1.1 基本插入
// 测试插入
@Test
public void testInsert() {
    User user = new User();
    user.setUsername("张三");
    user.setPassword("123456");
    user.setAge(25);
    user.setEmail("zhangsan@example.com");
    
    // 返回受影响的行数
    int result = userMapper.insert(user);
    System.out.println("影响行数:" + result);
    
    // 插入后,自动回填主键到user对象
    System.out.println("生成的主键ID:" + user.getId());
}
3.1.2 批量插入
// 测试批量插入
@Test
public void testBatchInsert() {
    List<User> userList = new ArrayList<>();
    for (int i = 1; i <= 5; i++) {
        User user = new User();
        user.setUsername("用户" + i);
        user.setPassword("pwd" + i);
        user.setAge(20 + i);
        user.setEmail("user" + i + "@example.com");
        userList.add(user);
    }
    
    // 注意:默认的insertBatchSomeColumn方法需要特殊配置
    // 这里使用循环单条插入演示
    userList.forEach(userMapper::insert);
    
    // 获取批量插入的ID列表
    List<Long> idList = userList.stream()
            .map(User::getId)
            .collect(Collectors.toList());
    System.out.println("批量插入的ID列表:" + idList);
}

3.2 删除操作

3.2.1 根据ID删除
// 测试根据ID删除
@Test
public void testDeleteById() {
    // 先插入一条测试数据
    User user = new User();
    user.setUsername("测试删除");
    userMapper.insert(user);
    
    // 根据ID删除
    int result = userMapper.deleteById(user.getId());
    System.out.println("删除影响行数:" + result);
}
3.2.2 根据条件删除
// 测试根据条件删除
@Test
public void testDeleteByMap() {
    // 构造删除条件
    Map<String, Object> columnMap = new HashMap<>();
    columnMap.put("username", "张三");
    columnMap.put("age", 25);
    
    // 根据Map构造的条件删除
    int result = userMapper.deleteByMap(columnMap);
    System.out.println("删除影响行数:" + result);
}
3.2.3 逻辑删除

MyBatis Plus提供了逻辑删除功能,只需要在配置中开启并在实体字段上添加@TableLogic注解即可:

// 测试逻辑删除
@Test
public void testLogicDelete() {
    // 先插入一条测试数据
    User user = new User();
    user.setUsername("逻辑删除测试");
    userMapper.insert(user);
    
    // 逻辑删除(实际是更新is_deleted字段)
    int result = userMapper.deleteById(user.getId());
    System.out.println("逻辑删除影响行数:" + result);
    
    // 再次查询该记录
    User deletedUser = userMapper.selectById(user.getId());
    System.out.println("逻辑删除后查询结果:" + deletedUser); // 应为null
}

3.3 更新操作

3.3.1 根据ID更新
// 测试根据ID更新
@Test
public void testUpdateById() {
    // 先插入一条测试数据
    User user = new User();
    user.setUsername("原始用户名");
    userMapper.insert(user);
    
    // 修改用户信息
    user.setUsername("修改后的用户名");
    user.setEmail("newemail@example.com");
    
    // 根据ID更新
    int result = userMapper.updateById(user);
    System.out.println("更新影响行数:" + result);
    
    // 查询验证
    User updatedUser = userMapper.selectById(user.getId());
    System.out.println("更新后的用户信息:" + updatedUser);
}
3.3.2 条件更新
// 测试条件更新
@Test
public void testUpdateByWrapper() {
    // 创建更新条件
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.eq("username", "张三")
                .set("age", 30)
                .set("email", "updated@example.com");
    
    // 执行更新
    int result = userMapper.update(null, updateWrapper);
    System.out.println("条件更新影响行数:" + result);
}

3.4 查询操作

3.4.1 根据ID查询
// 测试根据ID查询
@Test
public void testSelectById() {
    // 先插入一条测试数据
    User user = new User();
    user.setUsername("查询测试");
    userMapper.insert(user);
    
    // 根据ID查询
    User selectedUser = userMapper.selectById(user.getId());
    System.out.println("查询结果:" + selectedUser);
}
3.4.2 批量ID查询
// 测试批量ID查询
@Test
public void testSelectBatchIds() {
    // 先插入多条测试数据
    List<Long> idList = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
        User user = new User();
        user.setUsername("批量查询用户" + i);
        userMapper.insert(user);
        idList.add(user.getId());
    }
    
    // 根据ID集合批量查询
    List<User> userList = userMapper.selectBatchIds(idList);
    System.out.println("批量查询结果:");
    userList.forEach(System.out::println);
}
3.4.3 条件查询
// 测试条件查询
@Test
public void testSelectByWrapper() {
    // 创建查询条件
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("id", "username", "email")  // 指定查询字段
              .like("username", "张")
              .between("age", 20, 30)
              .isNotNull("email")
              .orderByDesc("age");
    
    // 执行查询
    List<User> userList = userMapper.selectList(queryWrapper);
    System.out.println("条件查询结果:");
    userList.forEach(System.out::println);
}

四、MyBatis Plus条件构造器详解

4.1 条件构造器概述

MyBatis Plus提供了强大的条件构造器Wrapper,用于构建复杂的查询条件。主要分为以下几类:

  1. QueryWrapper:用于构建查询条件
  2. UpdateWrapper:用于构建更新条件
  3. LambdaQueryWrapper:使用Lambda表达式构建查询条件
  4. LambdaUpdateWrapper:使用Lambda表达式构建更新条件

4.2 QueryWrapper常用方法

下表展示了QueryWrapper的常用方法:

方法名说明示例
eq等于 =eq(“name”, “张三”) → name = ‘张三’
ne不等于 !=ne(“age”, 18) → age != 18
gt大于 >gt(“age”, 18) → age > 18
ge大于等于 >=ge(“age”, 18) → age >= 18
lt小于 <lt(“age”, 30) → age < 30
le小于等于 <=le(“age”, 30) → age <= 30
betweenBETWEEN 值1 AND 值2between(“age”, 18, 30) → age BETWEEN 18 AND 30
notBetweenNOT BETWEEN 值1 AND 值2notBetween(“age”, 18, 30) → age NOT BETWEEN 18 AND 30
likeLIKE ‘%值%’like(“name”, “张”) → name LIKE ‘%张%’
notLikeNOT LIKE ‘%值%’notLike(“name”, “张”) → name NOT LIKE ‘%张%’
likeLeftLIKE ‘%值’likeLeft(“name”, “三”) → name LIKE ‘%三’
likeRightLIKE ‘值%’likeRight(“name”, “张”) → name LIKE ‘张%’
isNull字段 IS NULLisNull(“email”) → email IS NULL
isNotNull字段 IS NOT NULLisNotNull(“email”) → email IS NOT NULL
in字段 IN (值1, 值2, …)in(“age”, 18, 19, 20) → age IN (18,19,20)
notIn字段 NOT IN (值1, 值2, …)notIn(“age”, 18,19,20) → age NOT IN (18,19,20)
groupBy分组 GROUP BYgroupBy(“dept_id”) → GROUP BY dept_id
orderByAsc排序 ORDER BY ASCorderByAsc(“age”) → ORDER BY age ASC
orderByDesc排序 ORDER BY DESCorderByDesc(“age”) → ORDER BY age DESC
havingHAVING SQL语句having(“sum(age) > 100”) → HAVING sum(age) > 100

4.3 LambdaQueryWrapper使用

LambdaQueryWrapper通过Lambda表达式引用实体属性,避免了硬编码字段名:

// LambdaQueryWrapper示例
@Test
public void testLambdaQueryWrapper() {
    LambdaQueryWrapper<User> lambdaQuery = new LambdaQueryWrapper<>();
    lambdaQuery.select(User::getId, User::getUsername, User::getEmail)
              .like(User::getUsername, "张")
              .ge(User::getAge, 20)
              .orderByDesc(User::getAge);
    
    List<User> userList = userMapper.selectList(lambdaQuery);
    userList.forEach(System.out::println);
}

4.4 复杂条件组合

条件构造器支持复杂的条件组合:

// 复杂条件组合示例
@Test
public void testComplexCondition() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    
    // 组合条件:(age < 18 OR age > 30) AND username LIKE '%张%'
    queryWrapper.nested(wq -> wq.lt("age", 18).or().gt("age", 30))
               .like("username", "张");
    
    // 另一种写法
    queryWrapper.and(wq -> wq.lt("age", 18).or().gt("age", 30))
               .like("username", "张");
    
    List<User> userList = userMapper.selectList(queryWrapper);
    userList.forEach(System.out::println);
}

4.5 条件构造器最佳实践

  1. 优先使用Lambda表达式:避免硬编码字段名,提高代码可维护性
  2. 合理使用链式调用:保持代码简洁性
  3. 复杂条件使用nested:确保条件组合的正确性
  4. 复用Wrapper对象:对于常用条件可以封装复用
  5. 注意SQL注入风险:避免直接拼接用户输入

五、MyBatis Plus高级特性

5.1 分页查询

5.1.1 配置分页插件

首先需要配置分页插件:

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusPlugin;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusPlugin mybatisPlusPlugin() {
        MybatisPlusPlugin interceptor = new MybatisPlusPlugin();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
5.1.2 基本分页查询
// 测试分页查询
@Test
public void testSelectPage() {
    // 当前页(从1开始),每页显示条数
    Page<User> page = new Page<>(1, 3);
    
    // 构造查询条件(可选)
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.ge(User::getAge, 20);
    
    // 执行分页查询
    Page<User> userPage = userMapper.selectPage(page, queryWrapper);
    
    // 获取分页数据
    System.out.println("总记录数:" + userPage.getTotal());
    System.out.println("总页数:" + userPage.getPages());
    System.out.println("当前页记录:");
    userPage.getRecords().forEach(System.out::println);
}
5.1.3 自定义分页查询

对于自定义SQL的分页查询:

  1. 在Mapper接口中定义方法:
public interface UserMapper extends BaseMapper<User> {
    // 自定义分页查询
    IPage<User> selectUserPage(IPage<User> page, @Param("age") Integer age);
}
  1. 在XML中编写SQL:
<select id="selectUserPage" resultType="com.example.demo.entity.User">
    SELECT * FROM t_user 
    WHERE age > #{age}
</select>
  1. 调用测试:
// 测试自定义分页查询
@Test
public void testCustomSelectPage() {
    Page<User> page = new Page<>(1, 2);
    IPage<User> userPage = userMapper.selectUserPage(page, 20);
    
    System.out.println("自定义分页查询结果:");
    userPage.getRecords().forEach(System.out::println);
}

5.2 乐观锁实现

乐观锁通过版本号机制实现,防止并发更新导致的数据不一致问题。

5.2.1 配置乐观锁插件
// 在MybatisPlusConfig中添加乐观锁插件
@Bean
public MybatisPlusPlugin mybatisPlusPlugin() {
    MybatisPlusPlugin interceptor = new MybatisPlusPlugin();
    // 乐观锁插件
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}
5.2.2 实体类添加版本字段
@Data
@TableName("t_user")
public class User {
    // ...其他字段
    
    @Version
    private Integer version;
}
5.2.3 乐观锁测试
// 测试乐观锁
@Test
public void testOptimisticLocker() {
    // 1. 先查询用户
    User user = userMapper.selectById(1L);
    System.out.println("当前版本号:" + user.getVersion());
    
    // 2. 修改用户信息
    user.setUsername("新用户名");
    
    // 3. 模拟并发,另一个线程抢先更新了数据
    User user2 = userMapper.selectById(1L);
    user2.setUsername("抢先更新的用户名");
    userMapper.updateById(user2);
    System.out.println("抢先更新后的版本号:" + userMapper.selectById(1L).getVersion());
    
    // 4. 当前线程尝试更新
    int result = userMapper.updateById(user);
    System.out.println("乐观锁更新结果:" + result); // 应该为0,更新失败
    
    // 5. 解决方案:重新查询并更新
    User newUser = userMapper.selectById(1L);
    newUser.setUsername("最终更新的用户名");
    userMapper.updateById(newUser);
}

5.3 逻辑删除

逻辑删除配置已在2.2节中展示,这里补充使用方法:

// 测试逻辑删除后的查询
@Test
public void testLogicDeleteQuery() {
    // 1. 逻辑删除一条记录
    userMapper.deleteById(1L);
    
    // 2. 普通查询(自动过滤已删除记录)
    User user = userMapper.selectById(1L);
    System.out.println("普通查询结果:" + user); // 应为null
    
    // 3. 需要查询包含已删除的记录
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("id", 1L);
    // 使用注解忽略逻辑删除条件
    queryWrapper.apply("@SqlParser(filter=true)");
    
    User deletedUser = userMapper.selectOne(queryWrapper);
    System.out.println("包含已删除记录的查询结果:" + deletedUser);
}

5.4 自动填充功能

自动填充功能已在2.4节中配置,这里展示更多使用场景:

@Data
@TableName("t_operation_log")
public class OperationLog {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String operation;
    
    // 创建时填充当前用户ID
    @TableField(fill = FieldFill.INSERT)
    private Long createUserId;
    
    // 创建时填充当前时间
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    // 更新时填充当前用户ID
    @TableField(fill = FieldFill.UPDATE)
    private Long updateUserId;
    
    // 创建或更新时都填充当前时间
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

// 实现自动填充处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    // 模拟获取当前用户ID
    private Long getCurrentUserId() {
        return 1001L; // 实际项目中从安全上下文中获取
    }
    
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createUserId", Long.class, getCurrentUserId());
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateUserId", Long.class, getCurrentUserId());
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
}

六、MyBatis Plus代码生成器

6.1 代码生成器配置

MyBatis Plus提供了强大的代码生成器,可以快速生成Entity、Mapper、Service、Controller等层代码。

6.1.1 添加代码生成器依赖
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
</dependency>

<!-- 模板引擎依赖 -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>
6.1.2 代码生成器示例
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&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "123456";
        String author = "YourName";
        String outputDir = "D://code//mp-generator";
        String basePackage = "com.example.demo";
        String moduleName = "system";
        
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
                    builder.author(author) // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir(outputDir); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent(basePackage) // 设置父包名
                            .moduleName(moduleName) // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, outputDir + "/mapper")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_user", "t_role") // 设置需要生成的表名
                            .addTablePrefix("t_") // 设置过滤表前缀
                            .entityBuilder()
                            .enableLombok() // 开启Lombok
                            .versionColumnName("version") // 乐观锁字段名
                            .versionPropertyName("version") // 乐观锁属性名
                            .logicDeleteColumnName("is_deleted") // 逻辑删除字段名
                            .logicDeletePropertyName("isDeleted") // 逻辑删除属性名
                            .enableTableFieldAnnotation() // 开启字段注解
                            .controllerBuilder()
                            .enableRestStyle() // 开启生成@RestController控制器
                            .serviceBuilder()
                            .formatServiceFileName("%sService") // 服务接口名格式
                            .formatServiceImplFileName("%sServiceImpl"); // 服务实现类名格式
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板
                .execute();
    }
}

6.2 生成代码结构说明

运行代码生成器后,会生成以下结构的代码:

D://code//mp-generator/
├── com/
│   └── example/
│       └── demo/
│           └── system/
│               ├── controller/    # Controller层
│               ├── entity/        # 实体类
│               ├── mapper/        # Mapper接口
│               ├── service/       # Service接口
│               └── impl/          # Service实现类
└── mapper/
    ├── UserMapper.xml             # Mapper XML文件
    └── RoleMapper.xml

6.3 自定义模板

如果需要自定义生成的代码模板,可以在resources目录下创建templates目录,并放置自定义模板文件:

resources/
└── templates/
    ├── controller.java.ftl        # Controller模板
    ├── entity.java.ftl            # 实体类模板
    ├── mapper.java.ftl            # Mapper接口模板
    ├── mapper.xml.ftl             # Mapper XML模板
    ├── service.java.ftl           # Service接口模板
    └── serviceImpl.java.ftl       # Service实现类模板

然后在代码生成器配置中指定自定义模板路径:

.templateConfig(builder -> {
    builder.controller("/templates/controller.java")
           .entity("/templates/entity.java")
           .mapper("/templates/mapper.java")
           .xml("/templates/mapper.xml")
           .service("/templates/service.java")
           .serviceImpl("/templates/serviceImpl.java");
})

七、MyBatis Plus性能优化

7.1 SQL性能分析插件

SQL性能分析插件可以输出SQL语句及其执行时间,帮助开发者优化SQL性能。

7.1.1 配置性能分析插件
// 在MybatisPlusConfig中添加性能分析插件
@Bean
@Profile({"dev", "test"}) // 只在dev和test环境生效
public MybatisPlusPlugin mybatisPlusPlugin() {
    MybatisPlusPlugin interceptor = new MybatisPlusPlugin();
    // 性能分析插件
    interceptor.addInnerInterceptor(new PerformanceInnerInterceptor()
            .setMaxTime(1000) // 设置SQL执行最大时间,超过自动抛出异常(ms)
            .setFormat(true)); // 是否格式化SQL
    return interceptor;
}
7.1.2 性能分析输出示例

执行SQL时,控制台会输出类似信息:

Time:23 ms - ID:com.example.demo.mapper.UserMapper.selectById
Execute SQL:
    SELECT 
        id,username,password,age,email,create_time,update_time,version,is_deleted 
    FROM 
        t_user 
    WHERE 
        id=1 AND is_deleted=0

7.2 批量操作优化

7.2.1 批量插入优化

MyBatis Plus提供了saveBatch方法进行批量插入,但默认实现是循环单条插入。可以通过以下方式优化:

  1. 在application.yml中配置批量操作的rewriteBatchedStatements参数:
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mp_demo?rewriteBatchedStatements=true
  1. 使用自定义批量插入方法:
public interface UserMapper extends BaseMapper<User> {
    // 自定义批量插入方法
    int insertBatchSomeColumn(List<User> userList);
}

对应的XML映射文件:

<insert id="insertBatchSomeColumn">
    INSERT INTO t_user (username, password, age, email) 
    VALUES 
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.password}, #{item.age}, #{item.email})
    </foreach>
</insert>
7.2.2 批量更新优化

对于批量更新,可以使用updateBatchById方法:

// 测试批量更新
@Test
public void testUpdateBatch() {
    // 查询一批用户
    List<User> userList = userMapper.selectList(null);
    
    // 修改这批用户的信息
    userList.forEach(user -> {
        user.setEmail("batch-update@example.com");
        user.setAge(user.getAge() + 1);
    });
    
    // 批量更新
    boolean result = userService.updateBatchById(userList);
    System.out.println("批量更新结果:" + result);
}

7.3 缓存优化

虽然MyBatis本身提供了一级缓存和二级缓存,但在实际项目中,更推荐使用专门的缓存解决方案如Redis。

7.3.1 整合Redis缓存
  1. 添加Redis依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置Redis:
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
  1. 实现缓存服务:
@Service
public class UserCacheService {
    
    private final UserMapper userMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    
    private static final String USER_CACHE_PREFIX = "user:";
    private static final long CACHE_EXPIRE_SECONDS = 3600;
    
    public UserCacheService(UserMapper userMapper, RedisTemplate<String, Object> redisTemplate) {
        this.userMapper = userMapper;
        this.redisTemplate = redisTemplate;
    }
    
    public User getUserById(Long id) {
        String cacheKey = USER_CACHE_PREFIX + id;
        
        // 先从缓存获取
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user != null) {
            return user;
        }
        
        // 缓存不存在,查询数据库
        user = userMapper.selectById(id);
        if (user != null) {
            // 放入缓存
            redisTemplate.opsForValue().set(cacheKey, user, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
        }
        
        return user;
    }
    
    public boolean updateUser(User user) {
        // 更新数据库
        int result = userMapper.updateById(user);
        if (result > 0) {
            // 更新缓存
            String cacheKey = USER_CACHE_PREFIX + user.getId();
            redisTemplate.opsForValue().set(cacheKey, user, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);
            return true;
        }
        return false;
    }
    
    public boolean deleteUser(Long id) {
        // 删除数据库记录
        int result = userMapper.deleteById(id);
        if (result > 0) {
            // 删除缓存
            String cacheKey = USER_CACHE_PREFIX + id;
            redisTemplate.delete(cacheKey);
            return true;
        }
        return false;
    }
}

八、MyBatis Plus实战案例

8.1 多租户架构实现

多租户(Multi-Tenancy)是一种软件架构技术,可以让多个租户共用相同的系统组件,但数据相互隔离。

8.1.1 多租户方案选择

常见的多租户数据隔离方案有:

  1. 独立数据库:每个租户使用独立的数据库
  2. 共享数据库,独立Schema:同一个数据库,不同租户使用不同Schema
  3. 共享数据库,共享Schema:通过字段区分租户(最常用)

MyBatis Plus主要支持第三种方案,通过TenantLineInnerInterceptor实现。

8.1.2 多租户实现步骤
  1. 添加多租户拦截器:
// 在MybatisPlusConfig中添加多租户插件
@Bean
public MybatisPlusPlugin mybatisPlusPlugin() {
    MybatisPlusPlugin interceptor = new MybatisPlusPlugin();
    // 多租户插件
    interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
        // 获取租户ID
        @Override
        public Expression getTenantId() {
            // 实际项目中从上下文中获取租户ID
            return new LongValue(1L);
        }
        
        // 租户字段名
        @Override
        public String getTenantIdColumn() {
            return "tenant_id";
        }
        
        // 忽略多租户的表(可选)
        @Override
        public boolean ignoreTable(String tableName) {
            return "t_tenant".equalsIgnoreCase(tableName);
        }
    }));
    return interceptor;
}
  1. 实体类添加租户字段:
@Data
@TableName("t_user")
public class User {
    // ...其他字段
    
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
  1. 自动填充租户ID:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 模拟获取当前租户ID
    private Long getCurrentTenantId() {
        return 1L; // 实际项目中从安全上下文中获取
    }
    
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "tenantId", Long.class, getCurrentTenantId());
    }
}
8.1.3 多租户测试
// 测试多租户
@Test
public void testTenant() {
    // 插入数据会自动填充tenant_id
    User user = new User();
    user.setUsername("tenant_test");
    userMapper.insert(user);
    
    // 查询会自动添加租户条件
    User tenantUser = userMapper.selectById(user.getId());
    System.out.println("租户ID:" + tenantUser.getTenantId());
    
    // 手动查询SQL日志可以看到自动添加了tenant_id条件
    // SELECT ... FROM t_user WHERE id=? AND tenant_id=1
}

8.2 动态表名处理

在某些场景下,可能需要根据参数动态选择表名,MyBatis Plus提供了动态表名插件。

8.2.1 配置动态表名插件
// 在MybatisPlusConfig中添加动态表名插件
@Bean
public MybatisPlusPlugin mybatisPlusPlugin() {
    MybatisPlusPlugin interceptor = new MybatisPlusPlugin();
    // 动态表名插件
    interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor(new HashMap<String, TableNameHandler>() {{
        // 为t_user表设置动态表名处理器
        put("t_user", (sql, tableName) -> {
            // 模拟根据年份动态选择表名
            String year = LocalDate.now().getYear() + "";
            return "t_user_" + year; // 返回实际表名
        });
    }}));
    return interceptor;
}
8.2.2 动态表名测试
// 测试动态表名
@Test
public void testDynamicTable() {
    // 实际执行的SQL会是: SELECT * FROM t_user_2023 WHERE ...
    List<User> userList = userMapper.selectList(null);
    userList.forEach(System.out::println);
}

8.3 数据权限控制

数据权限是系统常见需求,MyBatis Plus可以通过自定义拦截器实现。

8.3.1 数据权限拦截器实现
public class DataPermissionInterceptor implements InnerInterceptor {
    
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, 
                          RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 获取原始SQL
        String originalSql = boundSql.getSql();
        
        // 获取当前用户数据权限(实际项目中从上下文中获取)
        DataPermission dataPermission = getCurrentDataPermission();
        
        // 修改SQL添加数据权限条件
        String newSql = addDataPermissionCondition(originalSql, dataPermission);
        
        // 重置BoundSql的SQL
        resetSql(ms, boundSql, newSql);
    }
    
    private DataPermission getCurrentDataPermission() {
        // 模拟返回当前用户的数据权限
        DataPermission permission = new DataPermission();
        permission.setDeptIds(Arrays.asList(101L, 102L, 103L));
        permission.setUserId(1001L);
        permission.setRole("manager");
        return permission;
    }
    
    private String addDataPermissionCondition(String sql, DataPermission permission) {
        // 简单示例:为查询添加部门限制
        if (sql.contains("WHERE")) {
            return sql + " AND dept_id IN (" + 
                   permission.getDeptIds().stream()
                           .map(String::valueOf)
                           .collect(Collectors.joining(",")) + ")";
        } else {
            return sql + " WHERE dept_id IN (" + 
                   permission.getDeptIds().stream()
                           .map(String::valueOf)
                           .collect(Collectors.joining(",")) + ")";
        }
    }
    
    private void resetSql(MappedStatement ms, BoundSql boundSql, String newSql) {
        // 通过反射修改BoundSql的sql字段
        try {
            Field field = BoundSql.class.getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, newSql);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

@Data
class DataPermission {
    private List<Long> deptIds;
    private Long userId;
    private String role;
}
8.3.2 注册数据权限拦截器
// 在MybatisPlusConfig中添加数据权限插件
@Bean
public MybatisPlusPlugin mybatisPlusPlugin() {
    MybatisPlusPlugin interceptor = new MybatisPlusPlugin();
    // 数据权限插件
    interceptor.addInnerInterceptor(new DataPermissionInterceptor());
    return interceptor;
}
8.3.3 数据权限测试
// 测试数据权限
@Test
public void testDataPermission() {
    // 实际执行的SQL会自动添加数据权限条件
    List<User> userList = userMapper.selectList(null);
    userList.forEach(System.out::println);
    
    // 日志输出示例:
    // SELECT * FROM t_user WHERE dept_id IN (101,102,103)
}

九、MyBatis Plus最佳实践

9.1 项目结构规范

推荐的项目结构如下:

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── demo/
│   │               ├── config/        # 配置类
│   │               ├── controller/    # 控制器
│   │               ├── entity/        # 实体类
│   │               ├── enums/         # 枚举类
│   │               ├── mapper/        # Mapper接口
│   │               ├── service/       # 服务接口
│   │               ├── impl/          # 服务实现
│   │               ├── util/          # 工具类
│   │               └── DemoApplication.java # 启动类
│   └── resources/
│       ├── mapper/       # XML映射文件
│       ├── static/       # 静态资源
│       ├── templates/    # 模板文件
│       ├── application.yml # 配置文件
│       └── banner.txt    # 启动banner
└── test/                # 测试代码

9.2 事务管理

MyBatis Plus本身不提供事务管理,需要结合Spring的事务机制使用:

@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean transfer(Long fromUserId, Long toUserId, BigDecimal amount) {
        // 1. 查询转出账户
        User fromUser = getById(fromUserId);
        if (fromUser.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("余额不足");
        }
        
        // 2. 查询转入账户
        User toUser = getById(toUserId);
        
        // 3. 更新转出账户余额
        fromUser.setBalance(fromUser.getBalance().subtract(amount));
        updateById(fromUser);
        
        // 4. 更新转入账户余额
        toUser.setBalance(toUser.getBalance().add(amount));
        updateById(toUser);
        
        return true;
    }
}

9.3 异常处理

统一异常处理示例:

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e) {
        logger.error("业务异常: {}", e.getMessage(), e);
        return Result.fail(e.getCode(), e.getMessage());
    }
    
    /**
     * 处理系统异常
     */
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        logger.error("系统异常: {}", e.getMessage(), e);
        return Result.fail(500, "系统繁忙,请稍后再试");
    }
    
    /**
     * 处理MyBatis Plus异常
     */
    @ExceptionHandler(MybatisPlusException.class)
    public Result<Void> handleMybatisPlusException(MybatisPlusException e) {
        logger.error("MyBatis Plus异常: {}", e.getMessage(), e);
        return Result.fail(400, "数据库操作失败: " + e.getMessage());
    }
}

@Data
class Result<T> {
    private int code;
    private String msg;
    private T data;
    
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMsg("成功");
        result.setData(data);
        return result;
    }
    
    public static <T> Result<T> fail(int code, String msg) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

9.4 日志记录

建议的日志配置:

logging:
  level:
    root: info
    com.example.demo: debug
    com.baomidou.mybatisplus: warn
  file:
    name: logs/application.log
  pattern:
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}) - %msg%n"

9.5 安全建议

  1. 防止SQL注入

    • 始终使用条件构造器而非字符串拼接
    • 避免直接使用@Select("${sql}")这种形式
    • 使用MP内置的SQL注入剥离器
  2. 敏感数据保护

    • 密码等敏感字段使用加密存储
    • 查询结果中排除敏感字段
// 敏感字段处理示例
@Data
@TableName("t_user")
public class User {
    @TableId
    private Long id;
    private String username;
    
    @TableField(select = false) // 查询时排除密码字段
    private String password;
    
    // 密码加密存储
    public void setPassword(String password) {
        this.password = DigestUtils.md5DigestAsHex(password.getBytes());
    }
}

十、MyBatis Plus扩展与集成

10.1 与Spring Security集成

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/public/**").permitAll()
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .logout()
            .and()
            .csrf().disable();
    }
}

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    private final UserMapper userMapper;
    
    public UserDetailsServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 使用MyBatis Plus查询用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getUsername, username));
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        
        // 查询用户角色(假设有t_role表)
        List<String> roles = userMapper.selectRolesByUserId(user.getId());
        
        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .roles(roles.toArray(new String[0]))
                .build();
    }
}

10.2 与Swagger集成

  1. 添加Swagger依赖:
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
  1. 配置Swagger:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo());
    }
    
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("MyBatis Plus Demo API")
                .description("MyBatis Plus集成示例")
                .version("1.0")
                .build();
    }
}
  1. 实体类添加Swagger注解:
@Data
@ApiModel("用户实体")
@TableName("t_user")
public class User {
    @ApiModelProperty("主键ID")
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    @ApiModelProperty("用户名")
    private String username;
    
    @ApiModelProperty(value = "密码", hidden = true)
    private String password;
    
    @ApiModelProperty("年龄")
    private Integer age;
    
    @ApiModelProperty("邮箱")
    private String email;
}

10.3 与Redis缓存集成

  1. 配置Redis缓存:
@Configuration
@EnableCaching
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson2JsonRedisSerializer序列化value
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(om);
        
        // 使用StringRedisSerializer序列化key
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1)) // 设置缓存有效期1小时
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();
        
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }
}
  1. 在Service中使用缓存:
@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    @Override
    @Cacheable(key = "#id")
    public User getByIdWithCache(Long id) {
        return getById(id);
    }
    
    @Override
    @CachePut(key = "#user.id")
    public User updateWithCache(User user) {
        updateById(user);
        return user;
    }
    
    @Override
    @CacheEvict(key = "#id")
    public boolean removeByIdWithCache(Long id) {
        return removeById(id);
    }
}

十一、MyBatis Plus常见问题与解决方案

11.1 常见问题汇总

问题描述可能原因解决方案
插入数据时主键未回填1. 主键策略配置错误
2. 实体类未加@TableId注解
1. 检查id-type配置
2. 添加@TableId注解
字段值为null未更新未开启null值更新在@TableField注解中添加update属性或配置全局策略
分页查询不生效未配置分页插件添加PaginationInnerInterceptor
逻辑删除不生效1. 未配置逻辑删除字段
2. 实体类字段未加@TableLogic
1. 检查全局配置
2. 添加注解
自动填充不工作1. 未实现MetaObjectHandler
2. 字段未加@TableField
1. 实现接口并注册为Bean
2. 添加注解
多租户不生效1. 未配置多租户插件
2. 实体类未加租户字段
1. 添加TenantLineInnerInterceptor
2. 添加租户字段

11.2 性能优化建议

  1. 批量操作

    • 使用saveBatchupdateBatchById等方法
    • 配置rewriteBatchedStatements=true
  2. 查询优化

    • 只查询需要的字段(使用select方法指定字段)
    • 合理使用索引
    • 避免全表扫描
  3. 缓存策略

    • 对热点数据使用二级缓存
    • 考虑集成Redis等分布式缓存
  4. 连接池配置

    • 使用高性能连接池如HikariCP
    • 合理配置连接池参数
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
      idle-timeout: 30000
      max-lifetime: 1800000
      connection-timeout: 30000

11.3 复杂SQL处理

对于复杂的多表关联查询,建议:

  1. 使用自定义SQL:
public interface UserMapper extends BaseMapper<User> {
    @Select("SELECT u.*, d.name AS deptName FROM t_user u LEFT JOIN t_dept d ON u.dept_id = d.id WHERE u.id = #{id}")
    User selectUserWithDept(Long id);
}
  1. 使用XML映射文件:
<select id="selectUserWithRoles" resultMap="userWithRoles">
    SELECT u.*, r.id AS role_id, r.name AS role_name
    FROM t_user u
    LEFT JOIN t_user_role ur ON u.id = ur.user_id
    LEFT JOIN t_role r ON ur.role_id = r.id
    WHERE u.id = #{id}
</select>

<resultMap id="userWithRoles" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 其他字段 -->
    <collection property="roles" ofType="Role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
    </collection>
</resultMap>
  1. 使用MyBatis Plus的JOIN功能(需要额外依赖):
<dependency>
    <groupId>com.github.yulichang</groupId>
    <artifactId>mybatis-plus-join</artifactId>
    <version>1.2.4</version>
</dependency>

使用示例:

public interface UserMapper extends MPJBaseMapper<User> {
    // 可以直接使用join方法
}

// 查询示例
@Test
public void testJoin() {
    List<UserDTO> userList = userMapper.selectJoinList(UserDTO.class,
        new MPJLambdaWrapper<User>()
            .selectAll(User.class)
            .select(Role::getName)
            .leftJoin(Role.class, Role::getId, User::getRoleId)
            .eq(User::getId, 1L));
}

十二、MyBatis Plus源码解析

12.1 核心架构分析

MyBatis Plus的核心架构可以分为以下几层:

  1. 接口层:提供BaseMapper、IService等接口
  2. 核心层:实现CRUD操作、条件构造器等核心功能
  3. 扩展层:提供代码生成器、分页插件等扩展功能
  4. 插件层:通过拦截器实现各种增强功能
«interface»
BaseMapper
+insert(T entity) : int
+deleteById(Serializable id) : int
+updateById(T entity) : int
+selectById(Serializable id) : T
«interface»
IService
+save(T entity) : boolean
+saveBatch(Collection entityList) : boolean
+getById(Serializable id) : T
ServiceImpl
+getBaseMapper() : BaseMapper
+save(T entity) : boolean
AbstractWrapper
+eq(String column, Object val) : Children
+like(String column, String val) : Children
MybatisPlusInterceptor
+addInnerInterceptor(InnerInterceptor interceptor) : void
«interface»
InnerInterceptor
+beforeQuery(Executor executor, ...) : void
+beforeUpdate(Executor executor, ...) : void
UserMapper
UserService
QueryWrapper

12.2 关键流程解析

12.2.1 SQL执行流程
  1. 调用Mapper方法:如userMapper.selectById(1L)
  2. MyBatis代理执行:通过动态代理调用MyBatis的SqlSession
  3. 拦截器链处理:经过MyBatis Plus的拦截器链
  4. SQL解析与改写:如分页、多租户等插件会改写SQL
  5. 执行原始SQL:最终由MyBatis执行JDBC操作
  6. 结果集处理:将ResultSet转换为实体对象
12.2.2 自动注入机制

MyBatis Plus通过MybatisMapperAnnotationBuilder在启动时自动注入CRUD方法:

  1. 扫描Mapper接口
  2. 解析泛型类型获取实体类
  3. 根据实体类字段信息生成对应的SQL方法
  4. 注册到MyBatis的MappedStatement中

12.3 插件机制详解

MyBatis Plus的插件都实现了InnerInterceptor接口,通过责任链模式执行:

public class MybatisPlusInterceptor implements Interceptor {
    private List<InnerInterceptor> interceptors = new ArrayList<>();
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 创建执行器链
        Executor executor = (Executor) invocation.getTarget();
        for (InnerInterceptor interceptor : interceptors) {
            executor = interceptor.plugin(executor);
        }
        return executor.execute(...);
    }
}

主要插件包括:

  • PaginationInnerInterceptor:分页插件
  • OptimisticLockerInnerInterceptor:乐观锁插件
  • TenantLineInnerInterceptor:多租户插件
  • DynamicTableNameInnerInterceptor:动态表名插件
  • IllegalSQLInnerInterceptor:SQL注入防护插件

十三、MyBatis Plus未来发展与生态

13.1 最新特性介绍

MyBatis Plus持续更新中,最新版本增加的功能包括:

  1. Lambda链式调用

    // 链式Lambda查询
    userMapper.lambdaQuery()
        .eq(User::getUsername, "张三")
        .gt(User::getAge, 18)
        .list();
    
    // 链式Lambda更新
    userMapper.lambdaUpdate()
        .eq(User::getUsername, "张三")
        .set(User::getAge, 20)
        .update();
    
  2. 增强的代码生成器

    • 支持更多模板引擎
    • 支持自定义模板
    • 支持多模块生成
  3. 新的插件系统

    • 更灵活的插件配置
    • 更强大的拦截能力

13.2 生态整合

MyBatis Plus可以与其他流行框架无缝整合:

  1. Spring Boot Starter:开箱即用的自动配置
  2. Spring Cloud:分布式环境下表现良好
  3. Dubbo:作为数据访问层服务
  4. 各种连接池:如HikariCP、Druid等
  5. 多种数据库:MySQL、PostgreSQL、Oracle等

13.3 社区资源

  1. 官方文档https://baomidou.com/
  2. GitHub仓库https://github.com/baomidou/mybatis-plus
  3. Gitee仓库https://gitee.com/baomidou/mybatis-plus
  4. 社区论坛:官方QQ群、钉钉群等

十四、总结与展望

MyBatis Plus作为MyBatis的增强工具,极大地简化了数据库操作代码的编写,提高了开发效率。通过本文的系统介绍,我们从基础配置到高级特性,从核心原理到实战应用,全面了解了MyBatis Plus的强大功能。

未来,MyBatis Plus可能会在以下方面继续发展:

  1. 更智能的代码生成
  2. 更强大的动态SQL支持
  3. 更好的云原生支持
  4. 更丰富的插件生态

无论是新项目还是老项目改造,MyBatis Plus都是一个值得考虑的持久层解决方案。希望本文能帮助读者更好地理解和使用MyBatis Plus,在实际项目中发挥它的最大价值。

喜欢的点个关注,想了解更多的可以关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clf丶忆笙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值