01-AI学习Mybatis-Plus

企业级MyBatis-Plus实战教学(详细版)

模块一:环境搭建与配置最佳实践

1.1 详细依赖配置

<!-- pom.xml 详细配置 -->
<dependencies>
    <!-- Spring Boot Web Starter - 提供Web开发能力 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.0</version>
    </dependency>

    <!-- MyBatis-Plus Starter - 核心依赖 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
        <!-- 
        重点说明:
        1. mybatis-plus-boot-starter 已经包含了mybatis-spring-boot-starter
        2. 无需单独引入MyBatis依赖,避免版本冲突
        3. 企业项目建议锁定版本号,避免自动升级导致兼容性问题
        -->
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
        <!-- 
        重点说明:
        1. MySQL 8.0+ 需要使用这个版本的驱动
        2. 如果使用MySQL 5.x,版本号应改为 5.1.47
        3. 注意时区配置,生产环境要设置 serverTimezone
        -->
    </dependency>

    <!-- Lombok - 减少样板代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
        <scope>provided</scope>
        <!-- 
        重点说明:
        1. scope=provided 表示编译时需要,运行时不需要
        2. 需要在IDE中安装Lombok插件
        3. @Data 注解会自动生成getter/setter/toString等方法
        -->
    </dependency>

    <!-- HikariCP 连接池(可选,Spring Boot 2.0+默认使用) -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.1</version>
    </dependency>
</dependencies>

1.2 配置文件详细解析

# application.yml 完整配置详解
spring:
  datasource:
    # 数据库连接URL - 企业级配置要点
    url: jdbc:mysql://localhost:3306/enterprise_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
    # 
    # 参数详细说明:
    # useUnicode=true&characterEncoding=UTF-8 - 保证中文不乱码
    # serverTimezone=Asia/Shanghai - 解决时区问题,避免时间差8小时
    # useSSL=false - 开发环境关闭SSL,生产环境应该为true
    # allowPublicKeyRetrieval=true - MySQL 8.0连接需要
    
    username: root
    password: 123456
    
    # Hikari连接池配置 - 企业性能优化关键
    hikari:
      # 连接池名称
      pool-name: EnterpriseHikariCP
      # 最小空闲连接数
      minimum-idle: 5
      # 最大连接数(根据业务量调整)
      maximum-pool-size: 20
      # 连接超时时间(毫秒)
      connection-timeout: 30000
      # 连接最大生命周期
      max-lifetime: 1800000
      # 空闲连接超时时间
      idle-timeout: 600000
      # 连接测试查询
      connection-test-query: SELECT 1

# MyBatis-Plus 配置详解
mybatis-plus:
  # 全局配置
  global-config:
    db-config:
      # 主键类型: 
      # AUTO-数据库自增, INPUT-手动输入, ASSIGN_ID-雪花算法, ASSIGN_UUID-UUID
      id-type: ASSIGN_ID
      # 
      # 重点说明:
      # ASSIGN_ID 使用雪花算法生成19位Long类型ID,适合分布式系统
      # 如果使用MySQL自增,需要设置为 AUTO,并在数据库字段设置 AUTO_INCREMENT
      
      # 逻辑删除配置
      logic-delete-field: deleted  # 逻辑删除字段名
      logic-delete-value: 1        # 逻辑已删除值
      logic-not-delete-value: 0    # 逻辑未删除值
      #
      # 重点说明:
      # 逻辑删除不是真正删除数据,而是更新deleted字段
      # 查询时会自动加上 WHERE deleted=0 条件
  
  # MyBatis原生配置
  configuration:
    # 自动驼峰命名规则映射
    map-underscore-to-camel-case: true
    #
    # 重点说明:
    # 开启后,数据库字段 user_name 会自动映射到Java属性 userName
    # 这是企业开发的标准规范
    
    # 日志实现 - 不同环境的配置
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #
    # 日志配置说明:
    # 开发环境:StdOutImpl 控制台打印SQL
    # 测试环境:Slf4jImpl 输出到日志文件
    # 生产环境:NoLoggingImpl 关闭日志,提高性能
    
    # 开启二级缓存
    cache-enabled: true
    
    # 配置默认的执行器
    default-executor-type: REUSE
    #
    # 执行器类型说明:
    # SIMPLE - 简单执行器,每次执行都创建新的Statement
    # REUSE - 复用执行器,复用PreparedStatement(推荐)
    # BATCH - 批量执行器,用于批量操作

  # Mapper文件位置
  mapper-locations: classpath:/mapper/*.xml
  # 类型别名包扫描
  type-aliases-package: com.enterprise.entity

1.3 配置类代码逐行解析

package com.enterprise.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MyBatis-Plus 配置类
 * 重点说明:@Configuration 注解表示这是一个配置类,Spring Boot启动时会自动加载
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * MyBatis-Plus 拦截器配置
     * 这是核心配置,所有的插件都在这里添加
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 创建拦截器实例
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 1. 分页插件 - 必须添加到第一个位置
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
        // 设置数据库类型
        paginationInterceptor.setDbType(DbType.MYSQL);
        // 设置最大单页限制数量,默认500条,-1表示不受限制
        paginationInterceptor.setMaxLimit(1000L);
        // 开启count的join优化,只针对部分left join
        paginationInterceptor.setOptimizeJoin(true);
        // 添加分页插件到拦截器
        interceptor.addInnerInterceptor(paginationInterceptor);
        
        // 2. 乐观锁插件
        OptimisticLockerInnerInterceptor optimisticLockerInterceptor = 
            new OptimisticLockerInnerInterceptor();
        interceptor.addInnerInterceptor(optimisticLockerInterceptor);
        
        return interceptor;
    }

    /**
     * 元对象字段填充控制器
     * 用于自动填充创建时间、更新时间等字段
     */
    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new MetaObjectHandler() {
            /**
             * 插入时的填充策略
             */
            @Override
            public void insertFill(MetaObject metaObject) {
                // 设置创建时间 - 如果字段为空则填充
                this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
                // 设置更新时间 - 新增时也设置
                this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
                // 设置创建人 - 实际项目中可以从SecurityContext中获取
                this.strictInsertFill(metaObject, "createBy", String.class, "system");
            }

            /**
             * 更新时的填充策略
             */
            @Override
            public void updateFill(MetaObject metaObject) {
                // 设置更新时间
                this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
                // 设置更新人
                this.strictUpdateFill(metaObject, "updateBy", String.class, "system");
            }
        };
    }
}

1.4 启动类详细说明

package com.enterprise;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 应用启动类
 * 
 * 重点注解说明:
 * @SpringBootApplication - 包含以下三个注解:
 *   @SpringBootConfiguration - 标识这是配置类
 *   @EnableAutoConfiguration - 开启自动配置
 *   @ComponentScan - 组件扫描,默认扫描当前包及其子包
 * 
 * @MapperScan - 指定Mapper接口的扫描路径
 */
@SpringBootApplication
@MapperScan("com.enterprise.mapper")  // 重点:必须指定Mapper接口所在包
public class EnterpriseApplication {

    public static void main(String[] args) {
        // 启动Spring Boot应用
        SpringApplication.run(EnterpriseApplication.class, args);
        
        System.out.println("企业级MyBatis-Plus应用启动成功!");
        System.out.println("Swagger地址: http://localhost:8080/swagger-ui.html");
        System.out.println("Druid监控: http://localhost:8080/druid");
    }
}

1.5 测试验证配置

package com.enterprise.test;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * 环境配置测试类
 * 重点:确保所有配置正确生效
 */
@SpringBootTest
public class EnvironmentTest {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 测试数据库连接是否正常
     */
    @Test
    public void testDatabaseConnection() {
        try (Connection connection = dataSource.getConnection()) {
            System.out.println("数据库连接成功!");
            System.out.println("数据库URL: " + connection.getMetaData().getURL());
            System.out.println("数据库产品: " + connection.getMetaData().getDatabaseProductName());
            System.out.println("驱动版本: " + connection.getMetaData().getDriverVersion());
        } catch (SQLException e) {
            System.err.println("数据库连接失败: " + e.getMessage());
        }
    }

    /**
     * 测试MyBatis-Plus配置是否生效
     */
    @Test
    public void testMybatisPlusConfig() {
        try {
            // 执行简单查询测试配置
            Integer result = jdbcTemplate.queryForObject("SELECT 1", Integer.class);
            System.out.println("MyBatis-Plus基础配置测试通过,查询结果: " + result);
        } catch (Exception e) {
            System.err.println("MyBatis-Plus配置测试失败: " + e.getMessage());
        }
    }
}

1.6 企业级避坑方案详解

坑点1:时区问题

# 错误配置 - 可能导致时间差8小时
url: jdbc:mysql://localhost:3306/test

# 正确配置 - 明确指定时区
url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai

坑点2:SSL连接问题

# MySQL 8.0 需要明确关闭SSL(开发环境)
url: jdbc:mysql://localhost:3306/test?useSSL=false&allowPublicKeyRetrieval=true

# 生产环境应该使用SSL
url: jdbc:mysql://localhost:3306/test?useSSL=true&verifyServerCertificate=false

坑点3:连接池配置

spring:
  datasource:
    hikari:
      # 避免连接泄漏的配置
      leak-detection-threshold: 60000  # 60秒后检测连接泄漏
      # 连接存活检查
      keepalive-time: 30000  # 30秒发送一次keepalive查询

坑点4:MyBatis-Plus版本兼容性

<!-- 确保版本兼容 -->
<properties>
    <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
    <spring-boot.version>2.7.0</spring-boot.version>
</properties>

<!-- 避免版本冲突,统一管理 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

模块二:CRUD接口封装与Lambda查询

2.1 实体类详细设计

package com.enterprise.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用户实体类
 * 重点:企业级实体类设计规范
 */
@Data // Lombok注解,自动生成getter、setter、toString、equals、hashCode
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true) // 链式调用:user.setName("张三").setAge(20)
@TableName("sys_user") // 指定数据库表名
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     * 重点:@TableId 注解配置主键策略
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;

    /**
     * 用户名
     */
    @TableField("user_name") // 指定数据库字段名
    private String userName;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 部门ID
     */
    private Long deptId;

    /**
     * 逻辑删除字段
     * 重点:@TableLogic 实现逻辑删除
     */
    @TableLogic
    private Integer deleted;

    /**
     * 版本号 - 乐观锁
     * 重点:@Version 实现乐观锁控制
     */
    @Version
    private Integer version;

    /**
     * 创建时间
     * 重点:@TableField 配置字段自动填充
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
     * 创建人
     */
    @TableField(fill = FieldFill.INSERT)
    private String createBy;

    /**
     * 更新人
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;

    // 构造方法
    public User() {}

    public User(String userName, Integer age, String email) {
        this.userName = userName;
        this.age = age;
        this.email = email;
    }
}

2.2 Mapper接口逐行解析

package com.enterprise.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.enterprise.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * 用户Mapper接口
 * 重点:继承 BaseMapper 就获得了所有基础CRUD方法
 */
@Mapper // 可省略,因为@MapperScan已经扫描了整个包
public interface UserMapper extends BaseMapper<User> {
    
    /**
     * 继承BaseMapper<User>后自动获得的方法:
     * int insert(T entity);              // 插入一条记录
     * int deleteById(Serializable id);   // 根据ID删除
     * int updateById(T entity);          // 根据ID修改
     * T selectById(Serializable id);     // 根据ID查询
     * List<T> selectList(Wrapper<T> queryWrapper); // 查询列表
     * ... 等等20多个方法
     */

    /**
     * 自定义查询:根据用户名查询用户
     * 重点:@Select注解实现简单SQL映射
     */
    @Select("SELECT * FROM sys_user WHERE user_name = #{userName} AND deleted = 0")
    User selectByUserName(@Param("userName") String userName);

    /**
     * 自定义查询:查询某个部门的所有用户
     */
    @Select("SELECT * FROM sys_user WHERE dept_id = #{deptId} AND deleted = 0 ORDER BY create_time DESC")
    List<User> selectByDeptId(@Param("deptId") Long deptId);

    /**
     * 复杂查询:查询年龄范围内的用户
     * 重点:使用XML方式处理复杂SQL
     */
    List<User> selectUsersByAgeRange(@Param("minAge") Integer minAge, 
                                   @Param("maxAge") Integer maxAge);
}

2.3 Service层详细实现

package com.enterprise.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.enterprise.entity.User;

/**
 * 用户服务接口
 * 重点:继承 IService 获得增强的CRUD方法
 */
public interface UserService extends IService<User> {
    
    /**
     * 自定义业务方法:根据用户名查询用户
     */
    User getUserByUserName(String userName);
    
    /**
     * 自定义业务方法:批量创建用户
     */
    boolean batchCreateUsers(List<User> users);
    
    /**
     * 自定义业务方法:根据部门查询用户
     */
    List<User> getUsersByDept(Long deptId);
}
package com.enterprise.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.enterprise.entity.User;
import com.enterprise.mapper.UserMapper;
import com.enterprise.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * 用户服务实现类
 * 重点:继承 ServiceImpl 并实现自定义接口
 */
@Slf4j // Lombok日志注解
@Service // Spring服务组件
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 
    implements UserService {

    /**
     * ServiceImpl 已经提供的常用方法:
     * save(T entity) - 保存一条记录
     * saveBatch(Collection<T> entityList) - 批量保存
     * getById(Serializable id) - 根据ID查询
     * updateById(T entity) - 根据ID更新
     * removeById(Serializable id) - 根据ID删除
     * list() - 查询所有
     * page(Page<T> page) - 分页查询
     */

    @Override
    public User getUserByUserName(String userName) {
        // 使用BaseMapper的自定义方法
        return baseMapper.selectByUserName(userName);
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 重点:事务管理
    public boolean batchCreateUsers(List<User> users) {
        try {
            // 使用MyBatis-Plus的批量保存
            // 重点:saveBatch 内部会分批提交,避免大数据量时的内存问题
            return saveBatch(users, 1000); // 每1000条提交一次
        } catch (Exception e) {
            log.error("批量创建用户失败: {}", e.getMessage(), e);
            throw new RuntimeException("批量创建用户失败", e);
        }
    }

    @Override
    public List<User> getUsersByDept(Long deptId) {
        // 使用Lambda查询,类型安全,编译期检查
        return lambdaQuery()
                .eq(User::getDeptId, deptId)    // WHERE dept_id = #{deptId}
                .eq(User::getDeleted, 0)        // AND deleted = 0
                .orderByDesc(User::getCreateTime) // ORDER BY create_time DESC
                .list(); // 执行查询返回List
    }

    /**
     * 复杂的Lambda查询示例
     */
    public List<User> complexLambdaQuery(String userName, Integer minAge, 
                                       Integer maxAge, Long deptId) {
        return lambdaQuery()
                .like(userName != null, User::getUserName, userName) // 动态like条件
                .ge(minAge != null, User::getAge, minAge)           // 动态 >= 条件
                .le(maxAge != null, User::getAge, maxAge)           // 动态 <= 条件
                .eq(deptId != null, User::getDeptId, deptId)        // 动态 = 条件
                .eq(User::getDeleted, 0)                            // 固定条件
                .orderByAsc(User::getAge)                           // 按年龄升序
                .orderByDesc(User::getCreateTime)                   // 按创建时间降序
                .list();
    }

    /**
     * Lambda更新示例
     */
    @Transactional
    public boolean updateUserEmail(Long userId, String newEmail) {
        return lambdaUpdate()
                .eq(User::getId, userId)           // WHERE id = #{userId}
                .set(User::getEmail, newEmail)     // SET email = #{newEmail}
                .set(User::getUpdateTime, LocalDateTime.now()) // SET update_time = NOW()
                .update();
    }

    /**
     * 链式查询示例
     */
    public void chainQueryExample() {
        // 链式编程,一步步构建查询
        List<User> users = query()
                .like("user_name", "张")     // WHERE user_name LIKE '%张%'
                .between("age", 18, 30)     // AND age BETWEEN 18 AND 30
                .isNotNull("email")          // AND email IS NOT NULL
                .orderByDesc("create_time")  // ORDER BY create_time DESC
                .list();
        
        log.info("查询到 {} 条用户记录", users.size());
    }
}

2.4 Controller层完整示例

package com.enterprise.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.enterprise.entity.User;
import com.enterprise.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 用户控制器
 * 重点:RESTful API设计
 */
@Slf4j
@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 创建用户 - POST /api/users
     */
    @PostMapping
    public ApiResult<User> createUser(@RequestBody User user) {
        try {
            boolean saved = userService.save(user);
            if (saved) {
                log.info("创建用户成功: {}", user.getUserName());
                return ApiResult.success(user);
            } else {
                return ApiResult.fail("创建用户失败");
            }
        } catch (Exception e) {
            log.error("创建用户异常: {}", e.getMessage(), e);
            return ApiResult.fail("系统异常: " + e.getMessage());
        }
    }

    /**
     * 根据ID查询用户 - GET /api/users/{id}
     */
    @GetMapping("/{id}")
    public ApiResult<User> getUserById(@PathVariable Long id) {
        User user = userService.getById(id);
        if (user != null) {
            return ApiResult.success(user);
        } else {
            return ApiResult.fail("用户不存在");
        }
    }

    /**
     * 分页查询用户 - GET /api/users/page
     */
    @GetMapping("/page")
    public ApiResult<Page<User>> getUserPage(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize,
            @RequestParam(required = false) String userName) {
        
        // 创建分页对象
        Page<User> page = new Page<>(pageNum, pageSize);
        
        // 构建查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (userName != null && !userName.trim().isEmpty()) {
            queryWrapper.like("user_name", userName.trim());
        }
        queryWrapper.eq("deleted", 0)
                   .orderByDesc("create_time");
        
        // 执行分页查询
        Page<User> userPage = userService.page(page, queryWrapper);
        return ApiResult.success(userPage);
    }

    /**
     * Lambda查询示例 - GET /api/users/lambda
     */
    @GetMapping("/lambda")
    public ApiResult<List<User>> getUsersByLambda(
            @RequestParam(required = false) String userName,
            @RequestParam(required = false) Integer minAge,
            @RequestParam(required = false) Integer maxAge) {
        
        List<User> users = userService.lambdaQuery()
                .like(userName != null, User::getUserName, userName)
                .ge(minAge != null, User::getAge, minAge)
                .le(maxAge != null, User::getAge, maxAge)
                .eq(User::getDeleted, 0)
                .orderByDesc(User::getCreateTime)
                .list();
        
        return ApiResult.success(users);
    }

    /**
     * 批量创建用户 - POST /api/users/batch
     */
    @PostMapping("/batch")
    public ApiResult<String> batchCreateUsers(@RequestBody List<User> users) {
        try {
            boolean success = userService.batchCreateUsers(users);
            if (success) {
                return ApiResult.success("批量创建成功,共" + users.size() + "个用户");
            } else {
                return ApiResult.fail("批量创建失败");
            }
        } catch (Exception e) {
            log.error("批量创建用户异常: {}", e.getMessage(), e);
            return ApiResult.fail("批量创建失败: " + e.getMessage());
        }
    }

    /**
     * 更新用户邮箱 - PUT /api/users/{id}/email
     */
    @PutMapping("/{id}/email")
    public ApiResult<String> updateUserEmail(@PathVariable Long id, 
                                           @RequestParam String email) {
        boolean updated = userService.lambdaUpdate()
                .eq(User::getId, id)
                .set(User::getEmail, email)
                .update();
        
        if (updated) {
            return ApiResult.success("邮箱更新成功");
        } else {
            return ApiResult.fail("邮箱更新失败,用户可能不存在");
        }
    }
}

/**
 * 统一API响应结果封装
 */
class ApiResult<T> {
    private boolean success;
    private String message;
    private T data;
    private long timestamp;

    // 构造方法、getter、setter 省略
    public static <T> ApiResult<T> success(T data) {
        ApiResult<T> result = new ApiResult<>();
        result.setSuccess(true);
        result.setMessage("success");
        result.setData(data);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }

    public static <T> ApiResult<T> fail(String message) {
        ApiResult<T> result = new ApiResult<>();
        result.setSuccess(false);
        result.setMessage(message);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }
}

2.5 完整的测试用例

package com.enterprise.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.enterprise.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * 用户服务测试类
 * 重点:完整的单元测试覆盖所有CRUD操作
 */
@Slf4j
@SpringBootTest
@Transactional // 测试完成后回滚数据,避免污染数据库
class UserServiceTest {

    @Autowired
    private UserService userService;

    /**
     * 测试插入用户
     */
    @Test
    void testInsertUser() {
        User user = new User();
        user.setUserName("测试用户");
        user.setAge(25);
        user.setEmail("test@example.com");
        
        boolean saved = userService.save(user);
        
        // 断言测试结果
        assert saved : "用户保存失败";
        assert user.getId() != null : "用户ID未生成";
        assert user.getCreateTime() != null : "创建时间未自动填充";
        
        log.info("测试用户插入成功,ID: {}", user.getId());
    }

    /**
     * 测试根据ID查询
     */
    @Test
    void testSelectById() {
        // 先插入测试数据
        User user = createTestUser();
        userService.save(user);
        
        // 根据ID查询
        User dbUser = userService.getById(user.getId());
        
        assert dbUser != null : "查询结果为空";
        assert "测试用户".equals(dbUser.getUserName()) : "用户名不匹配";
        assert dbUser.getAge().equals(25) : "年龄不匹配";
        
        log.info("根据ID查询测试通过");
    }

    /**
     * 测试Lambda查询
     */
    @Test
    void testLambdaQuery() {
        // 准备测试数据
        createTestUsers();
        
        // 执行Lambda查询
        List<User> users = userService.lambdaQuery()
                .like(User::getUserName, "测试")
                .ge(User::getAge, 20)
                .le(User::getAge, 30)
                .orderByDesc(User::getCreateTime)
                .list();
        
        assert users != null : "查询结果为空";
        assert users.size() >= 2 : "查询结果数量不正确";
        
        log.info("Lambda查询测试通过,查询到 {} 条记录", users.size());
    }

    /**
     * 测试批量操作
     */
    @Test
    void testBatchOperations() {
        List<User> users = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            User user = new User();
            user.setUserName("批量用户" + i);
            user.setAge(20 + i);
            user.setEmail("batch" + i + "@example.com");
            users.add(user);
        }
        
        // 批量保存
        boolean batchResult = userService.saveBatch(users);
        assert batchResult : "批量保存失败";
        
        // 验证批量插入的数据
        List<User> dbUsers = userService.lambdaQuery()
                .like(User::getUserName, "批量用户")
                .list();
        
        assert dbUsers.size() == 5 : "批量插入数据数量不正确";
        log.info("批量操作测试通过,成功插入 {} 条记录", dbUsers.size());
    }

    /**
     * 测试Lambda更新
     */
    @Test
    void testLambdaUpdate() {
        // 先创建测试用户
        User user = createTestUser();
        userService.save(user);
        
        // 使用Lambda更新
        boolean updated = userService.lambdaUpdate()
                .eq(User::getId, user.getId())
                .set(User::getEmail, "updated@example.com")
                .set(User::getAge, 30)
                .update();
        
        assert updated : "Lambda更新失败";
        
        // 验证更新结果
        User updatedUser = userService.getById(user.getId());
        assert "updated@example.com".equals(updatedUser.getEmail()) : "邮箱更新失败";
        assert updatedUser.getAge().equals(30) : "年龄更新失败";
        
        log.info("Lambda更新测试通过");
    }

    /**
     * 测试逻辑删除
     */
    @Test
    void testLogicDelete() {
        User user = createTestUser();
        userService.save(user);
        
        Long userId = user.getId();
        
        // 执行逻辑删除
        boolean deleted = userService.removeById(userId);
        assert deleted : "逻辑删除失败";
        
        // 验证逻辑删除后查询不到
        User deletedUser = userService.getById(userId);
        assert deletedUser == null : "逻辑删除后仍能查询到数据";
        
        // 但数据库实际数据还在,只是deleted字段变为1
        log.info("逻辑删除测试通过,用户ID: {}", userId);
    }

    // 辅助方法:创建测试用户
    private User createTestUser() {
        User user = new User();
        user.setUserName("测试用户");
        user.setAge(25);
        user.setEmail("test@example.com");
        return user;
    }
    
    // 辅助方法:创建多个测试用户
    private void createTestUsers() {
        List<User> users = new ArrayList<>();
        users.add(createUser("测试用户A", 20, "a@test.com"));
        users.add(createUser("测试用户B", 25, "b@test.com"));
        users.add(createUser("其他用户", 30, "c@test.com"));
        userService.saveBatch(users);
    }
    
    private User createUser(String name, Integer age, String email) {
        User user = new User();
        user.setUserName(name);
        user.setAge(age);
        user.setEmail(email);
        return user;
    }
}

2.6 企业级避坑方案详解

坑点1:ID生成策略选择

// 错误做法:混合使用不同的ID策略
@TableId(type = IdType.AUTO)  // 数据库自增
private Long id;

// 同时又有
user.setId(123L); // 手动设置ID,与自增冲突

// 正确做法:统一策略
@TableId(type = IdType.ASSIGN_ID) // 分布式ID,自动生成
private Long id;
// 或者
@TableId(type = IdType.AUTO) // 数据库自增,但不手动设置ID
private Long id;

坑点2:字段自动填充时机

// 错误:手动设置自动填充字段
user.setCreateTime(new Date()); // 这样会覆盖自动填充

// 正确:依赖框架自动填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime; // 框架在插入时自动设置

坑点3:Lambda表达式性能

// 错误:在循环中创建LambdaQueryWrapper
for (String name : names) {
    List<User> users = userService.lambdaQuery()
            .eq(User::getUserName, name)
            .list(); // 每次循环都创建新的Wrapper,性能差
}

// 正确:批量查询或使用IN语句
List<User> users = userService.lambdaQuery()
        .in(User::getUserName, names) // 一次查询所有
        .list();

坑点4:事务管理

// 错误:在Controller中处理事务
@RestController
public class UserController {
    @Autowired
    private UserMapper userMapper; // 直接使用Mapper
    
    @PostMapping
    public void createUser(@RequestBody User user) {
        userMapper.insert(user); // 没有事务管理,出错无法回滚
    }
}

// 正确:在Service层使用事务
@Service
public class UserService {
    @Transactional(rollbackFor = Exception.class) // 声明式事务
    public void createUser(User user) {
        userMapper.insert(user);
        // 其他业务操作...
    }
}

企业级MyBatis-Plus实战教学(续)

模块三:条件构造器(QueryWrapper)高级用法

3.1 QueryWrapper 基础用法详细解析

/**
 * QueryWrapper 基础用法详解
 * 重点:QueryWrapper 是 MyBatis-Plus 最强大的功能之一,用于动态构建SQL条件
 */
@Service
@Slf4j
public class QueryWrapperService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 1. 基础等值查询
     */
    public void basicEqualsQuery() {
        // 创建 QueryWrapper 实例
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // eq: 等于查询
        queryWrapper.eq("user_name", "张三");
        // 生成的SQL: WHERE user_name = '张三'
        
        // ne: 不等于查询
        queryWrapper.ne("age", 18);
        // 生成的SQL: WHERE user_name = '张三' AND age != 18
        
        List<User> users = userMapper.selectList(queryWrapper);
        log.info("基础等值查询结果: {} 条", users.size());
    }

    /**
     * 2. 范围查询详解
     */
    public void rangeQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // gt: 大于 greater than
        queryWrapper.gt("age", 18);
        // 生成的SQL: WHERE age > 18
        
        // ge: 大于等于 greater than or equal
        queryWrapper.ge("age", 18);
        // 生成的SQL: WHERE age >= 18
        
        // lt: 小于 less than
        queryWrapper.lt("age", 65);
        // 生成的SQL: WHERE age >= 18 AND age < 65
        
        // le: 小于等于 less than or equal
        queryWrapper.le("age", 65);
        // 生成的SQL: WHERE age >= 18 AND age <= 65
        
        // between: 区间查询
        queryWrapper.between("age", 18, 30);
        // 生成的SQL: WHERE age BETWEEN 18 AND 30
        
        // notBetween: 不在区间内
        queryWrapper.notBetween("age", 18, 30);
        // 生成的SQL: WHERE age NOT BETWEEN 18 AND 30
        
        List<User> users = userMapper.selectList(queryWrapper);
        log.info("范围查询结果: {} 条", users.size());
    }

    /**
     * 3. 模糊查询详解
     */
    public void fuzzyQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // like: 前后模糊匹配
        queryWrapper.like("user_name", "张");
        // 生成的SQL: WHERE user_name LIKE '%张%'
        
        // likeLeft: 左模糊匹配
        queryWrapper.likeLeft("user_name", "张");
        // 生成的SQL: WHERE user_name LIKE '%张'
        
        // likeRight: 右模糊匹配
        queryWrapper.likeRight("user_name", "张");
        // 生成的SQL: WHERE user_name LIKE '张%'
        
        // notLike: 不包含
        queryWrapper.notLike("user_name", "测试");
        // 生成的SQL: WHERE user_name NOT LIKE '%测试%'
        
        List<User> users = userMapper.selectList(queryWrapper);
        log.info("模糊查询结果: {} 条", users.size());
    }

    /**
     * 4. 空值判断查询
     */
    public void nullValueQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // isNull: 字段为NULL
        queryWrapper.isNull("email");
        // 生成的SQL: WHERE email IS NULL
        
        // isNotNull: 字段不为NULL
        queryWrapper.isNotNull("email");
        // 生成的SQL: WHERE email IS NOT NULL
        
        List<User> users = userMapper.selectList(queryWrapper);
        log.info("空值查询结果: {} 条", users.size());
    }

    /**
     * 5. IN 和 NOT IN 查询
     */
    public void inQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // in: 在列表中
        List<Integer> ages = Arrays.asList(18, 20, 22, 25);
        queryWrapper.in("age", ages);
        // 生成的SQL: WHERE age IN (18, 20, 22, 25)
        
        // notIn: 不在列表中
        queryWrapper.notIn("age", Arrays.asList(60, 65, 70));
        // 生成的SQL: WHERE age NOT IN (60, 65, 70)
        
        List<User> users = userMapper.selectList(queryWrapper);
        log.info("IN查询结果: {} 条", users.size());
    }

    /**
     * 6. 分组和排序
     */
    public void groupAndOrderQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // groupBy: 分组
        queryWrapper.groupBy("dept_id");
        // 生成的SQL: GROUP BY dept_id
        
        // having: 分组后过滤
        queryWrapper.having("COUNT(*) > 5");
        // 生成的SQL: HAVING COUNT(*) > 5
        
        // orderByAsc: 升序排序
        queryWrapper.orderByAsc("age");
        // 生成的SQL: ORDER BY age ASC
        
        // orderByDesc: 降序排序
        queryWrapper.orderByDesc("create_time");
        // 生成的SQL: ORDER BY create_time DESC
        
        // 多字段排序
        queryWrapper.orderByAsc("dept_id", "age")
                   .orderByDesc("create_time");
        // 生成的SQL: ORDER BY dept_id ASC, age ASC, create_time DESC
        
        List<User> users = userMapper.selectList(queryWrapper);
        log.info("分组排序查询结果: {} 条", users.size());
    }
}

3.2 QueryWrapper 高级用法详细解析

/**
 * QueryWrapper 高级用法详解
 * 重点:复杂查询条件的构建
 */
@Service
@Slf4j
public class AdvancedQueryWrapperService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 1. 动态条件构建
     * 重点:根据业务参数动态构建查询条件
     */
    public List<User> dynamicQuery(String userName, Integer minAge, 
                                 Integer maxAge, Long deptId) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 动态添加条件:只有当参数不为空时才添加条件
        if (StringUtils.isNotBlank(userName)) {
            queryWrapper.like("user_name", userName);
        }
        
        if (minAge != null) {
            queryWrapper.ge("age", minAge);
        }
        
        if (maxAge != null) {
            queryWrapper.le("age", maxAge);
        }
        
        if (deptId != null) {
            queryWrapper.eq("dept_id", deptId);
        }
        
        // 固定条件:逻辑删除过滤
        queryWrapper.eq("deleted", 0);
        
        // 排序
        queryWrapper.orderByDesc("create_time");
        
        return userMapper.selectList(queryWrapper);
    }

    /**
     * 2. 链式条件构建
     * 重点:使用链式调用构建复杂条件
     */
    public List<User> chainQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        queryWrapper.select("id", "user_name", "age", "email") // 指定查询字段
                   .like("user_name", "张")                    // 模糊查询
                   .between("age", 18, 30)                    // 范围查询
                   .isNotNull("email")                        // 非空查询
                   .eq("deleted", 0)                          // 等值查询
                   .orderByDesc("create_time")                // 排序
                   .last("LIMIT 100");                        // 最后拼接
        
        return userMapper.selectList(queryWrapper);
    }

    /**
     * 3. 嵌套查询(子查询)
     * 重点:使用 apply 和 exists 进行子查询
     */
    public List<User> nestedQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 使用 apply 进行子查询
        queryWrapper.apply("id IN (SELECT user_id FROM user_role WHERE role_id = {0}", 1);
        // 生成的SQL: WHERE id IN (SELECT user_id FROM user_role WHERE role_id = 1)
        
        // 使用 exists 进行存在性查询
        queryWrapper.exists("SELECT 1 FROM sys_dept WHERE id = dept_id AND status = 1");
        // 生成的SQL: WHERE EXISTS (SELECT 1 FROM sys_dept WHERE id = dept_id AND status = 1)
        
        return userMapper.selectList(queryWrapper);
    }

    /**
     * 4. OR 条件组合
     * 重点:构建 OR 逻辑的复杂查询
     */
    public List<User> orQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 方式1:直接使用 or()
        queryWrapper.eq("dept_id", 1)
                   .or()
                   .eq("dept_id", 2);
        // 生成的SQL: WHERE dept_id = 1 OR dept_id = 2
        
        // 方式2:嵌套 OR 条件
        queryWrapper.nested(i -> i.eq("dept_id", 1).or().eq("dept_id", 2))
                   .like("user_name", "张");
        // 生成的SQL: WHERE (dept_id = 1 OR dept_id = 2) AND user_name LIKE '%张%'
        
        // 方式3:复杂的 OR 条件组合
        queryWrapper.and(i -> i.eq("status", 1).gt("age", 18))
                   .or(i -> i.eq("status", 2).lt("age", 60));
        // 生成的SQL: WHERE (status = 1 AND age > 18) OR (status = 2 AND age < 60)
        
        return userMapper.selectList(queryWrapper);
    }

    /**
     * 5. 函数查询
     * 重点:使用数据库函数进行查询
     */
    public List<User> functionQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 使用日期函数
        queryWrapper.apply("DATE(create_time) = {0}", "2023-01-01");
        // 生成的SQL: WHERE DATE(create_time) = '2023-01-01'
        
        // 使用字符串函数
        queryWrapper.apply("LENGTH(user_name) > {0}", 5);
        // 生成的SQL: WHERE LENGTH(user_name) > 5
        
        // 使用数学函数
        queryWrapper.apply("MOD(age, 2) = {0}", 0);
        // 生成的SQL: WHERE MOD(age, 2) = 0
        
        return userMapper.selectList(queryWrapper);
    }

    /**
     * 6. 查询字段控制
     * 重点:精确控制查询的字段,提升性能
     */
    public List<Map<String, Object>> selectSpecificFields() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 指定查询字段
        queryWrapper.select("id", "user_name", "age", "email")
                   .eq("deleted", 0)
                   .orderByDesc("create_time");
        
        // 返回 Map 列表,只包含指定字段
        return userMapper.selectMaps(queryWrapper);
    }

    /**
     * 7. 聚合查询
     * 重点:使用聚合函数进行统计查询
     */
    public void aggregateQuery() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 计数查询
        queryWrapper.select("COUNT(*) as user_count")
                   .eq("deleted", 0);
        Map<String, Object> countMap = userMapper.selectMaps(queryWrapper).get(0);
        log.info("用户总数: {}", countMap.get("user_count"));
        
        // 分组统计
        queryWrapper = new QueryWrapper<>();
        queryWrapper.select("dept_id, COUNT(*) as dept_user_count")
                   .eq("deleted", 0)
                   .groupBy("dept_id")
                   .having("COUNT(*) > 0");
        List<Map<String, Object>> deptStats = userMapper.selectMaps(queryWrapper);
        
        for (Map<String, Object> stat : deptStats) {
            log.info("部门 {} 有 {} 个用户", 
                    stat.get("dept_id"), stat.get("dept_user_count"));
        }
    }
}

3.3 LambdaQueryWrapper 详细解析

