36-MyBatis-Plus详解

0 阅读19分钟

MyBatis-Plus详解

本章导读

MyBatis-Plus 是 MyBatis 的增强工具,在保持 MyBatis 灵活性的同时,提供了强大的 CRUD 操作、条件构造器、代码生成器等功能。本章系统讲解 MyBatis-Plus 的核心特性,从基础配置到高级用法,帮助你大幅提升数据访问层的开发效率。

学习目标

  • 目标1:掌握 MyBatis-Plus 的核心注解和 BaseMapper 的使用方法
  • 目标2:熟练使用 Lambda 条件构造器实现复杂查询
  • 目标3:理解分页插件、乐观锁、逻辑删除等高级特性并正确应用

前置知识:MyBatis 核心原理、Lambda 表达式

阅读时长:约 50 分钟

一、知识概述

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。它提供了通用的 CRUD 操作、强大的条件构造器、代码生成器、分页插件等功能,大大简化了数据访问层的开发工作。

本文将深入讲解 MyBatis-Plus 的核心功能、原理和最佳实践,帮助你快速掌握这个强大的 ORM 增强框架。

二、快速入门

2.1 引入依赖

<!-- Maven 依赖 -->
<dependencies>
    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.5</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>
        <optional>true</optional>
    </dependency>
</dependencies>

2.2 配置文件

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

# MyBatis-Plus 配置
mybatis-plus:
  # Mapper XML 文件位置
  mapper-locations: classpath*:/mapper/**/*.xml
  # 实体类扫描路径
  type-aliases-package: com.example.entity
  # 配置
  configuration:
    # 开启驼峰命名转换
    map-underscore-to-camel-case: true
    # 日志输出
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  # 全局配置
  global-config:
    db-config:
      # 主键策略
      id-type: auto
      # 逻辑删除字段
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

2.3 快速使用

/**
 * MyBatis-Plus 快速入门示例
 */
@SpringBootApplication
@MapperScan("com.example.mapper")
public class MyBatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisPlusApplication.class, args);
    }
}

/**
 * 实体类
 */
@Data
@TableName("sys_user")  // 指定表名
public class User {
    
    @TableId(type = IdType.AUTO)  // 主键自增
    private Long id;
    
    @TableField("username")  // 指定字段名
    private String username;
    
    private String email;
    
    private Integer age;
    
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)  // 自动填充
    private Date createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
    @TableLogic  // 逻辑删除
    @TableField("is_deleted")
    private Integer deleted;
    
    @Version  // 乐观锁
    private Integer version;
    
    @TableField(exist = false)  // 非数据库字段
    private String extra;
}

/**
 * Mapper 接口 - 继承 BaseMapper 即可获得 CRUD 能力
 */
public interface UserMapper extends BaseMapper<User> {
    // BaseMapper 已提供:
    // - insert(T entity)
    // - deleteById(Serializable id)
    // - updateById(T entity)
    // - selectById(Serializable id)
    // - selectList(Wrapper<T> wrapper)
    // - selectPage(Page<T> page, Wrapper<T> wrapper)
    // ... 等十余个通用方法
}

/**
 * Service 接口 - 继承 IService
 */
public interface UserService extends IService<User> {
    // IService 提供了更丰富的方法
}

/**
 * Service 实现
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 
        implements UserService {
    // 无需手动编写 CRUD 方法
}

/**
 * 测试使用
 */
@SpringBootTest
public class QuickStartTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testInsert() {
        User user = new User();
        user.setUsername("zhangsan");
        user.setEmail("zhangsan@example.com");
        user.setAge(25);
        
        // 插入
        int rows = userMapper.insert(user);
        System.out.println("插入行数: " + rows);
        System.out.println("自增ID: " + user.getId());
    }
    
    @Test
    public void testSelect() {
        // 根据 ID 查询
        User user = userMapper.selectById(1L);
        System.out.println(user);
        
        // 查询所有
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
        
        // 条件查询
        List<User> list = userMapper.selectList(
            new LambdaQueryWrapper<User>()
                .eq(User::getStatus, 1)
                .like(User::getUsername, "张")
                .orderByDesc(User::getCreateTime)
        );
    }
    
    @Test
    public void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setAge(26);
        
        // 根据 ID 更新(只更新非空字段)
        int rows = userMapper.updateById(user);
        System.out.println("更新行数: " + rows);
    }
    
    @Test
    public void testDelete() {
        // 根据 ID 删除(逻辑删除)
        int rows = userMapper.deleteById(1L);
        System.out.println("删除行数: " + rows);
    }
}

三、核心注解详解

3.1 @TableName - 表名注解

/**
 * @TableName - 指定实体对应的表名
 */
@Data
// 方式1:直接指定表名
@TableName("sys_user")
public class User1 {
    private Long id;
    private String username;
}

// 方式2:使用变量(支持动态表名)
@TableName("${prefix}_user")
public class User2 {
    private Long id;
    private String username;
}

// 方式3:指定 schema
@TableName(value = "user", schema = "mydb")
public class User3 {
    private Long id;
    private String username;
}

// 方式4:设置自动映射
@TableName(value = "sys_user", autoResultMap = true)
public class User4 {
    private Long id;
    private String username;
    
    // autoResultMap = true 时,可以使用 TypeHandler
    @TableField(typeHandler = JacksonTypeHandler.class)
    private Map<String, Object> extra;
}

/**
 * 动态表名处理器
 */
@Component
public class DynamicTableNameHandler implements TableNameHandler {
    
    @Override
    public String dynamicTableName(String sql, String tableName) {
        // 根据上下文动态设置表名
        String suffix = TenantContext.getTenantSuffix();
        if (StringUtils.hasText(suffix)) {
            return tableName + "_" + suffix;
        }
        return tableName;
    }
}

