Mybatis学习笔记(三)

发布于:2025-08-15 ⋅ 阅读:(15) ⋅ 点赞:(0)

MybatisPlus 快速入门

简要描述:MybatisPlus是MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

MybatisPlus简介与特性

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

核心概念

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小:启动即会自动注入基本CRUD,性能基本无损耗,直接面向对象操作
  • 强大的CRUD操作:内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作
  • 支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达4种主键策略(内含分布式唯一ID生成器-Sequence),可自由配置
  • 支持ActiveRecord模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作
  • 支持自定义全局通用操作:支持全局通用方法注入(Write once, use anywhere)
  • 内置代码生成器:采用代码或者Maven插件可快速生成Mapper、Model、Service、Controller层代码
  • 内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
  • 分页插件支持多种数据库:支持MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer等多种数据库
  • 内置性能分析插件:可输出SQL语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表delete、update操作智能分析阻断,也可自定义拦截规则,预防误操作

MybatisPlus架构

┌─────────────────────────────────────────────────────────────┐
│                    MybatisPlus 架构图                        │
├─────────────────────────────────────────────────────────────┤
│  Application Layer (应用层)                                 │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │ Controller  │ │   Service   │ │    Mapper   │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  MybatisPlus Enhancement Layer (增强层)                     │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │BaseMapper   │ │IService     │ │ServiceImpl  │           │
│  │CRUD接口     │ │通用Service  │ │Service实现  │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │Wrapper      │ │Page         │ │Plugins      │           │
│  │条件构造器   │ │分页对象     │ │插件机制     │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  MyBatis Core Layer (MyBatis核心层)                        │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │SqlSession   │ │Executor     │ │Configuration│           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  Database Layer (数据库层)                                  │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │              Database (MySQL/Oracle/...)               │ │
│  └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

MybatisPlus与MyBatis对比

特性 MyBatis MybatisPlus
学习成本 需要学习XML配置和SQL编写 基于MyBatis,学习成本低
开发效率 需要手写大量CRUD SQL 内置通用CRUD,开发效率高
代码量 XML文件较多,代码量大 大幅减少XML和SQL代码
维护性 XML和Java代码分离,维护复杂 代码集中,维护简单
扩展性 扩展需要手写SQL 提供丰富的扩展点和插件
性能 原生MyBatis性能 基于MyBatis,性能损耗极小
兼容性 MyBatis原生功能 完全兼容MyBatis所有功能

适用场景

  • 快速开发:需要快速构建CRUD功能的项目
  • 单表操作为主:业务以单表操作为主,复杂关联查询较少
  • 代码生成:需要自动生成代码的项目
  • 团队协作:团队成员技术水平参差不齐,需要统一开发规范
  • 微服务架构:微服务中单个服务的数据操作相对简单

环境搭建与依赖配置

简要描述:搭建MybatisPlus开发环境,包括Maven依赖配置、数据库驱动、连接池等基础环境。

核心概念

  • 版本兼容性:MybatisPlus与SpringBoot、MyBatis的版本兼容关系
  • 依赖管理:核心依赖和可选依赖的配置
  • 自动配置:SpringBoot自动配置机制
  • 数据源配置:数据库连接池的选择和配置

Maven依赖配置

<!-- 1. SpringBoot项目依赖配置 -->
<dependencies>
    <!-- SpringBoot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    
    <!-- SpringBoot Web Starter(如果是Web项目) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- MybatisPlus SpringBoot Starter -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    
    <!-- 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    
    <!-- 连接池(可选,SpringBoot默认使用HikariCP) -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.18</version>
    </dependency>
    
    <!-- 代码生成器(可选) -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    
    <!-- 模板引擎(代码生成器需要) -->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>2.3</version>
    </dependency>
    
    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle依赖配置