/**
 * LambdaQueryWrapper 详细用法
 * 重点:类型安全的查询条件构建,避免硬编码字段名
 */
@Service
@Slf4j
public class LambdaQueryWrapperService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 1. LambdaQueryWrapper 基础用法
     */
    public void basicLambdaQuery() {
        // 创建 LambdaQueryWrapper
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        
        // 等值查询 - 使用方法引用,编译期检查
        lambdaWrapper.eq(User::getUserName, "张三");
        // 生成的SQL: WHERE user_name = '张三'
        
        // 范围查询
        lambdaWrapper.between(User::getAge, 18, 30);
        // 生成的SQL: WHERE age BETWEEN 18 AND 30
        
        // 模糊查询
        lambdaWrapper.like(User::getUserName, "张");
        // 生成的SQL: WHERE user_name LIKE '%张%'
        
        List<User> users = userMapper.selectList(lambdaWrapper);
        log.info("Lambda查询结果: {} 条", users.size());
    }

    /**
     * 2. 动态Lambda查询
     * 重点:类型安全的动态条件构建
     */
    public List<User> dynamicLambdaQuery(UserQueryDTO queryDTO) {
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        
        // 动态条件构建 - 类型安全,IDE智能提示
        lambdaWrapper.eq(queryDTO.getDeptId() != null, User::getDeptId, queryDTO.getDeptId())
                    .like(StringUtils.isNotBlank(queryDTO.getUserName()), 
                          User::getUserName, queryDTO.getUserName())
                    .ge(queryDTO.getMinAge() != null, User::getAge, queryDTO.getMinAge())
                    .le(queryDTO.getMaxAge() != null, User::getAge, queryDTO.getMaxAge())
                    .eq(User::getDeleted, 0)
                    .orderByDesc(User::getCreateTime);
        
        return userMapper.selectList(lambdaWrapper);
    }

    /**
     * 3. Lambda查询字段选择
     * 重点:类型安全的字段选择
     */
    public List<User> lambdaSelectFields() {
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        
        // 指定查询字段 - 编译期检查字段名正确性
        lambdaWrapper.select(User::getId, User::getUserName, User::getAge, User::getEmail)
                    .eq(User::getDeleted, 0)
                    .orderByDesc(User::getCreateTime);
        
        return userMapper.selectList(lambdaWrapper);
    }

    /**
     * 4. 复杂的Lambda条件组合
     */
    public List<User> complexLambdaCondition() {
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        
        // 复杂的 AND/OR 条件组合
        lambdaWrapper.and(wrapper -> wrapper
                .eq(User::getStatus, 1)
                .or()
                .eq(User::getStatus, 2))
                .and(wrapper -> wrapper
                        .ge(User::getAge, 18)
                        .le(User::getAge, 60))
                .like(User::getUserName, "张")
                .orderByAsc(User::getAge)
                .orderByDesc(User::getCreateTime);
        
        return userMapper.selectList(lambdaWrapper);
    }

    /**
     * 5. Lambda表达式与普通条件混合使用
     */
    public List<User> mixedConditionQuery() {
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        
        // Lambda表达式条件
        lambdaWrapper.eq(User::getDeleted, 0)
                    .ge(User::getAge, 18);
        
        // 普通条件(复杂条件或函数条件)
        lambdaWrapper.apply("LENGTH(user_name) > {0}", 2)
                    .like("user_name", "张");
        
        return userMapper.selectList(lambdaWrapper);
    }
}

/**
 * 查询参数DTO
 */
@Data
class UserQueryDTO {
    private String userName;
    private Integer minAge;
    private Integer maxAge;
    private Long deptId;
    private Integer pageNum = 1;
    private Integer pageSize = 10;
}

3.4 QueryWrapper 底层原理深度解析

/**
 * QueryWrapper 底层原理分析
 * 重点:理解 MyBatis-Plus 如何将 Java 条件转换为 SQL
 */
public class QueryWrapperPrincipleAnalysis {

    /**
     * 1. QueryWrapper 核心结构分析
     */
    public void analyzeQueryWrapperStructure() {
        /*
        QueryWrapper 继承结构:
        QueryWrapper -> AbstractWrapper -> Wrapper
        
        核心字段:
        - List<Segment> expression: 存储所有条件表达式
        - String sqlSelect: 存储查询字段
        - String sqlComment: 存储注释
        - String sqlFirst: 存储前置SQL(如 FOR UPDATE)
        */
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_name", "张三")
                   .gt("age", 18)
                   .orderByDesc("create_time");
        
        // 底层会构建这样的表达式树:
        // [
        //   EqSegment(column=user_name, value=张三),
        //   GtSegment(column=age, value=18),
        //   OrderBySegment(column=create_time, isAsc=false)
        // ]
    }

    /**
     * 2. SQL 生成过程分析
     */
    public void analyzeSqlGeneration() {
        /*
        SQL 生成步骤:
        1. 解析 QueryWrapper 中的表达式列表
        2. 根据表达式类型生成对应的 SQL 片段
        3. 拼接 WHERE 条件
        4. 处理排序、分组等
        5. 参数化处理,防止 SQL 注入
        */
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_name", "张三")
                   .gt("age", 18);
        
        // 生成的 SQL 模板:
        // SELECT * FROM sys_user WHERE user_name = ? AND age > ?
        
        // 参数列表:
        // [0] = "张三"
        // [1] = 18
    }

    /**
     * 3. LambdaQueryWrapper 原理分析
     */
    public void analyzeLambdaQueryWrapper() {
        /*
        LambdaQueryWrapper 原理:
        1. 使用方法引用(如 User::getUserName)
        2. 通过 SerializedLambda 解析方法引用
        3. 获取对应的字段名(如 user_name)
        4. 后续处理与 QueryWrapper 相同
        */
        
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        lambdaWrapper.eq(User::getUserName, "张三");
        
        // 底层过程:
        // 1. User::getUserName -> 方法引用
        // 2. LambdaUtils.resolve(User::getUserName) -> 获取字段信息
        // 3. 转换为 "user_name" 字符串
        // 4. 构建 EqSegment(column=user_name, value=张三)
    }
}

3.5 与原生 MyBatis 对比详细示例

/**
 * QueryWrapper 与原生 MyBatis 对比
 * 重点:展示两种方式的差异和优劣
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    /**
     * 方式1:使用 MyBatis-Plus QueryWrapper
     * 优点:代码简洁,动态条件构建方便
     */
    default List<User> findUsersByWrapper(String userName, Integer minAge, 
                                        Integer maxAge, Long deptId) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        if (StringUtils.isNotBlank(userName)) {
            queryWrapper.like("user_name", userName);
        }
        if (minAge != null) {
            queryWrapper.ge("age", minAge);
        }
        if (maxAge != null) {
            queryWrapper.le("age", maxAge);
        }
        if (deptId != null) {
            queryWrapper.eq("dept_id", deptId);
        }
        queryWrapper.eq("deleted", 0)
                   .orderByDesc("create_time");
        
        return selectList(queryWrapper);
    }

    /**
     * 方式2:使用原生 MyBatis XML 配置
     * 优点:复杂SQL更清晰,SQL调优更方便
     */
    List<User> findUsersByXML(@Param("userName") String userName,
                            @Param("minAge") Integer minAge,
                            @Param("maxAge") Integer maxAge,
                            @Param("deptId") Long deptId);
}
<!-- UserMapper.xml - 原生 MyBatis 动态 SQL -->
<mapper namespace="com.enterprise.mapper.UserMapper">
    
    <select id="findUsersByXML" resultType="User">
        SELECT id, user_name, age, email, dept_id, create_time
        FROM sys_user
        WHERE deleted = 0
        <if test="userName != null and userName != ''">
            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age <= #{maxAge}
        </if>
        <if test="deptId != null">
            AND dept_id = #{deptId}
        </if>
        ORDER BY create_time DESC
    </select>
    
</mapper>

3.6 企业级避坑方案详细解析

/**
 * QueryWrapper 企业级避坑方案
 * 重点:生产环境中常见问题和解决方案
 */
@Service
@Slf4j
public class QueryWrapperBestPractice {

    @Autowired
    private UserMapper userMapper;

    /**
     * 坑点1:SQL注入风险
     */
    public void avoidSqlInjection() {
        // 错误做法:直接拼接用户输入
        String userInput = "张三'; DROP TABLE sys_user; --";
        QueryWrapper<User> wrongWrapper = new QueryWrapper<>();
        wrongWrapper.apply("user_name = '" + userInput + "'"); // 危险!
        
        // 正确做法1:使用参数化查询
        QueryWrapper<User> safeWrapper1 = new QueryWrapper<>();
        safeWrapper1.apply("user_name = {0}", userInput); // 安全
        
        // 正确做法2:使用预定义方法
        QueryWrapper<User> safeWrapper2 = new QueryWrapper<>();
        safeWrapper2.eq("user_name", userInput); // 安全
    }

    /**
     * 坑点2:条件覆盖问题
     */
    public void avoidConditionOverride() {
        // 错误做法:在循环中重复创建Wrapper
        List<String> names = Arrays.asList("张三", "李四", "王五");
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        for (String name : names) {
            wrapper = new QueryWrapper<>(); // 每次循环覆盖之前条件
            wrapper.eq("user_name", name);
            List<User> users = userMapper.selectList(wrapper); // 每次只查一个名字
        }
        
        // 正确做法:使用IN查询或OR条件
        QueryWrapper<User> correctWrapper = new QueryWrapper<>();
        correctWrapper.in("user_name", names); // 一次查询所有
        List<User> allUsers = userMapper.selectList(correctWrapper);
    }

    /**
     * 坑点3:N+1查询问题
     */
    public void avoidNPlusOneQuery() {
        // 错误做法:循环中查询关联数据
        List<User> users = userMapper.selectList(new QueryWrapper<>());
        for (User user : users) {
            // 为每个用户查询部门信息 - N+1查询
            // String deptName = deptMapper.selectDeptNameById(user.getDeptId());
        }
        
        // 正确做法:使用JOIN查询一次获取所有数据
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("u.*", "d.dept_name")
               .eq("u.deleted", 0)
               .last("LEFT JOIN sys_dept d ON u.dept_id = d.id");
        List<Map<String, Object>> userWithDept = userMapper.selectMaps(wrapper);
    }

    /**
     * 坑点4:索引失效问题
     */
    public void avoidIndexInvalidation() {
        // 错误做法:对索引列使用函数
        QueryWrapper<User> wrongWrapper = new QueryWrapper<>();
        wrongWrapper.apply("YEAR(create_time) = 2023"); // 可能导致索引失效
        
        // 正确做法:使用范围查询
        QueryWrapper<User> correctWrapper = new QueryWrapper<>();
        correctWrapper.between("create_time", 
            LocalDateTime.of(2023, 1, 1, 0, 0),
            LocalDateTime.of(2023, 12, 31, 23, 59));
    }

    /**
     * 坑点5:分页性能问题
     */
    public void avoidPaginationPerformanceIssue() {
        // 错误做法:直接对大偏移量分页
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("deleted", 0)
               .orderByDesc("create_time");
        
        // 当 pageNum 很大时性能极差
        Page<User> page = new Page<>(10000, 10);
        IPage<User> userPage = userMapper.selectPage(page, wrapper);
        
        // 正确做法:使用游标分页或子查询优化
        QueryWrapper<User> optimizedWrapper = new QueryWrapper<>();
        optimizedWrapper.eq("deleted", 0)
                       .gt("id", 100000) // 基于ID的游标分页
                       .orderByAsc("id")
                       .last("LIMIT 10");
        List<User> users = userMapper.selectList(optimizedWrapper);
    }

    /**
     * 坑点6:内存溢出问题
     */
    public void avoidMemoryOverflow() {
        // 错误做法:一次性查询大量数据
        QueryWrapper<User> wrongWrapper = new QueryWrapper<>();
        wrongWrapper.eq("deleted", 0);
        List<User> allUsers = userMapper.selectList(wrongWrapper); // 可能OOM
        
        // 正确做法:分批次查询
        int batchSize = 1000;
        long total = userMapper.selectCount(new QueryWrapper<>());
        
        for (int i = 0; i < total; i += batchSize) {
            QueryWrapper<User> batchWrapper = new QueryWrapper<>();
            batchWrapper.eq("deleted", 0)
                       .last("LIMIT " + batchSize + " OFFSET " + i);
            List<User> batchUsers = userMapper.selectList(batchWrapper);
            // 处理批次数据
            processBatchUsers(batchUsers);
        }
    }
    
    private void processBatchUsers(List<User> users) {
        // 处理批次数据
        log.info("处理 {} 条用户数据", users.size());
    }

    /**
     * 最佳实践:统一的查询构建方法
     */
    public QueryWrapper<User> buildUserQueryWrapper(UserQueryDTO queryDTO) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // 基础条件
        wrapper.eq("deleted", 0);
        
        // 动态条件
        if (StringUtils.isNotBlank(queryDTO.getUserName())) {
            wrapper.like("user_name", queryDTO.getUserName().trim());
        }
        
        if (queryDTO.getMinAge() != null && queryDTO.getMinAge() > 0) {
            wrapper.ge("age", queryDTO.getMinAge());
        }
        
        if (queryDTO.getMaxAge() != null && queryDTO.getMaxAge() > 0) {
            wrapper.le("age", queryDTO.getMaxAge());
        }
        
        if (queryDTO.getDeptId() != null) {
            wrapper.eq("dept_id", queryDTO.getDeptId());
        }
        
        // 排序
        wrapper.orderByDesc("create_time");
        
        // 限制最大查询数量(防止恶意查询)
        if (queryDTO.getPageSize() != null && queryDTO.getPageSize() > 1000) {
            wrapper.last("LIMIT 1000");
        }
        
        return wrapper;
    }
}

3.7 完整测试用例

/**
 * QueryWrapper 完整测试用例
 * 重点:覆盖所有重要场景的测试
 */
@SpringBootTest
@Slf4j
class QueryWrapperTest {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private QueryWrapperService queryWrapperService;

    @BeforeEach
    void setUp() {
        // 准备测试数据
        prepareTestData();
    }

    @Test
    void testBasicQueryWrapper() {
        // 测试基础查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("user_name", "测试用户1")
               .eq("deleted", 0);
        
        List<User> users = userMapper.selectList(wrapper);
        
        assertNotNull(users);
        assertEquals(1, users.size());
        assertEquals("测试用户1", users.get(0).getUserName());
    }

    @Test
    void testRangeQuery() {
        // 测试范围查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age", 20, 30)
               .eq("deleted", 0);
        
        List<User> users = userMapper.selectList(wrapper);
        
        assertNotNull(users);
        assertTrue(users.size() >= 2);
        
        // 验证所有结果都在指定范围内
        for (User user : users) {
            assertTrue(user.getAge() >= 20 && user.getAge() <= 30);
        }
    }

    @Test
    void testLambdaQueryWrapper() {
        // 测试Lambda查询
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        lambdaWrapper.eq(User::getUserName, "测试用户1")
                    .eq(User::getDeleted, 0);
        
        List<User> users = userMapper.selectList(lambdaWrapper);
        
        assertNotNull(users);
        assertEquals(1, users.size());
        assertEquals("测试用户1", users.get(0).getUserName());
    }

    @Test
    void testDynamicQuery() {
        // 测试动态查询
        UserQueryDTO queryDTO = new UserQueryDTO();
        queryDTO.setUserName("测试");
        queryDTO.setMinAge(20);
        queryDTO.setMaxAge(30);
        
        List<User> users = queryWrapperService.dynamicQuery(
            queryDTO.getUserName(), 
            queryDTO.getMinAge(), 
            queryDTO.getMaxAge(), 
            null
        );
        
        assertNotNull(users);
        assertTrue(users.size() >= 2);
    }

    @Test
    void testComplexCondition() {
        // 测试复杂条件
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("id", "user_name", "age")
               .like("user_name", "测试")
               .in("age", Arrays.asList(20, 25, 30))
               .eq("deleted", 0)
               .orderByDesc("create_time")
               .last("LIMIT 5");
        
        List<User> users = userMapper.selectList(wrapper);
        
        assertNotNull(users);
        assertTrue(users.size() <= 5);
        
        // 验证字段选择
        for (User user : users) {
            assertNotNull(user.getId());
            assertNotNull(user.getUserName());
            assertNotNull(user.getAge());
            assertNull(user.getEmail()); // 未选择email字段,应该为null
        }
    }

    @Test
    void testOrCondition() {
        // 测试OR条件
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("dept_id", 1)
               .or()
               .eq("dept_id", 2)
               .eq("deleted", 0);
        
        List<User> users = userMapper.selectList(wrapper);
        
        assertNotNull(users);
        
        // 验证所有结果都满足条件
        for (User user : users) {
            assertTrue(user.getDeptId() == 1 || user.getDeptId() == 2);
        }
    }

    private void prepareTestData() {
        // 清理测试数据
        userMapper.delete(new QueryWrapper<User>().like("user_name", "测试用户"));
        
        // 插入测试数据
        List<User> testUsers = Arrays.asList(
            createUser("测试用户1", 20, "test1@example.com", 1L),
            createUser("测试用户2", 25, "test2@example.com", 1L),
            createUser("测试用户3", 30, "test3@example.com", 2L),
            createUser("测试用户4", 35, "test4@example.com", 2L),
            createUser("其他用户", 40, "other@example.com", 3L)
        );
        
        for (User user : testUsers) {
            userMapper.insert(user);
        }
        
        log.info("测试数据准备完成,共插入 {} 条记录", testUsers.size());
    }

    private User createUser(String name, Integer age, String email, Long deptId) {
        User user = new User();
        user.setUserName(name);
        user.setAge(age);
        user.setEmail(email);
        user.setDeptId(deptId);
        return user;
    }
}

企业级MyBatis-Plus实战教学(续)

模块四:分页插件实现原理与性能优化

4.1 分页插件详细配置

/**
 * 分页插件深度配置详解
 * 重点:企业级分页配置和性能调优
 */
@Configuration
@Slf4j
public class PaginationConfig {

    /**
     * MyBatis-Plus 分页拦截器详细配置
     * 重点:拦截器执行顺序和配置参数详解
     */
    @Bean
    @Order(1) // 重点:确保分页拦截器在第一个执行
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 1. 分页插件配置(必须第一个添加)
        PaginationInnerInterceptor paginationInterceptor = createPaginationInterceptor();
        interceptor.addInnerInterceptor(paginationInterceptor);
        
        // 2. 乐观锁插件
        OptimisticLockerInnerInterceptor optimisticLockerInterceptor = 
            new OptimisticLockerInnerInterceptor();
        interceptor.addInnerInterceptor(optimisticLockerInterceptor);
        
        // 3. 防止全表更新与删除插件
        BlockAttackInnerInterceptor blockAttackInterceptor = 
            new BlockAttackInnerInterceptor();
        interceptor.addInnerInterceptor(blockAttackInterceptor);
        
        log.info("MyBatis-Plus 拦截器配置完成,包含分页、乐观锁、防全表操作插件");
        return interceptor;
    }

    /**
     * 创建分页拦截器详细配置
     */
    private PaginationInnerInterceptor createPaginationInterceptor() {
        PaginationInnerInterceptor paginationInterceptor = 
            new PaginationInnerInterceptor();
        
        // 1. 设置数据库类型
        paginationInterceptor.setDbType(DbType.MYSQL);
        log.info("设置数据库类型: {}", DbType.MYSQL);
        
        // 2. 设置最大单页限制数量
        paginationInterceptor.setMaxLimit(1000L);
        log.info("设置最大单页限制: {} 条", 1000);
        
        // 3. 开启溢出处理
        paginationInterceptor.setOverflow(true);
        log.info("开启溢出处理: {}", true);
        // 溢出处理说明:当请求页码大于最大页码时,true返回首页,false继续请求默认false
        
        // 4. 开启 count 查询 join 优化
        paginationInterceptor.setOptimizeJoin(true);
        log.info("开启count查询join优化: {}", true);
        // 优化说明:只针对部分 left join 进行优化
        
        // 5. 设置默认分页参数(可选)
        // paginationInterceptor.setProperties(new Properties());
        
        return paginationInterceptor;
    }

    /**
     * SQL 性能分析插件(仅开发环境使用)
     * 重点:生产环境必须关闭,否则有性能和安全风险
     */
    @Bean
    @Profile({"dev", "test"}) // 只在开发测试环境生效
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        
        // 设置SQL执行最大时间,单位:ms
        performanceInterceptor.setMaxTime(1000L);
        log.info("设置SQL执行最大时间: {}ms", 1000);
        
        // 设置是否格式化SQL
        performanceInterceptor.setFormat(true);
        log.info("设置SQL格式化: {}", true);
        
        // 设置SQL输出到控制台
        performanceInterceptor.setWriteInLog(true);
        log.info("设置SQL日志输出: {}", true);
        
        return performanceInterceptor;
    }
}

4.2 分页查询基础用法详细解析

/**
 * 分页查询基础用法详解
 * 重点:各种分页场景的完整实现
 */
@Service
@Slf4j
public class PaginationBasicService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 1. 基础分页查询
     * 重点:Page 对象的创建和使用
     */
    public Page<User> basicPagination(Integer pageNum, Integer pageSize) {
        // 创建分页对象
        // 重点:Page 构造函数参数 (当前页码, 每页显示条数)
        Page<User> page = new Page<>(pageNum, pageSize);
        
        // 构建查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 0)
                   .orderByDesc("create_time");
        
        log.info("执行基础分页查询 - 页码: {}, 页大小: {}", pageNum, pageSize);
        
        // 执行分页查询
        // 重点:selectPage 方法会自动执行两条SQL:
        // 1. SELECT COUNT(*) FROM sys_user WHERE deleted = 0
        // 2. SELECT * FROM sys_user WHERE deleted = 0 ORDER BY create_time DESC LIMIT ?
        Page<User> resultPage = userMapper.selectPage(page, queryWrapper);
        
        // 分页结果分析
        log.info("分页查询结果 - 总记录数: {}, 总页数: {}, 当前页记录数: {}", 
                resultPage.getTotal(), resultPage.getPages(), resultPage.getRecords().size());
        
        return resultPage;
    }

    /**
     * 2. 不进行 count 查询的分页
     * 重点:提升性能,适用于不需要知道总记录数的场景
     */
    public Page<User> paginationWithoutCount(Integer pageNum, Integer pageSize) {
        // 创建不分页对象,第三个参数为 false 表示不执行 count 查询
        Page<User> page = new Page<>(pageNum, pageSize, false);
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 0)
                   .orderByDesc("create_time");
        
        log.info("执行不分页查询 - 页码: {}, 页大小: {}, 不执行count查询", pageNum, pageSize);
        
        Page<User> resultPage = userMapper.selectPage(page, queryWrapper);
        
        // 注意:不执行count查询时,getTotal() 返回 0,getPages() 返回 0
        log.info("不分页查询结果 - 当前页记录数: {}", resultPage.getRecords().size());
        
        return resultPage;
    }

    /**
     * 3. 自定义 count 查询的分页
     * 重点:复杂查询时优化 count 查询性能
     */
    public Page<User> paginationWithCustomCount(Integer pageNum, Integer pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 0)
                   .like("user_name", "张")
                   .orderByDesc("create_time");
        
        log.info("执行自定义count分页查询");
        
        // 对于复杂查询,可以在XML中自定义count查询
        Page<User> resultPage = userMapper.selectUserPageWithCustomCount(page, queryWrapper);
        
        return resultPage;
    }

    /**
     * 4. 多表关联分页查询
     * 重点:关联查询的分页处理
     */
    public Page<UserVO> paginationWithJoin(Integer pageNum, Integer pageSize) {
        Page<UserVO> page = new Page<>(pageNum, pageSize);
        
        log.info("执行多表关联分页查询");
        
        // 使用自定义的关联查询方法
        Page<UserVO> resultPage = userMapper.selectUserWithDeptPage(page);
        
        log.info("关联分页查询结果 - 总记录数: {}, 总页数: {}", 
                resultPage.getTotal(), resultPage.getPages());
        
        return resultPage;
    }

    /**
     * 5. Lambda 表达式分页查询
     * 重点:类型安全的分页查询
     */
    public Page<User> lambdaPagination(Integer pageNum, Integer pageSize, String userName) {
        Page<User> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
        lambdaWrapper.eq(User::getDeleted, 0)
                    .like(StringUtils.isNotBlank(userName), User::getUserName, userName)
                    .orderByDesc(User::getCreateTime);
        
        log.info("执行Lambda分页查询 - 用户名过滤: {}", userName);
        
        Page<User> resultPage = userMapper.selectPage(page, lambdaWrapper);
        
        return resultPage;
    }
}

4.3 自定义分页 Mapper 详细实现

/**
 * 自定义分页 Mapper 详解
 * 重点:复杂分页查询的XML配置和接口定义
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

    /**
     * 1. 基础分页查询(使用BaseMapper自带方法)
     * 无需额外定义,直接使用 selectPage 方法
     */

    /**
     * 2. 自定义 count 查询的分页方法
     * 重点:使用 @Param("ew") 注解接收 QueryWrapper
     */
    IPage<User> selectUserPageWithCustomCount(IPage<User> page, 
                                            @Param("ew") QueryWrapper<User> queryWrapper);

    /**
     * 3. 多表关联分页查询
     * 重点:返回VO对象的分页查询
     */
    IPage<UserVO> selectUserWithDeptPage(IPage<UserVO> page);

    /**
     * 4. 复杂条件分页查询
     */
    IPage<User> selectUsersByComplexCondition(IPage<User> page,
                                            @Param("userName") String userName,
                                            @Param("minAge") Integer minAge,
                                            @Param("maxAge") Integer maxAge,
                                            @Param("deptIds") List<Long> deptIds);

    /**
     * 5. 分页查询返回 Map
     * 重点:灵活的结果集处理
     */
    IPage<Map<String, Object>> selectUserMapsPage(IPage<Map<String, Object>> page,
                                                @Param("ew") QueryWrapper<User> queryWrapper);
}
<!-- UserMapper.xml 分页查询详细配置 -->
<mapper namespace="com.enterprise.mapper.UserMapper">

    <!-- 1. 基础分页查询 - 使用MyBatis-Plus自动生成 -->

    <!-- 2. 自定义 count 查询的分页 -->
    <select id="selectUserPageWithCustomCount" resultType="User">
        SELECT 
            id, user_name, age, email, dept_id, create_time
        FROM sys_user
        <!-- 重点:使用 ${ew.customSqlSegment} 注入QueryWrapper条件 -->
        ${ew.customSqlSegment}
    </select>

    <!-- 自定义 count 查询 -->
    <select id="selectUserPageWithCustomCount_count" resultType="java.lang.Long">
        SELECT COUNT(*)
        FROM sys_user
        ${ew.customSqlSegment}
    </select>

    <!-- 3. 多表关联分页查询 -->
    <select id="selectUserWithDeptPage" resultType="UserVO">
        SELECT 
            u.id,
            u.user_name,
            u.age,
            u.email,
            u.create_time,
            d.dept_name,
            d.leader
        FROM sys_user u
        LEFT JOIN sys_dept d ON u.dept_id = d.id
        WHERE u.deleted = 0 AND d.deleted = 0
        ORDER BY u.create_time DESC
    </select>

    <!-- 关联查询的 count -->
    <select id="selectUserWithDeptPage_count" resultType="java.lang.Long">
        SELECT COUNT(*)
        FROM sys_user u
        LEFT JOIN sys_dept d ON u.dept_id = d.id
        WHERE u.deleted = 0 AND d.deleted = 0
    </select>

    <!-- 4. 复杂条件分页查询 -->
    <select id="selectUsersByComplexCondition" resultType="User">
        SELECT 
            id, user_name, age, email, dept_id, create_time
        FROM sys_user
        WHERE deleted = 0
        <if test="userName != null and userName != ''">
            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age <= #{maxAge}
        </if>
        <if test="deptIds != null and deptIds.size() > 0">
            AND dept_id IN
            <foreach collection="deptIds" item="deptId" open="(" close=")" separator=",">
                #{deptId}
            </foreach>
        </if>
        ORDER BY create_time DESC
    </select>

    <!-- 5. 分页查询返回 Map -->
    <select id="selectUserMapsPage" resultType="java.util.Map">
        SELECT 
            id, user_name, age, email
        FROM sys_user
        ${ew.customSqlSegment}
    </select>

</select>

4.4 分页结果封装和响应处理

/**
 * 分页结果统一封装
 * 重点:企业级分页响应格式标准化
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 响应状态码
     */
    private Integer code;

    /**
     * 响应消息
     */
    private String message;

    /**
     * 分页数据
     */
    private PageData<T> data;

    /**
     * 响应时间戳
     */
    private Long timestamp;

    /**
     * 成功响应
     */
    public static <T> PageResult<T> success(Page<T> page) {
        return PageResult.<T>builder()
                .code(200)
                .message("success")
                .data(PageData.of(page))
                .timestamp(System.currentTimeMillis())
                .build();
    }

    /**
     * 失败响应
     */
    public static <T> PageResult<T> error(String message) {
        return PageResult.<T>builder()
                .code(500)
                .message(message)
                .timestamp(System.currentTimeMillis())
                .build();
    }
}

/**
 * 分页数据封装
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageData<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 当前页
     */
    private Long current;

    /**
     * 每页大小
     */
    private Long size;

    /**
     * 总记录数
     */
    private Long total;

    /**
     * 总页数
     */
    private Long pages;

    /**
     * 数据列表
     */
    private List<T> records;

    /**
     * 是否有上一页
     */
    private Boolean hasPrevious;

    /**
     * 是否有下一页
     */
    private Boolean hasNext;

    public static <T> PageData<T> of(Page<T> page) {
        return PageData.<T>builder()
                .current(page.getCurrent())
                .size(page.getSize())
                .total(page.getTotal())
                .pages(page.getPages())
                .records(page.getRecords())
                .hasPrevious(page.getCurrent() > 1)
                .hasNext(page.getCurrent() < page.getPages())
                .build();
    }
}

/**
 * 分页查询参数基类
 */
@Data
public class BasePageQuery implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 当前页码,默认第一页
     */
    @Min(value = 1, message = "页码不能小于1")
    private Integer pageNum = 1;

    /**
     * 每页数量,默认10条
     */
    @Min(value = 1, message = "每页数量不能小于1")
    @Max(value = 1000, message = "每页数量不能大于1000")
    private Integer pageSize = 10;

    /**
     * 排序字段
     */
    private String sortBy;

    /**
     * 排序方向:ASC-升序,DESC-降序
     */
    private String sortDirection = "DESC";

    /**
     * 转换为 MyBatis-Plus Page 对象
     */
    public <T> Page<T> toPage() {
        Page<T> page = new Page<>(pageNum, pageSize);
        
        // 设置排序
        if (StringUtils.isNotBlank(sortBy)) {
            if ("ASC".equalsIgnoreCase(sortDirection)) {
                page.addOrder(OrderItem.asc(sortBy));
            } else {
                page.addOrder(OrderItem.desc(sortBy));
            }
        }
        
        return page;
    }

    /**
     * 计算偏移量
     */
    public Long getOffset() {
        return (long) (pageNum - 1) * pageSize;
    }
}

/**
 * 用户分页查询参数
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class UserPageQuery extends BasePageQuery {
    
    /**
     * 用户名模糊查询
     */
    private String userName;

    /**
     * 年龄范围查询 - 最小值
     */
    @Min(value = 0, message = "年龄不能小于0")
    private Integer minAge;

    /**
     * 年龄范围查询 - 最大值
     */
    @Min(value = 0, message = "年龄不能小于0")
    private Integer maxAge;

    /**
     * 部门ID
     */
    private Long deptId;

    /**
     * 邮箱模糊查询
     */
    private String email;

    /**
     * 创建时间范围 - 开始时间
     */
    private LocalDateTime createTimeStart;

    /**
     * 创建时间范围 - 结束时间
     */
    private LocalDateTime createTimeEnd;
}

4.5 分页插件底层原理深度解析

/**
 * 分页插件底层原理深度解析
 * 重点:理解分页查询的完整执行流程
 */
@Slf4j
public class PaginationPrincipleAnalysis {

    /**
     * 分页拦截器执行流程分析
     */
    public void analyzePaginationProcess() {
        /*
        分页拦截器(PaginationInnerInterceptor)执行流程:
        
        1. 拦截 Executor 的 query 方法
        2. 判断是否需要进行分页(方法参数中是否包含 IPage 对象)
        3. 如果需要分页:
           a. 执行 count 查询获取总记录数
           b. 根据数据库类型重写原始 SQL,添加分页条件
           c. 执行分页查询
           d. 将结果封装到 IPage 对象中
        4. 如果不需要分页,直接执行原始查询
        */
    }

    /**
     * 分页 SQL 生成原理
     */
    public void analyzePaginationSqlGeneration() {
        /*
        MySQL 分页 SQL 生成:
        原始SQL: SELECT * FROM sys_user WHERE deleted = 0 ORDER BY create_time DESC
        分页SQL: SELECT * FROM sys_user WHERE deleted = 0 ORDER BY create_time DESC LIMIT 0, 10
        
        CountSQL: SELECT COUNT(*) FROM sys_user WHERE deleted = 0
        
        生成步骤:
        1. 解析原始 SQL,识别查询主体
        2. 生成 count 查询 SQL
        3. 根据分页参数生成分页 SQL
        4. 参数化处理,防止 SQL 注入
        */
    }

    /**
     * 不同数据库的分页差异处理
     */
    public void analyzeDifferentDatabasePagination() {
        /*
        MySQL: LIMIT #{offset}, #{pageSize}
        PostgreSQL: LIMIT #{pageSize} OFFSET #{offset}
        Oracle: 
            SELECT * FROM (
                SELECT tmp.*, ROWNUM row_num FROM (
                    SELECT * FROM table ORDER BY id
                ) tmp WHERE ROWNUM <= #{end}
            ) WHERE row_num > #{start}
        SQL Server: OFFSET #{offset} ROWS FETCH NEXT #{pageSize} ROWS ONLY
        */
    }

    /**
     * 模拟分页拦截器核心代码
     */
    public class MockPaginationInterceptor implements Interceptor {
        
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 获取方法参数
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1];
            
            // 检查是否包含 IPage 参数
            IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
            
            if (page != null) {
                // 执行 count 查询
                Long total = executeCountQuery(invocation, ms, parameter);
                page.setTotal(total);
                
                // 重写 SQL 添加分页条件
                BoundSql boundSql = (BoundSql) args[5];
                String originalSql = boundSql.getSql();
                String paginationSql = buildPaginationSql(originalSql, page);
                
                // 使用反射修改 BoundSql 的 SQL
                Field sqlField = boundSql.getClass().getDeclaredField("sql");
                sqlField.setAccessible(true);
                sqlField.set(boundSql, paginationSql);
            }
            
            // 继续执行查询
            return invocation.proceed();
        }
        
        private Long executeCountQuery(Invocation invocation, MappedStatement ms, Object parameter) {
            // 创建 count 查询的 MappedStatement
            MappedStatement countMs = createCountMappedStatement(ms);
            
            // 执行 count 查询
            // ... 具体实现
            return 100L; // 模拟返回
        }
        
        private String buildPaginationSql(String originalSql, IPage<?> page) {
            // 根据数据库类型构建分页 SQL
            long offset = (page.getCurrent() - 1) * page.getSize();
            return originalSql + " LIMIT " + offset + ", " + page.getSize();
        }
    }
}

4.6 分页性能优化详细方案

/**
 * 分页性能优化详细方案
 * 重点:企业级大数据量分页性能优化
 */