// 配置动态表名
@Configuration
public class MyBatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        DynamicTableNameInnerInterceptor dynamicInterceptor = 
            new DynamicTableNameInnerInterceptor();
        dynamicInterceptor.setTableNameHandler((sql, tableName) -> {
            // 动态表名逻辑
            if ("sys_user".equals(tableName)) {
                return "sys_user_" + TenantContext.getTenantId();
            }
            return tableName;
        });
        
        interceptor.addInnerInterceptor(dynamicInterceptor);
        return interceptor;
    }
}

3.2 @TableId - 主键注解

/**
 * @TableId - 主键策略
 */
public class TableIdExamples {
    
    /**
     * IdType 枚举值
     */
    public enum IdType {
        AUTO,           // 数据库自增
        NONE,           // 无状态(默认)
        INPUT,          // 用户输入
        ASSIGN_ID,      // 雪花算法生成 Long 类型 ID
        ASSIGN_UUID     // UUID
    }
    
    @Data
    @TableName("user")
    public static class UserAuto {
        @TableId(type = IdType.AUTO)  // 数据库自增
        private Long id;
    }
    
    @Data
    @TableName("user")
    public static class UserAssignId {
        @TableId(type = IdType.ASSIGN_ID)  // 雪花算法(默认策略)
        private Long id;
    }
    
    @Data
    @TableName("user")
    public static class UserAssignUUID {
        @TableId(type = IdType.ASSIGN_UUID)  // UUID(去掉横线)
        private String id;
    }
    
    @Data
    @TableName("user")
    public static class UserInput {
        @TableId(type = IdType.INPUT)  // 用户输入(需要手动设置 ID)
        private Long id;
    }
}

/**
 * 自定义 ID 生成器
 */
@Component
public class CustomIdGenerator implements IdentifierGenerator {
    
    @Override
    public Number nextId(Object entity) {
        // 自定义 ID 生成逻辑
        // 可以使用雪花算法、数据库序列等
        return SnowflakeIdWorker.nextId();
    }
    
    @Override
    public String nextUUID(Object entity) {
        // 自定义 UUID 生成
        return UUID.randomUUID().toString().replace("-", "");
    }
}

3.3 @TableField - 字段注解

/**
 * @TableField - 字段映射
 */
public class TableFieldExamples {
    
    @Data
    @TableName("sys_user")
    public static class User {
        
        @TableId
        private Long id;
        
        // 指定数据库字段名
        @TableField("user_name")
        private String username;
        
        // 非数据库字段
        @TableField(exist = false)
        private String extraField;
        
        // 自动填充 - 插入时填充
        @TableField(fill = FieldFill.INSERT)
        private Date createTime;
        
        // 自动填充 - 插入和更新时填充
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private Date updateTime;
        
        // 自动填充 - 更新时填充
        @TableField(fill = FieldFill.UPDATE)
        private Date modifyTime;
        
        // 条件更新策略
        @TableField(updateStrategy = FieldStrategy.NOT_EMPTY)
        private String email;
        
        // 条件查询策略
        @TableField(whereStrategy = FieldStrategy.NOT_NULL)
        private Integer status;
        
        // 使用 TypeHandler
        @TableField(typeHandler = JacksonTypeHandler.class)
        private Map<String, Object> config;
        
        // 指定 JDBC 类型
        @TableField(jdbcType = JdbcType.VARCHAR)
        private String description;
        
        // 指定数值精度
        @TableField(numericScale = 2)
        private BigDecimal amount;
    }
    
    /**
     * 字段策略
     */
    public enum FieldStrategy {
        NOT_NULL,     // 非 NULL 判断
        NOT_EMPTY,    // 非空判断(仅针对字符串类型,排除空串)
        DEFAULT,      // 默认(跟随全局配置)
        IGNORED,      // 忽略判断
        NEVER         // 从不加入 SQL
    }
    
    /**
     * 自动填充处理器
     */
    @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());
            this.strictInsertFill(metaObject, "createBy", String.class, 
                                   getCurrentUserId());
            this.strictInsertFill(metaObject, "version", Integer.class, 1);
        }
        
        @Override
        public void updateFill(MetaObject metaObject) {
            // 更新时自动填充
            this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
            this.strictUpdateFill(metaObject, "updateBy", String.class, 
                                  getCurrentUserId());
        }
        
        private String getCurrentUserId() {
            return SecurityContextHolder.getContext()
                .getAuthentication().getName();
        }
    }
}

3.4 其他核心注解

/**
 * 其他常用注解
 */
public class OtherAnnotations {
    
    /**
     * @Version - 乐观锁
     */
    @Data
    @TableName("product")
    public static class Product {
        @TableId
        private Long id;
        
        private String name;
        
        private Integer stock;
        
        @Version  // 乐观锁版本号
        private Integer version;
    }
    
    /**
     * @TableLogic - 逻辑删除
     */
    @Data
    @TableName("user")
    public static class User {
        @TableId
        private Long id;
        
        private String username;
        
        @TableLogic
        private Integer deleted;  // 0-未删除,1-已删除
    }
    
    // 可以全局配置
    // mybatis-plus.global-config.db-config.logic-delete-field=deleted
    // mybatis-plus.global-config.db-config.logic-delete-value=1
    // mybatis-plus.global-config.db-config.logic-not-delete-value=0
    
    /**
     * @KeySequence - 序列主键(Oracle)
     */
    @Data
    @KeySequence("SEQ_USER")  // Oracle 序列名
    @TableName("SYS_USER")
    public static class OracleUser {
        @TableId(type = IdType.INPUT)
        private Long id;
        
        private String username;
    }
    
    /**
     * @InterceptorIgnore - 忽略拦截器
     */
    public interface UserMapper extends BaseMapper<User> {
        
        // 忽略租户拦截器
        @InterceptorIgnore(tenantLine = "true")
        List<User> selectAllIgnoreTenant();
        
        // 忽略数据权限
        @InterceptorIgnore(dataPermission = "true")
        List<User> selectAllIgnorePermission();
    }
}

四、条件构造器

4.1 QueryWrapper 基础

/**
 * QueryWrapper - 条件构造器基础用法
 */