dependencies {
    // SpringBoot
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    
    // MybatisPlus
    implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3.1'
    
    // 数据库驱动
    implementation 'mysql:mysql-connector-java:8.0.33'
    
    // 连接池
    implementation 'com.alibaba:druid-spring-boot-starter:1.2.18'
    
    // 代码生成器
    implementation 'com.baomidou:mybatis-plus-generator:3.5.3.1'
    implementation 'org.apache.velocity:velocity-engine-core:2.3'
    
    // 测试
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

版本兼容性说明

MybatisPlus版本 MyBatis版本 SpringBoot版本 JDK版本
3.5.x 3.5.x 2.5+ JDK8+
3.4.x 3.5.x 2.1+ JDK8+
3.3.x 3.5.x 2.0+ JDK8+
3.2.x 3.5.x 2.0+ JDK8+
3.1.x 3.5.x 2.0+ JDK8+

项目结构

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           ├── MybatisPlusApplication.java     # 启动类
│   │           ├── config/                         # 配置类
│   │           │   ├── MybatisPlusConfig.java     # MP配置
│   │           │   └── DataSourceConfig.java      # 数据源配置
│   │           ├── entity/                        # 实体类
│   │           │   └── User.java
│   │           ├── mapper/                        # Mapper接口
│   │           │   └── UserMapper.java
│   │           ├── service/                       # Service层
│   │           │   ├── UserService.java
│   │           │   └── impl/
│   │           │       └── UserServiceImpl.java
│   │           └── controller/                    # Controller层
│   │               └── UserController.java
│   └── resources/
│       ├── application.yml                        # 配置文件
│       ├── mapper/                               # Mapper XML(可选)
│       │   └── UserMapper.xml
│       └── db/
│           └── schema.sql                        # 数据库脚本
└── test/
    └── java/
        └── com/
            └── example/
                └── MybatisPlusApplicationTests.java

基本配置与数据源

简要描述:配置MybatisPlus的基本参数、数据源连接、日志输出等核心配置项。

核心概念

  • 全局配置:MybatisPlus的全局配置参数
  • 数据源配置:数据库连接池的配置
  • 日志配置:SQL日志的输出配置
  • 包扫描配置:Mapper接口的扫描配置

application.yml配置

# 数据源配置
spring:
  datasource:
    # 数据库连接信息
    url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    
    # 连接池配置(使用Druid)
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 初始连接数
      initial-size: 5
      # 最小连接池数量
      min-idle: 5
      # 最大连接池数量
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      max-evictable-idle-time-millis: 900000
      # 配置检测连接是否有效
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall,slf4j
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      # 配置DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern: "/*"
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      # 配置DruidStatViewServlet
      stat-view-servlet:
        enabled: true
        url-pattern: "/druid/*"
        # IP白名单(没有配置或者为空,则允许所有访问)
        allow: 127.0.0.1,192.168.163.1
        # IP黑名单 (存在共同时,deny优先于allow)
        deny: 192.168.1.73
        # 禁用HTML页面上的"Reset All"功能
        reset-enable: false
        # 登录名
        login-username: admin
        # 登录密码
        login-password: 123456

# MybatisPlus配置
mybatis-plus:
  # 配置扫描通用枚举,支持统配符 * 或者 ; 分割
  type-enums-package: com.example.enums
  # 启动时是否检查MyBatis XML文件的存在
  check-config-location: true
  # MyBatis配置文件位置,如果您有单独的MyBatis配置,请将其路径配置到configLocation中
  config-location: classpath:mybatis-config.xml
  # MyBatis Mapper所对应的XML文件位置
  mapper-locations: classpath*:mapper/**/*Mapper.xml
  # MyBaits别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.example.entity
  # 该配置请和 typeAliasesPackage 一起使用,如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象
  type-aliases-super-type: java.lang.Object
  # 枚举类扫描路径,如果配置了该属性,会将路径下的枚举类进行注入,让实体类字段能够简单快捷的使用枚举属性
  type-handlers-package: com.example.typehandler
  # 执行器类型:SIMPLE就是普通的执行器;REUSE执行器会重用预处理语句(prepared statements); BATCH执行器将重用语句并执行批量更新
  executor-type: SIMPLE
  # 指定外部化MyBatis Properties配置,通过该配置可以抽离配置,实现不同环境的配置部署
  configuration-properties: classpath:mybatis/config.properties
  # 原生MyBatis所支持的配置
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
    map-underscore-to-camel-case: true
    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 解决oracle更新数据为null时无法转换报错,mysql不会出现此情况
    jdbc-type-for-null: 'null'
    # 开启延迟加载
    lazy-loading-enabled: true
    # 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载
    aggressive-lazy-loading: false
    # 是否允许单一语句返回多结果集(需要兼容驱动)
    multiple-result-sets-enabled: true
    # 是否可以使用列的别名 (取决于使用的驱动)
    use-column-label: true
    # 允许 JDBC 支持自动生成主键,需要驱动兼容
    use-generated-keys: false
    # 配置默认的执行器.SIMPLE就是普通执行器,REUSE执行器会重用预处理语句,BATCH执行器将重用语句并执行批量更新
    default-executor-type: SIMPLE
    # 指定 MyBatis 应如何自动映射列到字段或属性
    auto-mapping-behavior: PARTIAL
    # 配置默认的语句超时时间
    default-statement-timeout: 25000
    # 设置关联对象加载的形态,此处为按需加载字段(加载字段或关联表的映射),不会加载关联表的所有字段,以提高性能
    auto-mapping-unknown-column-behavior: WARNING
  # 全局配置
  global-config:
    # 是否控制台 print mybatis-plus 的 LOGO
    banner: true
    # 机器 ID 部分(影响雪花ID)
    worker-id: 1
    # 数据标识 ID 部分(影响雪花ID)(workerId 和 datacenterId 一起配置才能重新初始化 Sequence)
    datacenter-id: 1
    # 是否开启 LOGO
    enable-sql-runner: false
    # 数据库相关配置
    db-config:
      # 主键类型(AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID", NONE:"无状态", ID_WORKER_STR:"字符串全局唯一ID")
      id-type: ASSIGN_ID
      # 字段策略(IGNORED:"忽略判断", NOT_NULL:"非 NULL 判断", NOT_EMPTY:"非空判断", DEFAULT:"默认", never:"不加入 SQL")
      field-strategy: NOT_NULL
      # 数据库大写下划线转换
      capital-mode: true
      # 数据库表名前缀
      table-prefix: t_
      # 逻辑删除配置
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
      # 字段验证策略之 insert,在 insert 的时候的字段验证策略
      insert-strategy: NOT_NULL
      # 字段验证策略之 update,在 update 的时候的字段验证策略
      update-strategy: NOT_NULL
      # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
      where-strategy: NOT_NULL

# 日志配置
logging:
  level:
    com.example.mapper: debug
    druid.sql.Statement: debug
    druid.sql.ResultSet: debug

Java配置类

// MybatisPlus配置类
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
    
    /**
     * 分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // 设置数据库类型
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(1000L);
        // 分页合理化
        paginationInnerInterceptor.setOverflow(false);
        // 单页分页条数限制
        paginationInnerInterceptor.setMaxLimit(500L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        
        // 乐观锁插件
        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor();
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
        
        // 阻断插件(防止全表更新与删除)
        BlockAttackInnerInterceptor blockAttackInnerInterceptor = new BlockAttackInnerInterceptor();
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor);
        
        return interceptor;
    }
    
    /**
     * 自定义主键生成器
     */
    @Bean
    public IdentifierGenerator identifierGenerator() {
        return new CustomIdGenerator();
    }
    
    /**
     * 自定义SQL注入器
     */
    @Bean
    public ISqlInjector sqlInjector() {
        return new DefaultSqlInjector() {
            @Override
            public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
                List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
                // 添加自定义方法
                methodList.add(new InsertBatchSomeColumn());
                return methodList;
            }
        };
    }
    
    /**
     * 元对象字段填充控制器
     */
    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new MyMetaObjectHandler();
    }
}