@Service
@Slf4j
public class PaginationOptimizationService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 1. 游标分页(基于ID的连续分页)
     * 适用场景:大数据量下的连续分页,如无限滚动
     */
    public List<User> cursorPagination(Long lastId, Integer pageSize) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.gt(User::getId, lastId)  // 从上一次最后一条记录的ID开始
                   .eq(User::getDeleted, 0)
                   .orderByAsc(User::getId)   // 必须按ID排序
                   .last("LIMIT " + pageSize);
        
        List<User> users = userMapper.selectList(queryWrapper);
        
        log.info("游标分页查询 - 最后ID: {}, 页大小: {}, 结果数量: {}", 
                lastId, pageSize, users.size());
        
        return users;
    }

    /**
     * 2. 延迟关联分页优化
     * 适用场景:多表关联查询的大数据量分页
     */
    public Page<UserVO> delayedJoinPagination(Integer pageNum, Integer pageSize) {
        // 第一步:先分页查询主表ID
        Page<User> idPage = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<User> idWrapper = new LambdaQueryWrapper<>();
        idWrapper.select(User::getId)
                .eq(User::getDeleted, 0)
                .orderByDesc(User::getCreateTime);
        
        Page<User> idResult = userMapper.selectPage(idPage, idWrapper);
        
        // 第二步:根据ID列表查询完整数据(包含关联信息)
        List<Long> userIds = idResult.getRecords().stream()
                .map(User::getId)
                .collect(Collectors.toList());
        
        if (userIds.isEmpty()) {
            return new Page<>(pageNum, pageSize);
        }
        
        // 执行关联查询
        List<UserVO> userVOs = userMapper.selectUserWithDeptByIds(userIds);
        
        // 第三步:组装分页结果
        Page<UserVO> resultPage = new Page<>(pageNum, pageSize);
        resultPage.setRecords(userVOs);
        resultPage.setTotal(idResult.getTotal());
        resultPage.setPages(idResult.getPages());
        
        log.info("延迟关联分页优化 - 页码: {}, 页大小: {}, 总记录数: {}", 
                pageNum, pageSize, resultPage.getTotal());
        
        return resultPage;
    }

    /**
     * 3. 覆盖索引分页优化
     * 适用场景:只需要部分字段的分页查询
     */
    public Page<Map<String, Object>> coverIndexPagination(Integer pageNum, Integer pageSize) {
        Page<Map<String, Object>> page = new Page<>(pageNum, pageSize);
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "user_name", "create_time") // 只查询需要的字段
                   .eq("deleted", 0)
                   .orderByDesc("create_time");
        
        IPage<Map<String, Object>> resultPage = userMapper.selectMapsPage(page, queryWrapper);
        
        log.info("覆盖索引分页优化 - 查询字段数量: 3, 总记录数: {}", resultPage.getTotal());
        
        return (Page<Map<String, Object>>) resultPage;
    }

    /**
     * 4. 子查询分页优化(大偏移量优化)
     * 适用场景:深度分页性能优化
     */
    public List<User> subqueryPagination(Integer pageNum, Integer pageSize) {
        long offset = (long) (pageNum - 1) * pageSize;
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("id", 
                "SELECT id FROM sys_user WHERE deleted = 0 ORDER BY id LIMIT " + 
                offset + ", " + pageSize)
                .orderByAsc("id");
        
        List<User> users = userMapper.selectList(queryWrapper);
        
        log.info("子查询分页优化 - 偏移量: {}, 页大小: {}, 结果数量: {}", 
                offset, pageSize, users.size());
        
        return users;
    }

    /**
     * 5. 分批查询大数据量
     * 适用场景:数据导出、批量处理等需要处理全量数据的场景
     */
    public void batchProcessLargeData() {
        int batchSize = 1000;
        long totalCount = userMapper.selectCount(new QueryWrapper<User>().eq("deleted", 0));
        long totalPages = (totalCount + batchSize - 1) / batchSize;
        
        log.info("开始分批处理大数据量 - 总记录数: {}, 批次大小: {}, 总批次数: {}", 
                totalCount, batchSize, totalPages);
        
        for (long pageNum = 1; pageNum <= totalPages; pageNum++) {
            Page<User> page = new Page<>(pageNum, batchSize, false); // 不执行count查询
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("deleted", 0);
            
            Page<User> batchPage = userMapper.selectPage(page, queryWrapper);
            processUserBatch(batchPage.getRecords(), pageNum, totalPages);
        }
        
        log.info("分批处理大数据量完成");
    }
    
    private void processUserBatch(List<User> users, long currentPage, long totalPages) {
        // 模拟处理逻辑
        log.info("处理第 {}/{} 批次数据,本批数量: {}", currentPage, totalPages, users.size());
        
        // 实际业务处理
        for (User user : users) {
            // 处理每个用户
            processSingleUser(user);
        }
    }
    
    private void processSingleUser(User user) {
        // 单用户处理逻辑
    }

    /**
     * 6. 缓存分页结果优化
     * 适用场景:数据变化不频繁的分页查询
     */
    @Cacheable(value = "userPage", key = "#pageNum + '-' + #pageSize + '-' + #userName")
    public Page<User> cachedPagination(Integer pageNum, Integer pageSize, String userName) {
        Page<User> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getDeleted, 0)
                   .like(StringUtils.isNotBlank(userName), User::getUserName, userName)
                   .orderByDesc(User::getCreateTime);
        
        Page<User> resultPage = userMapper.selectPage(page, queryWrapper);
        
        log.info("执行分页查询并缓存 - 页码: {}, 页大小: {}, 用户名: {}", 
                pageNum, pageSize, userName);
        
        return resultPage;
    }
}

4.7 分页查询完整测试用例

/**
 * 分页查询完整测试用例
 * 重点:覆盖所有分页场景的测试
 */
@SpringBootTest
@Slf4j
class PaginationTest {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PaginationBasicService paginationService;

    @Autowired
    private PaginationOptimizationService optimizationService;

    @BeforeEach
    void setUp() {
        prepareTestData();
    }

    @Test
    void testBasicPagination() {
        // 测试基础分页
        Page<User> page = paginationService.basicPagination(1, 10);
        
        assertNotNull(page);
        assertNotNull(page.getRecords());
        assertTrue(page.getTotal() >= 0);
        assertTrue(page.getPages() >= 0);
        assertTrue(page.getRecords().size() <= 10);
        
        log.info("基础分页测试通过 - 总记录数: {}, 总页数: {}", page.getTotal(), page.getPages());
    }

    @Test
    void testPaginationWithoutCount() {
        // 测试不进行count查询的分页
        Page<User> page = paginationService.paginationWithoutCount(1, 10);
        
        assertNotNull(page);
        assertNotNull(page.getRecords());
        assertEquals(0, page.getTotal()); // 不执行count查询,total为0
        assertEquals(0, page.getPages()); // 不执行count查询,pages为0
        assertTrue(page.getRecords().size() <= 10);
        
        log.info("不分页查询测试通过 - 当前页记录数: {}", page.getRecords().size());
    }

    @Test
    void testLambdaPagination() {
        // 测试Lambda分页查询
        Page<User> page = paginationService.lambdaPagination(1, 10, "测试");
        
        assertNotNull(page);
        assertNotNull(page.getRecords());
        
        // 验证查询结果都包含"测试"
        for (User user : page.getRecords()) {
            assertTrue(user.getUserName().contains("测试"));
        }
        
        log.info("Lambda分页测试通过 - 结果数量: {}", page.getRecords().size());
    }

    @Test
    void testCursorPagination() {
        // 测试游标分页
        Long lastId = 0L;
        Integer pageSize = 5;
        
        List<User> users = optimizationService.cursorPagination(lastId, pageSize);
        
        assertNotNull(users);
        assertTrue(users.size() <= pageSize);
        
        // 验证ID是递增的
        for (int i = 1; i < users.size(); i++) {
            assertTrue(users.get(i).getId() > users.get(i-1).getId());
        }
        
        log.info("游标分页测试通过 - 结果数量: {}", users.size());
    }

    @Test
    void testDelayedJoinPagination() {
        // 测试延迟关联分页
        Page<UserVO> page = optimizationService.delayedJoinPagination(1, 10);
        
        assertNotNull(page);
        assertNotNull(page.getRecords());
        
        // 验证关联数据
        for (UserVO userVO : page.getRecords()) {
            assertNotNull(userVO.getDeptName());
        }
        
        log.info("延迟关联分页测试通过 - 结果数量: {}", page.getRecords().size());
    }

    @Test
    void testCoverIndexPagination() {
        // 测试覆盖索引分页
        Page<Map<String, Object>> page = optimizationService.coverIndexPagination(1, 10);
        
        assertNotNull(page);
        assertNotNull(page.getRecords());
        
        // 验证只返回指定字段
        for (Map<String, Object> record : page.getRecords()) {
            assertTrue(record.containsKey("id"));
            assertTrue(record.containsKey("user_name"));
            assertTrue(record.containsKey("create_time"));
            assertFalse(record.containsKey("email")); // 未选择的字段
        }
        
        log.info("覆盖索引分页测试通过 - 结果数量: {}", page.getRecords().size());
    }

    @Test
    void testBatchProcessLargeData() {
        // 测试分批处理
        assertDoesNotThrow(() -> optimizationService.batchProcessLargeData());
        log.info("分批处理大数据量测试通过");
    }

    @Test
    void testPageResultWrapper() {
        // 测试分页结果封装
        Page<User> page = paginationService.basicPagination(1, 10);
        PageResult<User> pageResult = PageResult.success(page);
        
        assertNotNull(pageResult);
        assertEquals(200, pageResult.getCode().intValue());
        assertEquals("success", pageResult.getMessage());
        assertNotNull(pageResult.getData());
        assertNotNull(pageResult.getTimestamp());
        
        log.info("分页结果封装测试通过");
    }

    private void prepareTestData() {
        // 清理测试数据
        userMapper.delete(new QueryWrapper<User>().like("user_name", "测试用户"));
        
        // 插入测试数据
        List<User> testUsers = new ArrayList<>();
        for (int i = 1; i <= 100; i++) {
            User user = new User();
            user.setUserName("测试用户" + i);
            user.setAge(20 + i % 30);
            user.setEmail("test" + i + "@example.com");
            user.setDeptId((long) (i % 3 + 1));
            testUsers.add(user);
        }
        
        for (User user : testUsers) {
            userMapper.insert(user);
        }
        
        log.info("分页测试数据准备完成,共插入 {} 条记录", testUsers.size());
    }
}

4.8 企业级分页避坑方案

/**
 * 分页功能企业级避坑方案
 * 重点:生产环境中分页查询的常见问题和解决方案
 */
@Service
@Slf4j
public class PaginationBestPractice {

    @Autowired
    private UserMapper userMapper;

    /**
     * 坑点1:深度分页性能问题
     */
    public void avoidDeepPagination() {
        int pageNum = 10000;
        int pageSize = 10;
        
        // 错误做法:直接大偏移量分页
        Page<User> wrongPage = new Page<>(pageNum, pageSize);
        QueryWrapper<User> wrongWrapper = new QueryWrapper<>();
        Page<User> wrongResult = userMapper.selectPage(wrongPage, wrongWrapper); // 性能极差
        
        // 正确做法1:使用游标分页
        Long lastId = getLastIdFromPreviousPage(); // 获取上一页最后一条记录的ID
        List<User> users = userMapper.selectList(new QueryWrapper<User>()
                .gt("id", lastId)
                .orderByAsc("id")
                .last("LIMIT " + pageSize));
        
        // 正确做法2:使用子查询优化
        List<User> optimizedUsers = userMapper.selectList(new QueryWrapper<User>()
                .inSql("id", 
                    "SELECT id FROM sys_user ORDER BY id LIMIT " + 
                    ((pageNum - 1) * pageSize) + ", " + pageSize))
                .orderByAsc("id"));
    }

    /**
     * 坑点2:不必要的 count 查询
     */
    public void avoidUnnecessaryCount() {
        // 场景1:只需要判断是否有数据,不需要知道具体数量
        Page<User> page = new Page<>(1, 1, false); // 不执行count查询
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 0);
        
        Page<User> userPage = userMapper.selectPage(page, queryWrapper);
        boolean hasData = !userPage.getRecords().isEmpty(); // 只需要知道是否有数据
        
        // 场景2:移动端下拉刷新,通常只需要第一页数据
        Page<User> firstPage = new Page<>(1, 20, false); // 不关心总数
        Page<User> result = userMapper.selectPage(firstPage, queryWrapper);
    }

    /**
     * 坑点3:分页参数安全验证
     */
    public Page<User> safePagination(Integer pageNum, Integer pageSize, String sortBy) {
        // 参数验证和默认值设置
        if (pageNum == null || pageNum < 1) {
            pageNum = 1;
        }
        
        if (pageSize == null || pageSize < 1) {
            pageSize = 10;
        }
        
        // 限制最大页大小,防止恶意查询
        if (pageSize > 1000) {
            pageSize = 1000;
            log.warn("页大小超过限制,自动设置为最大值: {}", pageSize);
        }
        
        // 排序字段白名单验证
        Set<String> allowedSortFields = new HashSet<>(
            Arrays.asList("id", "user_name", "age", "create_time", "update_time"));
        
        Page<User> page = new Page<>(pageNum, pageSize);
        if (StringUtils.isNotBlank(sortBy) && allowedSortFields.contains(sortBy)) {
            page.addOrder(OrderItem.desc(sortBy));
        } else {
            page.addOrder(OrderItem.desc("create_time")); // 默认排序
        }
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 0);
        
        return userMapper.selectPage(page, queryWrapper);
    }

    /**
     * 坑点4:分布式环境下的分页一致性问题
     */
    public void handleDistributedPagination() {
        // 问题:在分页查询过程中,如果有数据插入/删除,可能导致数据重复或丢失
        
        // 解决方案1:使用基于ID的游标分页,避免偏移量分页
        Long lastId = getLastIdFromPreviousPage();
        List<User> users = userMapper.selectList(new QueryWrapper<User>()
                .gt("id", lastId)
                .orderByAsc("id")
                .last("LIMIT 10"));
        
        // 解决方案2:使用事务隔离级别保证一致性
        // @Transactional(isolation = Isolation.REPEATABLE_READ)
        // public Page<User> consistentPagination() { ... }
    }

    /**
     * 坑点5:内存分页的风险
     */
    public void avoidMemoryPagination() {
        // 错误做法:先查询所有数据,再在内存中分页
        List<User> allUsers = userMapper.selectList(new QueryWrapper<User>().eq("deleted", 0));
        List<User> pageUsers = allUsers.stream()
                .skip(10) // 内存分页
                .limit(10)
                .collect(Collectors.toList()); // 数据量大时会导致OOM
        
        // 正确做法:使用数据库分页
        Page<User> page = new Page<>(2, 10); // 第二页,每页10条
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 0);
        Page<User> result = userMapper.selectPage(page, queryWrapper); // 数据库分页
    }

    /**
     * 坑点6:count查询性能优化
     */
    public void optimizeCountQuery() {
        // 对于复杂查询,count查询可能很慢
        
        // 方案1:使用近似值(如Redis计数器)
        // Long approximateCount = redisTemplate.opsForValue().get("user_count");
        
        // 方案2:定期统计,缓存count结果
        // @Cacheable("user_count")
        // public Long getCachedUserCount() { ... }
        
        // 方案3:对于不需要精确count的场景,使用估算值
        Page<User> page = new Page<>(1, 10, false); // 不执行count查询
        // 或者使用 EXPLAIN 获取近似值
    }

    /**
     * 最佳实践:统一的分页查询模板
     */
    public <T> PageResult<T> executePageQuery(PageQueryFunction<T> queryFunction, 
                                            BasePageQuery query) {
        try {
            // 1. 参数验证
            validatePageParams(query);
            
            // 2. 执行分页查询
            Page<T> page = queryFunction.apply(query.toPage());
            
            // 3. 返回统一结果
            return PageResult.success(page);
            
        } catch (Exception e) {
            log.error("分页查询异常 - 参数: {}, 错误: {}", query, e.getMessage(), e);
            return PageResult.error("分页查询失败: " + e.getMessage());
        }
    }
    
    private void validatePageParams(BasePageQuery query) {
        if (query.getPageNum() == null || query.getPageNum() < 1) {
            query.setPageNum(1);
        }
        
        if (query.getPageSize() == null || query.getPageSize() < 1) {
            query.setPageSize(10);
        }
        
        if (query.getPageSize() > 1000) {
            log.warn("页大小过大,自动限制为1000 - 原始值: {}", query.getPageSize());
            query.setPageSize(1000);
        }
    }
    
    @FunctionalInterface
    public interface PageQueryFunction<T> {
        Page<T> apply(Page<T> page);
    }
    
    private Long getLastIdFromPreviousPage() {
        // 实际项目中应该从上一页响应中获取
        return 0L;
    }
}

企业级MyBatis-Plus实战教学(续)

模块五:代码生成器实战配置

5.1 代码生成器核心配置详解

/**
 * 企业级代码生成器详细配置
 * 重点:完整的代码生成解决方案,支持自定义模板和扩展
 */
@Slf4j
public class EnterpriseCodeGenerator {

    /**
     * 代码生成器核心配置
     * 重点:详细参数说明和企业级最佳实践
     */
    public static void main(String[] args) {
        // 1. 项目根路径获取
        String projectPath = System.getProperty("user.dir");
        log.info("项目根路径: {}", projectPath);
        
        // 2. 数据源详细配置
        String url = "jdbc:mysql://localhost:3306/enterprise_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true";
        String username = "root";
        String password = "123456";
        
        log.info("数据源配置 - URL: {}, 用户名: {}", url, username);

        // 3. 使用 FastAutoGenerator 进行代码生成
        FastAutoGenerator.create(url, username, password)
                // ==================== 全局配置 ====================
                .globalConfig(builder -> {
                    builder.author("EnterpriseDev")        // 设置作者
                          .enableSwagger()                // 开启 swagger 模式
                          .fileOverride()                 // 覆盖已生成文件
                          .disableOpenDir()               // 生成后不打开目录
                          .dateType(DateType.ONLY_DATE)   // 时间策略
                          .commentDate("yyyy-MM-dd")      // 注释日期
                          .outputDir(projectPath + "/src/main/java") // 输出目录
                          .build();
                    
                    log.info("全局配置完成 - 作者: EnterpriseDev, 输出目录: {}", 
                            projectPath + "/src/main/java");
                })
                // ==================== 包配置 ====================
                .packageConfig(builder -> {
                    builder.parent("com.enterprise")      // 父包名
                          .moduleName("system")           // 模块名
                          .entity("domain.entity")        // 实体类包名
                          .service("service")             // Service包名
                          .serviceImpl("service.impl")    // Service实现类包名
                          .mapper("mapper")               // Mapper包名
                          .controller("controller")       // Controller包名
                          .other("domain.dto")            // 其他包名(如DTO)
                          .pathInfo(Collections.singletonMap(
                              OutputFile.xml, 
                              projectPath + "/src/main/resources/mapper"
                          )) // XML输出路径
                          .build();
                    
                    log.info("包配置完成 - 父包: com.enterprise, 模块: system");
                })
                // ==================== 策略配置 ====================
                .strategyConfig(builder -> {
                    // 3.1 数据库表配置
                    builder.addInclude(getTables("sys_user,sys_role,sys_dept,sys_menu")) // 设置需要生成的表名
                          .addTablePrefix("sys_")         // 设置过滤表前缀
                          .build();
                    
                    // 3.2 实体类策略配置
                    builder.entityBuilder()
                          .enableLombok()                 // 开启 lombok
                          .enableTableFieldAnnotation()   // 开启字段注解
                          .naming(NamingStrategy.underline_to_camel)     // 表名映射策略
                          .columnNaming(NamingStrategy.underline_to_camel) // 列名映射策略
                          .idType(IdType.ASSIGN_ID)       // 主键策略
                          .formatFileName("%sEntity")     // 实体类文件名称格式
                          .addTableFills(
                              new Column("create_time", FieldFill.INSERT),
                              new Column("update_time", FieldFill.INSERT_UPDATE)
                          ) // 自动填充配置
                          .versionColumnName("version")   // 乐观锁字段名
                          .versionPropertyName("version") // 乐观锁属性名
                          .logicDeleteColumnName("deleted") // 逻辑删除字段名
                          .logicDeletePropertyName("deleted") // 逻辑删除属性名
                          .addIgnoreColumns("tenant_id")  // 忽略字段
                          .build();
                    
                    // 3.3 Controller 策略配置
                    builder.controllerBuilder()
                          .enableHyphenStyle()            // 开启驼峰转连字符
                          .enableRestStyle()              // 开启生成 @RestController
                          .formatFileName("%sController") // 控制器文件名称格式
                          .build();
                    
                    // 3.4 Service 策略配置
                    builder.serviceBuilder()
                          .formatServiceFileName("%sService")         // Service接口名称格式
                          .formatServiceImplFileName("%sServiceImpl") // Service实现类名称格式
                          .build();
                    
                    // 3.5 Mapper 策略配置
                    builder.mapperBuilder()
                          .enableBaseResultMap()          // 启用 BaseResultMap
                          .enableBaseColumnList()         // 启用 BaseColumnList
                          .formatMapperFileName("%sMapper")     // Mapper接口名称格式
                          .formatXmlFileName("%sMapper")        // Mapper XML名称格式
                          .build();
                    
                    log.info("策略配置完成 - 包含表: {}, 前缀过滤: sys_", 
                            getTables("sys_user,sys_role,sys_dept,sys_menu"));
                })
                // ==================== 模板配置 ====================
                .templateConfig(builder -> {
                    builder.entity("/templates/entity.java")          // 实体类模板
                          .service("/templates/service.java")         // Service接口模板
                          .serviceImpl("/templates/serviceImpl.java") // Service实现类模板
                          .mapper("/templates/mapper.java")           // Mapper接口模板
                          .mapperXml("/templates/mapper.xml")         // Mapper XML模板
                          .controller("/templates/controller.java")   // Controller模板
                          .build();
                    
                    log.info("模板配置完成 - 使用自定义模板");
                })
                // ==================== 注入配置 ====================
                .injectionConfig(builder -> {
                    // 4.1 自定义参数注入
                    Map<String, Object> customMap = new HashMap<>();
                    customMap.put("company", "Enterprise Ltd.");
                    customMap.put("projectVersion", "1.0.0");
                    customMap.put("basePackage", "com.enterprise");
                    
                    builder.customMap(customMap);
                    
                    // 4.2 自定义文件生成
                    Map<String, String> customFiles = new HashMap<>();
                    customFiles.put("DTO.java", "/templates/entityDTO.java.ftl");
                    customFiles.put("VO.java", "/templates/entityVO.java.ftl");
                    customFiles.put("Query.java", "/templates/entityQuery.java.ftl");
                    
                    builder.customFile(customFiles);
                    
                    // 4.3 文件生成前回调
                    builder.beforeOutputFile((tableInfo, objectMap) -> {
                        log.info("开始生成表: {}, 实体类: {}", 
                                tableInfo.getName(), tableInfo.getEntityName());
                        
                        // 添加表注释到自定义参数
                        objectMap.put("tableComment", tableInfo.getComment());
                        
                        // 根据表名设置模块名
                        String moduleName = getModuleName(tableInfo.getName());
                        objectMap.put("moduleName", moduleName);
                    });
                    
                    log.info("注入配置完成 - 自定义参数: {}, 自定义文件: {}", 
                            customMap.keySet(), customFiles.keySet());
                })
                // ==================== 模板引擎配置 ====================
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
        
        log.info("代码生成完成!");
    }

    /**
     * 处理表名字符串,支持逗号分隔
     */
    private static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

    /**
     * 根据表名获取模块名
     */
    private static String getModuleName(String tableName) {
        if (tableName.startsWith("sys_")) {
            return "system";
        } else if (tableName.startsWith("biz_")) {
            return "business";
        } else if (tableName.startsWith("org_")) {
            return "organization";
        }
        return "common";
    }
}

5.2 自定义模板详细实现

/**
 * 自定义模板配置
 * 重点:企业级定制化模板,符合项目规范
 */
public class CustomTemplateConfig {

    /**
     * 1. 实体类自定义模板
     * 位置:resources/templates/entity.java.ftl
     */
    public static final String ENTITY_TEMPLATE = 
        "/**\n" +
        " * <p>\n" +
        " * ${table.comment!} 实体类\n" +
        " * </p>\n" +
        " *\n" +
        " * @author ${author}\n" +
        " * @since ${date}\n" +
        " * @company ${company!\"\"}\n" +
        " * @version ${projectVersion!\"1.0.0\"}\n" +
        " */\n" +
        "@Data\n" +
        "@TableName(\"${table.name}\")\n" +
        "@ApiModel(value = \"${entity}对象\", description = \"${table.comment!}\")\n" +
        "@EqualsAndHashCode(callSuper = false)\n" +
        "@Accessors(chain = true)\n" +
        "public class ${entity} implements Serializable {\n" +
        "\n" +
        "    private static final long serialVersionUID = 1L;\n" +
        "\n" +
        "<#list table.fields as field>\n" +
        "    <#if field.comment!?length gt 0>\n" +
        "    @ApiModelProperty(value = \"${field.comment}\")\n" +
        "    </#if>\n" +
        "    <#if field.keyFlag>\n" +
        "        <#if field.keyIdentityFlag>\n" +
        "    @TableId(value = \"${field.name}\", type = IdType.AUTO)\n" +
        "        <#elseif idType??>\n" +
        "    @TableId(value = \"${field.name}\", type = IdType.${idType})\n" +
        "        <#else>\n" +
        "    @TableId(\"${field.name}\")\n" +
        "        </#if>\n" +
        "    <#elseif field.fill??>\n" +
        "        <#if field.convert>\n" +
        "    @TableField(value = \"${field.name}\", fill = FieldFill.${field.fill})\n" +
        "        <#else>\n" +
        "    @TableField(fill = FieldFill.${field.fill})\n" +
        "        </#if>\n" +
        "    <#elseif field.convert>\n" +
        "    @TableField(\"${field.name}\")\n" +
        "    </#if>\n" +
        "    <#if field.versionField>\n" +
        "    @Version\n" +
        "    </#if>\n" +
        "    <#if field.logicDeleteField>\n" +
        "    @TableLogic\n" +
        "    </#if>\n" +
        "    private ${field.propertyType} ${field.propertyName};\n" +
        "</#list>\n" +
        "\n" +
        "<#if !entityLombokModel>\n" +
        "    <#list table.fields as field>\n" +
        "        <#if field.propertyType == \"boolean\">\n" +
        "            <#assign getprefix=\"is\"/>\n" +
        "        <#else>\n" +
        "            <#assign getprefix=\"get\"/>\n" +
        "        </#if>\n" +
        "    public ${field.propertyType} ${getprefix}${field.capitalName}() {\n" +
        "        return ${field.propertyName};\n" +
        "    }\n" +
        "\n" +
        "    public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {\n" +
        "        this.${field.propertyName} = ${field.propertyName};\n" +
        "        return this;\n" +
        "    }\n" +
        "    </#list>\n" +
        "</#if>\n" +
        "}";

    /**
     * 2. Service接口自定义模板
     * 位置:resources/templates/service.java.ftl
     */
    public static final String SERVICE_TEMPLATE =
        "/**\n" +
        " * <p>\n" +
        " * ${table.comment!} 服务接口\n" +
        " * </p>\n" +
        " *\n" +
        " * @author ${author}\n" +
        " * @since ${date}\n" +
        " */\n" +
        "public interface ${table.serviceName} extends IService<${entity}> {\n" +
        "\n" +
        "    /**\n" +
        "     * 根据ID查询${table.comment!}\n" +
        "     */\n" +
        "    ${entity} get${entity}ById(Long id);\n" +
        "\n" +
        "    /**\n" +
        "     * 分页查询${table.comment!}\n" +
        "     */\n" +
        "    Page<${entity}> get${entity}Page(${entity}Query query);\n" +
        "\n" +
        "    /**\n" +
        "     * 创建${table.comment!}\n" +
        "     */\n" +
        "    boolean create${entity}(${entity}DTO dto);\n" +
        "\n" +
        "    /**\n" +
        "     * 更新${table.comment!}\n" +
        "     */\n" +
        "    boolean update${entity}(${entity}DTO dto);\n" +
        "\n" +
        "    /**\n" +
        "     * 删除${table.comment!}\n" +
        "     */\n" +
        "    boolean delete${entity}(Long id);\n" +
        "}";

    /**
     * 3. Controller自定义模板
     * 位置:resources/templates/controller.java.ftl
     */
    public static final String CONTROLLER_TEMPLATE =
        "/**\n" +
        " * <p>\n" +
        " * ${table.comment!} 前端控制器\n" +
        " * </p>\n" +
        " *\n" +
        " * @author ${author}\n" +
        " * @since ${date}\n" +
        " */\n" +
        "@Slf4j\n" +
        "@RestController\n" +
        "@RequestMapping(\"/api/${moduleName}/${table.entityPath}\")\n" +
        "@Api(tags = \"${table.comment!}管理\")\n" +
        "public class ${table.controllerName} {\n" +
        "\n" +
        "    @Autowired\n" +
        "    private ${table.serviceName} ${table.serviceName?uncap_first};\n" +
        "\n" +
        "    @GetMapping(\"/{id}\")\n" +
        "    @ApiOperation(\"根据ID查询${table.comment!}\")\n" +
        "    public ApiResult<${entity}VO> getById(@PathVariable Long id) {\n" +
        "        try {\n" +
        "            ${entity} entity = ${table.serviceName?uncap_first}.getById(id);\n" +
        "            if (entity == null) {\n" +
        "                return ApiResult.fail(\"${table.comment!}不存在\");\n" +
        "            }\n" +
        "            ${entity}VO vo = ${entity}Converter.INSTANCE.toVO(entity);\n" +
        "            return ApiResult.success(vo);\n" +
        "        } catch (Exception e) {\n" +
        "            log.error(\"查询${table.comment!}失败: {}\", e.getMessage(), e);\n" +
        "            return ApiResult.fail(\"查询失败\");\n" +
        "        }\n" +
        "    }\n" +
        "\n" +
        "    @PostMapping(\"/page\")\n" +
        "    @ApiOperation(\"分页查询${table.comment!}\")\n" +
        "    public ApiResult<Page<${entity}VO>> getPage(@RequestBody ${entity}Query query) {\n" +
        "        try {\n" +
        "            Page<${entity}> page = ${table.serviceName?uncap_first}.get${entity}Page(query);\n" +
        "            Page<${entity}VO> voPage = ${entity}Converter.INSTANCE.toVOPage(page);\n" +
        "            return ApiResult.success(voPage);\n" +
        "        } catch (Exception e) {\n" +
        "            log.error(\"分页查询${table.comment!}失败: {}\", e.getMessage(), e);\n" +
        "            return ApiResult.fail(\"分页查询失败\");\n" +
        "        }\n" +
        "    }\n" +
        "\n" +
        "    @PostMapping\n" +
        "    @ApiOperation(\"创建${table.comment!}\")\n" +
        "    public ApiResult<String> create(@RequestBody ${entity}DTO dto) {\n" +
        "        try {\n" +
        "            boolean success = ${table.serviceName?uncap_first}.create${entity}(dto);\n" +
        "            return success ? ApiResult.success(\"创建成功\") : ApiResult.fail(\"创建失败\");\n" +
        "        } catch (Exception e) {\n" +
        "            log.error(\"创建${table.comment!}失败: {}\", e.getMessage(), e);\n" +
        "            return ApiResult.fail(\"创建失败: \" + e.getMessage());\n" +
        "        }\n" +
        "    }\n" +
        "\n" +
        "    @PutMapping\n" +
        "    @ApiOperation(\"更新${table.comment!}\")\n" +
        "    public ApiResult<String> update(@RequestBody ${entity}DTO dto) {\n" +
        "        try {\n" +
        "            boolean success = ${table.serviceName?uncap_first}.update${entity}(dto);\n" +
        "            return success ? ApiResult.success(\"更新成功\") : ApiResult.fail(\"更新失败\");\n" +
        "        } catch (Exception e) {\n" +
        "            log.error(\"更新${table.comment!}失败: {}\", e.getMessage(), e);\n" +
        "            return ApiResult.fail(\"更新失败: \" + e.getMessage());\n" +
        "        }\n" +
        "    }\n" +
        "\n" +
        "    @DeleteMapping(\"/{id}\")\n" +
        "    @ApiOperation(\"删除${table.comment!}\")\n" +
        "    public ApiResult<String> delete(@PathVariable Long id) {\n" +
        "        try {\n" +
        "            boolean success = ${table.serviceName?uncap_first}.delete${entity}(id);\n" +
        "            return success ? ApiResult.success(\"删除成功\") : ApiResult.fail(\"删除失败\");\n" +
        "        } catch (Exception e) {\n" +
        "            log.error(\"删除${table.comment!}失败: {}\", e.getMessage(), e);\n" +
        "            return ApiResult.fail(\"删除失败: \" + e.getMessage());\n" +
        "        }\n" +
        "    }\n" +
        "}";

    /**
     * 4. DTO类模板
     * 位置:resources/templates/entityDTO.java.ftl
     */
    public static final String DTO_TEMPLATE =
        "/**\n" +
        " * <p>\n" +
        " * ${table.comment!} DTO\n" +
        " * </p>\n" +
        " *\n" +
        " * @author ${author}\n" +
        " * @since ${date}\n" +
        " */\n" +
        "@Data\n" +
        "@ApiModel(value = \"${entity}DTO对象\", description = \"${table.comment!}传输对象\")\n" +
        "@EqualsAndHashCode(callSuper = false)\n" +
        "public class ${entity}DTO implements Serializable {\n" +
        "\n" +
        "    private static final long serialVersionUID = 1L;\n" +
        "\n" +
        "<#list table.fields as field>\n" +
        "    <#if field.propertyName != \"id\" && \n" +
        "         field.propertyName != \"version\" && \n" +
        "         field.propertyName != \"deleted\" && \n" +
        "         field.propertyName != \"createTime\" && \n" +
        "         field.propertyName != \"updateTime\" && \n" +
        "         field.propertyName != \"createBy\" && \n" +
        "         field.propertyName != \"updateBy\">\n" +
        "    @ApiModelProperty(value = \"${field.comment}\")\n" +
        "    <#if field.propertyType == \"String\">\n" +
        "    @NotBlank(message = \"${field.comment}不能为空\")\n" +
        "    <#else>\n" +
        "    @NotNull(message = \"${field.comment}不能为空\")\n" +
        "    </#if>\n" +
        "    private ${field.propertyType} ${field.propertyName};\n" +
        "    </#if>\n" +
        "</#list>\n" +
        "}";
}

5.3 高级代码生成器配置

/**
 * 高级代码生成器配置
 * 重点:支持多数据源、自定义规则、批量生成等高级功能
 */
@Slf4j
public class AdvancedCodeGenerator {

    /**
     * 多数据源代码生成
     */
    public static void generateFromMultipleDataSource() {
        // 数据源列表
        List<DataSourceConfig> dataSources = Arrays.asList(
            new DataSourceConfig("jdbc:mysql://localhost:3306/enterprise_system", "root", "123456"),
            new DataSourceConfig("jdbc:mysql://localhost:3306/enterprise_business", "root", "123456"),
            new DataSourceConfig("jdbc:mysql://localhost:3306/enterprise_org", "root", "123456")
        );

        for (DataSourceConfig dataSource : dataSources) {
            log.info("开始从数据源生成代码: {}", dataSource.getUrl());
            
            String databaseName = extractDatabaseName(dataSource.getUrl());
            generateForDataSource(dataSource, databaseName);
        }
    }

    /**
     * 为单个数据源生成代码
     */
    private static void generateForDataSource(DataSourceConfig dataSource, String databaseName) {
        String projectPath = System.getProperty("user.dir");
        
        FastAutoGenerator.create(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword())
                .globalConfig(builder -> {
                    builder.author("EnterpriseDev")
                          .outputDir(projectPath + "/src/main/java")
                          .fileOverride()
                          .build();
                })
                .packageConfig(builder -> {
                    builder.parent("com.enterprise." + databaseName)
                          .entity("domain.entity")
                          .service("service")
                          .serviceImpl("service.impl")
                          .mapper("mapper")
                          .controller("controller")
                          .pathInfo(Collections.singletonMap(
                              OutputFile.xml, 
                              projectPath + "/src/main/resources/mapper/" + databaseName
                          ))
                          .build();
                })
                .strategyConfig(builder -> {
                    builder.addInclude(getAllTables(dataSource))
                          .addTablePrefix(databaseName + "_")
                          .entityBuilder()
                          .enableLombok()
                          .enableTableFieldAnnotation()
                          .controllerBuilder()
                          .enableRestStyle()
                          .build();
                })
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
        
        log.info("数据源 {} 代码生成完成", databaseName);
    }

    /**
     * 条件代码生成 - 只生成指定的表结构
     */
    public static void conditionalGeneration() {
        String projectPath = System.getProperty("user.dir");
        
        FastAutoGenerator.create(
            "jdbc:mysql://localhost:3306/enterprise_db", "root", "123456")
                .globalConfig(builder -> {
                    builder.author("EnterpriseDev")
                          .outputDir(projectPath + "/src/main/java")
                          .build();
                })
                .packageConfig(builder -> {
                    builder.parent("com.enterprise")
                          .build();
                })
                .strategyConfig(builder -> {
                    // 只生成包含特定字段的表
                    builder.addInclude(getTablesWithSpecificColumns())
                          .entityBuilder()
                          .enableLombok()
                          .addIgnoreColumns("tenant_id") // 忽略租户字段
                          .build();
                })
                .injectionConfig(builder -> {
                    builder.beforeOutputFile((tableInfo, objectMap) -> {
                        // 根据表注释决定是否生成
                        if (shouldGenerateTable(tableInfo)) {
                            log.info("生成表: {}", tableInfo.getName());
                        } else {
                            // 跳过该表
                            throw new RuntimeException("跳过表: " + tableInfo.getName());
                        }
                    });
                })
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
    }

