MyBatis-Plus 笔记
1. 主键生成策略
在 MyBatis-Plus 中,主键 ID 生成策略由 IdType
枚举类控制,常见的 ID 生成策略如下:
public enum IdType {
AUTO(0), // 数据库ID自增
NONE(1), // 未设置主键类型
INPUT(2), // 手动输入ID
ASSIGN_ID(3), // 默认使用雪花算法生成ID
ASSIGN_UUID(4); // UUID
private final int key;
IdType(int key) {
this.key = key;
}
}
默认策略说明
AUTO
:数据库 ID 自增,适用于 MySQL 主键自增。ASSIGN_ID
(默认):MyBatis-Plus 提供的雪花算法(Snowflake)自动生成唯一 ID,适用于分布式系统。ASSIGN_UUID
:使用 UUID 作为主键。- 默认 ID 生成策略时,数据库表的主键 不需要设置自增。
2. 字段自动填充
1. 在实体类字段上添加注解
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID) // 主键 ID 采用 MP 默认的雪花算法
private Long id;
private String name;
private String phone;
@TableField(fill = FieldFill.INSERT) // 仅在插入时填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时都会填充
private LocalDateTime updateTime;
}
2. 自定义 MetaObjectHandler
处理填充逻辑
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时填充(createTime、updateTime)
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
// 更新时填充(仅 updateTime)
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
说明
strictInsertFill
→ 只有null
时才填充字段,避免手动赋值被覆盖。strictUpdateFill
→ 仅在update
时填充updateTime
,不会影响createTime
。LocalDateTime.now()
→ 采用当前系统时间。
3. 乐观锁机制
乐观锁原理
- 乐观锁假设不会发生并发冲突,仅在更新数据时进行版本号校验。
- 具体实现:
- 读取数据时获取当前
version
。 - 更新时,带上
version
作为条件。 UPDATE table SET ... WHERE id = ? AND version = ?
,如果version
仍然匹配,则更新并version+1
。- 若
version
发生变化,说明数据被修改,更新失败。
- 读取数据时获取当前
数据库表添加 version
字段
ALTER TABLE user ADD COLUMN version INT DEFAULT 1;
实体类增加 version
字段
import com.baomidou.mybatisplus.annotation.Version;
public class User {
@Version
private Integer version;
}
MyBatis-Plus 乐观锁插件
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试多线程环境下的乐观锁
@Test
public void testOptimisticLock(){
// 线程 1
User user1 = userMapper.selectById(1L);
user1.setName("zhangshan");
userMapper.updateById(user1);
// 线程 2
User user2 = userMapper.selectById(1L);
user2.setName("lisi");
userMapper.updateById(user2);
}
加锁结果:
- 加锁后:第二个线程更新
version+1
,第一个线程因version
不匹配更新失败。 - 不加锁:线程 1 最后执行,覆盖了线程 2 更新的数据。
4. MyBatis-Plus 常用插件
插件配置
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // 防止全表更新或删除
interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor()); // 防止 SQL 注入
return interceptor;
}
}
5. P6Spy 日志格式化
spy.properties
配置
logMessageFormat=warren.reggie.common.P6SpyLogger
appender=com.p6spy.engine.spy.appender.StdoutLogger
dateformat=yyyy-MM-dd HH:mm:ss
outagedetection=true
outagedetectioninterval=2
自定义日志格式
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class P6SpyLogger implements MessageFormattingStrategy {
@Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
if (sql == null || sql.trim().isEmpty()) return "";
String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return String.format("[%s] 耗时: %d ms | 连接: %d | 分类: %s\n执行 SQL: %s;\n",
currentTime, elapsed, connectionId, category, sql.trim());
}
}