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对比:
特性 | 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 {// SpringBootimplementation 'org.springframework.boot:spring-boot-starter'implementation 'org.springframework.boot:spring-boot-starter-web'// MybatisPlusimplementation '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=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver# 连接池配置(使用Druid)type: com.alibaba.druid.pool.DruidDataSourcedruid:# 初始连接数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 DUALtest-while-idle: truetest-on-borrow: falsetest-on-return: false# 打开PSCache,并且指定每个连接上PSCache的大小pool-prepared-statements: truemax-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# 配置DruidStatFilterweb-stat-filter:enabled: trueurl-pattern: "/*"exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"# 配置DruidStatViewServletstat-view-servlet:enabled: trueurl-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 的 LOGObanner: true# 机器 ID 部分(影响雪花ID)worker-id: 1# 数据标识 ID 部分(影响雪花ID)(workerId 和 datacenterId 一起配置才能重新初始化 Sequence)datacenter-id: 1# 是否开启 LOGOenable-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: debugdruid.sql.Statement: debugdruid.sql.ResultSet: debug
Java配置类:
// MybatisPlus配置类
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {/*** 分页插件*/@Beanpublic 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;}/*** 自定义主键生成器*/@Beanpublic IdentifierGenerator identifierGenerator() {return new CustomIdGenerator();}/*** 自定义SQL注入器*/@Beanpublic ISqlInjector sqlInjector() {return new DefaultSqlInjector() {@Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);// 添加自定义方法methodList.add(new InsertBatchSomeColumn());return methodList;}};}/*** 元对象字段填充控制器*/@Beanpublic MetaObjectHandler metaObjectHandler() {return new MyMetaObjectHandler();}
}// 自定义元数据处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic 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);}@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate UserMapper userMapper;@Overridepublic List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {// 使用自定义Mapper方法return userMapper.selectByAgeRange(minAge, maxAge);}@Overridepublic boolean updateUserByEmail(String name, Integer age, String email) {// 使用自定义Mapper方法int result = userMapper.updateByEmail(name, age, email);return result > 0;}@Overridepublic boolean saveBatch(List<User> userList) {// 使用MybatisPlus提供的批量保存方法return super.saveBatch(userList);}@Overridepublic 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);}@Overridepublic 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 {@Autowiredprivate 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 {@Autowiredprivate UserService userService;@Autowiredprivate UserMapper userMapper;@Testvoid testSelect() {// 测试查询所有用户List<User> userList = userService.list();userList.forEach(System.out::println);}@Testvoid 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());}@Testvoid testUpdate() {// 测试更新用户User user = userService.getById(1L);if (user != null) {user.setAge(30);boolean result = userService.updateById(user);System.out.println("更新结果: " + result);}}@Testvoid testDelete() {// 测试删除用户(逻辑删除)boolean result = userService.removeById(1L);System.out.println("删除结果: " + result);}@Testvoid 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);}@Testvoid 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);}@Testvoid 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", // 数据库schemakeepGlobalPrefix = 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, // 自动构建ResultMapexcludeProperty = {"tempField", "cacheData"} // 排除临时字段
)
public class UserProfile {@TableIdprivate 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_idprivate 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 {@Overridepublic 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 {@Beanpublic 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: 1datacenter-id: 1
主键策略选择建议:
场景 | 推荐策略 | 原因 |
---|---|---|
单机应用 | AUTO | 简单高效,数据库自增 |
分布式应用 | ASSIGN_ID | 雪花算法,全局唯一 |
业务主键 | INPUT | 有业务含义的主键 |
日志记录 | ASSIGN_UUID | UUID字符串,适合日志 |
高并发写入 | ASSIGN_ID | 性能好,避免数据库锁 |
@TableField字段映射
简要描述:@TableField
注解用于配置实体类字段与数据库表字段的映射关系,支持字段名映射、填充策略、查询条件等。
核心概念:
- 字段映射:Java字段名与数据库列名的对应关系
- 字段策略:字段在SQL中的使用策略
- 自动填充:字段值的自动填充机制
- 条件构造:字段在条件构造器中的行为
基本使用:
// 1. 基本字段映射
@Data
public class User {@TableIdprivate Long id;// 默认映射:userName -> user_nameprivate String userName;// 自定义字段名映射@TableField("user_email")private String email;// 不参与数据库映射的字段@TableField(exist = false)private String tempData;
}// 2. 字段填充策略
@Data
public class User {@TableIdprivate 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 {@TableIdprivate 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 {@TableIdprivate Long id;private String name;// 自定义删除值@TableLogic(value = "0", delval = "1")@TableField("is_deleted")private Integer isDeleted; // 0-正常,1-删除
}// 3. 使用字符串标志
@Data
public class User {@TableIdprivate 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 {@Autowiredprivate 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 {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
}
乐观锁使用示例:
@Service
public class UserService {@Autowiredprivate 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 = 1User user2 = userMapper.selectById(userId); // version = 1// 用户1先更新user1.setName("用户1修改");int result1 = userMapper.updateById(user1); // 成功,version变为2System.out.println("用户1更新结果:" + (result1 > 0 ? "成功" : "失败"));// 用户2后更新(此时版本号已经不匹配)user2.setName("用户2修改");int result2 = userMapper.updateById(user2); // 失败,version仍为1System.out.println("用户2更新结果:" + (result2 > 0 ? "成功" : "失败"));}
}
高级乐观锁应用:
// 1. 自定义乐观锁重试机制
@Service
public class UserService {@Autowiredprivate 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 {@Autowiredprivate 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 {@Overridepublic 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);}@Overridepublic 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 {@Overridepublic 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();}@Overridepublic 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;
}