@SpringBootTest
public class QueryWrapperTest {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 基础比较操作
     */
    @Test
    public void testBasicConditions() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // 等于
        wrapper.eq("username", "zhangsan");
        // SQL: WHERE username = 'zhangsan'
        
        // 不等于
        wrapper.ne("status", 0);
        // SQL: AND status <> 0
        
        // 大于 / 大于等于
        wrapper.gt("age", 18);
        wrapper.ge("age", 18);
        // SQL: AND age > 18, AND age >= 18
        
        // 小于 / 小于等于
        wrapper.lt("age", 60);
        wrapper.le("age", 60);
        // SQL: AND age < 60, AND age <= 60
        
        // BETWEEN
        wrapper.between("age", 20, 30);
        // SQL: AND age BETWEEN 20 AND 30
        
        // NOT BETWEEN
        wrapper.notBetween("age", 10, 20);
        // SQL: AND age NOT BETWEEN 10 AND 20
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * 模糊查询
     */
    @Test
    public void testLikeConditions() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // LIKE '%keyword%'
        wrapper.like("username", "张");
        // SQL: WHERE username LIKE '%张%'
        
        // NOT LIKE '%keyword%'
        wrapper.notLike("email", "test");
        // SQL: AND email NOT LIKE '%test%'
        
        // LIKE 'keyword%'
        wrapper.likeRight("username", "张");
        // SQL: AND username LIKE '张%'
        
        // LIKE '%keyword'
        wrapper.likeLeft("username", "三");
        // SQL: AND username LIKE '%三'
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * 范围查询
     */
    @Test
    public void testInConditions() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // IN
        wrapper.in("status", 1, 2, 3);
        // SQL: WHERE status IN (1, 2, 3)
        
        // IN (集合)
        List<Integer> statusList = Arrays.asList(1, 2, 3);
        wrapper.in("status", statusList);
        
        // NOT IN
        wrapper.notIn("age", 18, 19, 20);
        // SQL: AND age NOT IN (18, 19, 20)
        
        // IN SQL (子查询)
        wrapper.inSql("dept_id", "SELECT id FROM dept WHERE level = 1");
        // SQL: AND dept_id IN (SELECT id FROM dept WHERE level = 1)
        
        // NOT IN SQL
        wrapper.notInSql("id", "SELECT user_id FROM blacklist");
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * NULL 判断
     */
    @Test
    public void testNullConditions() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // IS NULL
        wrapper.isNull("email");
        // SQL: WHERE email IS NULL
        
        // IS NOT NULL
        wrapper.isNotNull("phone");
        // SQL: AND phone IS NOT NULL
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * 排序
     */
    @Test
    public void testOrderBy() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // ORDER BY
        wrapper.orderByAsc("create_time");
        // SQL: ORDER BY create_time ASC
        
        wrapper.orderByDesc("age");
        // SQL: ORDER BY age DESC
        
        // 多字段排序
        wrapper.orderByAsc("status", "create_time");
        
        // 条件排序
        wrapper.orderBy(true, false, "id");  // 升序
        wrapper.orderBy(true, true, "id");   // 降序
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * 分组
     */
    @Test
    public void testGroupBy() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // GROUP BY
        wrapper.select("status", "COUNT(*) as count")
               .groupBy("status");
        // SQL: SELECT status, COUNT(*) as count FROM user GROUP BY status
        
        // HAVING
        wrapper.having("COUNT(*) > {0}", 5);
        // SQL: HAVING COUNT(*) > 5
        
        List<Map<String, Object>> result = userMapper.selectMaps(wrapper);
    }
}

4.2 LambdaQueryWrapper

/**
 * LambdaQueryWrapper - 使用 Lambda 表达式,避免硬编码字段名
 */
@SpringBootTest
public class LambdaQueryWrapperTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    public void testLambda() {
        // 使用 Lambda 避免字段名硬编码
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 基础条件
        wrapper.eq(User::getStatus, 1)
               .like(User::getUsername, "张")
               .between(User::getAge, 20, 30)
               .orderByDesc(User::getCreateTime);
        
        // 对应 SQL:
        // SELECT * FROM user 
        // WHERE status = 1 
        //   AND username LIKE '%张%'
        //   AND age BETWEEN 20 AND 30
        // ORDER BY create_time DESC
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * 条件构造 - 动态条件
     */
    @Test
    public void testDynamicCondition() {
        UserQuery query = new UserQuery();
        query.setUsername("张");
        query.setMinAge(20);
        query.setMaxAge(30);
        query.setStatus(1);
        
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 条件为 null 或空字符串时不加入 SQL
        wrapper.like(StringUtils.hasText(query.getUsername()), 
                     User::getUsername, query.getUsername())
               .ge(query.getMinAge() != null, User::getAge, query.getMinAge())
               .le(query.getMaxAge() != null, User::getAge, query.getMaxAge())
               .eq(query.getStatus() != null, User::getStatus, query.getStatus());
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * OR 条件
     */
    @Test
    public void testOrCondition() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 默认使用 AND 连接
        wrapper.eq(User::getStatus, 1)
               .eq(User::getAge, 25);
        // SQL: WHERE status = 1 AND age = 25
        
        // 使用 OR 连接
        wrapper.clear();
        wrapper.eq(User::getStatus, 1)
               .or()
               .eq(User::getAge, 25);
        // SQL: WHERE status = 1 OR age = 25
        
        // 嵌套 OR
        wrapper.clear();
        wrapper.eq(User::getStatus, 1)
               .and(w -> w.eq(User::getUsername, "张三")
                         .or()
                         .eq(User::getEmail, "test@example.com"));
        // SQL: WHERE status = 1 AND (username = '张三' OR email = 'test@example.com')
        
        // OR 嵌套
        wrapper.clear();
        wrapper.eq(User::getStatus, 1)
               .or(w -> w.eq(User::getUsername, "张三")
                        .eq(User::getEmail, "test@example.com"));
        // SQL: WHERE status = 1 OR (username = '张三' AND email = 'test@example.com')
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * SELECT 指定字段
     */
    @Test
    public void testSelectFields() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 指定查询字段
        wrapper.select(User::getId, User::getUsername, User::getEmail);
        // SQL: SELECT id, username, email FROM user
        
        // 排除字段
        wrapper.clear();
        wrapper.select(User.class, 
            info -> !"extra".equals(info.getColumn()) && 
                    !"create_time".equals(info.getColumn()));
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * 嵌套查询
     */
    @Test
    public void testNested() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // nested 方法
        wrapper.nested(i -> i.eq(User::getStatus, 1)
                            .like(User::getUsername, "张"))
               .between(User::getAge, 20, 30);
        // SQL: WHERE (status = 1 AND username LIKE '%张%') AND age BETWEEN 20 AND 30
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * EXISTS / NOT EXISTS
     */
    @Test
    public void testExists() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // EXISTS
        wrapper.exists("SELECT 1 FROM blacklist WHERE user_id = {0}", 123);
        // SQL: WHERE EXISTS (SELECT 1 FROM blacklist WHERE user_id = 123)
        
        // NOT EXISTS
        wrapper.notExists("SELECT 1 FROM blacklist WHERE user_id = {0}", 123);
        
        List<User> users = userMapper.selectList(wrapper);
    }
    