// 自定义元数据处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public void insertFill(MetaObject metaObject) {
        // 插入时自动填充
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUser());
        this.strictInsertFill(metaObject, "updateBy", String.class, getCurrentUser());
        this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        // 更新时自动填充
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUser());
    }
    
    private String getCurrentUser() {
        // 获取当前用户,这里简化处理
        return "system";
    }
}

// 自定义ID生成器
public class CustomIdGenerator implements IdentifierGenerator {
    
    @Override
    public Long nextId(Object entity) {
        // 自定义ID生成逻辑
        return IdWorker.getId();
    }
}

第一个MybatisPlus程序

简要描述:创建第一个MybatisPlus程序,包括实体类、Mapper接口、Service层和Controller层的基本实现。

核心概念

  • 实体类:对应数据库表的Java类
  • BaseMapper:MybatisPlus提供的基础Mapper接口
  • IService:MybatisPlus提供的基础Service接口
  • ServiceImpl:MybatisPlus提供的基础Service实现类

数据库表结构

-- 创建数据库
CREATE DATABASE IF NOT EXISTS mybatis_plus DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE mybatis_plus;

-- 创建用户表
CREATE TABLE t_user (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    create_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    create_by VARCHAR(50) NULL DEFAULT NULL COMMENT '创建人',
    update_by VARCHAR(50) NULL DEFAULT NULL COMMENT '更新人',
    deleted INT(1) NULL DEFAULT 0 COMMENT '逻辑删除标志(0代表未删除,1代表已删除)',
    version INT(11) NULL DEFAULT 1 COMMENT '版本号(乐观锁)',
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 插入测试数据
INSERT INTO t_user (name, age, email) VALUES
('张三', 18, 'zhangsan@example.com'),
('李四', 20, 'lisi@example.com'),
('王五', 28, 'wangwu@example.com'),
('赵六', 21, 'zhaoliu@example.com'),
('钱七', 24, 'qianqi@example.com');

实体类

// User实体类
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_user")
@ApiModel(value="User对象", description="用户表")
public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @ApiModelProperty(value = "主键ID")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    
    @ApiModelProperty(value = "姓名")
    @TableField("name")
    private String name;
    
    @ApiModelProperty(value = "年龄")
    @TableField("age")
    private Integer age;
    
    @ApiModelProperty(value = "邮箱")
    @TableField("email")
    private String email;
    
    @ApiModelProperty(value = "创建时间")
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @ApiModelProperty(value = "更新时间")
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @ApiModelProperty(value = "创建人")
    @TableField(value = "create_by", fill = FieldFill.INSERT)
    private String createBy;
    
    @ApiModelProperty(value = "更新人")
    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
    private String updateBy;
    
    @ApiModelProperty(value = "逻辑删除标志")
    @TableLogic
    @TableField("deleted")
    private Integer deleted;
    
    @ApiModelProperty(value = "版本号")
    @Version
    @TableField("version")
    private Integer version;
}

Mapper接口

// UserMapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
    
    /**
     * 自定义查询方法
     * 根据年龄范围查询用户
     */
    @Select("SELECT * FROM t_user WHERE age BETWEEN #{minAge} AND #{maxAge} AND deleted = 0")
    List<User> selectByAgeRange(@Param("minAge") Integer minAge, @Param("maxAge") Integer maxAge);
    
    /**
     * 自定义更新方法
     * 根据邮箱更新用户信息
     */
    @Update("UPDATE t_user SET name = #{name}, age = #{age}, update_time = NOW() WHERE email = #{email} AND deleted = 0")
    int updateByEmail(@Param("name") String name, @Param("age") Integer age, @Param("email") String email);
    
    /**
     * 复杂查询示例
     * 使用XML方式实现
     */
    List<User> selectUserWithCondition(@Param("condition") UserQueryCondition condition);
    
    /**
     * 分页查询示例
     */
    IPage<User> selectUserPage(IPage<User> page, @Param("condition") UserQueryCondition condition);
}

Service接口

// UserService接口
public interface UserService extends IService<User> {
    
    /**
     * 根据年龄范围查询用户
     */
    List<User> getUsersByAgeRange(Integer minAge, Integer maxAge);
    
    /**
     * 根据邮箱更新用户信息
     */
    boolean updateUserByEmail(String name, Integer age, String email);
    
    /**
     * 批量保存用户
     */
    boolean saveBatch(List<User> userList);
    
    /**
     * 分页查询用户
     */
    IPage<User> getUserPage(Integer current, Integer size, UserQueryCondition condition);
    
    /**
     * 根据条件查询用户数量
     */
    long getUserCount(UserQueryCondition condition);
}

Service实现类