    /**
     * 增量代码生成 - 只生成新增的表
     */
    public static void incrementalGeneration() {
        String projectPath = System.getProperty("user.dir");
        Set<String> existingTables = getExistingGeneratedTables(projectPath);
        
        FastAutoGenerator.create(
            "jdbc:mysql://localhost:3306/enterprise_db", "root", "123456")
                .globalConfig(builder -> {
                    builder.author("EnterpriseDev")
                          .outputDir(projectPath + "/src/main/java")
                          .build();
                })
                .packageConfig(builder -> {
                    builder.parent("com.enterprise")
                          .build();
                })
                .strategyConfig(builder -> {
                    builder.addInclude(getNewTables(existingTables))
                          .entityBuilder()
                          .enableLombok()
                          .build();
                })
                .injectionConfig(builder -> {
                    builder.beforeOutputFile((tableInfo, objectMap) -> {
                        log.info("增量生成表: {}", tableInfo.getName());
                    });
                })
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
    }

    // ==================== 工具方法 ====================

    private static String extractDatabaseName(String url) {
        // 从JDBC URL中提取数据库名
        Pattern pattern = Pattern.compile("jdbc:mysql://[^/]+/([^?]+)");
        Matcher matcher = pattern.matcher(url);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return "unknown";
    }

    private static List<String> getAllTables(DataSourceConfig dataSource) {
        // 获取数据源中的所有表
        // 实际实现需要连接数据库查询
        return Collections.emptyList();
    }

    private static List<String> getTablesWithSpecificColumns() {
        // 获取包含特定字段的表
        // 实际实现需要连接数据库查询
        return Arrays.asList("sys_user", "sys_role");
    }

    private static boolean shouldGenerateTable(TableInfo tableInfo) {
        // 根据表信息决定是否生成代码
        return !tableInfo.getName().startsWith("tmp_") && 
               !tableInfo.getName().endsWith("_bak");
    }

    private static Set<String> getExistingGeneratedTables(String projectPath) {
        // 获取已生成代码的表
        Set<String> tables = new HashSet<>();
        File entityDir = new File(projectPath + "/src/main/java/com/enterprise/domain/entity");
        if (entityDir.exists()) {
            File[] entityFiles = entityDir.listFiles((dir, name) -> name.endsWith("Entity.java"));
            if (entityFiles != null) {
                for (File file : entityFiles) {
                    String tableName = convertEntityNameToTableName(file.getName());
                    tables.add(tableName);
                }
            }
        }
        return tables;
    }

    private static List<String> getNewTables(Set<String> existingTables) {
        // 获取新增的表(数据库中存在但代码中不存在的表)
        // 实际实现需要连接数据库查询并与existingTables比较
        return Collections.emptyList();
    }

    private static String convertEntityNameToTableName(String entityFileName) {
        // 将实体类文件名转换为表名
        return entityFileName.replace("Entity.java", "")
                            .replaceAll("([a-z])([A-Z])", "$1_$2")
                            .toLowerCase();
    }

    /**
     * 数据源配置内部类
     */
    @Data
    @AllArgsConstructor
    private static class DataSourceConfig {
        private String url;
        private String username;
        private String password;
    }
}

5.4 代码生成器底层原理深度解析

/**
 * 代码生成器底层原理分析
 * 重点:理解代码生成器的工作机制和扩展点
 */
@Slf4j
public class CodeGeneratorPrincipleAnalysis {

    /**
     * 1. 代码生成器核心流程分析
     */
    public void analyzeGeneratorWorkflow() {
        /*
        代码生成器工作流程:
        
        1. 初始化配置
           - 全局配置(GlobalConfig)
           - 包配置(PackageConfig)
           - 策略配置(StrategyConfig)
           - 模板配置(TemplateConfig)
           - 注入配置(InjectionConfig)
        
        2. 数据源连接和元数据获取
           - 连接数据库
           - 获取表信息(TableInfo)
           - 获取字段信息(TableField)
        
        3. 模板引擎处理
           - 加载模板文件
           - 绑定数据模型
           - 渲染模板生成代码
        
        4. 文件输出
           - 创建目录结构
           - 写入生成的文件
        */
    }

    /**
     * 2. 表信息解析过程
     */
    public void analyzeTableInfoParsing() {
        /*
        TableInfo 解析过程:
        
        1. 从数据库元数据获取表基本信息
           - 表名
           - 表注释
           - 引擎类型
           - 字符集
        
        2. 解析字段信息
           - 字段名 -> 属性名(驼峰转换)
           - 字段类型 -> Java类型
           - 字段注释 -> 属性注释
           - 主键识别
           - 自增识别
        
        3. 应用策略配置
           - 表前缀过滤
           - 字段忽略
           - 命名策略应用
        */
    }

    /**
     * 3. 自定义代码生成器示例
     */
    public static class CustomCodeGenerator {
        
        /**
         * 自定义表信息处理
         */
        public TableInfo customizeTableInfo(TableInfo tableInfo) {
            // 添加自定义字段
            TableField customField = new TableField();
            customField.setColumnName("tenant_id");
            customField.setPropertyName("tenantId");
            customField.setPropertyType("Long");
            customField.setComment("租户ID");
            customField.setKeyFlag(false);
            customField.setKeyIdentityFlag(false);
            
            tableInfo.getFields().add(customField);
            
            // 修改表注释
            if (tableInfo.getComment() == null || tableInfo.getComment().isEmpty()) {
                tableInfo.setComment("系统表 - " + tableInfo.getName());
            }
            
            return tableInfo;
        }
        
        /**
         * 自定义文件生成逻辑
         */
        public void generateCustomFiles(TableInfo tableInfo, Map<String, Object> objectMap) {
            String projectPath = System.getProperty("user.dir");
            
            // 生成Converter类
            generateConverterClass(tableInfo, objectMap, projectPath);
            
            // 生成VO类
            generateVOClass(tableInfo, objectMap, projectPath);
            
            // 生成Query类
            generateQueryClass(tableInfo, objectMap, projectPath);
        }
        
        private void generateConverterClass(TableInfo tableInfo, Map<String, Object> objectMap, String projectPath) {
            try {
                String converterTemplate = loadTemplate("/templates/converter.java.ftl");
                String rendered = renderTemplate(converterTemplate, objectMap);
                
                String filePath = projectPath + "/src/main/java/com/enterprise/domain/converter/" 
                               + tableInfo.getEntityName() + "Converter.java";
                
                writeToFile(filePath, rendered);
                log.info("生成Converter类: {}", filePath);
            } catch (Exception e) {
                log.error("生成Converter类失败: {}", e.getMessage(), e);
            }
        }
        
        private void generateVOClass(TableInfo tableInfo, Map<String, Object> objectMap, String projectPath) {
            // 类似生成Converter的逻辑
        }
        
        private void generateQueryClass(TableInfo tableInfo, Map<String, Object> objectMap, String projectPath) {
            // 类似生成Converter的逻辑
        }
        
        private String loadTemplate(String templatePath) {
            // 加载模板文件
            return "";
        }
        
        private String renderTemplate(String template, Map<String, Object> dataModel) {
            // 渲染模板
            return "";
        }
        
        private void writeToFile(String filePath, String content) {
            // 写入文件
        }
    }

    /**
     * 4. 模板引擎原理分析
     */
    public void analyzeTemplateEngine() {
        /*
        模板引擎工作流程(以Freemarker为例):
        
        1. 模板加载
           - 从classpath或文件系统加载模板文件
           - 解析模板语法
        
        2. 数据模型绑定
           - TableInfo -> 表信息
           - GlobalConfig -> 全局配置
           - PackageConfig -> 包配置
           - 自定义参数
        
        3. 模板渲染
           - 处理条件语句(<#if>)
           - 处理循环语句(<#list>)
           - 处理变量替换(${})
        
        4. 输出生成
           - 生成Java源代码
           - 生成XML配置文件
        */
    }

    /**
     * 5. 扩展点分析
     */
    public void analyzeExtensionPoints() {
        /*
        代码生成器主要扩展点:
        
        1. IFileCreate - 文件创建策略
           - 控制是否覆盖已存在文件
           - 自定义文件创建逻辑
        
        2. ITemplate - 自定义模板
           - 完全自定义模板内容
           - 支持多种模板引擎
        
        3. InjectionConfig - 注入配置
           - 自定义文件生成
           - 自定义参数注入
           - 生成前后回调
        
        4. DataSourceConfig - 数据源配置
           - 支持多种数据库
           - 自定义连接池
        
        5. StrategyConfig - 策略配置
           - 表过滤策略
           - 字段过滤策略
           - 命名策略
        */
    }
}

5.5 企业级代码生成器最佳实践

/**
 * 企业级代码生成器最佳实践
 * 重点:生产环境中代码生成器的使用规范和注意事项
 */
@Slf4j
public class CodeGeneratorBestPractice {

    /**
     * 最佳实践1:配置文件外置
     * 将生成配置放在配置文件中,便于管理和版本控制
     */
    @Component
    public static class ConfigFileCodeGenerator {
        
        @Value("${code.generator.datasource.url}")
        private String dataSourceUrl;
        
        @Value("${code.generator.datasource.username}")
        private String dataSourceUsername;
        
        @Value("${code.generator.datasource.password}")
        private String dataSourcePassword;
        
        @Value("${code.generator.author:System}")
        private String author;
        
        @Value("${code.generator.base-package:com.enterprise}")
        private String basePackage;
        
        @Value("${code.generator.tables:all}")
        private String tables;
        
        public void generateFromConfig() {
            log.info("从配置文件加载代码生成配置");
            
            FastAutoGenerator.create(dataSourceUrl, dataSourceUsername, dataSourcePassword)
                    .globalConfig(builder -> {
                        builder.author(author)
                              .outputDir(System.getProperty("user.dir") + "/src/main/java")
                              .fileOverride(false) // 生产环境谨慎使用覆盖
                              .build();
                    })
                    .packageConfig(builder -> {
                        builder.parent(basePackage)
                              .build();
                    })
                    .strategyConfig(builder -> {
                        if (!"all".equals(tables)) {
                            builder.addInclude(Arrays.asList(tables.split(",")));
                        }
                        builder.entityBuilder()
                              .enableLombok()
                              .enableTableFieldAnnotation()
                              .build();
                    })
                    .templateEngine(new FreemarkerTemplateEngine())
                    .execute();
        }
    }

    /**
     * 最佳实践2:版本控制集成
     * 生成代码前检查版本,避免覆盖手动修改
     */
    public static class VersionControlAwareGenerator {
        
        public void generateWithVersionCheck() {
            String projectPath = System.getProperty("user.dir");
            
            // 检查Git状态
            if (hasUncommittedChanges(projectPath)) {
                log.warn("存在未提交的更改,建议先提交再生成代码");
                return;
            }
            
            // 创建生成分支
            String branchName = "feature/code-gen-" + LocalDate.now().toString();
            createBranch(branchName);
            
            try {
                // 执行代码生成
                generateCode();
                
                // 提交生成的代码
                commitGeneratedCode(branchName, "自动生成代码");
                
                log.info("代码生成完成,已提交到分支: {}", branchName);
            } catch (Exception e) {
                log.error("代码生成失败,回滚更改", e);
                rollbackChanges();
            }
        }
        
        private boolean hasUncommittedChanges(String projectPath) {
            // 检查Git状态
            return false;
        }
        
        private void createBranch(String branchName) {
            // 创建Git分支
        }
        
        private void commitGeneratedCode(String branchName, String message) {
            // 提交代码
        }
        
        private void rollbackChanges() {
            // 回滚更改
        }
        
        private void generateCode() {
            // 执行代码生成
        }
    }

    /**
     * 最佳实践3:模板版本管理
     * 管理不同版本的模板,支持回滚和对比
     */
    public static class TemplateVersionManager {
        
        private final String templateBasePath = "templates/version";
        
        public void useTemplateVersion(String version) {
            String templatePath = templateBasePath + "/" + version;
            
            FastAutoGenerator.create("jdbc:mysql://localhost:3306/enterprise_db", "root", "123456")
                    .templateConfig(builder -> {
                        builder.entity(templatePath + "/entity.java")
                              .service(templatePath + "/service.java")
                              .controller(templatePath + "/controller.java")
                              .build();
                    })
                    // 其他配置...
                    .execute();
        }
        
        public void backupCurrentTemplates(String version) {
            String sourcePath = "templates";
            String targetPath = templateBasePath + "/" + version;
            
            // 备份当前模板
            copyTemplates(sourcePath, targetPath);
            log.info("模板已备份到: {}", targetPath);
        }
        
        private void copyTemplates(String sourcePath, String targetPath) {
            // 复制模板文件
        }
    }

    /**
     * 最佳实践4:生成代码质量检查
     */
    public static class CodeQualityChecker {
        
        public void checkGeneratedCode(String projectPath) {
            log.info("开始检查生成代码质量");
            
            // 1. 编译检查
            boolean compileSuccess = checkCompilation(projectPath);
            if (!compileSuccess) {
                log.error("生成代码编译失败");
                return;
            }
            
            // 2. 代码规范检查
            boolean codeStylePass = checkCodeStyle(projectPath);
            if (!codeStylePass) {
                log.warn("生成代码风格检查未通过");
            }
            
            // 3. 重复代码检查
            boolean noDuplication = checkDuplication(projectPath);
            if (!noDuplication) {
                log.warn("发现重复代码");
            }
            
            log.info("代码质量检查完成");
        }
        
        private boolean checkCompilation(String projectPath) {
            // 执行编译检查
            return true;
        }
        
        private boolean checkCodeStyle(String projectPath) {
            // 检查代码风格
            return true;
        }
        
        private boolean checkDuplication(String projectPath) {
            // 检查重复代码
            return true;
        }
    }

    /**
     * 最佳实践5:生成报告生成
     */
    public static class GenerationReportGenerator {
        
        public void generateReport(List<TableInfo> generatedTables) {
            log.info("生成代码生成报告");
            
            StringBuilder report = new StringBuilder();
            report.append("代码生成报告\n");
            report.append("生成时间: ").append(LocalDateTime.now()).append("\n\n");
            
            report.append("生成的表:\n");
            for (TableInfo table : generatedTables) {
                report.append("- ").append(table.getName())
                      .append(" (").append(table.getComment()).append(")\n");
                
                report.append("  实体类: ").append(table.getEntityName()).append("Entity.java\n");
                report.append("  Mapper: ").append(table.getMapperName()).append(".java\n");
                report.append("  Service: ").append(table.getServiceName()).append(".java\n");
                report.append("  Controller: ").append(table.getControllerName()).append(".java\n\n");
            }
            
            // 保存报告到文件
            saveReportToFile(report.toString());
            log.info("代码生成报告已保存");
        }
        
        private void saveReportToFile(String report) {
            String reportPath = System.getProperty("user.dir") + "/code-generation-report.txt";
            try {
                Files.write(Paths.get(reportPath), report.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                log.error("保存报告失败: {}", e.getMessage(), e);
            }
        }
    }

    /**
     * 避坑指南总结:
     * 
     * 1. 文件覆盖风险
     *    - 生产环境谨慎使用 fileOverride()
     *    - 建议先备份再生成
     *    
     * 2. 模板兼容性问题
     *    - 不同版本的MyBatis-Plus模板可能不兼容
     *    - 建议对模板进行版本管理
     *    
     * 3. 数据库连接安全
     *    - 不要在代码中硬编码数据库密码
     *    - 使用配置中心或环境变量
     *    
     * 4. 生成代码规范
     *    - 生成后运行代码检查
     *    - 确保符合团队编码规范
     *    
     * 5. 版本控制集成
     *    - 在独立分支生成代码
     *    - 代码审查后再合并
     */
}

企业级MyBatis-Plus实战教学(续)

模块六:乐观锁/逻辑删除实现机制

6.1 乐观锁详细实现原理

6.1.1 乐观锁配置详解

/**
 * 乐观锁完整配置详解
 * 重点:理解乐观锁的实现原理和配置细节
 */
@Configuration
@Slf4j
public class OptimisticLockConfig {

    /**
     * 乐观锁插件详细配置
     * 重点:乐观锁拦截器的工作原理和配置参数
     */
    @Bean
    @Order(2) // 在分页插件之后执行
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 1. 乐观锁插件配置
        OptimisticLockerInnerInterceptor optimisticLockerInterceptor = 
            createOptimisticLockerInterceptor();
        interceptor.addInnerInterceptor(optimisticLockerInterceptor);
        
        log.info("乐观锁插件配置完成");
        return interceptor;
    }

    /**
     * 创建乐观锁拦截器详细配置
     */
    private OptimisticLockerInnerInterceptor createOptimisticLockerInterceptor() {
        OptimisticLockerInnerInterceptor interceptor = 
            new OptimisticLockerInnerInterceptor();
        
        // 详细说明乐观锁工作原理:
        /*
        乐观锁执行流程:
        1. 从实体对象中获取版本号字段值
        2. 在UPDATE语句的SET部分添加 version = version + 1
        3. 在WHERE条件中添加 version = 旧版本号
        4. 如果更新影响行数为0,说明版本号不匹配,抛出异常或返回false
        */
        
        log.info("乐观锁拦截器创建完成,版本号字段名: version");
        return interceptor;
    }

    /**
     * 乐观锁重试机制配置
     * 重点:处理乐观锁冲突时的重试策略
     */
    @Bean
    public OptimisticLockRetryTemplate optimisticLockRetryTemplate() {
        return new OptimisticLockRetryTemplate(3, 100L); // 最大重试3次,间隔100ms
    }
}

/**
 * 乐观锁重试模板
 * 重点:企业级乐观锁冲突解决方案
 */
@Slf4j
public class OptimisticLockRetryTemplate {
    
    private final int maxRetries;
    private final long retryInterval;
    
    public OptimisticLockRetryTemplate(int maxRetries, long retryInterval) {
        this.maxRetries = maxRetries;
        this.retryInterval = retryInterval;
    }
    
    /**
     * 执行带重试的乐观锁操作
     */
    public <T> T executeWithRetry(Supplier<T> operation) {
        int retryCount = 0;
        
        while (retryCount <= maxRetries) {
            try {
                return operation.get();
            } catch (OptimisticLockingFailureException e) {
                retryCount++;
                if (retryCount > maxRetries) {
                    log.error("乐观锁操作失败,已达到最大重试次数: {}", maxRetries);
                    throw e;
                }
                
                log.warn("乐观锁冲突,进行第 {} 次重试", retryCount);
                try {
                    Thread.sleep(retryInterval);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("重试被中断", ie);
                }
            }
        }
        
        throw new OptimisticLockingFailureException("乐观锁操作失败");
    }
}

/**
 * 自定义乐观锁异常
 */
public class OptimisticLockingFailureException extends RuntimeException {
    public OptimisticLockingFailureException(String message) {
        super(message);
    }
    
    public OptimisticLockingFailureException(String message, Throwable cause) {
        super(message, cause);
    }
}

6.1.2 乐观锁实体类设计

/**
 * 乐观锁实体类详细设计
 * 重点:版本号字段的设计规范和最佳实践
 */
@Data
@TableName("sys_product")
@ApiModel(value = "产品实体", description = "产品信息表,使用乐观锁控制并发")
public class Product implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     * 重点:使用分布式ID生成策略
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    @ApiModelProperty(value = "主键ID")
    private Long id;

    /**
     * 产品名称
     */
    @TableField("product_name")
    @ApiModelProperty(value = "产品名称", required = true)
    @NotBlank(message = "产品名称不能为空")
    @Size(max = 100, message = "产品名称长度不能超过100个字符")
    private String productName;

    /**
     * 产品库存
     * 重点:库存字段经常发生并发更新,需要乐观锁保护
     */
    @ApiModelProperty(value = "产品库存")
    @Min(value = 0, message = "库存不能小于0")
    private Integer stock;

    /**
     * 产品价格(分)
     */
    @ApiModelProperty(value = "产品价格(分)")
    @Min(value = 0, message = "价格不能小于0")
    private Long price;

    /**
     * 版本号 - 乐观锁核心字段
     * 重点:@Version 注解标记乐观锁版本号字段
     * 规则:
     * 1. 字段类型必须是数值类型(Integer, Long, etc.)
     * 2. 新增时版本号默认为1(如果为null)
     * 3. 更新时自动执行 version = version + 1
     * 4. WHERE条件中自动添加 version = 旧版本号
     */
    @Version
    @TableField("version")
    @ApiModelProperty(value = "版本号", hidden = true) // 隐藏版本号,不暴露给前端
    private Integer version;

    /**
     * 逻辑删除字段
     */
    @TableLogic
    @TableField("deleted")
    @ApiModelProperty(value = "逻辑删除", hidden = true)
    private Integer deleted;

    /**
     * 创建时间
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间", hidden = true)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(value = "更新时间", hidden = true)
    private LocalDateTime updateTime;

    /**
     * 构造方法 - 初始化版本号
     * 重点:确保新增实体时版本号不为null
     */
    public Product() {
        this.version = 1; // 新实体默认版本号为1
    }

    /**
     * 带参数构造方法
     */
    public Product(String productName, Integer stock, Long price) {
        this();
        this.productName = productName;
        this.stock = stock;
        this.price = price;
    }
}

/**
 * 库存变更记录实体
 * 重点:记录库存变更历史,用于审计和问题排查
 */
@Data
@TableName("sys_product_stock_log")
@ApiModel(value = "库存变更记录", description = "产品库存变更记录表")
public class ProductStockLog implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    /**
     * 产品ID
     */
    @TableField("product_id")
    private Long productId;

    /**
     * 变更前库存
     */
    @TableField("before_stock")
    private Integer beforeStock;

    /**
     * 变更后库存
     */
    @TableField("after_stock")
    private Integer afterStock;

    /**
     * 变更数量(正数表示入库,负数表示出库)
     */
    @TableField("change_quantity")
    private Integer changeQuantity;

    /**
     * 变更类型:1-入库 2-出库 3-调整
     */
    @TableField("change_type")
    private Integer changeType;

    /**
     * 变更原因
     */
    @TableField("change_reason")
    private String changeReason;

    /**
     * 业务单号
     */
    @TableField("biz_number")
    private String bizNumber;

    /**
     * 操作人
     */
    @TableField("operator")
    private String operator;

    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

6.1.3 乐观锁业务服务实现

/**
 * 乐观锁业务服务详细实现
 * 重点:各种业务场景下的乐观锁应用
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class ProductService extends ServiceImpl<ProductMapper, Product> {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private ProductStockLogMapper stockLogMapper;

    @Autowired
    private OptimisticLockRetryTemplate retryTemplate;

    /**
     * 1. 基础乐观锁更新
     * 重点:理解乐观锁更新失败的处理机制
     */
    public boolean updateProductWithOptimisticLock(Product product) {
        log.info("开始乐观锁更新产品,产品ID: {}, 当前版本: {}", 
                product.getId(), product.getVersion());
        
        // 执行更新操作
        // 生成的SQL: UPDATE sys_product SET product_name=?, stock=?, price=?, version=version+1 
        //           WHERE id=? AND version=?
        boolean success = updateById(product);
        
        if (!success) {
            log.warn("乐观锁更新失败,产品ID: {}, 版本号已变更", product.getId());
            throw new OptimisticLockingFailureException("数据已被其他用户修改,请刷新后重试");
        }
        
        log.info("乐观锁更新成功,产品ID: {}, 新版本: {}", product.getId(), product.getVersion() + 1);
        return true;
    }

    /**
     * 2. 库存扣减 - 带重试机制的乐观锁
     * 重点:高并发场景下的库存扣减解决方案
     */
    public boolean deductStockWithRetry(Long productId, Integer quantity) {
        log.info("开始扣减库存,产品ID: {}, 扣减数量: {}", productId, quantity);
        
        return retryTemplate.executeWithRetry(() -> {
            // 在重试逻辑中执行库存扣减
            return deductStock(productId, quantity);
        });
    }

    /**
     * 3. 库存扣减核心逻辑
     * 重点:完整的库存扣减业务流程
     */
    private boolean deductStock(Long productId, Integer quantity) {
        // 1. 查询当前产品信息(包含版本号)
        Product product = productMapper.selectById(productId);
        if (product == null) {
            throw new RuntimeException("产品不存在,ID: " + productId);
        }
        
        // 2. 检查库存是否充足
        if (product.getStock() < quantity) {
            throw new RuntimeException(String.format(
                "库存不足,当前库存: %d,需要扣减: %d", product.getStock(), quantity));
        }
        
        // 3. 记录库存变更前状态
        Integer beforeStock = product.getStock();
        Integer afterStock = beforeStock - quantity;
        
        // 4. 更新库存(乐观锁保护)
        product.setStock(afterStock);
        boolean updateSuccess = updateById(product);
        
        if (!updateSuccess) {
            // 乐观锁冲突,抛出异常触发重试
            throw new OptimisticLockingFailureException("库存扣减冲突,版本号已变更");
        }
        
        // 5. 记录库存变更日志
        recordStockChange(productId, beforeStock, afterStock, -quantity, 
                        2, "销售出库", "SALE_" + System.currentTimeMillis(), "system");
        
        log.info("库存扣减成功,产品ID: {}, 扣减数量: {}, 剩余库存: {}", 
                productId, quantity, afterStock);
        
        return true;
    }

    /**
     * 4. 批量库存扣减
     * 重点:多个产品库存同时扣减的事务处理
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDeductStock(Map<Long, Integer> productQuantities) {
        log.info("开始批量扣减库存,产品数量: {}", productQuantities.size());
        
        for (Map.Entry<Long, Integer> entry : productQuantities.entrySet()) {
            Long productId = entry.getKey();
            Integer quantity = entry.getValue();
            
            boolean success = deductStockWithRetry(productId, quantity);
            if (!success) {
                // 任何一个扣减失败,整个事务回滚
                throw new RuntimeException("批量扣减库存失败,产品ID: " + productId);
            }
        }
        
        log.info("批量扣减库存成功,涉及产品: {} 个", productQuantities.size());
        return true;
    }

    /**
     * 5. 库存增加 - 入库操作
     */
    public boolean increaseStock(Long productId, Integer quantity, String reason, String bizNumber) {
        log.info("开始增加库存,产品ID: {}, 增加数量: {}, 原因: {}", 
                productId, quantity, reason);
        
        return retryTemplate.executeWithRetry(() -> {
            Product product = productMapper.selectById(productId);
            if (product == null) {
                throw new RuntimeException("产品不存在,ID: " + productId);
            }
            
            Integer beforeStock = product.getStock();
            Integer afterStock = beforeStock + quantity;
            
            product.setStock(afterStock);
            boolean updateSuccess = updateById(product);
            
            if (!updateSuccess) {
                throw new OptimisticLockingFailureException("库存增加冲突,版本号已变更");
            }
            
            // 记录库存变更日志
            recordStockChange(productId, beforeStock, afterStock, quantity, 
                            1, reason, bizNumber, "system");
            
            log.info("库存增加成功,产品ID: {}, 增加数量: {}, 当前库存: {}", 
                    productId, quantity, afterStock);
            
            return true;
        });
    }

    /**
     * 6. 库存调整 - 直接设置库存值
     */
    public boolean adjustStock(Long productId, Integer newStock, String reason) {
        log.info("开始调整库存,产品ID: {}, 新库存: {}, 原因: {}", 
                productId, newStock, reason);
        
        return retryTemplate.executeWithRetry(() -> {
            Product product = productMapper.selectById(productId);
            if (product == null) {
                throw new RuntimeException("产品不存在,ID: " + productId);
            }
            
            Integer beforeStock = product.getStock();
            
            if (beforeStock.equals(newStock)) {
                log.info("库存无需调整,当前库存与目标库存相同");
                return true;
            }
            
            Integer changeQuantity = newStock - beforeStock;
            product.setStock(newStock);
            boolean updateSuccess = updateById(product);
            
            if (!updateSuccess) {
                throw new OptimisticLockingFailureException("库存调整冲突,版本号已变更");
            }
            
            // 记录库存变更日志
            recordStockChange(productId, beforeStock, newStock, changeQuantity, 
                            3, reason, "ADJUST_" + System.currentTimeMillis(), "system");
            
            log.info("库存调整成功,产品ID: {}, 调整数量: {}, 新库存: {}", 
                    productId, changeQuantity, newStock);
            
            return true;
        });
    }

    /**
     * 记录库存变更日志
     */
    private void recordStockChange(Long productId, Integer beforeStock, Integer afterStock,
                                 Integer changeQuantity, Integer changeType, 
                                 String changeReason, String bizNumber, String operator) {
        ProductStockLog stockLog = new ProductStockLog();
        stockLog.setProductId(productId);
        stockLog.setBeforeStock(beforeStock);
        stockLog.setAfterStock(afterStock);
        stockLog.setChangeQuantity(changeQuantity);
        stockLog.setChangeType(changeType);
        stockLog.setChangeReason(changeReason);
        stockLog.setBizNumber(bizNumber);
        stockLog.setOperator(operator);
        
        stockLogMapper.insert(stockLog);
    }

    /**
     * 7. 获取产品库存变更历史
     */
    public List<ProductStockLog> getStockChangeHistory(Long productId, LocalDateTime startTime, 
                                                     LocalDateTime endTime) {
        QueryWrapper<ProductStockLog> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("product_id", productId)
                   .between("create_time", startTime, endTime)
                   .orderByDesc("create_time");
        
        return stockLogMapper.selectList(queryWrapper);
    }

    /**
     * 8. 检查库存是否足够
     * 重点:只读操作,不需要乐观锁
     */
    public boolean checkStockSufficient(Long productId, Integer requiredQuantity) {
        Product product = productMapper.selectById(productId);
        if (product == null) {
            throw new RuntimeException("产品不存在,ID: " + productId);
        }
        
        return product.getStock() >= requiredQuantity;
    }

    /**
     * 9. 获取库存预警产品列表
     */
    public List<Product> getLowStockProducts(Integer warningThreshold) {
        LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.le(Product::getStock, warningThreshold)
                   .eq(Product::getDeleted, 0)
                   .orderByAsc(Product::getStock);
        
        return productMapper.selectList(queryWrapper);
    }
}

6.2 逻辑删除详细实现

6.2.1 逻辑删除配置详解

# application.yml 逻辑删除详细配置
mybatis-plus:
  global-config:
    db-config:
      # 逻辑删除配置
      logic-delete-field: deleted    # 全局逻辑删除字段名
      logic-delete-value: 1          # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0      # 逻辑未删除值(默认为 0)
      
      # 逻辑删除扩展配置(3.5.0+)
      logic-delete-strategy: logic   # 逻辑删除策略:logic-逻辑删除,physical-物理删除
      
  configuration:
    # 日志配置(开发环境查看逻辑删除SQL)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
/**
 * 逻辑删除配置类详解
 * 重点:逻辑删除的底层实现原理和扩展配置
 */
@Configuration
@Slf4j
public class LogicDeleteConfig {

    /**
     * 逻辑删除自动填充处理器
     * 重点:逻辑删除字段的自动填充策略
     */
    @Bean
    public MetaObjectHandler logicDeleteMetaObjectHandler() {
        return new MetaObjectHandler() {
            
            /**
             * 插入时自动填充逻辑删除字段
             */
            @Override
            public void insertFill(MetaObject metaObject) {
                log.debug("插入时自动填充逻辑删除字段");
                
                // 如果逻辑删除字段为null,设置为未删除状态
                this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
                
                // 设置删除时间为null
                this.strictInsertFill(metaObject, "deleteTime", LocalDateTime.class, null);
                
                // 设置删除人为null
                this.strictInsertFill(metaObject, "deleteBy", String.class, null);
            }

            /**
             * 更新时不需要填充逻辑删除字段
             */
            @Override
            public void updateFill(MetaObject metaObject) {
                // 逻辑删除字段在更新时不需要自动填充
            }
        };
    }

    /**
     * 自定义逻辑删除处理器
     * 重点:扩展逻辑删除功能,支持删除人、删除时间记录
     */
    @Component
    public static class CustomLogicDeleteHandler implements LogicSqlInjector {
        
        /**
         * 注入自定义的SQL方法
         */
        @Override
        public void injectSql(ISqlInjector sqlInjector) {
            // 可以在这里注入自定义的SQL方法
            log.info("注入自定义逻辑删除SQL方法");
        }
    }
}

/**
 * 基础实体类 - 包含完整的逻辑删除字段
 * 重点:企业级逻辑删除字段设计规范
 */
@Data
public class BaseLogicDeleteEntity implements Serializable {
    
    private static final long serialVersionUID = 1L;

    /**
     * 逻辑删除标志
     * 重点:使用 @TableLogic 注解标记逻辑删除字段
     * 规则:
     * 1. 查询时自动添加 WHERE deleted = 0 条件
     * 2. 删除时自动执行 UPDATE SET deleted = 1
     * 3. 支持自定义删除值和未删除值
     */
    @TableLogic
    @TableField(value = "deleted")
    @ApiModelProperty(value = "逻辑删除标志", hidden = true)
    private Integer deleted;

    /**
     * 删除时间
     * 重点:记录逻辑删除的具体时间
     */
    @TableField(value = "delete_time")
    @ApiModelProperty(value = "删除时间", hidden = true)
    private LocalDateTime deleteTime;

    /**
     * 删除人
     * 重点:记录执行逻辑删除的操作人
     */
    @TableField(value = "delete_by")
    @ApiModelProperty(value = "删除人", hidden = true)
    private String deleteBy;

    /**
     * 默认构造方法 - 初始化逻辑删除字段
     */
    public BaseLogicDeleteEntity() {
        this.deleted = 0; // 默认未删除
    }
}