    /**
     * APPLY - 自定义 SQL 片段
     */
    @Test
    public void testApply() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 直接拼接 SQL(有注入风险)
        wrapper.apply("date_format(create_time, '%Y-%m') = '2024-01'");
        // SQL: WHERE date_format(create_time, '%Y-%m') = '2024-01'
        
        // 使用参数绑定(推荐)
        wrapper.apply("date_format(create_time, '%Y-%m') = {0}", "2024-01");
        // SQL: WHERE date_format(create_time, '%Y-%m') = '2024-01'
        
        List<User> users = userMapper.selectList(wrapper);
    }
}

4.3 UpdateWrapper

/**
 * UpdateWrapper - 更新条件构造器
 */
@SpringBootTest
public class UpdateWrapperTest {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 基础更新
     */
    @Test
    public void testBasicUpdate() {
        UpdateWrapper<User> wrapper = new UpdateWrapper<>();
        
        // SET 子句
        wrapper.set("status", 2)
               .set("update_time", new Date())
               .eq("id", 1L);
        // SQL: UPDATE user SET status = 2, update_time = ? WHERE id = 1
        
        int rows = userMapper.update(null, wrapper);
    }
    
    /**
     * Lambda 更新
     */
    @Test
    public void testLambdaUpdate() {
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        
        wrapper.set(User::getStatus, 2)
               .set(User::getUpdateTime, new Date())
               .eq(User::getId, 1L);
        // SQL: UPDATE user SET status = 2, update_time = ? WHERE id = 1
        
        int rows = userMapper.update(null, wrapper);
    }
    
    /**
     * SET 条件更新
     */
    @Test
    public void testSetCondition() {
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        
        // 条件 SET(条件满足时才设置)
        wrapper.set(true, User::getStatus, 2)  // 条件为 true 时设置
               .set(StringUtils.hasText("张"), User::getUsername, "李四")
               .eq(User::getId, 1L);
        
        int rows = userMapper.update(null, wrapper);
    }
    
    /**
     * 增量更新
     */
    @Test
    public void testIncrement() {
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        
        // 使用 SQL 函数
        wrapper.setSql("age = age + 1")
               .eq(User::getId, 1L);
        // SQL: UPDATE user SET age = age + 1 WHERE id = 1
        
        int rows = userMapper.update(null, wrapper);
    }
    
    /**
     * 批量更新
     */
    @Test
    public void testBatchUpdate() {
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        
        wrapper.set(User::getStatus, 0)
               .in(User::getId, Arrays.asList(1L, 2L, 3L));
        // SQL: UPDATE user SET status = 0 WHERE id IN (1, 2, 3)
        
        int rows = userMapper.update(null, wrapper);
    }
    
    /**
     * Entity + Wrapper 更新
     */
    @Test
    public void testEntityUpdate() {
        // 使用 entity 设置更新值
        User user = new User();
        user.setStatus(2);
        user.setUpdateTime(new Date());
        
        // 使用 wrapper 设置条件
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(User::getId, 1L);
        
        // entity 非空字段作为 SET 子句,wrapper 条件作为 WHERE 子句
        int rows = userMapper.update(user, wrapper);
        // SQL: UPDATE user SET status = 2, update_time = ? WHERE id = 1
    }
}

五、IService 接口

5.1 IService 基础用法

/**
 * IService - Service 层封装
 */
public interface UserService extends IService<User> {
    // 自定义方法
    List<User> selectByCustomCondition(UserQuery query);
}

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 
        implements UserService {
    
    @Override
    public List<User> selectByCustomCondition(UserQuery query) {
        // 自定义实现
        return baseMapper.selectList(
            new LambdaQueryWrapper<User>()
                .like(StringUtils.hasText(query.getUsername()), 
                      User::getUsername, query.getUsername())
                .eq(query.getStatus() != null, User::getStatus, query.getStatus())
        );
    }
}

/**
 * IService 常用方法测试
 */
@SpringBootTest
public class IServiceTest {
    
    @Autowired
    private UserService userService;
    
    /**
     * 保存操作
     */
    @Test
    public void testSave() {
        User user = new User();
        user.setUsername("wangwu");
        user.setEmail("wangwu@example.com");
        user.setAge(28);
        
        // 保存单个
        boolean success = userService.save(user);
        
        // 批量保存
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User u = new User();
            u.setUsername("user" + i);
            users.add(u);
        }
        boolean batchSuccess = userService.saveBatch(users);
        
        // 批量保存(指定批次大小)
        userService.saveBatch(users, 100);
        
        // 保存或更新(根据 ID 判断)
        userService.saveOrUpdate(user);
        