// UserServiceImpl实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {
        // 使用自定义Mapper方法
        return userMapper.selectByAgeRange(minAge, maxAge);
    }
    
    @Override
    public boolean updateUserByEmail(String name, Integer age, String email) {
        // 使用自定义Mapper方法
        int result = userMapper.updateByEmail(name, age, email);
        return result > 0;
    }
    
    @Override
    public boolean saveBatch(List<User> userList) {
        // 使用MybatisPlus提供的批量保存方法
        return super.saveBatch(userList);
    }
    
    @Override
    public IPage<User> getUserPage(Integer current, Integer size, UserQueryCondition condition) {
        // 创建分页对象
        Page<User> page = new Page<>(current, size);
        
        // 使用条件构造器
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(condition.getName()), User::getName, condition.getName())
               .ge(condition.getMinAge() != null, User::getAge, condition.getMinAge())
               .le(condition.getMaxAge() != null, User::getAge, condition.getMaxAge())
               .like(StringUtils.isNotBlank(condition.getEmail()), User::getEmail, condition.getEmail())
               .orderByDesc(User::getCreateTime);
        
        return this.page(page, wrapper);
    }
    
    @Override
    public long getUserCount(UserQueryCondition condition) {
        // 使用条件构造器统计数量
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(condition.getName()), User::getName, condition.getName())
               .ge(condition.getMinAge() != null, User::getAge, condition.getMinAge())
               .le(condition.getMaxAge() != null, User::getAge, condition.getMaxAge())
               .like(StringUtils.isNotBlank(condition.getEmail()), User::getEmail, condition.getEmail());
        
        return this.count(wrapper);
    }
}

Controller层

// UserController控制器
@RestController
@RequestMapping("/api/user")
@Api(tags = "用户管理")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    /**
     * 查询所有用户
     */
    @GetMapping("/list")
    @ApiOperation("查询所有用户")
    public Result<List<User>> getAllUsers() {
        List<User> users = userService.list();
        return Result.success(users);
    }
    
    /**
     * 根据ID查询用户
     */
    @GetMapping("/{id}")
    @ApiOperation("根据ID查询用户")
    public Result<User> getUserById(@PathVariable Long id) {
        User user = userService.getById(id);
        if (user != null) {
            return Result.success(user);
        } else {
            return Result.error("用户不存在");
        }
    }
    
    /**
     * 新增用户
     */
    @PostMapping
    @ApiOperation("新增用户")
    public Result<String> saveUser(@RequestBody User user) {
        boolean result = userService.save(user);
        if (result) {
            return Result.success("用户创建成功");
        } else {
            return Result.error("用户创建失败");
        }
    }
    
    /**
     * 更新用户
     */
    @PutMapping
    @ApiOperation("更新用户")
    public Result<String> updateUser(@RequestBody User user) {
        boolean result = userService.updateById(user);
        if (result) {
            return Result.success("用户更新成功");
        } else {
            return Result.error("用户更新失败");
        }
    }
    
    /**
     * 删除用户
     */
    @DeleteMapping("/{id}")
    @ApiOperation("删除用户")
    public Result<String> deleteUser(@PathVariable Long id) {
        boolean result = userService.removeById(id);
        if (result) {
            return Result.success("用户删除成功");
        } else {
            return Result.error("用户删除失败");
        }
    }
    
    /**
     * 分页查询用户
     */
    @GetMapping("/page")
    @ApiOperation("分页查询用户")
    public Result<IPage<User>> getUserPage(
            @RequestParam(defaultValue = "1") Integer current,
            @RequestParam(defaultValue = "10") Integer size,
            UserQueryCondition condition) {
        IPage<User> page = userService.getUserPage(current, size, condition);
        return Result.success(page);
    }
    
    /**
     * 根据年龄范围查询用户
     */
    @GetMapping("/age-range")
    @ApiOperation("根据年龄范围查询用户")
    public Result<List<User>> getUsersByAgeRange(
            @RequestParam Integer minAge,
            @RequestParam Integer maxAge) {
        List<User> users = userService.getUsersByAgeRange(minAge, maxAge);
        return Result.success(users);
    }
    
    /**
     * 批量保存用户
     */
    @PostMapping("/batch")
    @ApiOperation("批量保存用户")
    public Result<String> saveBatch(@RequestBody List<User> userList) {
        boolean result = userService.saveBatch(userList);
        if (result) {
            return Result.success("批量保存成功");
        } else {
            return Result.error("批量保存失败");
        }
    }
}

启动类

// 应用启动类
@SpringBootApplication
@MapperScan("com.example.mapper")
public class MybatisPlusApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }
}

测试类

// 测试类
@SpringBootTest
class MybatisPlusApplicationTests {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    void testSelect() {
        // 测试查询所有用户
        List<User> userList = userService.list();
        userList.forEach(System.out::println);
    }
    
    @Test
    void testInsert() {
        // 测试插入用户
        User user = new User();
        user.setName("测试用户");
        user.setAge(25);
        user.setEmail("test@example.com");
        
        boolean result = userService.save(user);
        System.out.println("插入结果: " + result);
        System.out.println("用户ID: " + user.getId());
    }
    
    @Test
    void testUpdate() {
        // 测试更新用户
        User user = userService.getById(1L);
        if (user != null) {
            user.setAge(30);
            boolean result = userService.updateById(user);
            System.out.println("更新结果: " + result);
        }
    }
    
    @Test
    void testDelete() {
        // 测试删除用户(逻辑删除)
        boolean result = userService.removeById(1L);
        System.out.println("删除结果: " + result);
    }
    
    @Test
    void testPage() {
        // 测试分页查询
        Page<User> page = new Page<>(1, 3);
        IPage<User> userPage = userService.page(page);
        
        System.out.println("总记录数: " + userPage.getTotal());
        System.out.println("总页数: " + userPage.getPages());
        System.out.println("当前页: " + userPage.getCurrent());
        System.out.println("每页大小: " + userPage.getSize());
        
        userPage.getRecords().forEach(System.out::println);
    }
    