/**
 * 用户实体类 - 继承基础逻辑删除实体
 * 重点:展示如何在业务实体中使用逻辑删除
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user")
@ApiModel(value = "用户实体", description = "用户信息表,使用逻辑删除")
public class User extends BaseLogicDeleteEntity {

    @TableId(type = IdType.ASSIGN_ID)
    @ApiModelProperty(value = "用户ID")
    private Long id;

    @TableField("user_name")
    @ApiModelProperty(value = "用户名", required = true)
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 50, message = "用户名长度必须在2-50个字符之间")
    private String userName;

    @TableField("real_name")
    @ApiModelProperty(value = "真实姓名")
    @Size(max = 50, message = "真实姓名长度不能超过50个字符")
    private String realName;

    @ApiModelProperty(value = "年龄")
    @Min(value = 0, message = "年龄不能小于0")
    @Max(value = 150, message = "年龄不能大于150")
    private Integer age;

    @ApiModelProperty(value = "邮箱")
    @Email(message = "邮箱格式不正确")
    private String email;

    @TableField("phone_number")
    @ApiModelProperty(value = "手机号")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phoneNumber;

    @TableField("dept_id")
    @ApiModelProperty(value = "部门ID")
    private Long deptId;

    @Version
    @TableField("version")
    @ApiModelProperty(value = "版本号", hidden = true)
    private Integer version;

    @TableField(value = "create_time", fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间", hidden = true)
    private LocalDateTime createTime;

    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(value = "更新时间", hidden = true)
    private LocalDateTime updateTime;

    @TableField(value = "create_by", fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建人", hidden = true)
    private String createBy;

    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(value = "更新人", hidden = true)
    private String updateBy;
}

6.2.2 逻辑删除业务服务实现

/**
 * 逻辑删除业务服务详细实现
 * 重点:各种业务场景下的逻辑删除应用
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class UserService extends ServiceImpl<UserMapper, User> {

    @Autowired
    private UserMapper userMapper;

    /**
     * 1. 逻辑删除用户
     * 重点:理解逻辑删除与物理删除的区别
     */
    public boolean logicDeleteUser(Long userId, String operator) {
        log.info("开始逻辑删除用户,用户ID: {}, 操作人: {}", userId, operator);
        
        // 方法1:使用MyBatis-Plus的removeById方法
        // boolean success = removeById(userId);
        
        // 方法2:自定义逻辑删除,记录删除人和删除时间
        User user = new User();
        user.setId(userId);
        user.setDeleteTime(LocalDateTime.now());
        user.setDeleteBy(operator);
        
        // 执行逻辑删除
        // 生成的SQL: UPDATE sys_user SET delete_time=?, delete_by=?, deleted=1 WHERE id=? AND deleted=0
        boolean success = updateById(user);
        
        if (success) {
            log.info("逻辑删除用户成功,用户ID: {}", userId);
        } else {
            log.warn("逻辑删除用户失败,用户ID: {} 可能不存在或已被删除", userId);
        }
        
        return success;
    }

    /**
     * 2. 批量逻辑删除用户
     */
    public boolean batchLogicDeleteUsers(List<Long> userIds, String operator) {
        log.info("开始批量逻辑删除用户,用户数量: {}, 操作人: {}", userIds.size(), operator);
        
        if (userIds == null || userIds.isEmpty()) {
            log.warn("用户ID列表为空,跳过批量删除");
            return true;
        }
        
        // 构建更新条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("id", userIds)
                    .eq("deleted", 0) // 只删除未删除的用户
                    .set("delete_time", LocalDateTime.now())
                    .set("delete_by", operator)
                    .set("deleted", 1);
        
        int affectedRows = userMapper.update(null, updateWrapper);
        
        log.info("批量逻辑删除完成,成功删除 {} 个用户", affectedRows);
        return affectedRows > 0;
    }

    /**
     * 3. 恢复被逻辑删除的用户
     */
    public boolean recoverUser(Long userId, String operator) {
        log.info("开始恢复用户,用户ID: {}, 操作人: {}", userId, operator);
        
        User user = new User();
        user.setId(userId);
        user.setDeleted(0); // 恢复为未删除状态
        user.setDeleteTime(null); // 清空删除时间
        user.setDeleteBy(null); // 清空删除人
        user.setUpdateBy(operator);
        
        boolean success = updateById(user);
        
        if (success) {
            log.info("恢复用户成功,用户ID: {}", userId);
        } else {
            log.warn("恢复用户失败,用户ID: {}", userId);
        }
        
        return success;
    }

    /**
     * 4. 查询用户(自动过滤已删除用户)
     * 重点:MyBatis-Plus自动添加 deleted=0 条件
     */
    public User getUserById(Long userId) {
        log.debug("查询用户,用户ID: {}", userId);
        
        // 自动生成的SQL: SELECT * FROM sys_user WHERE id=? AND deleted=0
        User user = getById(userId);
        
        if (user == null) {
            log.debug("用户不存在或已被删除,用户ID: {}", userId);
        }
        
        return user;
    }

    /**
     * 5. 查询所有用户(包括已删除用户)
     * 重点:使用自定义查询方法,忽略逻辑删除条件
     */
    public List<User> getAllUsersIncludingDeleted() {
        log.debug("查询所有用户(包括已删除用户)");
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 不添加 deleted=0 条件,查询所有记录
        
        return userMapper.selectList(queryWrapper);
        // 生成的SQL: SELECT * FROM sys_user
    }

    /**
     * 6. 查询已删除用户
     */
    public List<User> getDeletedUsers() {
        log.debug("查询已删除用户");
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 1)
                   .orderByDesc("delete_time");
        
        return userMapper.selectList(queryWrapper);
        // 生成的SQL: SELECT * FROM sys_user WHERE deleted=1 ORDER BY delete_time DESC
    }

    /**
     * 7. 物理删除用户(谨慎使用)
     * 重点:真正的数据库删除操作,数据不可恢复
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean physicalDeleteUser(Long userId) {
        log.warn("执行物理删除用户,用户ID: {}", userId);
        
        // 先备份重要数据
        backupUserData(userId);
        
        // 执行物理删除
        int affectedRows = userMapper.physicalDeleteById(userId);
        
        if (affectedRows > 0) {
            log.warn("物理删除用户完成,用户ID: {}", userId);
            return true;
        } else {
            log.warn("物理删除用户失败,用户ID: {}", userId);
            return false;
        }
    }

    /**
     * 8. 清理长时间已逻辑删除的用户
     * 重点:定期清理逻辑删除数据,避免数据膨胀
     */
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    @Transactional(rollbackFor = Exception.class)
    public void cleanDeletedUsers() {
        log.info("开始清理长时间已逻辑删除的用户");
        
        // 清理30天前删除的用户
        LocalDateTime threshold = LocalDateTime.now().minusDays(30);
        
        // 查询需要清理的用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 1)
                   .le("delete_time", threshold)
                   .last("LIMIT 1000"); // 每次最多清理1000条
        
        List<User> deletedUsers = userMapper.selectList(queryWrapper);
        
        if (deletedUsers.isEmpty()) {
            log.info("没有需要清理的已删除用户");
            return;
        }
        
        // 备份数据
        for (User user : deletedUsers) {
            backupUserData(user.getId());
        }
        
        // 执行物理删除
        List<Long> userIds = deletedUsers.stream()
                .map(User::getId)
                .collect(Collectors.toList());
        
        int deletedCount = userMapper.physicalDeleteBatchIds(userIds);
        
        log.info("清理已删除用户完成,共清理 {} 条记录", deletedCount);
    }

    /**
     * 9. 用户统计(排除已删除用户)
     */
    public UserStatistics getUserStatistics() {
        log.debug("获取用户统计信息");
        
        UserStatistics statistics = new UserStatistics();
        
        // 总用户数(排除已删除)
        Integer totalUsers = userMapper.selectCount(new QueryWrapper<User>().eq("deleted", 0));
        statistics.setTotalUsers(totalUsers);
        
        // 今日新增用户
        LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
        Integer todayNewUsers = userMapper.selectCount(new QueryWrapper<User>()
                .eq("deleted", 0)
                .ge("create_time", todayStart));
        statistics.setTodayNewUsers(todayNewUsers);
        
        // 已删除用户数
        Integer deletedUsers = userMapper.selectCount(new QueryWrapper<User>().eq("deleted", 1));
        statistics.setDeletedUsers(deletedUsers);
        
        log.debug("用户统计完成: 总数={}, 今日新增={}, 已删除={}", 
                totalUsers, todayNewUsers, deletedUsers);
        
        return statistics;
    }

    /**
     * 备份用户数据
     */
    private void backupUserData(Long userId) {
        // 在实际项目中,这里应该将用户数据备份到历史表或归档存储
        log.debug("备份用户数据,用户ID: {}", userId);
        
        try {
            User user = userMapper.selectById(userId);
            if (user != null) {
                // 备份到历史表
                // userHistoryMapper.insert(user);
                log.info("用户数据备份完成,用户ID: {}", userId);
            }
        } catch (Exception e) {
            log.error("备份用户数据失败,用户ID: {}", userId, e);
            throw new RuntimeException("备份用户数据失败", e);
        }
    }
}

/**
 * 用户统计信息
 */
@Data
class UserStatistics {
    private Integer totalUsers;
    private Integer todayNewUsers;
    private Integer deletedUsers;
    private Integer activeUsers;
}

6.3 乐观锁与逻辑删除的底层原理深度解析

/**
 * 乐观锁与逻辑删除底层原理深度解析
 * 重点:理解MyBatis-Plus如何实现乐观锁和逻辑删除
 */
@Slf4j
public class OptimisticLockAndLogicDeletePrinciple {

    /**
     * 1. 乐观锁拦截器原理分析
     */
    public void analyzeOptimisticLockInterceptor() {
        /*
        乐观锁拦截器(OptimisticLockerInnerInterceptor)工作流程:
        
        1. 拦截 Executor 的 update 方法
        2. 检查实体类是否有 @Version 注解字段
        3. 如果有版本号字段:
           a. 获取当前版本号值
           b. 在 SET 部分添加 version = version + 1
           c. 在 WHERE 条件中添加 version = 当前版本号
           d. 执行更新操作
           e. 检查影响行数,如果为0则抛出异常
        
        4. 源码关键方法:
           - beforeUpdate(): 更新前处理
           - processUpdate(): 处理更新逻辑
        */
        
        // 模拟乐观锁SQL生成过程
        String originalSql = "UPDATE sys_product SET product_name=?, stock=? WHERE id=?";
        String optimisticLockSql = "UPDATE sys_product SET product_name=?, stock=?, version=version+1 WHERE id=? AND version=?";
        
        log.info("乐观锁SQL转换: {} -> {}", originalSql, optimisticLockSql);
    }

    /**
     * 2. 逻辑删除拦截器原理分析
     */
    public void analyzeLogicDeleteInterceptor() {
        /*
        逻辑删除拦截器工作流程:
        
        1. 拦截 Executor 的 query 和 update 方法
        
        2. 对于查询操作(SELECT):
           a. 解析原始SQL
           b. 检查是否已经包含逻辑删除条件
           c. 如果没有,自动添加 WHERE deleted=0 条件
           d. 修改 BoundSql 的 SQL 内容
        
        3. 对于删除操作(DELETE):
           a. 将 DELETE 语句转换为 UPDATE 语句
           b. 设置 deleted=1 和其他删除相关字段
           c. 修改 MappedStatement
        
        4. 源码关键方法:
           - beforeQuery(): 查询前处理
           - beforeUpdate(): 更新前处理
           - logicDelete(): 处理逻辑删除
        */
        
        // 模拟逻辑删除SQL转换过程
        String originalSelectSql = "SELECT * FROM sys_user";
        String logicDeleteSelectSql = "SELECT * FROM sys_user WHERE deleted=0";
        
        String originalDeleteSql = "DELETE FROM sys_user WHERE id=1";
        String logicDeleteUpdateSql = "UPDATE sys_user SET deleted=1, delete_time=NOW() WHERE id=1";
        
        log.info("逻辑删除SELECT转换: {} -> {}", originalSelectSql, logicDeleteSelectSql);
        log.info("逻辑删除DELETE转换: {} -> {}", originalDeleteSql, logicDeleteUpdateSql);
    }

    /**
     * 3. 自定义逻辑删除处理器示例
     */
    public static class CustomLogicDeleteHandler extends AbstractLogicMethodHandler {
        
        /**
         * 重写删除方法,支持自定义逻辑删除逻辑
         */
        @Override
        public void logicDelete(LogicDelete logicDelete, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            // 获取逻辑删除字段配置
            String logicDeleteField = tableInfo.getLogicDeleteField();
            String logicDeleteValue = tableInfo.getLogicDeleteValue();
            String logicNotDeleteValue = tableInfo.getLogicNotDeleteValue();
            
            log.info("自定义逻辑删除处理 - 字段: {}, 删除值: {}, 未删除值: {}", 
                    logicDeleteField, logicDeleteValue, logicNotDeleteValue);
            
            // 可以在这里添加自定义逻辑,如记录删除日志等
        }
    }

    /**
     * 4. 乐观锁冲突检测机制
     */
    public void analyzeOptimisticLockConflictDetection() {
        /*
        乐观锁冲突检测机制:
        
        场景:两个线程同时更新同一条记录
        线程A:version=1 -> 执行更新,version变为2
        线程B:version=1 -> 执行更新,WHERE version=1 条件不满足,更新0行
        
        检测方式:
        1. 执行更新操作
        2. 检查 affectedRows(影响行数)
        3. 如果 affectedRows == 0,说明版本号不匹配
        4. 抛出 OptimisticLockException 或返回 false
        
        企业级处理:
        1. 重试机制
        2. 告警通知
        3. 冲突记录
        */
        
        // 模拟乐观锁冲突场景
        int affectedRows = 0; // 模拟更新影响0行
        if (affectedRows == 0) {
            throw new OptimisticLockingFailureException("数据已被其他用户修改");
        }
    }

    /**
     * 5. 逻辑删除性能优化分析
     */
    public void analyzeLogicDeletePerformance() {
        /*
        逻辑删除性能考虑:
        
        优势:
        1. 数据可恢复,避免误删
        2. 保留数据历史,便于审计
        3. 关联数据完整性
        
        劣势:
        1. 表数据量会不断增长
        2. 查询需要额外过滤条件
        3. 索引需要包含 deleted 字段
        
        优化方案:
        1. 定期归档已删除数据
        2. 为 deleted 字段建立索引
        3. 使用分区表分离活跃和删除数据
        4. 查询时明确指定 deleted 条件
        */
        
        // 建议的索引设计
        String recommendedIndex = "CREATE INDEX idx_user_deleted ON sys_user(deleted, create_time)";
        log.info("逻辑删除性能优化建议索引: {}", recommendedIndex);
    }

    /**
     * 6. 混合使用乐观锁和逻辑删除
     */
    public void analyzeMixedUsage() {
        /*
        乐观锁 + 逻辑删除的SQL示例:
        
        原始更新SQL:
        UPDATE sys_product SET stock=90 WHERE id=1
        
        添加乐观锁和逻辑删除后:
        UPDATE sys_product SET stock=90, version=version+1 
        WHERE id=1 AND version=1 AND deleted=0
        
        这样同时保证了:
        1. 数据并发安全(乐观锁)
        2. 不会更新已删除的数据(逻辑删除)
        3. 版本号自动递增
        */
        
        String mixedSql = "UPDATE sys_product SET stock=90, version=version+1 " +
                         "WHERE id=1 AND version=1 AND deleted=0";
        log.info("乐观锁+逻辑删除混合SQL: {}", mixedSql);
    }
}

6.4 企业级避坑方案详细解析

/**
 * 乐观锁和逻辑删除企业级避坑方案
 * 重点:生产环境中常见问题和解决方案
 */
@Service
@Slf4j
public class OptimisticLockAndLogicDeleteBestPractice {

    @Autowired
    private ProductService productService;

    @Autowired
    private UserService userService;

    /**
     * 避坑1:乐观锁版本号初始化
     */
    public void avoidVersionNullIssue() {
        // 错误做法:新增实体时版本号为null
        Product product = new Product();
        // product.setVersion(null); // 默认就是null
        
        // 正确做法:在构造方法或设置默认值
        Product safeProduct = new Product("测试产品", 100, 1000L);
        // version 在构造方法中初始化为1
        
        log.info("产品版本号: {}", safeProduct.getVersion()); // 输出: 1
    }

    /**
     * 避坑2:乐观锁更新前检查
     */
    public boolean safeUpdateProduct(Product product) {
        // 错误做法:直接更新,不检查数据是否存在
        // return productService.updateById(product);
        
        // 正确做法:先查询确认数据存在且版本号匹配
        Product existingProduct = productService.getById(product.getId());
        if (existingProduct == null) {
            throw new RuntimeException("产品不存在,ID: " + product.getId());
        }
        
        // 检查版本号(可选,框架会自动检查)
        if (!existingProduct.getVersion().equals(product.getVersion())) {
            throw new OptimisticLockingFailureException("数据版本不匹配,请刷新后重试");
        }
        
        return productService.updateById(product);
    }

    /**
     * 避坑3:逻辑删除关联数据处理
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean safeDeleteUserWithRelations(Long userId, String operator) {
        // 错误做法:只删除用户,不处理关联数据
        // return userService.logicDeleteUser(userId, operator);
        
        // 正确做法:检查并处理所有关联数据
        User user = userService.getById(userId);
        if (user == null) {
            throw new RuntimeException("用户不存在,ID: " + userId);
        }
        
        // 1. 检查用户是否有未完成的订单
        // boolean hasPendingOrders = orderService.hasPendingOrders(userId);
        // if (hasPendingOrders) {
        //     throw new RuntimeException("用户有待处理订单,无法删除");
        // }
        
        // 2. 逻辑删除用户的关联数据
        // userRoleService.logicDeleteByUserId(userId, operator);
        // userAddressService.logicDeleteByUserId(userId, operator);
        
        // 3. 记录删除操作日志
        // auditService.logUserDelete(user, operator);
        
        // 4. 执行用户逻辑删除
        return userService.logicDeleteUser(userId, operator);
    }

    /**
     * 避坑4:批量操作乐观锁处理
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean safeBatchUpdateProducts(List<Product> products) {
        // 错误做法:直接批量更新,某个失败不会回滚
        // for (Product product : products) {
        //     productService.updateById(product);
        // }
        
        // 正确做法:使用事务,任何一个失败就回滚
        for (Product product : products) {
            boolean success = productService.updateById(product);
            if (!success) {
                throw new OptimisticLockingFailureException(
                    "批量更新失败,产品ID: " + product.getId() + " 版本号冲突");
            }
        }
        return true;
    }

    /**
     * 避坑5:逻辑删除数据的查询优化
     */
    public List<User> optimizeQueryWithLogicDelete() {
        // 错误做法:查询所有字段,包括已删除数据
        // return userService.getAllUsersIncludingDeleted();
        
        // 正确做法1:明确指定只查询未删除数据
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 0)
                   .select("id", "user_name", "email", "create_time"); // 只查询需要的字段
        
        // 正确做法2:使用索引优化的查询条件
        queryWrapper.orderByDesc("create_time")
                   .last("LIMIT 100");
        
        return userService.list(queryWrapper);
    }

    /**
     * 避坑6:物理删除的风险控制
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean safePhysicalDelete(Long userId) {
        // 错误做法:直接物理删除
        // return userService.physicalDeleteUser(userId);
        
        // 正确做法:多重确认和备份
        User user = userService.getById(userId);
        if (user == null) {
            throw new RuntimeException("用户不存在,ID: " + userId);
        }
        
        // 1. 权限检查(只有管理员可以物理删除)
        // if (!hasPhysicalDeletePermission()) {
        //     throw new RuntimeException("没有物理删除权限");
        // }
        
        // 2. 重要数据备份
        userService.backupUserData(userId);
        
        // 3. 记录物理删除操作
        log.warn("执行物理删除用户,用户ID: {}, 操作人: {}", userId, getCurrentUser());
        // auditService.logPhysicalDelete(user, getCurrentUser());
        
        // 4. 执行物理删除
        return userService.physicalDeleteUser(userId);
    }

    /**
     * 避坑7:乐观锁重试策略配置
     */
    public void configureOptimisticLockRetry() {
        // 错误做法:无限制重试或重试间隔不合理
        // while (true) {
        //     try {
        //         deductStock(productId, quantity);
        //         break;
        //     } catch (OptimisticLockingFailureException e) {
        //         // 无限重试
        //     }
        // }
        
        // 正确做法:合理的重试次数和间隔
        OptimisticLockRetryTemplate retryTemplate = new OptimisticLockRetryTemplate(3, 100L);
        
        // 使用重试模板执行操作
        retryTemplate.executeWithRetry(() -> {
            return productService.deductStock(1L, 1);
        });
    }

    /**
     * 避坑8:逻辑删除数据的统计准确性
     */
    public UserStatistics getAccurateUserStatistics() {
        // 错误做法:使用默认的count方法,可能包含已删除数据
        // int totalUsers = userService.count();
        
        // 正确做法:明确指定统计条件
        QueryWrapper<User> countWrapper = new QueryWrapper<>();
        countWrapper.eq("deleted", 0);
        
        int totalUsers = userService.count(countWrapper);
        
        UserStatistics statistics = new UserStatistics();
        statistics.setTotalUsers(totalUsers);
        
        return statistics;
    }

    private String getCurrentUser() {
        // 获取当前登录用户
        return "system";
    }
}

企业级MyBatis-Plus实战教学(续)

模块七:多表关联查询解决方案

7.1 关联查询基础概念详解

/**
 * 关联查询基础概念详解
 * 重点:理解各种关联类型和使用场景
 */
@Slf4j
public class AssociationQueryConcepts {

    /**
     * 关联查询类型说明:
     * 
     * 1. 一对一(One-to-One)
     *    - 一个用户对应一个身份证
     *    - 一个订单对应一个物流信息
     *    
     * 2. 一对多(One-to-Many)  
     *    - 一个部门对应多个用户
     *    - 一个订单对应多个订单项
     *    
     * 3. 多对一(Many-to-One)
     *    - 多个用户属于一个部门
     *    - 多个订单项属于一个订单
     *    
     * 4. 多对多(Many-to-Many)
     *    - 多个用户对应多个角色
     *    - 多个商品对应多个分类
     */
    
    public void explainAssociationTypes() {
        log.info("""
            ==================== 关联查询类型说明 ====================
            一对一:使用 @One 注解或 <association> 标签
            一对多:使用 @Many 注解或 <collection> 标签  
            多对一:本质是一对多的反向关系
            多对多:需要通过中间表实现
            """);
    }
}

7.2 VO(View Object)类详细设计

/**
 * 关联查询VO类详细设计
 * 重点:VO类用于封装关联查询结果,避免污染实体类
 */
@Data
@ApiModel(value = "用户详情VO", description = "用户详细信息展示对象")
public class UserDetailVO implements Serializable {
    
    private static final long serialVersionUID = 1L;

    // ==================== 用户基本信息 ====================
    @ApiModelProperty(value = "用户ID")
    private Long id;
    
    @ApiModelProperty(value = "用户名")
    private String userName;
    
    @ApiModelProperty(value = "真实姓名")
    private String realName;
    
    @ApiModelProperty(value = "年龄")
    private Integer age;
    
    @ApiModelProperty(value = "邮箱")
    private String email;
    
    @ApiModelProperty(value = "手机号")
    private String phoneNumber;
    
    @ApiModelProperty(value = "用户状态:0-正常 1-禁用")
    private Integer status;
    
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;

    // ==================== 部门信息(一对一) ====================
    @ApiModelProperty(value = "部门信息")
    private DeptVO dept;
    
    @ApiModelProperty(value = "部门ID")
    private Long deptId;
    
    @ApiModelProperty(value = "部门名称")
    private String deptName;
    
    @ApiModelProperty(value = "部门负责人")
    private String deptLeader;

    // ==================== 角色列表(一对多) ====================
    @ApiModelProperty(value = "角色列表")
    private List<RoleVO> roles;
    
    @ApiModelProperty(value = "角色名称列表(逗号分隔)")
    private String roleNames;
    
    @ApiModelProperty(value = "角色Key列表(逗号分隔)")
    private String roleKeys;

    // ==================== 权限信息(多对多) ====================
    @ApiModelProperty(value = "权限列表")
    private List<PermissionVO> permissions;
    
    @ApiModelProperty(value = "权限标识列表")
    private List<String> permissionKeys;

    // ==================== 统计信息 ====================
    @ApiModelProperty(value = "订单数量")
    private Integer orderCount;
    
    @ApiModelProperty(value = "最后登录时间")
    private LocalDateTime lastLoginTime;
    
    @ApiModelProperty(value = "登录次数")
    private Integer loginCount;
}

/**
 * 部门VO
 */
@Data
@ApiModel(value = "部门VO", description = "部门信息展示对象")
public class DeptVO implements Serializable {
    
    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "部门ID")
    private Long id;
    
    @ApiModelProperty(value = "部门名称")
    private String deptName;
    
    @ApiModelProperty(value = "部门编码")
    private String deptCode;
    
    @ApiModelProperty(value = "负责人")
    private String leader;
    
    @ApiModelProperty(value = "联系电话")
    private String phone;
    
    @ApiModelProperty(value = "部门状态:0-正常 1-停用")
    private Integer status;
    
    @ApiModelProperty(value = "父部门ID")
    private Long parentId;
    
    @ApiModelProperty(value = "祖级列表")
    private String ancestors;
    
    @ApiModelProperty(value = "显示顺序")
    private Integer orderNum;
    
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
}

/**
 * 角色VO
 */
@Data
@ApiModel(value = "角色VO", description = "角色信息展示对象")
public class RoleVO implements Serializable {
    
    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "角色ID")
    private Long id;
    
    @ApiModelProperty(value = "角色名称")
    private String roleName;
    
    @ApiModelProperty(value = "角色权限字符串")
    private String roleKey;
    
    @ApiModelProperty(value = "显示顺序")
    private Integer roleSort;
    
    @ApiModelProperty(value = "角色状态:0-正常 1-停用")
    private Integer status;
    
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
}

/**
 * 权限VO
 */
@Data
@ApiModel(value = "权限VO", description = "权限信息展示对象")
public class PermissionVO implements Serializable {
    
    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "权限ID")
    private Long id;
    
    @ApiModelProperty(value = "权限名称")
    private String permissionName;
    
    @ApiModelProperty(value = "权限标识")
    private String permissionKey;
    
    @ApiModelProperty(value = "权限类型:M-菜单 B-按钮")
    private String permissionType;
    
    @ApiModelProperty(value = "显示顺序")
    private Integer orderNum;
    
    @ApiModelProperty(value = "父权限ID")
    private Long parentId;
}

7.3 注解方式关联查询详细实现

/**
 * 注解方式关联查询详细实现
 * 重点:使用MyBatis注解实现各种关联查询
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

    /**
     * 1. 一对一关联查询:用户和部门
     * 重点:@One注解实现一对一关联
     */
    @Select("SELECT * FROM sys_user WHERE id = #{userId} AND deleted = 0")
    @Results({
        @Result(column = "id", property = "id"),
        @Result(column = "user_name", property = "userName"),
        @Result(column = "real_name", property = "realName"),
        @Result(column = "age", property = "age"),
        @Result(column = "email", property = "email"),
        @Result(column = "phone_number", property = "phoneNumber"),
        @Result(column = "dept_id", property = "deptId"),
        @Result(column = "status", property = "status"),
        @Result(column = "create_time", property = "createTime"),
        // 一对一关联:部门信息
        @Result(column = "dept_id", property = "dept",
                one = @One(select = "com.enterprise.mapper.DeptMapper.selectById", 
                          fetchType = FetchType.EAGER))
    })
    UserDetailVO selectUserWithDept(Long userId);

    /**
     * 2. 一对多关联查询:用户和角色
     * 重点:@Many注解实现一对多关联
     */
    @Select("SELECT * FROM sys_user WHERE id = #{userId} AND deleted = 0")
    @Results({
        @Result(column = "id", property = "id"),
        @Result(column = "user_name", property = "userName"),
        @Result(column = "real_name", property = "realName"),
        // 一对多关联:角色列表
        @Result(column = "id", property = "roles",
                many = @Many(select = "com.enterprise.mapper.RoleMapper.selectRolesByUserId", 
                            fetchType = FetchType.LAZY))
    })
    UserDetailVO selectUserWithRoles(Long userId);

    /**
     * 3. 多对多关联查询:用户和权限(通过角色中间表)
     * 重点:复杂的多对多关联实现
     */
    @Select("SELECT * FROM sys_user WHERE id = #{userId} AND deleted = 0")
    @Results({
        @Result(column = "id", property = "id"),
        @Result(column = "user_name", property = "userName"),
        // 多对多关联:权限列表(通过角色)
        @Result(column = "id", property = "permissions",
                many = @Many(select = "com.enterprise.mapper.PermissionMapper.selectPermissionsByUserId", 
                            fetchType = FetchType.LAZY))
    })
    UserDetailVO selectUserWithPermissions(Long userId);

    /**
     * 4. 复杂关联查询:用户完整信息
     * 重点:同时包含一对一、一对多、多对多关联
     */
    @Select("SELECT * FROM sys_user WHERE id = #{userId} AND deleted = 0")
    @Results({
        @Result(column = "id", property = "id"),
        @Result(column = "user_name", property = "userName"),
        @Result(column = "real_name", property = "realName"),
        @Result(column = "age", property = "age"),
        @Result(column = "email", property = "email"),
        @Result(column = "phone_number", property = "phoneNumber"),
        @Result(column = "dept_id", property = "deptId"),
        @Result(column = "status", property = "status"),
        @Result(column = "create_time", property = "createTime"),
        // 一对一:部门信息
        @Result(column = "dept_id", property = "dept",
                one = @One(select = "com.enterprise.mapper.DeptMapper.selectById")),
        // 一对多:角色列表
        @Result(column = "id", property = "roles",
                many = @Many(select = "com.enterprise.mapper.RoleMapper.selectRolesByUserId")),
        // 多对多:权限列表
        @Result(column = "id", property = "permissions",
                many = @Many(select = "com.enterprise.mapper.PermissionMapper.selectPermissionsByUserId"))
    })
    UserDetailVO selectUserDetail(Long userId);

    /**
     * 5. 关联查询分页
     * 重点:使用@SelectProvider实现复杂的分页关联查询
     */
    @SelectProvider(type = UserSqlProvider.class, method = "selectUserPageWithAssociations")
    List<UserDetailVO> selectUserPageWithAssociations(@Param("page") Page<UserDetailVO> page, 
                                                     @Param("query") UserQueryDTO query);
}

/**
 * 部门Mapper - 关联查询相关方法
 */
@Mapper
public interface DeptMapper extends BaseMapper<Dept> {
    
    @Select("SELECT * FROM sys_dept WHERE id = #{deptId} AND deleted = 0")
    DeptVO selectById(Long deptId);
}

/**
 * 角色Mapper - 关联查询相关方法
 */
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
    
    @Select("SELECT r.* FROM sys_role r " +
            "INNER JOIN sys_user_role ur ON r.id = ur.role_id " +
            "WHERE ur.user_id = #{userId} AND r.deleted = 0 " +
            "ORDER BY r.role_sort")
    List<RoleVO> selectRolesByUserId(Long userId);
}

/**
 * 权限Mapper - 关联查询相关方法
 */
@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {
    
    @Select("SELECT DISTINCT p.* FROM sys_permission p " +
            "INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " +
            "INNER JOIN sys_user_role ur ON rp.role_id = ur.role_id " +
            "WHERE ur.user_id = #{userId} AND p.status = 0 AND p.deleted = 0 " +
            "ORDER BY p.order_num")
    List<PermissionVO> selectPermissionsByUserId(Long userId);
}

/**
 * SQL提供类 - 动态SQL生成
 */
@Slf4j
public class UserSqlProvider {
    
    /**
     * 构建用户分页关联查询SQL
     */
    public String selectUserPageWithAssociations(Map<String, Object> params) {
        Page<?> page = (Page<?>) params.get("page");
        UserQueryDTO query = (UserQueryDTO) params.get("query");
        
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT ");
        sql.append("u.id, u.user_name, u.real_name, u.age, u.email, u.phone_number, ");
        sql.append("u.dept_id, u.status, u.create_time, ");
        sql.append("d.dept_name, d.leader as dept_leader, ");
        sql.append("(SELECT COUNT(*) FROM sys_order o WHERE o.user_id = u.id AND o.deleted = 0) as order_count, ");
        sql.append("(SELECT MAX(login_time) FROM sys_login_log l WHERE l.user_id = u.id) as last_login_time ");
        sql.append("FROM sys_user u ");
        sql.append("LEFT JOIN sys_dept d ON u.dept_id = d.id AND d.deleted = 0 ");
        sql.append("WHERE u.deleted = 0 ");
        
        // 动态条件
        if (StringUtils.isNotBlank(query.getUserName())) {
            sql.append("AND u.user_name LIKE CONCAT('%', #{query.userName}, '%') ");
        }
        if (StringUtils.isNotBlank(query.getRealName())) {
            sql.append("AND u.real_name LIKE CONCAT('%', #{query.realName}, '%') ");
        }
        if (query.getDeptId() != null) {
            sql.append("AND u.dept_id = #{query.deptId} ");
        }
        if (query.getStatus() != null) {
            sql.append("AND u.status = #{query.status} ");
        }
        if (query.getStartTime() != null) {
            sql.append("AND u.create_time >= #{query.startTime} ");
        }
        if (query.getEndTime() != null) {
            sql.append("AND u.create_time <= #{query.endTime} ");
        }
        
        // 排序
        sql.append("ORDER BY u.create_time DESC ");
        
        // 分页
        if (page != null) {
            long offset = (page.getCurrent() - 1) * page.getSize();
            sql.append("LIMIT ").append(offset).append(", ").append(page.getSize());
        }
        
        log.debug("生成的SQL: {}", sql.toString());
        return sql.toString();
    }
}

7.4 XML配置方式关联查询详细实现

