Mybatis学习笔记(三)

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对比

特性MyBatisMybatisPlus
学习成本需要学习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.x3.5.x2.5+JDK8+
3.4.x3.5.x2.1+JDK8+
3.3.x3.5.x2.0+JDK8+
3.2.x3.5.x2.0+JDK8+
3.1.x3.5.x2.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 {
    // 实体类字段
}

注解属性详解

属性类型必填默认值描述
valueString“”表名
schemaString“”schema
keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值
resultMapString“”xml 中 resultMap 的 id
autoResultMapbooleanfalse是否自动构建 resultMap 并使用
excludePropertyString[]{}需要排除的属性名

实际应用案例

// 案例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;
}

注解属性详解

属性类型必填默认值描述
valueString“”主键字段名
typeIdTypeIdType.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_UUIDUUID字符串,适合日志
高并发写入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;
}

注解属性详解

属性类型必填默认值描述
valueString“”数据库字段名
existbooleantrue是否为数据库表字段
conditionSqlConditionEQUAL字段 where 实体查询比较条件
updateString“”字段 update set 部分注入
insertStrategyFieldStrategyDEFAULT字段验证策略之 insert
updateStrategyFieldStrategyDEFAULT字段验证策略之 update
whereStrategyFieldStrategyDEFAULT字段验证策略之 where
fillFieldFillDEFAULT字段自动填充策略
selectbooleantrue是否进行 select 查询
keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理
jdbcTypeJdbcTypeUNDEFINEDJDBC类型
typeHandlerClass<? 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值