    @Test
    void testWrapper() {
        // 测试条件构造器
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("name", "张")
               .between("age", 18, 30)
               .isNotNull("email")
               .orderByDesc("create_time");
        
        List<User> users = userService.list(wrapper);
        users.forEach(System.out::println);
    }
    
    @Test
    void testLambdaWrapper() {
        // 测试Lambda条件构造器
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(User::getName, "李")
               .ge(User::getAge, 20)
               .orderByAsc(User::getAge);
        
        List<User> users = userService.list(wrapper);
        users.forEach(System.out::println);
    }
}

实体类与表映射

简要描述:MybatisPlus通过注解实现实体类与数据库表的映射关系,包括表名映射、主键映射、字段映射等核心功能。

@TableName表名映射

简要描述@TableName注解用于指定实体类对应的数据库表名,支持表名前缀、结果映射等配置。

核心概念

  • 表名映射:实体类名与数据库表名的对应关系
  • 命名策略:驼峰命名与下划线命名的转换
  • 表名前缀:统一的表名前缀配置
  • 结果映射:自定义结果映射处理

基本使用

// 1. 基本表名映射
@TableName("t_user")  // 指定表名为 t_user
public class User {
    // 实体类字段
}

// 2. 不使用注解(使用默认映射规则)
public class UserInfo {
    // 默认映射到表名:user_info(驼峰转下划线)
}

// 3. 复杂表名映射配置
@TableName(
    value = "sys_user",                    // 表名
    schema = "mybatis_plus",               // 数据库schema
    keepGlobalPrefix = true,               // 保持全局表前缀
    resultMap = "userResultMap",           // 自定义结果映射
    autoResultMap = true,                  // 自动构建结果映射
    excludeProperty = {"field1", "field2"} // 排除字段
)
public class User {
    // 实体类字段
}

注解属性详解

属性 类型 必填 默认值 描述
value String “” 表名
schema String “” schema
keepGlobalPrefix boolean false 是否保持使用全局的 tablePrefix 的值
resultMap String “” xml 中 resultMap 的 id
autoResultMap boolean false 是否自动构建 resultMap 并使用
excludeProperty String[] {} 需要排除的属性名

实际应用案例

// 案例1:用户表映射
@Data
@TableName("sys_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String username;
    private String password;
    private String email;
    private LocalDateTime createTime;
}

// 案例2:订单表映射(带schema)
@Data
@TableName(value = "t_order", schema = "order_db")
public class Order {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String orderNo;
    private BigDecimal amount;
    private Integer status;
}

// 案例3:复杂映射配置
@Data
@TableName(
    value = "user_profile",
    autoResultMap = true,  // 自动构建ResultMap
    excludeProperty = {"tempField", "cacheData"}  // 排除临时字段
)
public class UserProfile {
    @TableId
    private Long id;
    
    private String nickname;
    private String avatar;
    private String bio;
    
    // 这些字段不会映射到数据库
    @TableField(exist = false)
    private String tempField;
    
    @TableField(exist = false)
    private Map<String, Object> cacheData;
}

全局配置

# application.yml 全局表名配置
mybatis-plus:
  global-config:
    db-config:
      # 表名前缀
      table-prefix: t_
      # 表名大写
      capital-mode: false
      # 表名下划线转换
      table-underline: true

命名策略示例

// 实体类名 -> 表名映射规则
public class User {}           // -> user
public class UserInfo {}       // -> user_info  
public class OrderDetail {}    // -> order_detail
public class SysUser {}        // -> sys_user

// 使用@TableName指定表名
@TableName("t_user")
public class User {}           // -> t_user

// 全局前缀 + @TableName
// 配置:table-prefix: sys_
@TableName("user")
public class User {}           // -> sys_user

// 保持全局前缀
@TableName(value = "user", keepGlobalPrefix = true)
public class User {}           // -> sys_user(如果配置了全局前缀)

@TableId主键策略

简要描述@TableId注解用于标识实体类中的主键字段,支持多种主键生成策略和自定义配置。

核心概念

  • 主键策略:不同的主键生成方式
  • 主键类型:数据库主键的数据类型
  • 自定义生成器:用户自定义的主键生成逻辑
  • 分布式ID:适用于分布式系统的唯一ID生成

主键策略类型

public enum IdType {
    AUTO(0),          // 数据库ID自增
    NONE(1),          // 无状态,该类型为未设置主键类型
    INPUT(2),         // insert前自行set主键值
    ASSIGN_ID(3),     // 分配ID(主键类型为Number(Long和Integer)或String)
    ASSIGN_UUID(4);   // 分配UUID,主键类型为String
}

基本使用

// 1. 数据库自增主键
@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;  // 对应数据库自增主键
    
    private String name;
}

// 2. 手动输入主键
@Data
public class User {
    @TableId(type = IdType.INPUT)
    private String id;  // 需要手动设置主键值
    
    private String name;
}

// 3. 分配ID(雪花算法)
@Data
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;  // 自动生成19位长整型数字
    
    private String name;
}

// 4. 分配UUID
@Data
public class User {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;  // 自动生成32位UUID字符串
    
    private String name;
}

// 5. 自定义主键字段名
@Data
public class User {
    @TableId(value = "user_id", type = IdType.ASSIGN_ID)
    private Long userId;  // 对应数据库字段 user_id
    
    private String name;
}

注解属性详解

属性 类型 必填 默认值 描述
value String “” 主键字段名
type IdType IdType.NONE 指定主键类型

实际应用案例

// 案例1:用户表(雪花算法ID)
@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String username;
    private String email;
    private LocalDateTime createTime;
}

// 案例2:订单表(自定义字符串ID)
@Data
@TableName("t_order")
public class Order {
    @TableId(value = "order_id", type = IdType.INPUT)
    private String orderId;  // 手动设置订单号
    