        // 批量保存或更新
        userService.saveOrUpdateBatch(users);
    }
    
    /**
     * 查询操作
     */
    @Test
    public void testQuery() {
        // 根据 ID 查询
        User user = userService.getById(1L);
        
        // 根据 Wrapper 查询一个
        User one = userService.getOne(
            new LambdaQueryWrapper<User>()
                .eq(User::getUsername, "zhangsan")
        );
        
        // 查询列表
        List<User> list = userService.list();
        
        // 条件查询列表
        List<User> activeList = userService.list(
            new LambdaQueryWrapper<User>()
                .eq(User::getStatus, 1)
                .orderByDesc(User::getCreateTime)
        );
        
        // 查询所有 ID
        List<Long> ids = userService.list().stream()
            .map(User::getId)
            .collect(Collectors.toList());
        
        // 根据 ID 列表查询
        List<User> users = userService.listByIds(ids);
        
        // 查询总数
        long count = userService.count();
        
        // 条件查询总数
        long activeCount = userService.count(
            new LambdaQueryWrapper<User>()
                .eq(User::getStatus, 1)
        );
        
        // 查询 Map 列表
        List<Map<String, Object>> maps = userService.listMaps();
        
        // 查询 Objs(单列)
        List<Object> usernames = userService.listObjs();
    }
    
    /**
     * 更新操作
     */
    @Test
    public void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setAge(30);
        
        // 根据 ID 更新
        boolean success = userService.updateById(user);
        
        // 根据 Wrapper 更新
        boolean updateSuccess = userService.update(
            new LambdaUpdateWrapper<User>()
                .set(User::getStatus, 2)
                .eq(User::getId, 1L)
        );
        
        // 批量更新
        List<User> users = new ArrayList<>();
        // ... 添加用户
        boolean batchSuccess = userService.updateBatchById(users);
        
        // 批量更新(指定批次大小)
        userService.updateBatchById(users, 100);
    }
    
    /**
     * 删除操作
     */
    @Test
    public void testRemove() {
        // 根据 ID 删除
        boolean success = userService.removeById(1L);
        
        // 根据 ID 列表删除
        boolean batchSuccess = userService.removeByIds(
            Arrays.asList(1L, 2L, 3L));
        
        // 根据 Wrapper 删除
        boolean removeSuccess = userService.remove(
            new LambdaQueryWrapper<User>()
                .eq(User::getStatus, 0)
        );
    }
    
    /**
     * 分页查询
     */
    @Test
    public void testPage() {
        // 分页参数
        int current = 1;  // 当前页
        int size = 10;    // 每页大小
        
        // 分页查询
        Page<User> page = userService.page(
            new Page<>(current, size),
            new LambdaQueryWrapper<User>()
                .eq(User::getStatus, 1)
                .orderByDesc(User::getCreateTime)
        );
        
        // 获取结果
        List<User> records = page.getRecords();  // 数据列表
        long total = page.getTotal();             // 总记录数
        long pages = page.getPages();             // 总页数
        long current1 = page.getCurrent();        // 当前页
        long size1 = page.getSize();              // 每页大小
        
        System.out.println("总记录数: " + total);
        System.out.println("总页数: " + pages);
        System.out.println("数据: " + records);
    }
}

5.2 链式调用

/**
 * IService 链式调用
 */
@SpringBootTest
public class ChainQueryTest {
    
    @Autowired
    private UserService userService;
    
    /**
     * 查询链式调用
     */
    @Test
    public void testQueryChain() {
        // 链式查询
        User user = userService.lambdaQuery()
            .eq(User::getUsername, "zhangsan")
            .one();
        
        // 链式查询列表
        List<User> list = userService.lambdaQuery()
            .eq(User::getStatus, 1)
            .like(User::getUsername, "张")
            .orderByDesc(User::getCreateTime)
            .list();
        
        // 链式分页
        Page<User> page = userService.lambdaQuery()
            .eq(User::getStatus, 1)
            .page(new Page<>(1, 10));
        
        // 链式统计
        long count = userService.lambdaQuery()
            .eq(User::getStatus, 1)
            .count();
        
        // 链式条件判断
        List<User> users = userService.lambdaQuery()
            .eq(User::getStatus, 1)
            .like(StringUtils.hasText("张"), User::getUsername, "张")
            .between(User::getAge, 20, 30)
            .list();
    }
    
    /**
     * 更新链式调用
     */
    @Test
    public void testUpdateChain() {
        // 链式更新
        boolean success = userService.lambdaUpdate()
            .set(User::getStatus, 2)
            .set(User::getUpdateTime, new Date())
            .eq(User::getId, 1L)
            .update();
        
        // 链式更新(带条件)
        boolean updateSuccess = userService.lambdaUpdate()
            .set(User::getStatus, 0)
            .in(User::getId, Arrays.asList(1L, 2L, 3L))
            .update();
        
        // 链式删除
        boolean removeSuccess = userService.lambdaUpdate()
            .eq(User::getStatus, 0)
            .remove();
    }
}

六、分页插件

6.1 分页插件配置

/**
 * 分页插件配置
 */
@Configuration
public class MyBatisPlusConfig {
    
    /**
     * 分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 分页插件
        PaginationInnerInterceptor paginationInterceptor = 
            new PaginationInnerInterceptor(DbType.MYSQL);
        
        // 设置最大单页限制数量(-1 不受限制)
        paginationInterceptor.setMaxLimit(500L);
        
        // 溢出总页数后是否进行处理
        paginationInterceptor.setOverflow(true);
        
        interceptor.addInnerInterceptor(paginationInterceptor);
        
        return interceptor;
    }
}

/**
 * 分页使用示例
 */
@SpringBootTest
public class PaginationTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private UserService userService;
    
    /**
     * 基础分页
     */
    @Test
    public void testBasicPage() {
        Page<User> page = new Page<>(1, 10);
        
        // 无条件分页
        Page<User> result = userMapper.selectPage(page, null);
        
        // 有条件分页
        Page<User> result2 = userMapper.selectPage(
            page,
            new LambdaQueryWrapper<User>()
                .eq(User::getStatus, 1)
                .orderByDesc(User::getCreateTime)
        );
        
        // 结果
        System.out.println("当前页: " + result.getCurrent());
        System.out.println("每页大小: " + result.getSize());
        System.out.println("总记录数: " + result.getTotal());
        System.out.println("总页数: " + result.getPages());
        System.out.println("数据: " + result.getRecords());
    }
    