<!-- UserMapper.xml - 详细的XML关联查询配置 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.enterprise.mapper.UserMapper">

    <!-- ==================== 基础结果映射 ==================== -->
    
    <!-- 用户基础结果映射 -->
    <resultMap id="UserBaseResultMap" type="User">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
        <result column="real_name" property="realName"/>
        <result column="age" property="age"/>
        <result column="email" property="email"/>
        <result column="phone_number" property="phoneNumber"/>
        <result column="dept_id" property="deptId"/>
        <result column="status" property="status"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
        <result column="deleted" property="deleted"/>
    </resultMap>

    <!-- 用户详情VO结果映射 -->
    <resultMap id="UserDetailVOResultMap" type="UserDetailVO">
        <!-- 用户基础字段 -->
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
        <result column="real_name" property="realName"/>
        <result column="age" property="age"/>
        <result column="email" property="email"/>
        <result column="phone_number" property="phoneNumber"/>
        <result column="dept_id" property="deptId"/>
        <result column="status" property="status"/>
        <result column="create_time" property="createTime"/>
        
        <!-- 部门字段(直接映射) -->
        <result column="dept_name" property="deptName"/>
        <result column="dept_leader" property="deptLeader"/>
        <result column="order_count" property="orderCount"/>
        <result column="last_login_time" property="lastLoginTime"/>
        
        <!-- 一对一关联:部门信息 -->
        <association property="dept" javaType="DeptVO" 
                     select="com.enterprise.mapper.DeptMapper.selectById" 
                     column="dept_id" fetchType="lazy"/>
        
        <!-- 一对多关联:角色列表 -->
        <collection property="roles" ofType="RoleVO" 
                   select="com.enterprise.mapper.RoleMapper.selectRolesByUserId" 
                   column="id" fetchType="lazy"/>
        
        <!-- 多对多关联:权限列表 -->
        <collection property="permissions" ofType="PermissionVO" 
                   select="com.enterprise.mapper.PermissionMapper.selectPermissionsByUserId" 
                   column="id" fetchType="lazy"/>
    </resultMap>

    <!-- 用户与部门JOIN结果映射 -->
    <resultMap id="UserWithDeptResultMap" type="UserDetailVO">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
        <result column="real_name" property="realName"/>
        <result column="age" property="age"/>
        <result column="email" property="email"/>
        <result column="phone_number" property="phoneNumber"/>
        <result column="status" property="status"/>
        <result column="create_time" property="createTime"/>
        
        <!-- 部门信息 -->
        <result column="dept_id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
        <result column="dept_leader" property="deptLeader"/>
        <result column="dept_status" property="deptStatus"/>
    </resultMap>

    <!-- 用户完整信息结果映射 -->
    <resultMap id="UserFullInfoResultMap" type="UserDetailVO" extends="UserWithDeptResultMap">
        <!-- 角色信息(嵌套结果) -->
        <collection property="roles" ofType="RoleVO" resultMap="RoleBaseResultMap" 
                   columnPrefix="role_"/>
    </resultMap>

    <!-- 角色基础结果映射 -->
    <resultMap id="RoleBaseResultMap" type="RoleVO">
        <id column="id" property="id"/>
        <result column="role_name" property="roleName"/>
        <result column="role_key" property="roleKey"/>
        <result column="role_sort" property="roleSort"/>
        <result column="status" property="status"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <!-- ==================== 关联查询SQL定义 ==================== -->

    <!-- 1. 一对一关联:用户和部门 -->
    <select id="selectUserWithDeptByXml" resultMap="UserWithDeptResultMap">
        SELECT 
            u.id,
            u.user_name,
            u.real_name,
            u.age,
            u.email,
            u.phone_number,
            u.status,
            u.create_time,
            u.dept_id,
            d.dept_name,
            d.leader as dept_leader,
            d.status as dept_status
        FROM sys_user u
        LEFT JOIN sys_dept d ON u.dept_id = d.id AND d.deleted = 0
        WHERE u.id = #{userId} AND u.deleted = 0
    </select>

    <!-- 2. 一对多关联:用户和角色(嵌套结果) -->
    <select id="selectUserWithRolesByXml" resultMap="UserFullInfoResultMap">
        SELECT 
            u.id,
            u.user_name,
            u.real_name,
            u.age,
            u.email,
            u.phone_number,
            u.status,
            u.create_time,
            u.dept_id,
            d.dept_name,
            d.leader as dept_leader,
            r.id as role_id,
            r.role_name as role_role_name,
            r.role_key as role_role_key,
            r.role_sort as role_role_sort,
            r.status as role_status,
            r.create_time as role_create_time
        FROM sys_user u
        LEFT JOIN sys_dept d ON u.dept_id = d.id AND d.deleted = 0
        LEFT JOIN sys_user_role ur ON u.id = ur.user_id
        LEFT JOIN sys_role r ON ur.role_id = r.id AND r.deleted = 0
        WHERE u.id = #{userId} AND u.deleted = 0
        ORDER BY r.role_sort
    </select>

    <!-- 3. 多表关联分页查询 -->
    <select id="selectUserPageWithMultipleAssociations" resultMap="UserDetailVOResultMap">
        SELECT 
            u.id,
            u.user_name,
            u.real_name,
            u.age,
            u.email,
            u.phone_number,
            u.dept_id,
            u.status,
            u.create_time,
            d.dept_name,
            d.leader as dept_leader,
            (SELECT COUNT(*) FROM sys_order o WHERE o.user_id = u.id AND o.deleted = 0) as order_count,
            (SELECT MAX(login_time) FROM sys_login_log l WHERE l.user_id = u.id) as last_login_time
        FROM sys_user u
        LEFT JOIN sys_dept d ON u.dept_id = d.id AND d.deleted = 0
        WHERE u.deleted = 0
        <if test="query.userName != null and query.userName != ''">
            AND u.user_name LIKE CONCAT('%', #{query.userName}, '%')
        </if>
        <if test="query.realName != null and query.realName != ''">
            AND u.real_name LIKE CONCAT('%', #{query.realName}, '%')
        </if>
        <if test="query.deptId != null">
            AND u.dept_id = #{query.deptId}
        </if>
        <if test="query.status != null">
            AND u.status = #{query.status}
        </if>
        <if test="query.startTime != null">
            AND u.create_time >= #{query.startTime}
        </if>
        <if test="query.endTime != null">
            AND u.create_time <= #{query.endTime}
        </if>
        ORDER BY u.create_time DESC
        LIMIT #{offset}, #{pageSize}
    </select>

    <!-- 4. 动态关联查询 -->
    <select id="selectUserWithDynamicAssociations" resultMap="UserDetailVOResultMap">
        SELECT 
            u.id,
            u.user_name,
            u.real_name,
            u.age,
            u.email,
            u.phone_number,
            u.dept_id,
            u.status,
            u.create_time
            <if test="includeDept != null and includeDept">
                ,d.dept_name, d.leader as dept_leader
            </if>
            <if test="includeStats != null and includeStats">
                ,(SELECT COUNT(*) FROM sys_order o WHERE o.user_id = u.id AND o.deleted = 0) as order_count
                ,(SELECT MAX(login_time) FROM sys_login_log l WHERE l.user_id = u.id) as last_login_time
            </if>
        FROM sys_user u
        <if test="includeDept != null and includeDept">
            LEFT JOIN sys_dept d ON u.dept_id = d.id AND d.deleted = 0
        </if>
        WHERE u.deleted = 0
        <if test="userId != null">
            AND u.id = #{userId}
        </if>
        ORDER BY u.create_time DESC
    </select>

    <!-- 5. 嵌套查询:分步加载关联数据 -->
    <select id="selectUserWithStepByStep" resultMap="UserDetailVOResultMap">
        SELECT 
            id,
            user_name,
            real_name,
            age,
            email,
            phone_number,
            dept_id,
            status,
            create_time
        FROM sys_user
        WHERE deleted = 0
        <if test="userId != null">
            AND id = #{userId}
        </if>
        ORDER BY create_time DESC
    </select>

</mapper>

7.5 关联查询业务服务实现

/**
 * 关联查询业务服务详细实现
 * 重点:各种关联查询场景的业务逻辑处理
 */
@Service
@Slf4j
@Transactional(readOnly = true)
public class UserAssociationService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    /**
     * 1. 获取用户详情(包含部门信息)
     */
    public UserDetailVO getUserDetailWithDept(Long userId) {
        log.info("获取用户详情,用户ID: {}", userId);
        
        // 方式1:使用注解方式查询
        // UserDetailVO userDetail = userMapper.selectUserWithDept(userId);
        
        // 方式2:使用XML方式查询
        UserDetailVO userDetail = userMapper.selectUserWithDeptByXml(userId);
        
        if (userDetail == null) {
            throw new RuntimeException("用户不存在,ID: " + userId);
        }
        
        return userDetail;
    }

    /**
     * 2. 获取用户完整信息(包含所有关联数据)
     */
    public UserDetailVO getUserFullInfo(Long userId) {
        log.info("获取用户完整信息,用户ID: {}", userId);
        
        // 第一步:查询用户基础信息
        UserDetailVO userDetail = userMapper.selectUserWithStepByStep(userId);
        if (userDetail == null) {
            throw new RuntimeException("用户不存在,ID: " + userId);
        }
        
        // 第二步:查询部门信息
        if (userDetail.getDeptId() != null) {
            DeptVO dept = deptMapper.selectById(userDetail.getDeptId());
            userDetail.setDept(dept);
            userDetail.setDeptName(dept.getDeptName());
            userDetail.setDeptLeader(dept.getLeader());
        }
        
        // 第三步:查询角色信息
        List<RoleVO> roles = roleMapper.selectRolesByUserId(userId);
        userDetail.setRoles(roles);
        
        // 设置角色名称和Key的字符串形式
        if (!roles.isEmpty()) {
            String roleNames = roles.stream()
                    .map(RoleVO::getRoleName)
                    .collect(Collectors.joining(","));
            String roleKeys = roles.stream()
                    .map(RoleVO::getRoleKey)
                    .collect(Collectors.joining(","));
            userDetail.setRoleNames(roleNames);
            userDetail.setRoleKeys(roleKeys);
        }
        
        // 第四步:查询权限信息
        List<PermissionVO> permissions = permissionMapper.selectPermissionsByUserId(userId);
        userDetail.setPermissions(permissions);
        
        // 设置权限Key列表
        if (!permissions.isEmpty()) {
            List<String> permissionKeys = permissions.stream()
                    .map(PermissionVO::getPermissionKey)
                    .collect(Collectors.toList());
            userDetail.setPermissionKeys(permissionKeys);
        }
        
        log.info("用户完整信息查询完成,用户: {}", userDetail.getUserName());
        return userDetail;
    }

    /**
     * 3. 分页查询用户列表(包含关联信息)
     */
    public PageResult<UserDetailVO> getUserPageWithAssociations(UserQueryDTO query) {
        log.info("分页查询用户列表,查询条件: {}", query);
        
        // 创建分页对象
        Page<UserDetailVO> page = new Page<>(query.getPageNum(), query.getPageSize());
        
        // 执行分页查询
        List<UserDetailVO> userList = userMapper.selectUserPageWithAssociations(page, query);
        
        // 如果需要,可以在这里补充其他关联数据
        if (query.isIncludeRoles()) {
            userList.forEach(user -> {
                List<RoleVO> roles = roleMapper.selectRolesByUserId(user.getId());
                user.setRoles(roles);
            });
        }
        
        // 构建分页结果
        PageResult<UserDetailVO> pageResult = new PageResult<>();
        pageResult.setCurrent(page.getCurrent());
        pageResult.setSize(page.getSize());
        pageResult.setTotal(page.getTotal());
        pageResult.setRecords(userList);
        
        log.info("用户分页查询完成,总数: {}, 当前页记录数: {}", page.getTotal(), userList.size());
        return pageResult;
    }

    /**
     * 4. 根据部门查询用户列表
     */
    public List<UserDetailVO> getUsersByDept(Long deptId, boolean includeChildren) {
        log.info("根据部门查询用户列表,部门ID: {}, 包含子部门: {}", deptId, includeChildren);
        
        List<Long> deptIds = new ArrayList<>();
        deptIds.add(deptId);
        
        // 如果包含子部门,查询所有子部门ID
        if (includeChildren) {
            List<Long> childDeptIds = deptMapper.selectChildrenDeptIds(deptId);
            deptIds.addAll(childDeptIds);
        }
        
        // 构建查询条件
        UserQueryDTO query = new UserQueryDTO();
        query.setDeptIds(deptIds);
        
        // 执行查询
        List<UserDetailVO> users = userMapper.selectUsersByDeptIds(query);
        
        log.info("部门用户查询完成,部门ID: {}, 用户数量: {}", deptId, users.size());
        return users;
    }

    /**
     * 5. 根据角色查询用户列表
     */
    public List<UserDetailVO> getUsersByRole(String roleKey) {
        log.info("根据角色查询用户列表,角色Key: {}", roleKey);
        
        // 执行查询
        List<UserDetailVO> users = userMapper.selectUsersByRoleKey(roleKey);
        
        log.info("角色用户查询完成,角色Key: {}, 用户数量: {}", roleKey, users.size());
        return users;
    }

    /**
     * 6. 动态关联查询(按需加载关联数据)
     */
    public UserDetailVO getUserWithDynamicAssociations(Long userId, boolean includeDept, 
                                                      boolean includeRoles, boolean includePermissions) {
        log.info("动态关联查询用户,用户ID: {}, 包含部门: {}, 包含角色: {}, 包含权限: {}", 
                userId, includeDept, includeRoles, includePermissions);
        
        // 执行动态查询
        UserDetailVO userDetail = userMapper.selectUserWithDynamicAssociations(
            userId, includeDept, includeDept); // includeDept同时用于统计信息
        
        if (userDetail == null) {
            throw new RuntimeException("用户不存在,ID: " + userId);
        }
        
        // 按需加载角色信息
        if (includeRoles) {
            List<RoleVO> roles = roleMapper.selectRolesByUserId(userId);
            userDetail.setRoles(roles);
        }
        
        // 按需加载权限信息
        if (includePermissions) {
            List<PermissionVO> permissions = permissionMapper.selectPermissionsByUserId(userId);
            userDetail.setPermissions(permissions);
        }
        
        return userDetail;
    }

    /**
     * 7. 批量查询用户详情(优化N+1查询)
     */
    public Map<Long, UserDetailVO> batchGetUserDetails(List<Long> userIds) {
        log.info("批量查询用户详情,用户ID数量: {}", userIds.size());
        
        if (userIds == null || userIds.isEmpty()) {
            return Collections.emptyMap();
        }
        
        // 批量查询用户基础信息
        List<UserDetailVO> userList = userMapper.selectUserListByIds(userIds);
        
        // 批量查询部门信息
        List<Long> deptIds = userList.stream()
                .map(UserDetailVO::getDeptId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        
        Map<Long, DeptVO> deptMap = Collections.emptyMap();
        if (!deptIds.isEmpty()) {
            List<DeptVO> depts = deptMapper.selectBatchIds(deptIds);
            deptMap = depts.stream()
                    .collect(Collectors.toMap(DeptVO::getId, Function.identity()));
        }
        
        // 批量查询角色信息
        Map<Long, List<RoleVO>> userRolesMap = roleMapper.selectRolesByUserIds(userIds);
        
        // 组装数据
        Map<Long, UserDetailVO> resultMap = new HashMap<>();
        for (UserDetailVO user : userList) {
            // 设置部门信息
            if (user.getDeptId() != null && deptMap.containsKey(user.getDeptId())) {
                DeptVO dept = deptMap.get(user.getDeptId());
                user.setDept(dept);
                user.setDeptName(dept.getDeptName());
                user.setDeptLeader(dept.getLeader());
            }
            
            // 设置角色信息
            if (userRolesMap.containsKey(user.getId())) {
                List<RoleVO> roles = userRolesMap.get(user.getId());
                user.setRoles(roles);
                
                // 设置角色字符串
                String roleNames = roles.stream()
                        .map(RoleVO::getRoleName)
                        .collect(Collectors.joining(","));
                user.setRoleNames(roleNames);
            }
            
            resultMap.put(user.getId(), user);
        }
        
        log.info("批量用户详情查询完成,用户数量: {}", resultMap.size());
        return resultMap;
    }

    /**
     * 8. 用户权限验证
     */
    public boolean checkUserPermission(Long userId, String permissionKey) {
        log.debug("验证用户权限,用户ID: {}, 权限Key: {}", userId, permissionKey);
        
        List<PermissionVO> permissions = permissionMapper.selectPermissionsByUserId(userId);
        
        boolean hasPermission = permissions.stream()
                .anyMatch(permission -> permissionKey.equals(permission.getPermissionKey()));
        
        log.debug("用户权限验证结果: {} -> {}", permissionKey, hasPermission);
        return hasPermission;
    }

    /**
     * 9. 用户菜单树查询
     */
    public List<MenuTreeNodeVO> getUserMenuTree(Long userId) {
        log.info("查询用户菜单树,用户ID: {}", userId);
        
        // 查询用户所有权限
        List<PermissionVO> permissions = permissionMapper.selectPermissionsByUserId(userId);
        
        // 过滤出菜单权限
        List<PermissionVO> menuPermissions = permissions.stream()
                .filter(p -> "M".equals(p.getPermissionType())) // M表示菜单
                .sorted(Comparator.comparing(PermissionVO::getOrderNum))
                .collect(Collectors.toList());
        
        // 构建菜单树
        List<MenuTreeNodeVO> menuTree = buildMenuTree(menuPermissions, 0L);
        
        log.info("用户菜单树查询完成,菜单数量: {}", menuTree.size());
        return menuTree;
    }
    
    private List<MenuTreeNodeVO> buildMenuTree(List<PermissionVO> permissions, Long parentId) {
        return permissions.stream()
                .filter(p -> Objects.equals(p.getParentId(), parentId))
                .map(permission -> {
                    MenuTreeNodeVO node = new MenuTreeNodeVO();
                    node.setId(permission.getId());
                    node.setName(permission.getPermissionName());
                    node.setKey(permission.getPermissionKey());
                    node.setOrderNum(permission.getOrderNum());
                    
                    // 递归构建子节点
                    List<MenuTreeNodeVO> children = buildMenuTree(permissions, permission.getId());
                    node.setChildren(children);
                    
                    return node;
                })
                .sorted(Comparator.comparing(MenuTreeNodeVO::getOrderNum))
                .collect(Collectors.toList());
    }
}

/**
 * 菜单树节点VO
 */
@Data
class MenuTreeNodeVO {
    private Long id;
    private String name;
    private String key;
    private Integer orderNum;
    private List<MenuTreeNodeVO> children;
}

7.6 关联查询性能优化方案

/**
 * 关联查询性能优化方案
 * 重点:解决N+1查询问题,提升关联查询性能
 */
@Service
@Slf4j
public class AssociationQueryOptimizationService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private RoleMapper roleMapper;

    /**
     * 1. 解决N+1查询问题 - JOIN方式
     * 重点:使用JOIN一次性查询所有数据,避免多次查询
     */
    public List<UserDetailVO> getUserListWithJoin() {
        log.info("使用JOIN方式查询用户列表(解决N+1问题)");
        
        // 一次性JOIN查询所有需要的数据
        List<UserDetailVO> users = userMapper.selectUserListWithJoin();
        
        log.info("JOIN查询完成,用户数量: {}", users.size());
        return users;
    }

    /**
     * 2. 解决N+1查询问题 - 批量查询方式
     * 重点:先查询主表,再批量查询关联表
     */
    public List<UserDetailVO> getUserListWithBatchQuery() {
        log.info("使用批量查询方式查询用户列表(解决N+1问题)");
        
        // 第一步:查询所有用户
        List<UserDetailVO> users = userMapper.selectUserListWithStepByStep(null);
        
        // 第二步:批量查询部门信息
        List<Long> deptIds = users.stream()
                .map(UserDetailVO::getDeptId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        
        Map<Long, DeptVO> deptMap = Collections.emptyMap();
        if (!deptIds.isEmpty()) {
            List<DeptVO> depts = deptMapper.selectBatchIds(deptIds);
            deptMap = depts.stream()
                    .collect(Collectors.toMap(DeptVO::getId, Function.identity()));
        }
        
        // 第三步:批量查询角色信息
        List<Long> userIds = users.stream()
                .map(UserDetailVO::getId)
                .collect(Collectors.toList());
        
        Map<Long, List<RoleVO>> userRolesMap = roleMapper.selectRolesByUserIds(userIds);
        
        // 第四步:组装数据
        for (UserDetailVO user : users) {
            // 设置部门信息
            if (user.getDeptId() != null && deptMap.containsKey(user.getDeptId())) {
                DeptVO dept = deptMap.get(user.getDeptId());
                user.setDept(dept);
                user.setDeptName(dept.getDeptName());
            }
            
            // 设置角色信息
            if (userRolesMap.containsKey(user.getId())) {
                user.setRoles(userRolesMap.get(user.getId()));
            }
        }
        
        log.info("批量查询完成,用户数量: {}", users.size());
        return users;
    }

    /**
     * 3. 延迟加载优化
     * 重点:按需加载关联数据,减少不必要的查询
     */
    public UserDetailVO getUserWithLazyLoading(Long userId, Set<String> includeFields) {
        log.info("使用延迟加载查询用户,用户ID: {}, 包含字段: {}", userId, includeFields);
        
        UserDetailVO userDetail = new UserDetailVO();
        
        // 总是加载基础信息
        UserDetailVO baseInfo = userMapper.selectUserBaseInfo(userId);
        if (baseInfo == null) {
            throw new RuntimeException("用户不存在,ID: " + userId);
        }
        
        BeanUtils.copyProperties(baseInfo, userDetail);
        
        // 按需加载部门信息
        if (includeFields.contains("dept")) {
            DeptVO dept = deptMapper.selectById(userDetail.getDeptId());
            userDetail.setDept(dept);
        }
        
        // 按需加载角色信息
        if (includeFields.contains("roles")) {
            List<RoleVO> roles = roleMapper.selectRolesByUserId(userId);
            userDetail.setRoles(roles);
        }
        
        // 按需加载权限信息
        if (includeFields.contains("permissions")) {
            List<PermissionVO> permissions = permissionMapper.selectPermissionsByUserId(userId);
            userDetail.setPermissions(permissions);
        }
        
        return userDetail;
    }

    /**
     * 4. 分页查询优化 - 延迟关联
     * 重点:先分页查询ID,再关联查询详细数据
     */
    public PageResult<UserDetailVO> getUserPageWithDeferredJoin(UserQueryDTO query) {
        log.info("使用延迟关联分页查询用户列表");
        
        // 第一步:分页查询用户ID
        Page<User> idPage = new Page<>(query.getPageNum(), query.getPageSize());
        QueryWrapper<User> idWrapper = new QueryWrapper<>();
        idWrapper.select("id")
                .eq("deleted", 0);
        
        // 添加查询条件
        if (StringUtils.isNotBlank(query.getUserName())) {
            idWrapper.like("user_name", query.getUserName());
        }
        if (query.getDeptId() != null) {
            idWrapper.eq("dept_id", query.getDeptId());
        }
        
        Page<User> idResult = userMapper.selectPage(idPage, idWrapper);
        List<Long> userIds = idResult.getRecords().stream()
                .map(User::getId)
                .collect(Collectors.toList());
        
        if (userIds.isEmpty()) {
            return new PageResult<>(query.getPageNum(), query.getPageSize());
        }
        
        // 第二步:根据ID列表查询完整数据(包含关联信息)
        List<UserDetailVO> userDetails = userMapper.selectUserListWithAssociationsByIds(userIds);
        
        // 第三步:组装分页结果
        PageResult<UserDetailVO> pageResult = new PageResult<>();
        pageResult.setCurrent(idResult.getCurrent());
        pageResult.setSize(idResult.getSize());
        pageResult.setTotal(idResult.getTotal());
        pageResult.setRecords(userDetails);
        
        log.info("延迟关联分页查询完成,总数: {}, 当前页: {}", pageResult.getTotal(), userDetails.size());
        return pageResult;
    }

    /**
     * 5. 缓存优化
     * 重点:使用缓存减少数据库查询
     */
    @Cacheable(value = "user:detail", key = "#userId")
    public UserDetailVO getUserDetailWithCache(Long userId) {
        log.info("查询用户详情(使用缓存),用户ID: {}", userId);
        return getUserFullInfo(userId);
    }

    /**
     * 6. 索引优化建议
     */
    public void suggestIndexes() {
        log.info("""
            ==================== 关联查询索引优化建议 ====================
            1. 用户表索引:
               - PRIMARY KEY (id)
               - INDEX idx_user_dept (dept_id, deleted)
               - INDEX idx_user_status (status, deleted) 
               - INDEX idx_user_create_time (create_time)
               
            2. 部门表索引:
               - PRIMARY KEY (id)
               - INDEX idx_dept_parent (parent_id)
               - INDEX idx_dept_status (status, deleted)
               
            3. 用户角色关联表索引:
               - UNIQUE KEY uk_user_role (user_id, role_id)
               - INDEX idx_role_user (role_id, user_id)
               
            4. 角色权限关联表索引:
               - UNIQUE KEY uk_role_permission (role_id, permission_id)
               - INDEX idx_permission_role (permission_id, role_id)
            """);
    }

    /**
     * 7. 查询执行计划分析
     */
    public void analyzeQueryExecutionPlan(String sql) {
        log.info("分析查询执行计划,SQL: {}", sql);
        
        // 在实际项目中,这里可以:
        // 1. 使用EXPLAIN分析SQL执行计划
        // 2. 检查是否使用了正确的索引
        // 3. 识别全表扫描和性能瓶颈
        // 4. 提供优化建议
        
        log.info("""
            执行计划分析要点:
            1. 检查type列:应该避免ALL(全表扫描)
            2. 检查key列:确保使用了正确的索引
            3. 检查rows列:预估扫描行数应该尽可能少
            4. 检查Extra列:避免Using filesort和Using temporary
            """);
    }
}

企业级MyBatis-Plus实战教学(续)

模块八:事务管理与批量操作优化

8.1 Spring事务管理深度解析

8.1.1 事务配置完整实现

/**
 * 事务管理完整配置详解
 * 重点:Spring事务的完整配置和最佳实践
 */
@Configuration
@EnableTransactionManagement // 开启注解事务管理
@EnableAspectJAutoProxy(exposeProxy = true) // 暴露代理对象,解决内部调用事务失效问题
@Slf4j
public class TransactionConfig {

    /**
     * 事务管理器配置
     * 重点:配置DataSourceTransactionManager,这是Spring事务管理的核心
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        
        // 设置数据源
        transactionManager.setDataSource(dataSource);
        
        // 设置全局超时时间(单位:秒)
        transactionManager.setDefaultTimeout(30);
        
        // 设置是否允许嵌套事务
        transactionManager.setNestedTransactionAllowed(true);
        
        // 设置是否在回滚时设置全局回滚标记
        transactionManager.setGlobalRollbackOnParticipationFailure(true);
        
        log.info("数据源事务管理器配置完成 - 超时时间: {}秒, 允许嵌套事务: {}", 
                30, true);
        
        return transactionManager;
    }

    /**
     * 事务模板配置
     * 重点:用于编程式事务管理
     */
    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
        TransactionTemplate transactionTemplate = new TransactionTemplate();
        
        // 设置事务管理器
        transactionTemplate.setTransactionManager(transactionManager);
        
        // 设置传播行为
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        
        // 设置隔离级别
        transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        
        // 设置超时时间
        transactionTemplate.setTimeout(30);
        
        // 设置是否只读
        transactionTemplate.setReadOnly(false);
        
        log.info("事务模板配置完成 - 传播行为: REQUIRED, 隔离级别: READ_COMMITTED");
        
        return transactionTemplate;
    }

    /**
     * 事务拦截器配置
     * 重点:自定义事务拦截逻辑
     */
    @Bean
    public TransactionInterceptor transactionInterceptor(PlatformTransactionManager transactionManager) {
        // 事务属性配置
        NameMatchTransactionAttributeSource attributeSource = new NameMatchTransactionAttributeSource();
        
        Map<String, TransactionAttribute> txMap = new HashMap<>();
        
        // ==================== 只读事务配置 ====================
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        
        // ==================== 写事务配置 ====================
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        requiredTx.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        requiredTx.setTimeout(30);
        
        // 设置回滚规则
        List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
        rollbackRules.add(new RollbackRuleAttribute(Exception.class));
        requiredTx.setRollbackRules(rollbackRules);
        
        // ==================== 新事务配置 ====================
        RuleBasedTransactionAttribute requiresNewTx = new RuleBasedTransactionAttribute();
        requiresNewTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        requiresNewTx.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        requiresNewTx.setTimeout(30);
        requiresNewTx.setRollbackRules(rollbackRules);
        
        // ==================== 方法匹配规则 ====================
        
        // 查询方法 - 只读事务
        txMap.put("get*", readOnlyTx);
        txMap.put("find*", readOnlyTx);
        txMap.put("query*", readOnlyTx);
        txMap.put("select*", readOnlyTx);
        txMap.put("count*", readOnlyTx);
        txMap.put("list*", readOnlyTx);
        txMap.put("search*", readOnlyTx);
        
        // 新增方法 - 写事务
        txMap.put("save*", requiredTx);
        txMap.put("add*", requiredTx);
        txMap.put("create*", requiredTx);
        txMap.put("insert*", requiredTx);
        txMap.put("register*", requiredTx);
        
        // 更新方法 - 写事务
        txMap.put("update*", requiredTx);
        txMap.put("modify*", requiredTx);
        txMap.put("change*", requiredTx);
        txMap.put("set*", requiredTx);
        
        // 删除方法 - 写事务
        txMap.put("delete*", requiredTx);
        txMap.put("remove*", requiredTx);
        txMap.put("cancel*", requiredTx);
        
        // 批量操作 - 写事务
        txMap.put("batch*", requiredTx);
        txMap.put("process*", requiredTx);
        
        // 特殊方法 - 新事务
        txMap.put("async*", requiresNewTx);
        txMap.put("background*", requiresNewTx);
        
        attributeSource.setNameMap(txMap);
        
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionManager(transactionManager);
        interceptor.setTransactionAttributeSource(attributeSource);
        
        log.info("事务拦截器配置完成 - 配置了 {} 个方法匹配规则", txMap.size());
        
        return interceptor;
    }
}

/**
 * 事务监控配置
 * 重点:监控事务执行情况,用于性能分析和问题排查
 */
@Configuration
@Slf4j
public class TransactionMonitorConfig {

    /**
     * 事务监控切面
     */
    @Bean
    public TransactionMonitorAspect transactionMonitorAspect() {
        return new TransactionMonitorAspect();
    }

    /**
     * 事务监控切面实现
     */
    @Aspect
    @Component
    @Slf4j
    public static class TransactionMonitorAspect {
        
        private final ThreadLocal<Long> startTime = new ThreadLocal<>();
        
        /**
         * 监控所有@Transactional注解的方法
         */
        @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
        public Object monitorTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
            String methodName = joinPoint.getSignature().toShortString();
            startTime.set(System.currentTimeMillis());
            
            log.debug("事务开始 - 方法: {}", methodName);
            
            try {
                Object result = joinPoint.proceed();
                
                long costTime = System.currentTimeMillis() - startTime.get();
                log.debug("事务提交成功 - 方法: {}, 耗时: {}ms", methodName, costTime);
                
                return result;
            } catch (Exception e) {
                long costTime = System.currentTimeMillis() - startTime.get();
                log.error("事务回滚 - 方法: {}, 耗时: {}ms, 异常: {}", 
                         methodName, costTime, e.getMessage(), e);
                throw e;
            } finally {
                startTime.remove();
            }
        }
    }
}

8.1.2 事务传播行为深度解析

/**
 * 事务传播行为深度解析
 * 重点:七种传播行为的详细说明和实战应用
 */
@Service
@Slf4j
@Transactional
public class TransactionPropagationService {

    @Autowired
    private UserService userService;

    @Autowired
    private DeptService deptService;

    @Autowired
    private TransactionPropagationService selfProxy; // 用于解决内部调用事务失效问题

    /**
     * 1. REQUIRED(默认)- 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
     * 使用场景:大多数业务方法
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void requiredExample() {
        log.info("REQUIRED传播行为 - 当前方法将在事务中执行");
        
        // 业务操作1
        userService.updateUserStatus(1L, 1);
        
        // 业务操作2 - 在同一个事务中
        deptService.updateDeptInfo(1L, "技术部");
        
        // 如果这里抛出异常,两个操作都会回滚
        // throw new RuntimeException("测试异常");
    }

    /**
     * 2. REQUIRES_NEW - 创建一个新的事务,如果当前存在事务,则把当前事务挂起
     * 使用场景:需要独立事务的操作,如日志记录、消息发送
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requiresNewExample() {
        log.info("REQUIRES_NEW传播行为 - 创建独立事务执行");
        
        // 这个方法的操作在独立事务中执行
        userService.recordOperationLog("REQUIRES_NEW示例");
        
        // 即使外部事务回滚,这个方法的操作也不会回滚
    }

    /**
     * 3. NESTED - 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行
     * 使用场景:可独立回滚的子操作
     */
    @Transactional(propagation = Propagation.NESTED)
    public void nestedExample() {
        log.info("NESTED传播行为 - 在嵌套事务中执行");
        
        // 嵌套事务可以独立回滚,不影响外部事务
        userService.updateUserProfile(1L, "新的个人资料");
        
        // 如果这里抛出异常,只会回滚这个嵌套事务,外部事务继续执行
        // throw new RuntimeException("嵌套事务异常");
    }

    /**
     * 4. SUPPORTS - 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
     * 使用场景:查询操作,可以适应调用方的事务状态
     */
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public User supportsExample(Long userId) {
        log.info("SUPPORTS传播行为 - 有事务则加入,没有则非事务运行");
        
        // 这个方法可以适应调用方的事务状态
        return userService.getUserById(userId);
    }

    /**
     * 5. NOT_SUPPORTED - 以非事务方式运行,如果当前存在事务,则把当前事务挂起
     * 使用场景:不需要事务的操作,如数据导出、文件操作
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void notSupportedExample() {
        log.info("NOT_SUPPORTED传播行为 - 非事务方式运行");
        
        // 这个方法总是在非事务环境下执行
        userService.exportUserData();
    }

    /**
     * 6. MANDATORY - 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
     * 使用场景:必须在事务中调用的方法
     */
    @Transactional(propagation = Propagation.MANDATORY)
    public void mandatoryExample() {
        log.info("MANDATORY传播行为 - 必须在事务中运行");
        
        // 如果调用方没有事务,会抛出IllegalTransactionStateException
        userService.criticalBusinessOperation();
    }

    /**
     * 7. NEVER - 以非事务方式运行,如果当前存在事务,则抛出异常
     * 使用场景:绝对不能在有事务的情况下调用的方法
     */
    @Transactional(propagation = Propagation.NEVER)
    public void neverExample() {
        log.info("NEVER传播行为 - 必须在非事务中运行");
        
        // 如果调用方有事务,会抛出IllegalTransactionStateException
        userService.nonTransactionalOperation();
    }

    /**
     * 复杂事务场景示例:混合使用不同传播行为
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void complexTransactionScenario() {
        log.info("复杂事务场景 - 开始执行");
        
        // 1. 主业务操作(在外部事务中)
        userService.updateUserStatus(1L, 1);
        
        try {
            // 2. 独立事务操作:即使这里失败,也不影响外部事务
            selfProxy.independentOperation();
        } catch (Exception e) {
            log.error("独立操作失败,但外部事务继续", e);
            // 这里捕获异常,防止影响外部事务
        }
        
        try {
            // 3. 嵌套事务操作:可以独立回滚
            selfProxy.nestedOperation();
        } catch (Exception e) {
            log.error("嵌套操作失败,但外部事务继续", e);
            // 嵌套事务独立回滚,不影响外部事务
        }
        
        // 4. 非事务操作
        selfProxy.nonTransactionalOperation();
        
        // 如果这里抛出异常,只有主业务操作会回滚
        log.info("复杂事务场景 - 执行完成");
    }
    
    /**
     * 独立事务操作
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void independentOperation() {
        log.info("独立事务操作 - 开始");
        deptService.updateDeptInfo(1L, "独立事务更新的部门");
        
        // 模拟可能失败的独立操作
        if (System.currentTimeMillis() % 2 == 0) {
            throw new RuntimeException("独立事务操作失败");
        }
        log.info("独立事务操作 - 完成");
    }
    
    /**
     * 嵌套事务操作
     */
    @Transactional(propagation = Propagation.NESTED)
    public void nestedOperation() {
        log.info("嵌套事务操作 - 开始");
        userService.updateUserProfile(1L, "嵌套事务更新的资料");
        
        // 模拟可能失败的嵌套操作
        if (System.currentTimeMillis() % 3 == 0) {
            throw new RuntimeException("嵌套事务操作失败");
        }
        log.info("嵌套事务操作 - 完成");
    }
    
    /**
     * 非事务操作
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void nonTransactionalOperation() {
        log.info("非事务操作 - 开始");
        // 执行不需要事务的操作
        userService.recordAccessLog();
        log.info("非事务操作 - 完成");
    }

    /**
     * 事务传播行为总结表
     */
    public void printPropagationSummary() {
        log.info("""
            ==================== 事务传播行为总结 ====================
            传播行为          当前有事务       当前无事务
            REQUIRED(默认)    加入当前事务     创建新事务
            REQUIRES_NEW     挂起当前事务     创建新事务
            NESTED           嵌套事务执行     创建新事务  
            SUPPORTS         加入当前事务     非事务执行
            NOT_SUPPORTED    挂起当前事务     非事务执行
            MANDATORY        加入当前事务     抛出异常
            NEVER            抛出异常        非事务执行
            ======================================================
            """);
    }
}

8.1.3 事务隔离级别深度解析

/**
 * 事务隔离级别深度解析
 * 重点:四种隔离级别解决的数据并发问题
 */
@Service
@Slf4j
@Transactional
public class TransactionIsolationService {

    @Autowired
    private UserService userService;

    @Autowired
    private AccountService accountService;

    /**
     * 1. READ_UNCOMMITTED(读未提交)
     * 问题:可能读取到其他事务未提交的数据(脏读)
     * 使用场景:对数据一致性要求不高的场景,如统计报表
     */
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void readUncommittedExample() {
        log.info("READ_UNCOMMITTED隔离级别 - 开始事务");
        
        // 第一次读取
        User user1 = userService.getUserById(1L);
        log.info("第一次读取用户: {}", user1);
        
        // 模拟其他事务修改了数据但未提交
        simulateConcurrentUncommittedUpdate();
        
        // 第二次读取(可能读取到未提交的数据 - 脏读)
        User user2 = userService.getUserById(1L);
        log.info("第二次读取用户: {}", user2);
        
        log.info("READ_UNCOMMITTED隔离级别 - 可能发生脏读");
    }

    /**
     * 2. READ_COMMITTED(读已提交)- Oracle、SQL Server默认级别
     * 解决:脏读问题
     * 问题:不可重复读(同一事务中多次读取同一数据可能结果不同)
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void readCommittedExample() {
        log.info("READ_COMMITTED隔离级别 - 开始事务");
        
        // 第一次读取
        User user1 = userService.getUserById(1L);
        log.info("第一次读取用户: {}", user1);
        
        // 模拟其他事务提交了数据修改
        simulateConcurrentCommittedUpdate();
        
        // 第二次读取(可能得到不同的结果 - 不可重复读)
        User user2 = userService.getUserById(1L);
        log.info("第二次读取用户: {}", user2);
        
        log.info("READ_COMMITTED隔离级别 - 解决脏读,可能发生不可重复读");
    }

    /**
     * 3. REPEATABLE_READ(可重复读)- MySQL默认级别
     * 解决:脏读、不可重复读问题
     * 问题:幻读(同一事务中多次查询的结果集数量可能不同)
     */
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void repeatableReadExample() {
        log.info("REPEATABLE_READ隔离级别 - 开始事务");
        
        // 第一次查询用户列表
        List<User> users1 = userService.getUserList();
        log.info("第一次查询用户数量: {}", users1.size());
        
        // 模拟其他事务插入了新用户
        simulateConcurrentInsert();
        
        // 第二次查询用户列表(可能得到不同的结果集数量 - 幻读)
        List<User> users2 = userService.getUserList();
        log.info("第二次查询用户数量: {}", users2.size());
        
        log.info("REPEATABLE_READ隔离级别 - 解决脏读、不可重复读,可能发生幻读");
    }

    /**
     * 4. SERIALIZABLE(串行化)
     * 解决:所有并发问题(脏读、不可重复读、幻读)
     * 问题:性能最低,完全串行执行
     */
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void serializableExample() {
        log.info("SERIALIZABLE隔离级别 - 开始事务");
        
        // 这个事务会串行执行,不会受到其他事务的干扰
        List<User> users = userService.getUserList();
        log.info("查询用户数量: {}", users.size());
        
        // 执行更新操作
        userService.updateUserStatus(1L, 1);
        
        log.info("SERIALIZABLE隔离级别 - 解决所有并发问题,性能最低");
    }

    /**
     * 5. 默认隔离级别(使用数据库默认设置)
     */
    @Transactional(isolation = Isolation.DEFAULT)
    public void defaultIsolationExample() {
        log.info("DEFAULT隔离级别 - 使用数据库默认隔离级别");
        
        User user = userService.getUserById(1L);
        log.info("读取用户: {}", user);
        
        // 更新用户信息
        userService.updateUserProfile(1L, "默认隔离级别测试");
        
        log.info("DEFAULT隔离级别 - 操作完成");
    }