    private Long userId;
    private BigDecimal amount;
    private Integer status;
    
    // 在保存前设置订单号
    public void generateOrderId() {
        this.orderId = "ORD" + System.currentTimeMillis();
    }
}

// 案例3:日志表(UUID主键)
@Data
@TableName("t_log")
public class Log {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    
    private String module;
    private String operation;
    private String content;
    private LocalDateTime createTime;
}

// 案例4:传统自增主键
@Data
@TableName("t_category")
public class Category {
    @TableId(type = IdType.AUTO)
    private Integer id;  // 数据库自增
    
    private String name;
    private Integer parentId;
    private Integer sort;
}

自定义主键生成器

// 1. 实现IdentifierGenerator接口
@Component
public class CustomIdGenerator implements IdentifierGenerator {
    
    @Override
    public Long nextId(Object entity) {
        // 自定义ID生成逻辑
        if (entity instanceof User) {
            // 用户ID生成规则:时间戳 + 随机数
            return Long.valueOf(System.currentTimeMillis() + "" + 
                               new Random().nextInt(1000));
        }
        // 默认使用雪花算法
        return IdWorker.getId();
    }
}

// 2. 配置自定义生成器
@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public IdentifierGenerator identifierGenerator() {
        return new CustomIdGenerator();
    }
}

// 3. 使用自定义生成器
@Data
public class User {
    @TableId(type = IdType.ASSIGN_ID)  // 会使用自定义生成器
    private Long id;
    
    private String name;
}

全局主键配置

# application.yml 全局主键配置
mybatis-plus:
  global-config:
    db-config:
      # 全局默认主键类型
      id-type: ASSIGN_ID
      # 雪花算法机器ID配置
      worker-id: 1
      datacenter-id: 1

主键策略选择建议

场景 推荐策略 原因
单机应用 AUTO 简单高效,数据库自增
分布式应用 ASSIGN_ID 雪花算法,全局唯一
业务主键 INPUT 有业务含义的主键
日志记录 ASSIGN_UUID UUID字符串,适合日志
高并发写入 ASSIGN_ID 性能好,避免数据库锁

@TableField字段映射

简要描述@TableField注解用于配置实体类字段与数据库表字段的映射关系,支持字段名映射、填充策略、查询条件等。

核心概念

  • 字段映射:Java字段名与数据库列名的对应关系
  • 字段策略:字段在SQL中的使用策略
  • 自动填充:字段值的自动填充机制
  • 条件构造:字段在条件构造器中的行为

基本使用

// 1. 基本字段映射
@Data
public class User {
    @TableId
    private Long id;
    
    // 默认映射:userName -> user_name
    private String userName;
    
    // 自定义字段名映射
    @TableField("user_email")
    private String email;
    
    // 不参与数据库映射的字段
    @TableField(exist = false)
    private String tempData;
}

// 2. 字段填充策略
@Data
public class User {
    @TableId
    private Long id;
    
    private String name;
    
    // 插入时自动填充
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    // 插入和更新时都自动填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    // 只在更新时自动填充
    @TableField(fill = FieldFill.UPDATE)
    private String updateBy;
}

// 3. 字段策略配置
@Data
public class User {
    @TableId
    private Long id;
    
    // 非空判断策略
    @TableField(strategy = FieldStrategy.NOT_NULL)
    private String name;
    
    // 非空字符串判断策略
    @TableField(strategy = FieldStrategy.NOT_EMPTY)
    private String email;
    
    // 忽略判断策略
    @TableField(strategy = FieldStrategy.IGNORED)
    private String remark;
}

注解属性详解

属性 类型 必填 默认值 描述
value String “” 数据库字段名
exist boolean true 是否为数据库表字段
condition SqlCondition EQUAL 字段 where 实体查询比较条件
update String “” 字段 update set 部分注入
insertStrategy FieldStrategy DEFAULT 字段验证策略之 insert
updateStrategy FieldStrategy DEFAULT 字段验证策略之 update
whereStrategy FieldStrategy DEFAULT 字段验证策略之 where
fill FieldFill DEFAULT 字段自动填充策略
select boolean true 是否进行 select 查询
keepGlobalFormat boolean false 是否保持使用全局的 format 进行处理
jdbcType JdbcType UNDEFINED JDBC类型
typeHandler Class<? extends TypeHandler> UnknownTypeHandler.class 类型处理器

字段策略枚举

public enum FieldStrategy {
    IGNORED,    // 忽略判断
    NOT_NULL,   // 非NULL判断
    NOT_EMPTY,  // 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
    DEFAULT,    // 追随全局配置
    NEVER       // 不加入 SQL
}

字段填充枚举

public enum FieldFill {
    DEFAULT,      // 默认不处理
    INSERT,       // 插入时填充字段
    UPDATE,       // 更新时填充字段
    INSERT_UPDATE // 插入和更新时填充字段
}

实际应用案例

// 案例1:用户表完整字段映射
@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    // 用户名(非空验证)
    @TableField(value = "user_name", strategy = FieldStrategy.NOT_EMPTY)
    private String userName;
    
    // 密码(不参与查询)
    @TableField(value = "password", select = false)
    private String password;
    
    // 邮箱(自定义字段名)
    @TableField("email")
    private String email;
    
    // 状态(忽略空值判断)
    @TableField(strategy = FieldStrategy.IGNORED)
    private Integer status;
    
    // 创建时间(插入时自动填充)
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    // 更新时间(插入和更新时自动填充)
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    // 创建人(插入时自动填充)
    @TableField(value = "create_by", fill = FieldFill.INSERT)
    private String createBy;
    
    // 更新人(更新时自动填充)
    @TableField(value = "update_by", fill = FieldFill.UPDATE)
    private String updateBy;
    
    // 版本号(乐观锁)
    @Version
    @TableField("version")
    private Integer version;
    
    // 逻辑删除标志
    @TableLogic
    @TableField("deleted")
    private Integer deleted;
    
    // 临时字段(不映射到数据库)
    @TableField(exist = false)
    private String token;
    
    // 角色列表(不映射到数据库)
    @TableField(exist = false)
    private List<Role> roles;
}