    /**
     * 自定义分页
     */
    public interface UserMapper extends BaseMapper<User> {
        
        /**
         * 自定义分页查询
         */
        IPage<UserVO> selectUserPage(
            Page<UserVO> page,
            @Param("query") UserQuery query
        );
    }
    
    /*
    <!-- UserMapper.xml -->
    <select id="selectUserPage" resultType="com.example.vo.UserVO">
        SELECT u.*, d.name as dept_name
        FROM user u
        LEFT JOIN dept d ON u.dept_id = d.id
        <where>
            <if test="query.username != null and query.username != ''">
                AND u.username LIKE CONCAT('%', #{query.username}, '%')
            </if>
            <if test="query.status != null">
                AND u.status = #{query.status}
            </if>
        </where>
        ORDER BY u.create_time DESC
    </select>
    */
    
    @Test
    public void testCustomPage() {
        Page<UserVO> page = new Page<>(1, 10);
        UserQuery query = new UserQuery();
        query.setUsername("张");
        query.setStatus(1);
        
        IPage<UserVO> result = userMapper.selectUserPage(page, query);
    }
    
    /**
     * count 查询优化
     */
    @Test
    public void testCountQuery() {
        Page<User> page = new Page<>(1, 10);
        
        // 默认查询 count
        Page<User> result1 = userMapper.selectPage(page, wrapper);
        
        // 不查询 count(适用于不需要总数场景)
        Page<User> page2 = new Page<>(1, 10, false);
        Page<User> result2 = userMapper.selectPage(page2, wrapper);
        
        // 自定义 count 查询
        Page<User> page3 = new Page<>(1, 10);
        page3.setOptimizeCountSql(true);  // 优化 count SQL
    }
}

七、代码生成器

7.1 MyBatis-Plus 代码生成器

/**
 * MyBatis-Plus 代码生成器(3.5.x 新版)
 */
public class CodeGenerator {
    
    public static void main(String[] args) {
        // 数据库配置
        DataSourceConfig.Builder dataSourceConfig = 
            new DataSourceConfig.Builder(
                "jdbc:mysql://localhost:3306/mydb",
                "root",
                "123456"
            );
        
        // 创建代码生成器
        AutoGenerator generator = new AutoGenerator(dataSourceConfig);
        
        // 全局配置
        GlobalConfig globalConfig = new GlobalConfig.Builder()
            .outputDir(System.getProperty("user.dir") + "/src/main/java")
            .author("author")
            .enableSwagger()  // 开启 Swagger 注解
            .dateType(DateType.TIME_PACK)  // 使用 java.time 包
            .build();
        
        // 包配置
        PackageConfig packageConfig = new PackageConfig.Builder()
            .parent("com.example")
            .moduleName("system")
            .entity("entity")
            .mapper("mapper")
            .service("service")
            .serviceImpl("service.impl")
            .xml("mapper.xml")
            .controller("controller")
            .build();
        
        // 策略配置
        StrategyConfig strategyConfig = new StrategyConfig.Builder()
            .addInclude("sys_user", "sys_dept")  // 需要生成的表
            .addTablePrefix("sys_", "tb_")       // 表前缀过滤
            
            // Entity 策略
            .entityBuilder()
                .enableLombok()           // 使用 Lombok
                .enableChainModel()       // 链式模型
                .enableTableFieldAnnotation()  // 生成字段注解
                .logicDeleteColumnName("is_deleted")  // 逻辑删除字段
                .versionColumnName("version")   // 乐观锁字段
                .addTableFills(
                    new Column("create_time", FieldFill.INSERT),
                    new Column("update_time", FieldFill.INSERT_UPDATE)
                )
                .formatFileName("%s")
            
            // Controller 策略
            .controllerBuilder()
                .enableRestStyle()        // REST 风格
                .formatFileName("%sController")
            
            // Service 策略
            .serviceBuilder()
                .formatServiceFileName("%sService")
                .formatServiceImplFileName("%sServiceImpl")
            
            // Mapper 策略
            .mapperBuilder()
                .enableMapperAnnotation() // @Mapper 注解
                .formatMapperFileName("%sMapper")
                .formatXmlFileName("%sMapper")
            .build();
        
        // 注入配置
        InjectionConfig injectionConfig = new InjectionConfig.Builder()
            .beforeOutputFile((tableInfo, objectMap) -> {
                System.out.println("生成文件: " + tableInfo.getEntityName());
            })
            .build();
        
        // 配置生成器
        generator.global(globalConfig)
                 .packageInfo(packageConfig)
                 .strategy(strategyConfig)
                 .injection(injectionConfig);
        
        // 执行生成
        generator.execute();
    }
}

/**
 * 生成的 Entity 示例
 */
@Data
@TableName("sys_user")
public class User implements Serializable {
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    @TableField("username")
    private String username;
    
    @TableField("email")
    private String email;
    
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;
    
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
    @TableLogic
    @TableField("is_deleted")
    private Integer deleted;
    
    @Version
    @TableField("version")
    private Integer version;
}

/**
 * 生成的 Mapper 示例
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

/**
 * 生成的 Service 接口示例
 */
public interface UserService extends IService<User> {

}

/**
 * 生成的 Service 实现示例
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 
        implements UserService {

}

/**
 * 生成的 Controller 示例
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id) {
        return userService.getById(id);
    }
}

八、高级特性

8.1 逻辑删除

/**
 * 逻辑删除配置与使用
 */
@SpringBootTest
public class LogicDeleteTest {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 实体类配置
     */
    @Data
    @TableName("sys_user")
    public static class User {
        @TableId
        private Long id;
        
        private String username;
        
        @TableLogic
        private Integer deleted;  // 0-未删除,1-已删除
    }
    