    /**
     * 转账业务示例 - 使用合适的隔离级别
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
    public boolean transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        log.info("开始转账业务 - 从账户 {} 向账户 {} 转账 {}", fromAccountId, toAccountId, amount);
        
        // 1. 检查转出账户余额(需要读取最新提交的数据)
        Account fromAccount = accountService.getAccountById(fromAccountId);
        if (fromAccount.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("账户余额不足");
        }
        
        // 2. 检查转入账户状态
        Account toAccount = accountService.getAccountById(toAccountId);
        if (toAccount.getStatus() != 1) {
            throw new RuntimeException("转入账户状态异常");
        }
        
        // 3. 执行转账操作
        boolean deductSuccess = accountService.deductBalance(fromAccountId, amount);
        boolean addSuccess = accountService.addBalance(toAccountId, amount);
        
        if (!deductSuccess || !addSuccess) {
            throw new RuntimeException("转账操作失败");
        }
        
        // 4. 记录转账流水
        accountService.recordTransferLog(fromAccountId, toAccountId, amount);
        
        log.info("转账业务完成");
        return true;
    }

    /**
     * 模拟并发未提交更新
     */
    private void simulateConcurrentUncommittedUpdate() {
        // 在实际项目中,这里会有另一个事务执行更新但未提交
        new Thread(() -> {
            log.info("并发线程 - 开始未提交更新");
            // 这里应该启动一个新事务执行更新但不提交
        }).start();
        
        try {
            Thread.sleep(100); // 给并发线程一些时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 模拟并发已提交更新
     */
    private void simulateConcurrentCommittedUpdate() {
        new Thread(() -> {
            log.info("并发线程 - 开始已提交更新");
            // 这里应该启动一个新事务执行更新并提交
        }).start();
        
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 模拟并发插入
     */
    private void simulateConcurrentInsert() {
        new Thread(() -> {
            log.info("并发线程 - 开始插入新数据");
            // 这里应该启动一个新事务执行插入
        }).start();
        
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 隔离级别与并发问题总结
     */
    public void printIsolationSummary() {
        log.info("""
            ==================== 事务隔离级别总结 ====================
            隔离级别          脏读    不可重复读    幻读      性能
            READ_UNCOMMITTED  可能     可能        可能      最高
            READ_COMMITTED    不会     可能        可能      较高  
            REPEATABLE_READ   不会     不会        可能      中等
            SERIALIZABLE      不会     不会        不会      最低
            ======================================================
            
            选择建议:
            1. 数据一致性要求高:REPEATABLE_READ 或 SERIALIZABLE
            2. 高并发查询场景:READ_COMMITTED
            3. 统计报表场景:READ_UNCOMMITTED
            4. 默认选择:数据库默认级别
            """);
    }
}

8.2 声明式事务企业级实践

/**
 * 声明式事务企业级实践
 * 重点:@Transactional注解的各种用法和最佳实践
 */
@Service
@Slf4j
@Transactional(readOnly = true) // 类级别默认只读事务
public class DeclarativeTransactionService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private OperationLogMapper logMapper;

    /**
     * 1. 只读事务 - 用于查询操作
     * 重点:readOnly = true 可以优化查询性能,避免不必要的写锁
     */
    @Transactional(readOnly = true)
    public User getUserById(Long userId) {
        log.debug("只读事务 - 查询用户,用户ID: {}", userId);
        
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new RuntimeException("用户不存在,ID: " + userId);
        }
        
        return user;
    }

    /**
     * 2. 写事务 - 用于数据修改操作
     * 重点:readOnly = false(默认),遇到异常自动回滚
     */
    @Transactional(readOnly = false, rollbackFor = Exception.class)
    public boolean updateUser(User user) {
        log.info("写事务 - 更新用户,用户ID: {}", user.getId());
        
        // 检查用户是否存在
        User existingUser = userMapper.selectById(user.getId());
        if (existingUser == null) {
            throw new RuntimeException("用户不存在,ID: " + user.getId());
        }
        
        // 执行更新
        int affectedRows = userMapper.updateById(user);
        if (affectedRows == 0) {
            throw new RuntimeException("更新用户失败");
        }
        
        // 记录操作日志
        recordOperationLog("UPDATE_USER", "更新用户信息", user.getId());
        
        log.info("用户更新成功,用户ID: {}", user.getId());
        return true;
    }

    /**
     * 3. 自定义回滚规则
     * 重点:指定哪些异常回滚,哪些异常不回滚
     */
    @Transactional(rollbackFor = {RuntimeException.class, Exception.class},
                   noRollbackFor = {BusinessException.class})
    public boolean updateUserWithCustomRollback(User user) {
        log.info("自定义回滚规则 - 更新用户,用户ID: {}", user.getId());
        
        try {
            int affectedRows = userMapper.updateById(user);
            if (affectedRows == 0) {
                throw new BusinessException("业务异常,用户可能不存在"); // 不会触发回滚
            }
            
            // 模拟可能抛出系统异常的情况
            if (user.getUserName().contains("admin")) {
                throw new RuntimeException("系统异常,触发回滚"); // 会触发回滚
            }
            
            return true;
        } catch (BusinessException e) {
            log.warn("业务异常,继续执行: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 4. 超时设置 - 防止长时间事务
     */
    @Transactional(timeout = 10, rollbackFor = Exception.class) // 10秒超时
    public boolean updateUserWithTimeout(User user) {
        log.info("超时事务 - 更新用户,用户ID: {}", user.getId());
        
        // 模拟耗时操作
        try {
            Thread.sleep(5000); // 5秒
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("操作被中断", e);
        }
        
        int affectedRows = userMapper.updateById(user);
        if (affectedRows == 0) {
            throw new RuntimeException("更新用户失败");
        }
        
        // 如果总操作时间超过10秒,会抛出TransactionTimedOutException
        return true;
    }

    /**
     * 5. 复杂业务事务 - 多表操作
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean createUserWithRoles(User user, List<Long> roleIds) {
        log.info("复杂业务事务 - 创建用户并分配角色,用户名: {}", user.getUserName());
        
        // 1. 验证用户名唯一性
        if (isUserNameExists(user.getUserName())) {
            throw new BusinessException("用户名已存在: " + user.getUserName());
        }
        
        // 2. 创建用户
        int userResult = userMapper.insert(user);
        if (userResult == 0) {
            throw new RuntimeException("创建用户失败");
        }
        
        log.info("用户创建成功,用户ID: {}", user.getId());
        
        // 3. 分配角色
        if (roleIds != null && !roleIds.isEmpty()) {
            for (Long roleId : roleIds) {
                UserRole userRole = new UserRole();
                userRole.setUserId(user.getId());
                userRole.setRoleId(roleId);
                
                int roleResult = userRoleMapper.insert(userRole);
                if (roleResult == 0) {
                    throw new RuntimeException("分配角色失败,角色ID: " + roleId);
                }
            }
            log.info("角色分配成功,分配 {} 个角色", roleIds.size());
        }
        
        // 4. 记录操作日志
        recordOperationLog("CREATE_USER", "创建用户并分配角色", user.getId());
        
        log.info("创建用户并分配角色成功,用户ID: {}", user.getId());
        return true;
    }

    /**
     * 6. 嵌套事务示例
     */
    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public boolean nestedTransactionOperation(User user) {
        log.info("嵌套事务操作 - 更新用户,用户ID: {}", user.getId());
        
        int affectedRows = userMapper.updateById(user);
        if (affectedRows == 0) {
            // 嵌套事务可以单独回滚,不影响外部事务
            throw new RuntimeException("嵌套事务操作失败");
        }
        
        log.info("嵌套事务操作成功");
        return true;
    }

    /**
     * 7. 手动回滚示例
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean manualRollbackExample(User user) {
        log.info("手动回滚示例 - 更新用户,用户ID: {}", user.getId());
        
        try {
            int affectedRows = userMapper.updateById(user);
            if (affectedRows == 0) {
                // 手动设置回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                log.warn("更新失败,手动设置回滚");
                return false;
            }
            
            // 业务逻辑检查
            if (!isBusinessValid(user)) {
                // 手动设置回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                log.warn("业务验证失败,手动设置回滚");
                return false;
            }
            
            return true;
        } catch (Exception e) {
            // 异常时会自动回滚
            log.error("操作异常", e);
            throw e;
        }
    }

    /**
     * 8. 编程式事务示例
     */
    @Autowired
    private TransactionTemplate transactionTemplate;

    public boolean programmaticTransaction(User user) {
        log.info("编程式事务 - 更新用户,用户ID: {}", user.getId());
        
        return transactionTemplate.execute(status -> {
            try {
                // 业务操作1
                int affectedRows = userMapper.updateById(user);
                if (affectedRows == 0) {
                    status.setRollbackOnly(); // 手动回滚
                    return false;
                }
                
                // 业务操作2
                recordOperationLog("UPDATE_USER", "编程式事务更新用户", user.getId());
                
                return true;
            } catch (Exception e) {
                status.setRollbackOnly(); // 手动回滚
                log.error("编程式事务执行失败", e);
                throw new RuntimeException("事务执行失败", e);
            }
        });
    }

    /**
     * 9. 事务方法调用注意事项
     * 重点:在同一个类中调用事务方法,事务不会生效(代理问题)
     */
    public void transactionCallWarning() {
        log.info("事务方法调用警告示例");
        
        // 错误调用:在同一个类中调用事务方法,事务不会生效
        User user = new User();
        user.setId(1L);
        user.setUserName("test");
        
        // 这种方式事务不会生效!
        this.updateUser(user);
        
        log.warn("注意:在同一个类中调用事务方法,事务不会生效!");
        
        // 正确做法:
        // 1. 将事务方法放在另一个Service中调用
        // 2. 使用AopContext.currentProxy()获取代理对象
        // 3. 使用@Autowired注入自身代理(需要@EnableAspectJAutoProxy(exposeProxy = true))
    }

    // ==================== 辅助方法 ====================

    /**
     * 检查用户名是否存在
     */
    private boolean isUserNameExists(String userName) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_name", userName)
                   .eq("deleted", 0);
        
        Long count = userMapper.selectCount(queryWrapper);
        return count > 0;
    }

    /**
     * 业务验证
     */
    private boolean isBusinessValid(User user) {
        // 实际业务验证逻辑
        return user.getUserName() != null && !user.getUserName().trim().isEmpty();
    }

    /**
     * 记录操作日志
     */
    private void recordOperationLog(String operationType, String description, Long targetId) {
        OperationLog log = new OperationLog();
        log.setOperationType(operationType);
        log.setDescription(description);
        log.setTargetId(targetId);
        log.setOperator(getCurrentUser());
        log.setOperationTime(LocalDateTime.now());
        
        logMapper.insert(log);
    }

    /**
     * 获取当前用户
     */
    private String getCurrentUser() {
        // 实际项目中从SecurityContext获取
        return "system";
    }
}

/**
 * 自定义业务异常(不回滚)
 */
class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
    
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

/**
 * 操作日志实体
 */
@Data
@TableName("sys_operation_log")
class OperationLog {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String operationType;
    private String description;
    private Long targetId;
    private String operator;
    private LocalDateTime operationTime;
}

8.3 批量操作优化企业级实践

8.3.1 批量插入优化

/**
 * 批量插入优化企业级实践
 * 重点:各种批量插入方案的性能对比和最佳实践
 */
@Service
@Slf4j
@Transactional
public class BatchInsertService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    @Autowired
    private TransactionTemplate transactionTemplate;

    /**
     * 1. 使用MyBatis-Plus的saveBatch方法
     * 重点:适合小批量数据插入(1000条以内),内部自动分批
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchInsertWithSaveBatch(List<User> users) {
        log.info("使用saveBatch批量插入,数据量: {}", users.size());
        
        long startTime = System.currentTimeMillis();
        
        // MyBatis-Plus的saveBatch方法内部会分批处理
        // 第三个参数batchSize表示每批的数量,默认1000
        boolean success = userService.saveBatch(users, 1000);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("saveBatch批量插入完成,数据量: {}, 耗时: {} ms, 性能: {}/s", 
                users.size(), costTime, calculatePerformance(users.size(), costTime));
        
        return success;
    }

    /**
     * 2. 使用MyBatis-Plus的saveBatch方法(自定义批次大小)
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchInsertWithCustomBatchSize(List<User> users, int batchSize) {
        log.info("使用saveBatch批量插入(自定义批次大小),数据量: {}, 批次大小: {}", 
                users.size(), batchSize);
        
        long startTime = System.currentTimeMillis();
        
        boolean success = userService.saveBatch(users, batchSize);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("saveBatch批量插入完成,数据量: {}, 批次: {}, 耗时: {} ms", 
                users.size(), batchSize, costTime);
        
        return success;
    }

    /**
     * 3. 使用MyBatis的BatchExecutor(最高性能)
     * 重点:适合大批量数据插入(万级以上)
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchInsertWithBatchExecutor(List<User> users) {
        log.info("使用BatchExecutor批量插入,数据量: {}", users.size());
        
        long startTime = System.currentTimeMillis();
        
        // 获取批量模式的SqlSession
        SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory()
                .openSession(ExecutorType.BATCH);
        
        try {
            UserMapper batchMapper = sqlSession.getMapper(UserMapper.class);
            
            int count = 0;
            for (User user : users) {
                batchMapper.insert(user);
                count++;
                
                // 每1000条提交一次,防止内存溢出
                if (count % 1000 == 0) {
                    sqlSession.commit();
                    sqlSession.clearCache(); // 清理缓存,防止内存溢出
                    log.debug("已提交 {} 条数据", count);
                }
            }
            
            // 提交剩余数据
            sqlSession.commit();
            
            long costTime = System.currentTimeMillis() - startTime;
            log.info("BatchExecutor批量插入完成,总数据量: {}, 耗时: {} ms, 性能: {}/s", 
                    users.size(), costTime, calculatePerformance(users.size(), costTime));
            
            return true;
        } catch (Exception e) {
            sqlSession.rollback();
            log.error("BatchExecutor批量插入失败", e);
            throw new RuntimeException("批量插入失败", e);
        } finally {
            sqlSession.close();
        }
    }

    /**
     * 4. 使用SQL的foreach语句(单条INSERT多值)
     * 重点:MySQL有SQL长度限制(max_allowed_packet),需要分批处理
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchInsertWithForeachSql(List<User> users) {
        log.info("使用foreach SQL批量插入,数据量: {}", users.size());
        
        long startTime = System.currentTimeMillis();
        
        // 分批处理,每批500条(避免SQL过长)
        int batchSize = 500;
        int total = users.size();
        int batchCount = (total + batchSize - 1) / batchSize;
        
        int totalInserted = 0;
        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min(fromIndex + batchSize, total);
            List<User> batchList = users.subList(fromIndex, toIndex);
            
            // 执行批量插入
            int affectedRows = userMapper.batchInsert(batchList);
            totalInserted += affectedRows;
            
            log.debug("第 {} 批插入完成,插入 {} 条数据", i + 1, affectedRows);
        }
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("foreach SQL批量插入完成,总数据量: {}, 成功插入: {}, 耗时: {} ms", 
                users.size(), totalInserted, costTime);
        
        return totalInserted == users.size();
    }

    /**
     * 5. 使用编程式事务的批量插入
     * 重点:更灵活的事务控制
     */
    public boolean batchInsertWithProgrammaticTransaction(List<User> users) {
        log.info("使用编程式事务批量插入,数据量: {}", users.size());
        
        return transactionTemplate.execute(status -> {
            try {
                SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory()
                        .openSession(ExecutorType.BATCH);
                
                try {
                    UserMapper batchMapper = sqlSession.getMapper(UserMapper.class);
                    
                    int count = 0;
                    for (User user : users) {
                        batchMapper.insert(user);
                        count++;
                        
                        if (count % 1000 == 0) {
                            sqlSession.commit();
                            sqlSession.clearCache();
                        }
                    }
                    
                    sqlSession.commit();
                    log.info("编程式事务批量插入成功,数据量: {}", users.size());
                    return true;
                } catch (Exception e) {
                    sqlSession.rollback();
                    status.setRollbackOnly(); // 标记事务回滚
                    throw new RuntimeException("批量插入失败", e);
                } finally {
                    sqlSession.close();
                }
            } catch (Exception e) {
                log.error("编程式事务批量插入失败", e);
                throw e;
            }
        });
    }

    /**
     * 6. 批量插入性能测试
     */
    public void performanceTest() {
        log.info("开始批量插入性能测试");
        
        // 生成测试数据
        List<User> testData = generateTestData(10000);
        
        // 测试1:MyBatis-Plus saveBatch
        long start1 = System.currentTimeMillis();
        batchInsertWithSaveBatch(new ArrayList<>(testData));
        long time1 = System.currentTimeMillis() - start1;
        
        // 测试2:BatchExecutor
        long start2 = System.currentTimeMillis();
        batchInsertWithBatchExecutor(new ArrayList<>(testData));
        long time2 = System.currentTimeMillis() - start2;
        
        // 测试3:foreach SQL
        long start3 = System.currentTimeMillis();
        batchInsertWithForeachSql(new ArrayList<>(testData));
        long time3 = System.currentTimeMillis() - start3;
        
        log.info("""
            ==================== 批量插入性能测试结果 ====================
            测试数据量: {}
            MyBatis-Plus saveBatch: {} ms
            BatchExecutor: {} ms  
            foreach SQL: {} ms
            =========================================================
            """, testData.size(), time1, time2, time3);
    }

    /**
     * 7. 内存优化批量插入(适合超大数据量)
     */
    public boolean memoryOptimizedBatchInsert(List<User> users, int batchSize) {
        log.info("内存优化批量插入,总数据量: {}, 批次大小: {}", users.size(), batchSize);
        
        long startTime = System.currentTimeMillis();
        int totalBatches = (users.size() + batchSize - 1) / batchSize;
        
        for (int i = 0; i < totalBatches; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min(fromIndex + batchSize, users.size());
            List<User> batch = users.subList(fromIndex, toIndex);
            
            // 每批使用独立事务
            transactionTemplate.execute(status -> {
                userService.saveBatch(batch, batch.size());
                return null;
            });
            
            // 手动触发GC(谨慎使用)
            if (i % 10 == 0) {
                System.gc();
            }
            
            log.debug("已完成第 {}/{} 批插入", i + 1, totalBatches);
        }
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("内存优化批量插入完成,总数据量: {}, 耗时: {} ms", users.size(), costTime);
        
        return true;
    }

    // ==================== 辅助方法 ====================

    /**
     * 计算性能(条/秒)
     */
    private String calculatePerformance(int dataSize, long costTime) {
        if (costTime == 0) {
            return "N/A";
        }
        double performance = (double) dataSize / costTime * 1000;
        return String.format("%.2f", performance);
    }

    /**
     * 生成测试数据
     */
    private List<User> generateTestData(int count) {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            User user = new User();
            user.setUserName("testuser" + i);
            user.setRealName("测试用户" + i);
            user.setAge(20 + i % 30);
            user.setEmail("test" + i + "@example.com");
            user.setPhoneNumber("13800138000");
            user.setDeptId((long) (i % 5 + 1));
            user.setStatus(0);
            users.add(user);
        }
        return users;
    }
}

8.3.2 批量更新优化

/**
 * 批量更新优化企业级实践
 * 重点:各种批量更新方案的性能对比和最佳实践
 */
@Service
@Slf4j
@Transactional
public class BatchUpdateService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    /**
     * 1. 使用MyBatis-Plus的updateBatchById方法
     * 重点:适合小批量数据更新
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateWithUpdateBatchById(List<User> users) {
        log.info("使用updateBatchById批量更新,数据量: {}", users.size());
        
        long startTime = System.currentTimeMillis();
        
        boolean success = userService.updateBatchById(users, 1000); // 每批1000条
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("updateBatchById批量更新完成,数据量: {}, 耗时: {} ms", 
                users.size(), costTime);
        
        return success;
    }

    /**
     * 2. 使用MyBatis的BatchExecutor批量更新
     * 重点:适合大批量数据更新
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateWithBatchExecutor(List<User> users) {
        log.info("使用BatchExecutor批量更新,数据量: {}", users.size());
        
        long startTime = System.currentTimeMillis();
        
        SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory()
                .openSession(ExecutorType.BATCH);
        
        try {
            UserMapper batchMapper = sqlSession.getMapper(UserMapper.class);
            
            int count = 0;
            for (User user : users) {
                batchMapper.updateById(user);
                count++;
                
                // 每1000条提交一次
                if (count % 1000 == 0) {
                    sqlSession.commit();
                    sqlSession.clearCache();
                    log.debug("已提交 {} 条数据", count);
                }
            }
            
            sqlSession.commit();
            
            long costTime = System.currentTimeMillis() - startTime;
            log.info("BatchExecutor批量更新完成,总数据量: {}, 耗时: {} ms", 
                    users.size(), costTime);
            
            return true;
        } catch (Exception e) {
            sqlSession.rollback();
            log.error("BatchExecutor批量更新失败", e);
            throw new RuntimeException("批量更新失败", e);
        } finally {
            sqlSession.close();
        }
    }

    /**
     * 3. 使用CASE WHEN语句进行批量更新
     * 重点:单条SQL更新多条数据,性能最好
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateWithCaseWhen(List<User> users) {
        log.info("使用CASE WHEN批量更新,数据量: {}", users.size());
        
        long startTime = System.currentTimeMillis();
        
        // 分批处理,避免SQL过长
        int batchSize = 500; // CASE WHEN语句建议批次更小
        int total = users.size();
        int batchCount = (total + batchSize - 1) / batchSize;
        
        int totalUpdated = 0;
        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min(fromIndex + batchSize, total);
            List<User> batchList = users.subList(fromIndex, toIndex);
            
            int affectedRows = userMapper.batchUpdateWithCaseWhen(batchList);
            totalUpdated += affectedRows;
            
            log.debug("第 {} 批更新完成,影响 {} 条数据", i + 1, affectedRows);
        }
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("CASE WHEN批量更新完成,总数据量: {}, 成功更新: {}, 耗时: {} ms", 
                users.size(), totalUpdated, costTime);
        
        return totalUpdated > 0;
    }

    /**
     * 4. 批量更新特定字段
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateSpecificField(List<Long> userIds, String fieldName, Object value) {
        log.info("批量更新特定字段,用户数量: {}, 字段: {}, 值: {}", 
                userIds.size(), fieldName, value);
        
        long startTime = System.currentTimeMillis();
        
        // 构建更新条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("id", userIds)
                    .eq("deleted", 0) // 只更新未删除的数据
                    .set(fieldName, value);
        
        int affectedRows = userMapper.update(null, updateWrapper);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("批量更新特定字段完成,影响 {} 条数据,耗时: {} ms", 
                affectedRows, costTime);
        
        return affectedRows > 0;
    }

    /**
     * 5. 批量更新状态
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateStatus(List<Long> userIds, Integer status) {
        log.info("批量更新用户状态,用户数量: {}, 状态: {}", userIds.size(), status);
        
        if (userIds == null || userIds.isEmpty()) {
            log.warn("用户ID列表为空");
            return true;
        }
        
        // 分批处理
        int batchSize = 1000;
        int total = userIds.size();
        int batchCount = (total + batchSize - 1) / batchSize;
        
        int totalUpdated = 0;
        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min(fromIndex + batchSize, total);
            List<Long> batchIds = userIds.subList(fromIndex, toIndex);
            
            UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
            updateWrapper.in("id", batchIds)
                        .eq("deleted", 0)
                        .set("status", status)
                        .set("update_time", LocalDateTime.now());
            
            int affectedRows = userMapper.update(null, updateWrapper);
            totalUpdated += affectedRows;
            
            log.debug("第 {} 批状态更新完成,更新 {} 条数据", i + 1, affectedRows);
        }
        
        log.info("批量更新用户状态完成,总更新 {} 条数据", totalUpdated);
        return totalUpdated > 0;
    }

    /**
     * 6. 条件批量更新
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean conditionalBatchUpdate(UserQueryDTO query, User updateUser) {
        log.info("条件批量更新,查询条件: {}", query);
        
        long startTime = System.currentTimeMillis();
        
        // 构建查询条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        
        // 动态添加条件
        if (StringUtils.isNotBlank(query.getUserName())) {
            updateWrapper.like("user_name", query.getUserName());
        }
        if (query.getDeptId() != null) {
            updateWrapper.eq("dept_id", query.getDeptId());
        }
        if (query.getStatus() != null) {
            updateWrapper.eq("status", query.getStatus());
        }
        if (query.getMinAge() != null) {
            updateWrapper.ge("age", query.getMinAge());
        }
        if (query.getMaxAge() != null) {
            updateWrapper.le("age", query.getMaxAge());
        }
        
        updateWrapper.eq("deleted", 0);
        
        // 设置更新字段
        if (updateUser.getUserName() != null) {
            updateWrapper.set("user_name", updateUser.getUserName());
        }
        if (updateUser.getRealName() != null) {
            updateWrapper.set("real_name", updateUser.getRealName());
        }
        if (updateUser.getStatus() != null) {
            updateWrapper.set("status", updateUser.getStatus());
        }
        
        updateWrapper.set("update_time", LocalDateTime.now());
        
        int affectedRows = userMapper.update(null, updateWrapper);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("条件批量更新完成,影响 {} 条数据,耗时: {} ms", affectedRows, costTime);
        
        return affectedRows > 0;
    }
}

8.3.3 批量删除优化

/**
 * 批量删除优化企业级实践
 * 重点:逻辑删除和物理删除的最佳实践
 */
@Service
@Slf4j
@Transactional
public class BatchDeleteService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private OperationLogMapper logMapper;

    /**
     * 1. 使用MyBatis-Plus的removeByIds方法(逻辑删除)
     * 重点:默认执行逻辑删除,实际是UPDATE操作
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchLogicDeleteWithRemoveByIds(List<Long> userIds) {
        log.info("使用removeByIds批量逻辑删除,数据量: {}", userIds.size());
        
        long startTime = System.currentTimeMillis();
        
        boolean success = userService.removeByIds(userIds);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("removeByIds批量逻辑删除完成,耗时: {} ms", costTime);
        
        return success;
    }

    /**
     * 2. 使用UpdateWrapper进行批量逻辑删除
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchLogicDeleteWithWrapper(List<Long> userIds, String operator) {
        log.info("使用UpdateWrapper批量逻辑删除,数据量: {}, 操作人: {}", userIds.size(), operator);
        
        long startTime = System.currentTimeMillis();
        
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("id", userIds)
                    .eq("deleted", 0) // 只删除未删除的数据
                    .set("deleted", 1)
                    .set("delete_time", LocalDateTime.now())
                    .set("delete_by", operator);
        
        int affectedRows = userMapper.update(null, updateWrapper);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("UpdateWrapper批量逻辑删除完成,影响 {} 条数据,耗时: {} ms", 
                affectedRows, costTime);
        
        // 记录操作日志
        recordBatchDeleteLog(userIds, operator);
        
        return affectedRows > 0;
    }

    /**
     * 3. 条件批量逻辑删除
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean conditionalBatchLogicDelete(UserQueryDTO query, String operator) {
        log.info("条件批量逻辑删除,查询条件: {}, 操作人: {}", query, operator);
        
        long startTime = System.currentTimeMillis();
        
        // 构建查询条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        
        // 动态添加条件
        if (StringUtils.isNotBlank(query.getUserName())) {
            updateWrapper.like("user_name", query.getUserName());
        }
        if (query.getDeptId() != null) {
            updateWrapper.eq("dept_id", query.getDeptId());
        }
        if (query.getStatus() != null) {
            updateWrapper.eq("status", query.getStatus());
        }
        if (query.getCreateTimeStart() != null) {
            updateWrapper.ge("create_time", query.getCreateTimeStart());
        }
        if (query.getCreateTimeEnd() != null) {
            updateWrapper.le("create_time", query.getCreateTimeEnd());
        }
        
        updateWrapper.eq("deleted", 0)
                    .set("deleted", 1)
                    .set("delete_time", LocalDateTime.now())
                    .set("delete_by", operator);
        
        int affectedRows = userMapper.update(null, updateWrapper);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("条件批量逻辑删除完成,影响 {} 条数据,耗时: {} ms", affectedRows, costTime);
        
        return affectedRows > 0;
    }

    /**
     * 4. 物理批量删除(谨慎使用)
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchPhysicalDelete(List<Long> userIds, String operator) {
        log.warn("批量物理删除,数据量: {}, 操作人: {}", userIds.size(), operator);
        
        // 权限验证
        if (!hasPhysicalDeletePermission(operator)) {
            throw new RuntimeException("没有物理删除权限");
        }
        
        long startTime = System.currentTimeMillis();
        
        // 先备份重要数据
        backupUsersBeforeDelete(userIds, operator);
        
        // 删除用户角色关联
        deleteUserRoles(userIds);
        
        // 执行物理删除
        int affectedRows = userMapper.deleteBatchIds(userIds);
        
        long costTime = System.currentTimeMillis() - startTime;
        log.warn("批量物理删除完成,删除 {} 条数据,耗时: {} ms", affectedRows, costTime);
        
        // 记录物理删除日志
        recordPhysicalDeleteLog(userIds, operator);
        
        return affectedRows > 0;
    }

    /**
     * 5. 分批物理删除(避免SQL过长)
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchPhysicalDeleteWithChunk(List<Long> userIds, int chunkSize, String operator) {
        log.warn("分批物理删除,总数据量: {}, 每批大小: {}, 操作人: {}", 
                userIds.size(), chunkSize, operator);
        
        // 权限验证
        if (!hasPhysicalDeletePermission(operator)) {
            throw new RuntimeException("没有物理删除权限");
        }
        
        long startTime = System.currentTimeMillis();
        
        int total = userIds.size();
        int chunkCount = (total + chunkSize - 1) / chunkSize;
        int totalDeleted = 0;
        
        for (int i = 0; i < chunkCount; i++) {
            int fromIndex = i * chunkSize;
            int toIndex = Math.min(fromIndex + chunkSize, total);
            List<Long> chunkIds = userIds.subList(fromIndex, toIndex);
            
            // 备份当前批次数据
            backupUsersBeforeDelete(chunkIds, operator);
            
            // 删除用户角色关联
            deleteUserRoles(chunkIds);
            
            // 删除当前批次
            int deleted = userMapper.deleteBatchIds(chunkIds);
            totalDeleted += deleted;
            
            log.debug("第 {} 批删除完成,删除 {} 条数据", i + 1, deleted);
        }
        
        long costTime = System.currentTimeMillis() - startTime;
        log.warn("分批物理删除完成,总删除 {} 条数据,耗时: {} ms", totalDeleted, costTime);
        
        recordPhysicalDeleteLog(userIds, operator);
        
        return totalDeleted > 0;
    }

    /**
     * 6. 定时清理已逻辑删除的数据
     */
    @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
    @Transactional(rollbackFor = Exception.class)
    public void cleanDeletedUsers() {
        log.info("开始清理长时间已逻辑删除的用户");
        
        // 清理30天前删除的用户
        LocalDateTime threshold = LocalDateTime.now().minusDays(30);
        
        // 查询需要清理的用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("deleted", 1)
                   .le("delete_time", threshold)
                   .last("LIMIT 1000"); // 每次最多清理1000条
        
        List<User> deletedUsers = userMapper.selectList(queryWrapper);
        
        if (deletedUsers.isEmpty()) {
            log.info("没有需要清理的已删除用户");
            return;
        }
        
        List<Long> userIds = deletedUsers.stream()
                .map(User::getId)
                .collect(Collectors.toList());
        
        // 备份数据
        backupUsersBeforeDelete(userIds, "system_cleanup");
        
        // 执行物理删除
        int deletedCount = userMapper.deleteBatchIds(userIds);
        
        log.info("清理已删除用户完成,共清理 {} 条记录", deletedCount);
        
        // 记录清理日志
        recordCleanupLog(userIds, "system_cleanup");
    }

    // ==================== 辅助方法 ====================

    /**
     * 备份删除前的数据
     */
    private void backupUsersBeforeDelete(List<Long> userIds, String operator) {
        log.debug("备份用户数据,用户数量: {}", userIds.size());
        
        try {
            List<User> users = userMapper.selectBatchIds(userIds);
            
            // 在实际项目中,这里应该将用户数据备份到历史表
            // userHistoryMapper.batchInsert(users);
            
            log.info("用户数据备份完成,数量: {}", users.size());
        } catch (Exception e) {
            log.error("备份用户数据失败", e);
            throw new RuntimeException("备份用户数据失败", e);
        }
    }

    /**
     * 删除用户角色关联
     */
    private void deleteUserRoles(List<Long> userIds) {
        QueryWrapper<UserRole> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("user_id", userIds);
        
        int deleted = userRoleMapper.delete(queryWrapper);
        log.debug("删除用户角色关联,删除 {} 条记录", deleted);
    }

    /**
     * 记录批量删除日志
     */
    private void recordBatchDeleteLog(List<Long> userIds, String operator) {
        OperationLog log = new OperationLog();
        log.setOperationType("BATCH_DELETE_USER");
        log.setDescription("批量删除用户,数量: " + userIds.size());
        log.setOperator(operator);
        log.setOperationTime(LocalDateTime.now());
        
        logMapper.insert(log);
    }

    /**
     * 记录物理删除日志
     */
    private void recordPhysicalDeleteLog(List<Long> userIds, String operator) {
        OperationLog log = new OperationLog();
        log.setOperationType("PHYSICAL_DELETE_USER");
        log.setDescription("物理删除用户,数量: " + userIds.size());
        log.setOperator(operator);
        log.setOperationTime(LocalDateTime.now());
        
        logMapper.insert(log);
    }

    /**
     * 记录清理日志
     */
    private void recordCleanupLog(List<Long> userIds, String operator) {
        OperationLog log = new OperationLog();
        log.setOperationType("CLEANUP_DELETED_USERS");
        log.setDescription("清理已删除用户,数量: " + userIds.size());
        log.setOperator(operator);
        log.setOperationTime(LocalDateTime.now());
        
        logMapper.insert(log);
    }

    /**
     * 检查物理删除权限
     */
    private boolean hasPhysicalDeletePermission(String operator) {
        // 实际项目中检查用户权限
        return "admin".equals(operator) || "system".equals(operator);
    }
}

8.4 批量操作性能监控和优化

/**
 * 批量操作性能监控和优化
 * 重点:监控批量操作性能,提供优化建议
 */
@Service
@Slf4j
public class BatchOperationMonitor {

    @Autowired
    private BatchInsertService batchInsertService;

    @Autowired
    private BatchUpdateService batchUpdateService;

    @Autowired
    private BatchDeleteService batchDeleteService;

    /**
     * 批量操作性能测试套件
     */
    public void runPerformanceTestSuite() {
        log.info("开始批量操作性能测试套件");
        
        // 生成测试数据
        List<User> testData = generateTestData(10000);
        List<Long> testIds = testData.stream()
                .map(User::getId)
                .collect(Collectors.toList());
        
        // 1. 批量插入性能测试
        testBatchInsertPerformance(testData);
        
        // 2. 批量更新性能测试
        testBatchUpdatePerformance(testData);
        
        // 3. 批量删除性能测试
        testBatchDeletePerformance(testIds);
        
        // 4. 内存使用监控
        monitorMemoryUsage();
        
        // 5. 输出优化建议
        printOptimizationSuggestions();
        
        log.info("批量操作性能测试套件完成");
    }

    /**
     * 批量插入性能测试
     */
    public void testBatchInsertPerformance(List<User> testData) {
        log.info("开始批量插入性能测试");
        
        // 准备测试数据副本
        List<User> data1 = new ArrayList<>(testData);
        List<User> data2 = new ArrayList<>(testData);
        List<User> data3 = new ArrayList<>(testData);
        
        // 测试1:MyBatis-Plus saveBatch
        long start1 = System.currentTimeMillis();
        batchInsertService.batchInsertWithSaveBatch(data1);
        long time1 = System.currentTimeMillis() - start1;
        
        // 测试2:BatchExecutor
        long start2 = System.currentTimeMillis();
        batchInsertService.batchInsertWithBatchExecutor(data2);
        long time2 = System.currentTimeMillis() - start2;
        
        // 测试3:foreach SQL
        long start3 = System.currentTimeMillis();
        batchInsertService.batchInsertWithForeachSql(data3);
        long time3 = System.currentTimeMillis() - start3;
        
        log.info("""
            ==================== 批量插入性能测试结果 ====================
            测试数据量: {}
            MyBatis-Plus saveBatch: {} ms
            BatchExecutor: {} ms  
            foreach SQL: {} ms
            =========================================================
            """, testData.size(), time1, time2, time3);
    }