@TableLogic逻辑删除

简要描述@TableLogic注解用于标识逻辑删除字段,实现软删除功能,删除时不物理删除数据,而是修改标志位。

核心概念

  • 逻辑删除:通过标志位标记删除状态,不物理删除数据
  • 删除标志:用于标识记录是否被删除的字段
  • 自动处理:查询和删除操作的自动处理
  • 全局配置:统一的逻辑删除配置

基本使用

// 1. 基本逻辑删除配置
@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String name;
    private String email;
    
    // 逻辑删除字段
    @TableLogic
    @TableField("deleted")
    private Integer deleted;  // 0-未删除,1-已删除
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

// 2. 自定义删除值
@Data
public class User {
    @TableId
    private Long id;
    
    private String name;
    
    // 自定义删除值
    @TableLogic(value = "0", delval = "1")
    @TableField("is_deleted")
    private Integer isDeleted;  // 0-正常,1-删除
}

// 3. 使用字符串标志
@Data
public class User {
    @TableId
    private Long id;
    
    private String name;
    
    // 字符串类型的逻辑删除
    @TableLogic(value = "ACTIVE", delval = "DELETED")
    @TableField("status")
    private String status;
}

全局配置

# application.yml 全局逻辑删除配置
mybatis-plus:
  global-config:
    db-config:
      # 逻辑删除字段名
      logic-delete-field: deleted
      # 逻辑已删除值(默认为1)
      logic-delete-value: 1
      # 逻辑未删除值(默认为0)
      logic-not-delete-value: 0

逻辑删除行为示例

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public void testLogicDelete() {
        // 1. 插入数据
        User user = new User();
        user.setName("张三");
        user.setEmail("zhangsan@example.com");
        userMapper.insert(user);  // deleted字段自动设置为0
        
        // 2. 查询数据(自动过滤已删除数据)
        List<User> users = userMapper.selectList(null);
        // 生成SQL: SELECT * FROM t_user WHERE deleted = 0
        
        // 3. 逻辑删除
        userMapper.deleteById(user.getId());
        // 生成SQL: UPDATE t_user SET deleted = 1 WHERE id = ? AND deleted = 0
        
        // 4. 再次查询(已删除数据不会被查出)
        User deletedUser = userMapper.selectById(user.getId());
        // deletedUser 为 null
        // 生成SQL: SELECT * FROM t_user WHERE id = ? AND deleted = 0
    }
}

@Version乐观锁

简要描述@Version注解用于实现乐观锁机制,通过版本号控制并发更新,防止数据被意外覆盖。

核心概念

  • 乐观锁:假设并发冲突很少发生,在更新时检查版本号
  • 版本控制:通过版本号字段控制数据的并发访问
  • CAS操作:Compare And Swap,比较并交换
  • 并发安全:防止并发更新导致的数据不一致

基本使用

// 1. 基本乐观锁配置
@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String name;
    private String email;
    
    // 乐观锁版本号字段
    @Version
    @TableField("version")
    private Integer version;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

// 2. 配置乐观锁插件
@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }
}

乐观锁使用示例

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 乐观锁更新示例
     */
    public boolean updateUserWithOptimisticLock(Long userId, String newName) {
        // 1. 先查询获取当前版本号
        User user = userMapper.selectById(userId);
        if (user == null) {
            return false;
        }
        
        // 2. 修改数据
        user.setName(newName);
        
        // 3. 执行更新(会自动检查版本号)
        int updateCount = userMapper.updateById(user);
        // 生成SQL: UPDATE t_user SET name = ?, version = version + 1 
        //          WHERE id = ? AND version = ?
        
        // 4. 检查更新结果
        if (updateCount > 0) {
            System.out.println("更新成功,新版本号:" + (user.getVersion() + 1));
            return true;
        } else {
            System.out.println("更新失败,数据已被其他用户修改");
            return false;
        }
    }
    
    /**
     * 并发更新测试
     */
    public void testConcurrentUpdate() {
        Long userId = 1L;
        
        // 模拟两个用户同时获取数据
        User user1 = userMapper.selectById(userId);  // version = 1
        User user2 = userMapper.selectById(userId);  // version = 1
        
        // 用户1先更新
        user1.setName("用户1修改");
        int result1 = userMapper.updateById(user1);  // 成功,version变为2
        System.out.println("用户1更新结果:" + (result1 > 0 ? "成功" : "失败"));
        
        // 用户2后更新(此时版本号已经不匹配)
        user2.setName("用户2修改");
        int result2 = userMapper.updateById(user2);  // 失败,version仍为1
        System.out.println("用户2更新结果:" + (result2 > 0 ? "成功" : "失败"));
    }
}

高级乐观锁应用

