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;
}