    /**
     * 批量更新性能测试
     */
    public void testBatchUpdatePerformance(List<User> testData) {
        log.info("开始批量更新性能测试");
        
        // 修改测试数据
        testData.forEach(user -> user.setRealName("更新测试"));
        
        List<User> data1 = new ArrayList<>(testData);
        List<User> data2 = new ArrayList<>(testData);
        List<User> data3 = new ArrayList<>(testData);
        
        // 测试1:MyBatis-Plus updateBatchById
        long start1 = System.currentTimeMillis();
        batchUpdateService.batchUpdateWithUpdateBatchById(data1);
        long time1 = System.currentTimeMillis() - start1;
        
        // 测试2:BatchExecutor
        long start2 = System.currentTimeMillis();
        batchUpdateService.batchUpdateWithBatchExecutor(data2);
        long time2 = System.currentTimeMillis() - start2;
        
        // 测试3:CASE WHEN
        long start3 = System.currentTimeMillis();
        batchUpdateService.batchUpdateWithCaseWhen(data3);
        long time3 = System.currentTimeMillis() - start3;
        
        log.info("""
            ==================== 批量更新性能测试结果 ====================
            测试数据量: {}
            MyBatis-Plus updateBatchById: {} ms
            BatchExecutor: {} ms
            CASE WHEN: {} ms
            =========================================================
            """, testData.size(), time1, time2, time3);
    }

    /**
     * 批量删除性能测试
     */
    public void testBatchDeletePerformance(List<Long> testIds) {
        log.info("开始批量删除性能测试");
        
        List<Long> ids1 = new ArrayList<>(testIds);
        List<Long> ids2 = new ArrayList<>(testIds);
        
        // 测试1:逻辑删除
        long start1 = System.currentTimeMillis();
        batchDeleteService.batchLogicDeleteWithWrapper(ids1, "test");
        long time1 = System.currentTimeMillis() - start1;
        
        // 测试2:物理删除
        long start2 = System.currentTimeMillis();
        batchDeleteService.batchPhysicalDeleteWithChunk(ids2, 1000, "test");
        long time2 = System.currentTimeMillis() - start2;
        
        log.info("""
            ==================== 批量删除性能测试结果 ====================
            测试数据量: {}
            逻辑删除: {} ms
            物理删除: {} ms
            =========================================================
            """, testIds.size(), time1, time2);
    }

    /**
     * 内存使用监控
     */
    public void monitorMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        
        // 运行GC,获取更准确的内存信息
        System.gc();
        
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long usedMemory = totalMemory - freeMemory;
        long maxMemory = runtime.maxMemory();
        
        log.info("""
            ==================== 内存使用情况 ====================
            总内存: {} MB
            已使用: {} MB  
            剩余内存: {} MB
            最大内存: {} MB
            内存使用率: {}%
            ===================================================
            """, 
            bytesToMB(totalMemory),
            bytesToMB(usedMemory),
            bytesToMB(freeMemory),
            bytesToMB(maxMemory),
            String.format("%.2f", (double) usedMemory / totalMemory * 100));
    }

    /**
     * 生成测试数据
     */
    private List<User> generateTestData(int count) {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            User user = new User();
            user.setUserName("testuser" + i);
            user.setRealName("测试用户" + i);
            user.setAge(20 + i % 30);
            user.setEmail("test" + i + "@example.com");
            user.setPhoneNumber("13800138000");
            user.setDeptId((long) (i % 5 + 1));
            user.setStatus(0);
            users.add(user);
        }
        return users;
    }

    /**
     * 字节转MB
     */
    private long bytesToMB(long bytes) {
        return bytes / 1024 / 1024;
    }

    /**
     * 输出优化建议
     */
    public void printOptimizationSuggestions() {
        log.info("""
            ==================== 批量操作优化建议 ====================
            
            一、批量插入优化建议:
            1. 小批量数据(1000条以内):使用MyBatis-Plus的saveBatch
            2. 大批量数据(万级以上):使用BatchExecutor
            3. 超大批量数据(10万+):考虑使用数据库原生批量导入工具
            4. 内存优化:合理设置批次大小,及时清理缓存
            
            二、批量更新优化建议:
            1. 小批量数据:使用MyBatis-Plus的updateBatchById
            2. 大批量相同值更新:使用UpdateWrapper的set方法
            3. 大批量不同值更新:使用BatchExecutor或CASE WHEN
            4. 性能优先:CASE WHEN语句性能最好
            
            三、批量删除优化建议:
            1. 优先使用逻辑删除,保留数据恢复可能
            2. 物理删除前一定要备份数据
            3. 大批量删除使用分批处理,避免长事务
            4. 定期清理已逻辑删除的数据
            
            四、通用优化建议:
            1. 合理设置批次大小(通常500-1000)
            2. 监控内存使用,防止OOM
            3. 使用事务保证数据一致性
            4. 考虑使用异步处理超大批量操作
            5. 做好异常处理和重试机制
            6. 定期分析慢SQL,优化数据库索引
            
            五、数据库配置优化:
            1. 调整max_allowed_packet参数(MySQL)
            2. 优化innodb_buffer_pool_size(MySQL)
            3. 调整批量操作超时时间
            4. 合理设置事务隔离级别
            =====================================================
            """);
    }

    /**
     * 批次大小优化测试
     */
    public void testBatchSizeOptimization() {
        log.info("开始批次大小优化测试");
        
        List<User> testData = generateTestData(5000);
        
        int[] batchSizes = {100, 500, 1000, 2000, 5000};
        
        for (int batchSize : batchSizes) {
            List<User> data = new ArrayList<>(testData);
            
            long startTime = System.currentTimeMillis();
            batchInsertService.batchInsertWithCustomBatchSize(data, batchSize);
            long costTime = System.currentTimeMillis() - startTime;
            
            log.info("批次大小: {}, 耗时: {} ms", batchSize, costTime);
        }
        
        log.info("批次大小优化测试完成");
    }
}

企业级MyBatis-Plus实战教学(完结篇)

课程总结与进阶指导

9.1 课程核心知识点回顾

/**
 * MyBatis-Plus企业级实战核心知识点总结
 * 重点:八大模块核心要点回顾和最佳实践总结
 */
@Slf4j
public class MyBatisPlusCourseSummary {

    /**
     * 模块一:环境搭建与配置最佳实践
     */
    public void summaryModule1() {
        log.info("""
            ==================== 模块一核心要点 ====================
            1. 依赖管理:
               - 使用mybatis-plus-boot-starter简化配置
               - 统一管理版本号,避免冲突
               
            2. 配置详解:
               - 数据源配置:时区、字符集、SSL
               - 连接池配置:HikariCP性能优化
               - MyBatis-Plus全局配置:ID策略、逻辑删除
               
            3. 配置类设计:
               - 拦截器顺序:分页->乐观锁->防全表操作
               - 自动填充:创建时间、更新时间等
               
            4. 避坑指南:
               - 生产环境关闭SQL日志
               - 多数据源手动配置SqlSessionFactory
               - 时区问题导致的时间差
            =====================================================
            """);
    }

    /**
     * 模块二:CRUD接口封装与Lambda查询
     */
    public void summaryModule2() {
        log.info("""
            ==================== 模块二核心要点 ====================
            1. 实体类设计:
               - @TableName、@TableId、@TableField注解
               - 逻辑删除@TableLogic、乐观锁@Version
               - 自动填充字段设计
               
            2. 三层架构规范:
               - Mapper继承BaseMapper获得基础CRUD
               - Service继承ServiceImpl获得增强方法
               - Controller统一响应格式
               
            3. Lambda查询:
               - 类型安全,编译期检查
               - 避免硬编码字段名
               - 链式调用,代码简洁
               
            4. 最佳实践:
               - 使用VO、DTO进行数据传递
               - Service层统一异常处理
               - 完整的单元测试覆盖
            =====================================================
            """);
    }

    /**
     * 模块三:条件构造器高级用法
     */
    public void summaryModule3() {
        log.info("""
            ==================== 模块三核心要点 ====================
            1. QueryWrapper核心功能:
               - 等值查询:eq、ne
               - 范围查询:gt、ge、lt、le、between
               - 模糊查询:like、likeLeft、likeRight
               - 空值判断:isNull、isNotNull
               - IN查询:in、notIn
               
            2. 动态条件构建:
               - 根据业务参数动态添加条件
               - 链式调用构建复杂查询
               
            3. LambdaQueryWrapper:
               - 类型安全的条件构建
               - 避免SQL注入风险
               - IDE智能提示支持
               
            4. 性能优化:
               - 避免N+1查询问题
               - 合理使用索引
               - 分页查询优化
            =====================================================
            """);
    }

    /**
     * 模块四:分页插件实现原理与性能优化
     */
    public void summaryModule4() {
        log.info("""
            ==================== 模块四核心要点 ====================
            1. 分页插件配置:
               - 必须第一个添加到拦截器链
               - 设置数据库类型、最大单页限制
               - 开启溢出处理和count优化
               
            2. 分页查询方式:
               - 基础分页:Page对象 + selectPage
               - 不计数分页:提升性能
               - 自定义count查询:复杂查询优化
               
            3. 性能优化方案:
               - 游标分页:基于ID的连续分页
               - 延迟关联:先查ID再查详情
               - 覆盖索引:只查询需要的字段
               
            4. 企业级实践:
               - 统一分页响应格式
               - 参数安全验证
               - 深度分页性能问题解决
            =====================================================
            """);
    }

    /**
     * 模块五:代码生成器实战配置
     */
    public void summaryModule5() {
        log.info("""
            ==================== 模块五核心要点 ====================
            1. 代码生成器配置:
               - 全局配置:作者、输出目录、文件覆盖
               - 包配置:分层结构、模块划分
               - 策略配置:表过滤、字段策略、命名规则
               
            2. 自定义模板:
               - 实体类模板:Lombok、Swagger集成
               - Service模板:业务方法规范
               - Controller模板:统一响应格式
               
            3. 高级功能:
               - 多数据源代码生成
               - 增量代码生成
               - 自定义文件生成
               
            4. 企业级实践:
               - 模板版本管理
               - 生成代码质量检查
               - 版本控制集成
            =====================================================
            """);
    }

    /**
     * 模块六:乐观锁/逻辑删除实现机制
     */
    public void summaryModule6() {
        log.info("""
            ==================== 模块六核心要点 ====================
            1. 乐观锁实现:
               - @Version注解标记版本号字段
               - 更新时自动version=version+1
               - 冲突时抛出OptimisticLockException
               
            2. 乐观锁最佳实践:
               - 重试机制解决冲突
               - 合理的重试次数和间隔
               - 业务层面的冲突处理
               
            3. 逻辑删除实现:
               - @TableLogic注解标记删除字段
               - 查询自动添加deleted=0条件
               - 删除操作变为更新操作
               
            4. 逻辑删除扩展:
               - 记录删除人和删除时间
               - 定期清理已删除数据
               - 数据恢复机制
            =====================================================
            """);
    }

    /**
     * 模块七:多表关联查询解决方案
     */
    public void summaryModule7() {
        log.info("""
            ==================== 模块七核心要点 ====================
            1. 关联查询方式:
               - 注解方式:@One、@Many、@Results
               - XML方式:<association>、<collection>
               - JOIN查询:一次性获取所有数据
               
            2. VO对象设计:
               - 专门用于前端展示的对象
               - 包含多个实体类的字段
               - 避免污染实体类
               
            3. 性能优化方案:
               - 解决N+1查询问题
               - 延迟加载关联数据
               - 批量查询优化
               
            4. 企业级实践:
               - 按需加载关联数据
               - 缓存优化查询性能
               - 合理的索引设计
            =====================================================
            """);
    }

    /**
     * 模块八:事务管理与批量操作优化
     */
    public void summaryModule8() {
        log.info("""
            ==================== 模块八核心要点 ====================
            1. 事务管理:
               - 声明式事务:@Transactional注解
               - 编程式事务:TransactionTemplate
               - 传播行为:7种传播行为适用场景
               
            2. 隔离级别:
               - 4种隔离级别解决不同并发问题
               - 默认使用数据库隔离级别
               - 根据业务需求选择合适的隔离级别
               
            3. 批量操作优化:
               - 批量插入:saveBatch、BatchExecutor
               - 批量更新:updateBatchById、CASE WHEN
               - 批量删除:逻辑删除优先
               
            4. 性能监控:
               - 事务执行时间监控
               - 内存使用监控
               - 批次大小优化测试
            =====================================================
            """);
    }

    /**
     * 技术选型决策指南
     */
    public void technologySelectionGuide() {
        log.info("""
            ==================== 技术选型决策指南 ====================
            
            场景一:简单CRUD操作
            ✅ 推荐:MyBatis-Plus基础CRUD + Lambda查询
            ❌ 避免:原生MyBatis手动编写SQL
            
            场景二:复杂动态查询
            ✅ 推荐:QueryWrapper动态条件构建
            ❌ 避免:XML中大量<if>标签
            
            场景三:多表关联查询
            ✅ 推荐:XML配置关联查询 + VO对象
            ❌ 避免:在代码中循环查询关联数据
            
            场景四:高并发更新
            ✅ 推荐:乐观锁 + 重试机制
            ❌ 避免:悲观锁或没有并发控制
            
            场景五:大批量数据处理
            ✅ 推荐:BatchExecutor + 分批处理
            ❌ 避免:单条记录循环处理
            
            场景六:分页查询
            ✅ 推荐:MyBatis-Plus分页插件
            ❌ 避免:内存分页或手动分页
            
            场景七:需要事务管理
            ✅ 推荐:Spring声明式事务
            ❌ 避免:手动控制事务
            
            =====================================================
            """);
    }
}

9.2 企业级项目架构设计

/**
 * 企业级项目架构设计指南
 * 重点:基于MyBatis-Plus的大型项目架构设计
 */
@Slf4j
public class EnterpriseArchitectureDesign {

    /**
     * 分层架构设计
     */
    public void layeredArchitecture() {
        log.info("""
            ==================== 企业级分层架构设计 ====================
            
            1. 表现层(Presentation Layer)
               - Controller:RESTful API接口
               - DTO:数据传输对象
               - VO:视图展示对象
               - 参数校验、统一响应格式
               
            2. 业务层(Business Layer)
               - Service:业务逻辑实现
               - Manager:复杂业务协调
               - 事务管理、业务校验
               
            3. 持久层(Persistence Layer)
               - Mapper:数据访问接口
               - Entity:实体对象
               - 数据库操作、缓存集成
               
            4. 通用层(Common Layer)
               - 配置类、工具类
               - 常量定义、枚举类
               - 异常处理、日志处理
               
            5. 依赖关系:
               表现层 → 业务层 → 持久层
               ↓
               通用层
               
            =====================================================
            """);
    }

    /**
     * 包结构设计
     */
    public void packageStructureDesign() {
        log.info("""
            ==================== 企业级包结构设计 ====================
            
            com.enterprise
            ├── application          # 应用层
            │   ├── controller       # 控制器
            │   ├── dto             # 数据传输对象
            │   └── vo              # 视图对象
            ├── domain               # 领域层
            │   ├── entity          # 实体类
            │   ├── repository      # 仓储接口
            │   ├── service         # 服务接口
            │   └── manager         # 业务协调
            ├── infrastructure       # 基础设施层
            │   ├── mapper          # 数据访问
            │   ├── config          # 配置类
            │   └── util            # 工具类
            ├── common               # 通用层
            │   ├── constant        # 常量
            │   ├── enums           # 枚举
            │   ├── exception       # 异常
            │   └── response        # 统一响应
            └── EnterpriseApplication.java # 启动类
            
            =====================================================
            """);
    }

    /**
     * 数据库设计规范
     */
    public void databaseDesignSpecification() {
        log.info("""
            ==================== 数据库设计规范 ====================
            
            1. 命名规范:
               - 表名:小写+下划线,如:sys_user
               - 字段名:小写+下划线,如:user_name
               - 索引名:idx_表名_字段名,如:idx_user_name
               
            2. 字段设计:
               - 主键:BIGINT自增或雪花算法
               - 时间:DATETIME或TIMESTAMP
               - 金额:DECIMAL(precision, scale)
               - 状态:TINYINT或VARCHAR
               
            3. 必备字段:
               - id:主键
               - version:乐观锁版本号
               - deleted:逻辑删除标志
               - create_time:创建时间
               - update_time:更新时间
               - create_by:创建人
               - update_by:更新人
               
            4. 索引设计:
               - 主键索引:id
               - 唯一索引:业务唯一字段
               - 普通索引:查询条件字段
               - 联合索引:多字段查询条件
               
            =====================================================
            """);
    }

    /**
     * 配置文件组织
     */
    public void configurationOrganization() {
        log.info("""
            ==================== 配置文件组织 ====================
            
            resources/
            ├── application.yml              # 主配置文件
            ├── application-dev.yml          # 开发环境
            ├── application-test.yml         # 测试环境
            ├── application-prod.yml         # 生产环境
            ├── mapper/                      # MyBatis映射文件
            │   ├── UserMapper.xml
            │   ├── DeptMapper.xml
            │   └── ...
            ├── templates/                   # 代码生成器模板
            │   ├── entity.java.ftl
            │   ├── mapper.java.ftl
            │   └── ...
            └── static/                      # 静态资源
                ├── sql/                     # SQL脚本
                └── ...
                
            配置分离原则:
            - 环境相关配置:数据库、Redis、MQ等
            - 应用配置:端口、上下文路径等
            - 第三方配置:Swagger、监控等
            
            =====================================================
            """);
    }
}

9.3 性能优化完整方案

/**
 * MyBatis-Plus性能优化完整方案
 * 重点:从数据库到应用层的全方位性能优化
 */
@Service
@Slf4j
public class ComprehensivePerformanceOptimization {

    /**
     * 1. 数据库层面优化
     */
    public void databaseLevelOptimization() {
        log.info("""
            ==================== 数据库层面优化 ====================
            
            1. 索引优化:
               - 为查询条件字段创建索引
               - 使用覆盖索引减少回表
               - 避免在索引列上使用函数
               - 定期分析索引使用情况
               
            2. SQL优化:
               - 避免SELECT *,只查询需要的字段
               - 使用EXPLAIN分析SQL执行计划
               - 避免在WHERE子句中使用!=或<>
               - 使用JOIN代替子查询
               
            3. 表结构优化:
               - 选择合适的数据类型
               - 适当使用字段冗余
               - 大字段分离到扩展表
               - 分区表处理大数据量
               
            4. 数据库参数优化:
               - 调整连接池参数
               - 优化缓冲区大小
               - 设置合理的超时时间
               
            =====================================================
            """);
    }

    /**
     * 2. MyBatis-Plus层面优化
     */
    public void mybatisPlusLevelOptimization() {
        log.info("""
            ==================== MyBatis-Plus层面优化 ====================
            
            1. 查询优化:
               - 使用select()指定查询字段
               - 合理使用LambdaQueryWrapper
               - 避免在循环中执行查询
               - 使用批量查询代替循环单条查询
               
            2. 分页优化:
               - 深度分页使用游标分页
               - 不需要总数时关闭count查询
               - 复杂分页使用延迟关联
               
            3. 批量操作优化:
               - 使用BatchExecutor进行大批量操作
               - 合理设置批次大小(500-1000)
               - 分批提交避免大事务
               
            4. 缓存优化:
               - 合理使用二级缓存
               - 业务缓存减少数据库访问
               - 缓存穿透、击穿、雪崩防护
               
            =====================================================
            """);
    }

    /**
     * 3. 应用层面优化
     */
    public void applicationLevelOptimization() {
        log.info("""
            ==================== 应用层面优化 ====================
            
            1. 连接池优化:
               - 使用HikariCP高性能连接池
               - 合理设置最大最小连接数
               - 设置合理的连接超时时间
               
            2. 事务优化:
               - 使用合适的事务隔离级别
               - 避免长事务,设置超时时间
               - 只读事务优化查询性能
               
            3. 线程池优化:
               - 合理设置线程池参数
               - 使用异步处理耗时操作
               - 避免线程阻塞
               
            4. JVM优化:
               - 合理设置堆内存大小
               - 选择合适的垃圾回收器
               - 监控GC情况
               
            =====================================================
            """);
    }

    /**
     * 4. 架构层面优化
     */
    public void architectureLevelOptimization() {
        log.info("""
            ==================== 架构层面优化 ====================
            
            1. 读写分离:
               - 主库处理写操作
               - 从库处理读操作
               - 使用MyBatis-Plus多数据源支持
               
            2. 分库分表:
               - 水平分表解决单表数据过大
               - 垂直分库按业务拆分
               - 使用ShardingSphere等中间件
               
            3. 缓存架构:
               - 多级缓存:本地缓存 + Redis
               - 缓存更新策略
               - 缓存一致性保证
               
            4. 异步处理:
               - 消息队列解耦耗时操作
               - 异步线程处理非实时任务
               - 事件驱动架构
               
            =====================================================
            """);
    }

    /**
     * 性能监控方案
     */
    public void performanceMonitoringSolution() {
        log.info("""
            ==================== 性能监控方案 ====================
            
            1. SQL监控:
               - 开启MyBatis SQL日志(开发环境)
               - 使用Druid监控SQL执行
               - 慢SQL查询和优化
               
            2. 应用监控:
               - Spring Boot Actuator健康检查
               - Micrometer + Prometheus指标收集
               - APM工具:SkyWalking、Pinpoint
               
            3. 业务监控:
               - 关键业务指标监控
               - 自定义业务埋点
               - 实时告警机制
               
            4. 日志监控:
               - 结构化日志输出
               - 日志聚合分析
               - 错误日志实时告警
               
            =====================================================
            """);
    }
}

9.4 安全防护最佳实践

/**
 * MyBatis-Plus安全防护最佳实践
 * 重点:防止SQL注入、数据泄露等安全问题
 */
@Service
@Slf4j
public class SecurityBestPractices {

    /**
     * 1. SQL注入防护
     */
    public void sqlInjectionPrevention() {
        log.info("""
            ==================== SQL注入防护 ====================
            
            1. 使用MyBatis-Plus内置方法:
               - 优先使用eq、like等内置方法
               - 避免直接拼接SQL参数
               
            2. XML中参数化查询:
               - 使用#{param}而不是${param}
               - #{}会预编译,${}会直接拼接
               
            3. 动态SQL安全:
               - 使用<if>标签而不是字符串拼接
               - 对用户输入进行严格校验
               
            4. 自定义SQL安全:
               - 使用@Param注解传递参数
               - 避免在注解SQL中拼接字符串
               
            危险示例:
            ❌ @Select("SELECT * FROM user WHERE name = '" + name + "'")
            ✅ @Select("SELECT * FROM user WHERE name = #{name}")
            
            =====================================================
            """);
    }

    /**
     * 2. 数据权限控制
     */
    public void dataPermissionControl() {
        log.info("""
            ==================== 数据权限控制 ====================
            
            1. 基于租户的数据隔离:
               - 使用MyBatis-Plus多租户插件
               - 自动添加tenant_id条件
               
            2. 基于组织的数据权限:
               - 查询时自动添加部门过滤条件
               - 使用自定义拦截器实现
               
            3. 基于用户的数据权限:
               - 查询时添加创建人过滤
               - 细粒度数据权限控制
               
            4. 数据脱敏:
               - 敏感字段在查询时脱敏
               - 使用@JsonIgnore忽略敏感字段
               - 自定义序列化器处理敏感数据
               
            =====================================================
            """);
    }

    /**
     * 3. 防全表操作
     */
    public void preventFullTableOperations() {
        log.info("""
            ==================== 防全表操作 ====================
            
            1. 使用BlockAttackInnerInterceptor:
               - 防止全表更新和删除
               - 必须包含WHERE条件
               
            2. 分页查询限制:
               - 设置最大分页大小
               - 深度分页限制
               
            3. 批量操作限制:
               - 限制单次批量操作数量
               - 重要操作需要审批流程
               
            4. 操作日志记录:
               - 记录所有数据修改操作
               - 定期审计操作日志
               
            =====================================================
            """);
    }

    /**
     * 4. 敏感数据处理
     */
    public void sensitiveDataHandling() {
        log.info("""
            ==================== 敏感数据处理 ====================
            
            1. 数据加密:
               - 数据库字段加密存储
               - 使用MyBatis类型处理器加解密
               
            2. 数据脱敏:
               - 手机号:138****8888
               - 身份证:110***********1234
               - 邮箱:te***@example.com
               
            3. 访问控制:
               - 接口级别权限控制
               - 数据级别权限控制
               
            4. 审计日志:
               - 敏感数据访问日志
               - 数据导出操作日志
               
            =====================================================
            """);
    }
}

9.5 扩展与集成方案

/**
 * MyBatis-Plus扩展与集成方案
 * 重点:与其他技术栈的集成和自定义扩展
 */
@Slf4j
public class ExtensionAndIntegrationSolutions {

    /**
     * 1. Spring Boot集成
     */
    public void springBootIntegration() {
        log.info("""
            ==================== Spring Boot集成 ====================
            
            1. 自动配置:
               - MyBatisPlusAutoConfiguration
               - 自动配置SqlSessionFactory
               - 自动配置Mapper扫描
               
            2. 配置属性:
               - mybatis-plus.configuration.*
               - mybatis-plus.global-config.*
               - 支持YAML和Properties配置
               
            3. 健康检查:
               - 数据库连接健康检查
               - 自定义健康检查指标
               
            4. 监控集成:
               - Actuator端点监控
               - Micrometer指标收集
               
            =====================================================
            """);
    }

    /**
     * 2. Spring Cloud集成
     */
    public void springCloudIntegration() {
        log.info("""
            ==================== Spring Cloud集成 ====================
            
            1. 配置中心:
               - 从配置中心读取数据库配置
               - 动态刷新数据源配置
               
            2. 服务发现:
               - 多数据源动态路由
               - 读写分离数据源配置
               
            3. 分布式事务:
               - Seata分布式事务集成
               - 多数据源事务管理
               
            4. 链路追踪:
               - SQL执行链路追踪
               - 业务操作链路追踪
               
            =====================================================
            """);
    }

    /**
     * 3. 多数据源集成
     */
    public void multiDataSourceIntegration() {
        log.info("""
            ==================== 多数据源集成 ====================
            
            1. 动态数据源:
               - 使用AbstractRoutingDataSource
               - 基于注解的数据源切换
               
            2. 读写分离:
               - 主从数据源配置
               - 读操作路由到从库
               
            3. 分库分表:
               - ShardingSphere集成
               - 自定义分片策略
               
            4. 多租户:
               - 基于数据库的租户隔离
               - 基于Schema的租户隔离
               
            =====================================================
            """);
    }

    /**
     * 4. 缓存集成
     */
    public void cacheIntegration() {
        log.info("""
            ==================== 缓存集成 ====================
            
            1. Redis缓存:
               - MyBatis二级缓存Redis实现
               - 业务数据缓存
               
            2. 本地缓存:
               - Caffeine本地缓存
               - 多级缓存架构
               
            3. 缓存策略:
               - 读多写少:缓存优先
               - 写多读少:数据库优先
               
            4. 缓存一致性:
               - 双写策略
               - 失效策略
               - 延迟双删
               
            =====================================================
            """);
    }

    /**
     * 5. 自定义扩展
     */
    public void customExtensions() {
        log.info("""
            ==================== 自定义扩展 ====================
            
            1. 自定义注入器:
               - 扩展BaseMapper方法
               - 自定义通用方法
               
            2. 自定义拦截器:
               - SQL执行时间监控
               - 数据权限过滤
               - 多租户数据隔离
               
            3. 自定义类型处理器:
               - 数据加密解密
               - JSON字段处理
               - 枚举类型处理
               
            4. 自定义ID生成器:
               - 扩展雪花算法
               - 自定义业务ID生成
               
            =====================================================
            """);
    }
}

9.6 学习路径和进阶方向

/**
 * MyBatis-Plus学习路径和进阶方向
 * 重点:从入门到专家的学习路线规划
 */
@Slf4j
public class LearningPathAndAdvancedDirections {

    /**
     * 初学者阶段(1-2个月)
     */
    public void beginnerStage() {
        log.info("""
            ==================== 初学者阶段 ====================
            
            学习目标:
            - 掌握MyBatis-Plus基础CRUD操作
            - 理解实体类注解使用
            - 学会简单的条件查询
            
            学习内容:
            1. 环境搭建和基础配置
            2. 实体类设计和注解使用
            3. BaseMapper基础方法使用
            4. 简单的QueryWrapper查询
            5. 分页查询基础使用
            
            实践项目:
            - 用户管理系统
            - 商品管理系统
            
            考核标准:
            - 能够独立完成基础CRUD功能
            - 理解MyBatis-Plus基本概念
            - 能够解决简单的配置问题
            
            =====================================================
            """);
    }

    /**
     * 进阶阶段(2-4个月)
     */
    public void intermediateStage() {
        log.info("""
            ==================== 进阶阶段 ====================
            
            学习目标:
            - 掌握复杂查询和关联查询
            - 理解事务管理和性能优化
            - 学会代码生成器使用
            
            学习内容:
            1. 复杂的QueryWrapper和LambdaQueryWrapper
            2. 多表关联查询实现
            3. 事务管理和传播行为
            4. 乐观锁和逻辑删除
            5. 代码生成器配置和使用
            
            实践项目:
            - 电商订单系统
            - 内容管理系统
            
            考核标准:
            - 能够处理复杂业务查询
            - 理解事务的传播行为和隔离级别
            - 能够使用代码生成器提高开发效率
            
            =====================================================
            """);
    }

    /**
     * 高级阶段(4-6个月)
     */
    public void advancedStage() {
        log.info("""
            ==================== 高级阶段 ====================
            
            学习目标:
            - 掌握性能优化和调优
            - 理解源码实现原理
            - 能够进行自定义扩展
            
            学习内容:
            1. SQL性能分析和优化
            2. MyBatis-Plus源码分析
            3. 自定义拦截器和注入器
            4. 多数据源和分布式事务
            5. 企业级架构设计
            
            实践项目:
            - 高并发秒杀系统
            - 分布式电商平台
            
            考核标准:
            - 能够分析和解决性能瓶颈
            - 理解框架底层实现原理
            - 能够根据业务需求进行框架扩展
            
            =====================================================
            """);
    }

    /**
     * 专家阶段(6个月以上)
     */
    public void expertStage() {
        log.info("""
            ==================== 专家阶段 ====================
            
            学习目标:
            - 框架源码深度理解
            - 性能极致优化
            - 架构设计能力
            
            学习内容:
            1. MyBatis和MyBatis-Plus源码深度分析
            2. 数据库深度优化和调优
            3. 分布式系统架构设计
            4. 高并发系统设计
            5. 技术团队管理和架构决策
            
            实践项目:
            - 自研ORM框架
            - 大型分布式系统架构
            
            考核标准:
            - 能够参与开源项目贡献
            - 具备大型系统架构设计能力
            - 能够指导团队技术方向
            
            =====================================================
            """);
    }

    /**
     * 持续学习资源
     */
    public void continuousLearningResources() {
        log.info("""
            ==================== 持续学习资源 ====================
            
            官方资源:
            - MyBatis-Plus官网:https://baomidou.com/
            - GitHub仓库:https://github.com/baomidou/mybatis-plus
            - 官方文档:https://baomidou.com/guide/
            
            社区资源:
            - MyBatis-Plus社区
            - 技术博客和文章
            - 开源项目源码学习
            
            实践资源:
            - 参与开源项目
            - 技术分享和演讲
            - 个人技术博客写作
            
            进阶方向:
            - 数据库深度优化
            - 分布式系统架构
            - 云原生技术栈
            - 大数据处理技术
            
            =====================================================
            """);
    }
}

9.7 企业级项目 Checklist

/**
 * 企业级MyBatis-Plus项目Checklist
 * 重点:项目开发和上线的检查清单
 */
@Slf4j
public class EnterpriseProjectChecklist {

    /**
     * 开发阶段检查项
     */
    public void developmentChecklist() {
        log.info("""
            ==================== 开发阶段检查项 ====================
            
            ✅ 1. 项目结构规范
               - 分层架构清晰
               - 包结构合理
               - 命名规范统一
               
            ✅ 2. 实体类设计
               - 正确使用注解
               - 包含必要的基础字段
               - 数据类型选择合理
               
            ✅ 3. Mapper设计
               - 继承BaseMapper
               - 自定义方法命名规范
               - XML文件位置正确
               
            ✅ 4. Service设计
               - 业务逻辑清晰
               - 事务管理正确
               - 异常处理完善
               
            ✅ 5. 查询优化
               - 避免N+1查询
               - 合理使用索引
               - 分页查询优化
               
            ✅ 6. 代码质量
               - 单元测试覆盖
               - 代码规范检查
               - 性能测试通过
               
            =====================================================
            """);
    }

    /**
     * 测试阶段检查项
     */
    public void testingChecklist() {
        log.info("""
            ==================== 测试阶段检查项 ====================
            
            ✅ 1. 单元测试
               - 业务逻辑测试
               - 数据库操作测试
               - 边界条件测试
               
            ✅ 2. 集成测试
               - 多表关联测试
               - 事务管理测试
               - 并发操作测试
               
            ✅ 3. 性能测试
               - 响应时间测试
               - 并发用户测试
               - 大数据量测试
               
            ✅ 4. 安全测试
               - SQL注入测试
               - 数据权限测试
               - 敏感数据测试
               
            ✅ 5. 兼容性测试
               - 数据库版本兼容
               - 不同环境测试
               - 浏览器兼容性
               
            =====================================================
            """);
    }

    /**
     * 上线前检查项
     */
    public void preProductionChecklist() {
        log.info("""
            ==================== 上线前检查项 ====================
            
            ✅ 1. 配置检查
               - 生产环境配置正确
               - 数据库连接配置
               - 缓存配置正确
               
            ✅ 2. 数据库检查
               - 表结构正确
               - 索引创建完整
               - 初始数据准备
               
            ✅ 3. 性能检查
               - 慢SQL优化
               - 连接池配置优化
               - JVM参数优化
               
            ✅ 4. 安全检查
               - 敏感信息加密
               - 权限控制完善
               - 防攻击措施
               
            ✅ 5. 监控检查
               - 日志配置正确
               - 监控告警设置
               - 健康检查通过
               
            =====================================================
            """);
    }

    /**
     * 运维阶段检查项
     */
    public void operationChecklist() {
        log.info("""
            ==================== 运维阶段检查项 ====================
            
            🔄 1. 日常监控
               - 数据库连接数监控
               - 慢SQL监控和优化
               - 系统资源监控
               
            🔄 2. 性能优化
               - 定期分析执行计划
               - 索引优化和重建
               - 数据库参数调优
               
            🔄 3. 数据管理
               - 数据备份和恢复测试
               - 数据清理和归档
               - 数据一致性检查
               
            🔄 4. 版本管理
               - 数据库脚本版本管理
               - 应用版本回滚计划
               - 依赖版本管理
               
            🔄 5. 故障处理
               - 故障应急预案
               - 数据恢复流程
               - 问题排查手册
               
            =====================================================
            """);
    }
}

课程总结

通过这八个模块的系统学习,您已经掌握了MyBatis-Plus在企业级开发中的完整知识体系:

🎯 核心收获

  1. 完整的知识体系:从基础配置到高级特性,从单表操作到复杂关联查询
  2. 企业级最佳实践:基于真实项目经验的配置和优化方案
  3. 性能优化能力:全方位的性能监控和优化策略
  4. 架构设计思维:大型项目的架构设计和扩展方案

🚀 下一步建议

  1. 实践项目:选择真实业务场景进行项目实践
  2. 源码学习:深入理解MyBatis-Plus的底层实现原理
  3. 技术拓展:学习相关的分布式、云原生技术
  4. 社区参与:参与开源项目,分享技术经验

📚 持续学习

技术永远在进步,建议您:

  • 关注MyBatis-Plus官方更新
  • 参与技术社区讨论
  • 定期复盘和总结项目经验
  • 分享和学习最佳实践

感谢您完成本课程的学习!希望这套企业级MyBatis-Plus实战教学能够帮助您在实际工作中更加游刃有余,构建高性能、可维护的企业级应用。

posted on 2025-10-05 22:16  笨忠  阅读(15)  评论(0)    收藏  举报