// 1. 自定义乐观锁重试机制
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 带重试的乐观锁更新
     */
    @Retryable(value = OptimisticLockerException.class, maxAttempts = 3)
    public boolean updateUserWithRetry(Long userId, String newName) {
        User user = userMapper.selectById(userId);
        if (user == null) {
            return false;
        }
        
        user.setName(newName);
        int updateCount = userMapper.updateById(user);
        
        if (updateCount == 0) {
            throw new OptimisticLockerException("乐观锁更新失败,数据已被修改");
        }
        
        return true;
    }
    
    /**
     * 批量更新(需要逐个处理版本号)
     */
    public boolean batchUpdateWithOptimisticLock(List<User> userList) {
        boolean allSuccess = true;
        
        for (User user : userList) {
            // 先查询获取最新版本号
            User currentUser = userMapper.selectById(user.getId());
            if (currentUser != null) {
                // 保持版本号一致
                user.setVersion(currentUser.getVersion());
                
                int updateCount = userMapper.updateById(user);
                if (updateCount == 0) {
                    allSuccess = false;
                    log.warn("用户{}更新失败,版本号冲突", user.getId());
                }
            }
        }
        
        return allSuccess;
    }
}

// 2. 自定义乐观锁异常
public class OptimisticLockerException extends RuntimeException {
    public OptimisticLockerException(String message) {
        super(message);
    }
}

// 3. 乐观锁与逻辑删除结合
@Data
@TableName("t_product")
public class Product {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String name;
    private BigDecimal price;
    private Integer stock;  // 库存数量
    
    // 乐观锁版本号
    @Version
    @TableField("version")
    private Integer version;
    
    // 逻辑删除标志
    @TableLogic
    @TableField("deleted")
    private Integer deleted;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

// 4. 库存扣减示例(乐观锁防止超卖)
@Service
public class ProductService {
    
    @Autowired
    private ProductMapper productMapper;
    
    /**
     * 扣减库存(防止超卖)
     */
    public boolean reduceStock(Long productId, Integer quantity) {
        // 查询商品信息
        Product product = productMapper.selectById(productId);
        if (product == null) {
            throw new BusinessException("商品不存在");
        }
        
        // 检查库存是否充足
        if (product.getStock() < quantity) {
            throw new BusinessException("库存不足");
        }
        
        // 扣减库存
        product.setStock(product.getStock() - quantity);
        
        // 使用乐观锁更新
        int updateCount = productMapper.updateById(product);
        
        if (updateCount == 0) {
            throw new OptimisticLockerException("库存扣减失败,请重试");
        }
        
        return true;
    }
}

字段填充策略

简要描述:MybatisPlus提供字段自动填充功能,可以在插入或更新时自动填充指定字段的值,如创建时间、更新时间、操作人等。

核心概念

  • 自动填充:在特定操作时自动为字段赋值
  • 填充策略:不同操作场景下的填充规则
  • 元数据处理器:实现自动填充逻辑的处理器
  • 字段忽略:排除不需要映射的字段

填充策略枚举

public enum FieldFill {
    DEFAULT,      // 默认不处理
    INSERT,       // 插入时填充字段
    UPDATE,       // 更新时填充字段
    INSERT_UPDATE // 插入和更新时填充字段
}

基本配置

// 1. 实体类字段配置
@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String name;
    
    // 插入时自动填充
    @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.UPDATE)
    private String updateBy;
    
    // 插入时自动填充
    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;
    
    // 插入时自动填充
    @TableField(fill = FieldFill.INSERT)
    private Integer version;
}

// 2. 元数据处理器实现
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        
        // 填充创建时间
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        
        // 填充更新时间
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        
        // 填充创建人
        this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUser());
        
        // 填充更新人
        this.strictInsertFill(metaObject, "updateBy", String.class, getCurrentUser());
        
        // 填充逻辑删除标志
        this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
        
        // 填充版本号
        this.strictInsertFill(metaObject, "version", Integer.class, 1);
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充...");
        
        // 填充更新时间
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        
        // 填充更新人
        this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUser());
    }
    
    /**
     * 获取当前用户
     */
    private String getCurrentUser() {
        // 从SecurityContext或Session中获取当前用户
        try {
            // 假设从Spring Security获取
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.isAuthenticated()) {
                return authentication.getName();
            }
        } catch (Exception e) {
            log.warn("获取当前用户失败", e);
        }
        return "system";
    }
}

高级填充应用

// 1. 基于实体类型的差异化填充
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public void insertFill(MetaObject metaObject) {
        Object entity = metaObject.getOriginalObject();
        
        // 通用字段填充
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        
        // 基于实体类型的特殊填充
        if (entity instanceof User) {
            this.handleUserInsert(metaObject);
        } else if (entity instanceof Order) {
            this.handleOrderInsert(metaObject);
        }
    }
    
    private void handleUserInsert(MetaObject metaObject) {
        // 用户特殊字段填充
        this.strictInsertFill(metaObject, "status", Integer.class, 1);
        this.strictInsertFill(metaObject, "userType", Integer.class, 0);
    }
    
    private void handleOrderInsert(MetaObject metaObject) {
        // 订单特殊字段填充
        this.strictInsertFill(metaObject, "orderStatus", Integer.class, 0);
        this.strictInsertFill(metaObject, "orderNo", String.class, generateOrderNo());
    }
    
    private String generateOrderNo() {
        return "ORD" + System.currentTimeMillis();
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUser());
    }
    
    private String getCurrentUser() {
        return "system";
    }
}

// 2. 统一的实体基类
@Data
public abstract class BaseEntity {
    
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @TableField(value = "create_by", fill = FieldFill.INSERT)
    private String createBy;
    
    @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
    private String updateBy;
    
    @TableLogic
    @TableField(value = "deleted", fill = FieldFill.INSERT)
    private Integer deleted;
    
    @Version
    @TableField(value = "version", fill = FieldFill.INSERT)
    private Integer version;
}

// 3. 继承基类的实体
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_user")
public class User extends BaseEntity {
    
    private String name;
    private String email;
    private Integer status;
}

网站公告

今日签到

点亮在社区的每一天
去签到