    /**
     * 全局配置(application.yml)
     */
    /*
    mybatis-plus:
      global-config:
        db-config:
          logic-delete-field: deleted
          logic-delete-value: 1
          logic-not-delete-value: 0
    */
    
    /**
     * 删除操作(实际执行 UPDATE)
     */
    @Test
    public void testLogicDelete() {
        // 删除单条
        int rows = userMapper.deleteById(1L);
        // SQL: UPDATE sys_user SET deleted = 1 WHERE id = 1 AND deleted = 0
        
        // 批量删除
        int batchRows = userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));
        // SQL: UPDATE sys_user SET deleted = 1 WHERE id IN (1, 2, 3) AND deleted = 0
        
        // 条件删除
        int condRows = userMapper.delete(
            new LambdaQueryWrapper<User>()
                .eq(User::getStatus, 0)
        );
        // SQL: UPDATE sys_user SET deleted = 1 WHERE status = 0 AND deleted = 0
    }
    
    /**
     * 查询操作(自动过滤已删除数据)
     */
    @Test
    public void testSelectWithLogicDelete() {
        // 自动添加 deleted = 0 条件
        List<User> users = userMapper.selectList(null);
        // SQL: SELECT * FROM sys_user WHERE deleted = 0
        
        // 根据 ID 查询
        User user = userMapper.selectById(1L);
        // SQL: SELECT * FROM sys_user WHERE id = 1 AND deleted = 0
    }
    
    /**
     * 查询包含已删除数据
     */
    @Test
    public void testSelectIncludeDeleted() {
        // 方式1:在 Mapper 中自定义方法,忽略逻辑删除
        // @InterceptorIgnore(tenantLine = "true")
        
        // 方式2:使用原生 SQL
        List<User> allUsers = userMapper.selectList(
            new QueryWrapper<User>().apply("1=1 OR 1=1")
        );
    }
}

/**
 * 自定义逻辑删除处理器
 */
@Component
public class CustomLogicDeleteHandler implements ISqlParser {
    
    @Override
    public void parser(MetaObject metaObject, String sql) {
        // 自定义逻辑删除处理逻辑
    }
}

8.2 乐观锁

/**
 * 乐观锁配置与使用
 */
@Configuration
public class OptimisticLockerConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        
        return interceptor;
    }
}

/**
 * 乐观锁使用示例
 */
@SpringBootTest
public class OptimisticLockerTest {
    
    @Autowired
    private ProductMapper productMapper;
    
    /**
     * 实体类
     */
    @Data
    @TableName("product")
    public static class Product {
        @TableId
        private Long id;
        
        private String name;
        
        private Integer stock;
        
        @Version
        private Integer version;
    }
    
    /**
     * 更新时自动处理版本号
     */
    @Test
    public void testOptimisticLock() {
        // 1. 查询
        Product product = productMapper.selectById(1L);
        System.out.println("当前版本: " + product.getVersion());  // 例如 1
        
        // 2. 修改
        product.setStock(product.getStock() - 1);
        
        // 3. 更新
        int rows = productMapper.updateById(product);
        // SQL: UPDATE product SET stock = ?, version = 2 
        //      WHERE id = 1 AND version = 1
        
        System.out.println("更新结果: " + rows);  // 1-成功,0-失败
        
        if (rows == 0) {
            // 更新失败,数据已被其他线程修改
            System.out.println("更新失败,请重试");
        }
    }
    
    /**
     * 并发场景测试
     */
    @Test
    public void testConcurrent() throws InterruptedException {
        int threadCount = 10;
        CountDownLatch latch = new CountDownLatch(threadCount);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failCount = new AtomicInteger(0);
        
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    Product product = productMapper.selectById(1L);
                    product.setStock(product.getStock() - 1);
                    
                    int rows = productMapper.updateById(product);
                    if (rows > 0) {
                        successCount.incrementAndGet();
                    } else {
                        failCount.incrementAndGet();
                    }
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        latch.await();
        
        System.out.println("成功: " + successCount.get());
        System.out.println("失败: " + failCount.get());
    }
    
    /**
     * 重试机制
     */
    @Test
    public void testRetry() {
        int maxRetry = 3;
        int retry = 0;
        boolean success = false;
        
        while (retry < maxRetry && !success) {
            Product product = productMapper.selectById(1L);
            product.setStock(product.getStock() - 1);
            
            int rows = productMapper.updateById(product);
            if (rows > 0) {
                success = true;
            } else {
                retry++;
                System.out.println("重试第 " + retry + " 次");
            }
        }
        
        if (!success) {
            throw new RuntimeException("更新失败,请稍后重试");
        }
    }
}

8.3 多租户

/**
 * 多租户配置
 */
@Configuration
public class TenantConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 多租户插件
        TenantLineInnerInterceptor tenantInterceptor = 
            new TenantLineInnerInterceptor();
        tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
            
            @Override
            public Expression getTenantId() {
                // 获取当前租户 ID
                Long tenantId = TenantContext.getTenantId();
                return new LongValue(tenantId);
            }
            
            @Override
            public boolean ignoreTable(String tableName) {
                // 忽略的表(不需要租户过滤)
                return "sys_config".equals(tableName) || 
                       "sys_tenant".equals(tableName);
            }
            
            @Override
            public String getTenantIdColumn() {
                // 租户字段名
                return "tenant_id";
            }
        });
        
        interceptor.addInnerInterceptor(tenantInterceptor);
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        
        return interceptor;
    }
}

/**
 * 租户上下文
 */
public class TenantContext {
    
    private static final ThreadLocal<Long> TENANT_ID = new ThreadLocal<>();
    
    public static void setTenantId(Long tenantId) {
        TENANT_ID.set(tenantId);
    }
    
    public static Long getTenantId() {
        return TENANT_ID.get();
    }
    
    public static void clear() {
        TENANT_ID.remove();
    }
}

/**
 * 租户拦截器示例
 */
@SpringBootTest
public class TenantTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    public void testTenant() {
        // 设置租户 ID
        TenantContext.setTenantId(1L);
        
        // 查询会自动添加租户条件
        List<User> users = userMapper.selectList(null);
        // SQL: SELECT * FROM user WHERE tenant_id = 1
        
        // 插入会自动填充租户 ID
        User user = new User();
        user.setUsername("test");
        userMapper.insert(user);
        // SQL: INSERT INTO user(username, tenant_id) VALUES('test', 1)
    }
    
    /**
     * 忽略租户过滤
     */
    public interface UserMapper extends BaseMapper<User> {
        
        @InterceptorIgnore(tenantLine = "true")
        List<User> selectAllIgnoreTenant();
    }
}

8.4 数据权限

/**
 * 数据权限拦截器
 */
@Component
public class DataPermissionInterceptor implements InnerInterceptor {
    
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, 
                            Object parameter, RowBounds rowBounds, 
                            ResultHandler resultHandler, BoundSql boundSql) {
        
        // 获取当前用户的数据权限范围
        DataScope dataScope = SecurityContext.getDataScope();
        
        if (dataScope == null) {
            return;
        }
        
        // 获取原始 SQL
        String originalSql = boundSql.getSql();
        
        // 解析并重写 SQL,添加数据权限条件
        String newSql = addDataPermissionCondition(originalSql, dataScope);
        
        // 更新 SQL
        MetaObject metaObject = SystemMetaObject.forObject(boundSql);
        metaObject.setValue("sql", newSql);
    }
    
    private String addDataPermissionCondition(String sql, DataScope dataScope) {
        // 使用 JSqlParser 解析 SQL
        try {
            Statement statement = CCJSqlParserUtil.parse(sql);
            
            if (statement instanceof Select) {
                Select select = (Select) statement;
                PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
                
                Expression where = plainSelect.getWhere();
                Expression dataPermissionExpr = buildDataPermissionExpression(dataScope);
                
                if (where == null) {
                    plainSelect.setWhere(dataPermissionExpr);
                } else {
                    plainSelect.setWhere(new AndExpression(where, dataPermissionExpr));
                }
            }
            
            return statement.toString();
        } catch (JSQLParserException e) {
            throw new RuntimeException("SQL 解析失败", e);
        }
    }
    
    private Expression buildDataPermissionExpression(DataScope dataScope) {
        // 根据数据权限构建条件表达式
        // 例如:dept_id IN (1, 2, 3)
        return new InExpression(
            new Column("dept_id"),
            new ExpressionList(
                dataScope.getDeptIds().stream()
                    .map(id -> new LongValue(id))
                    .collect(Collectors.toList())
            )
        );
    }
}

/**
 * 数据权限配置
 */
@Configuration
public class DataPermissionConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 数据权限插件
        interceptor.addInnerInterceptor(new DataPermissionInterceptor());
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        
        return interceptor;
    }
}

九、总结与最佳实践

9.1 核心优势总结

┌─────────────────────────────────────────────────────────────────────────┐
│                     MyBatis-Plus 核心优势                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 无侵入设计                                                           │
│     - 只做增强不做改变                                                   │
│     - 兼容原生 MyBatis 所有功能                                          │
│                                                                         │
│  2. 强大的 CRUD 操作                                                     │
│     - BaseMapper 提供通用 CRUD                                          │
│     - IService 提供更丰富的业务方法                                      │
│                                                                         │
│  3. 条件构造器                                                           │
│     - QueryWrapper / LambdaQueryWrapper                                 │
│     - UpdateWrapper / LambdaUpdateWrapper                               │
│     - 链式调用,代码优雅                                                 │
│                                                                         │
│  4. 丰富的插件                                                           │
│     - 分页插件(物理分页)                                               │
│     - 乐观锁插件                                                         │
│     - 多租户插件                                                         │
│     - 数据权限插件                                                       │
│                                                                         │
│  5. 开发效率                                                             │
│     - 代码生成器                                                         │
│     - 自动填充                                                           │
│     - 逻辑删除                                                           │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

9.2 最佳实践

  1. 实体设计

    • 使用 @TableName 明确表名
    • 使用 @TableId 指定主键策略
    • 逻辑删除、乐观锁字段统一命名
  2. 条件构造

    • 优先使用 LambdaWrapper 避免字段名硬编码
    • 使用条件构造器实现动态 SQL
    • 复杂查询考虑自定义 SQL
  3. 性能优化

    • 合理使用索引
    • 避免 N+1 查询
    • 批量操作使用 saveBatch/updateBatchById
  4. 插件使用

    • 分页插件配置最大限制
    • 多租户注意忽略特殊表
    • 数据权限考虑性能影响

参考资料:


六、思考与练习

思考题

  1. 基础题:MyBatis-Plus 的 BaseMapper 提供了哪些通用 CRUD 方法?与传统 MyBatis 相比有什么优势?

  2. 进阶题:LambdaQueryWrapper 相比 QueryWrapper 有什么优势?在什么场景下应该优先使用 Lambda 表达式?

  3. 实战题:在一个电商系统中,如何使用 MyBatis-Plus 实现商品库存的并发扣减?需要考虑哪些并发问题?

编程练习

练习:基于 MyBatis-Plus 实现一个用户管理系统,要求:

  1. 使用 Lambda 条件构造器实现多条件动态查询(用户名模糊、状态精确、年龄范围)
  2. 实现分页查询功能,每页显示 10 条记录
  3. 使用 @TableLogic 实现逻辑删除
  4. 使用 @Version 实现乐观锁,防止并发修改冲突
  5. 使用代码生成器生成 Entity、Mapper、Service、Controller 代码

章节关联

  • 前置章节:MyBatis 核心原理
  • 后续章节:Spring Data JPA 详解
  • 扩展阅读
    • MyBatis 插件开发指南
    • 数据库性能优化实践
    • 分布式事务解决方案

📝 下一章预告

下一章将深入讲解 Spring Data JPA,这是 Spring 生态中另一个重要的数据访问框架。我们将对比 JPA 与 MyBatis-Plus 的设计理念、使用场景和性能特点,帮助你在实际项目中选择最合适的数据访问方案。


本章完