跳到主要内容

MyBatis-Plus 使用指南

Personal Blog Backend 使用 MyBatis-Plus 3.5.14 作为 ORM 框架,简化数据库操作并提供丰富的增强功能。

🎯 简介

MyBatis-Plus (opens in a new tab) 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

核心特性:

  • 无侵入 - 只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小 - 启动即会自动注入基本 CRUD,性能基本无损耗
  • 强大的 CRUD 操作 - 内置通用 Mapper、Service,仅需少量配置即可实现单表大部分 CRUD
  • 支持 Lambda 形式调用 - 通过 Lambda 表达式,方便编写查询条件
  • 支持主键自动生成 - 支持 4 种主键策略(含分布式唯一 ID),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式 - 支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可
  • 支持自定义全局通用操作 - 支持全局通用方法注入(Write once, use anywhere)
  • 内置分页插件 - 基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 内置性能分析插件 - 可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能
  • 内置全局拦截插件 - 提供全表 delete、update 操作智能分析阻断,也可自定义拦截规则,预防误操作

🔧 项目配置

1. 依赖引入

blog-common/pom.xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.14</version>
</dependency>

2. application.yaml 配置

blog-application/src/main/resources/application.yaml
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml # Mapper XML 扫描路径
type-aliases-package: com.blog.**.entity # 实体别名包

global-config:
banner: false # 关闭启动 Banner
db-config:
id-type: assign_id # 主键策略(雪花算法生成Long类型ID)
logic-delete-field: isDeleted # 逻辑删除字段
logic-delete-value: 1 # 删除值
logic-not-delete-value: 0 # 未删除值

configuration:
map-underscore-to-camel-case: true # 开启驼峰命名转换
call-setters-on-nulls: true # NULL值也调用Setter
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl # SQL日志实现

3. 插件配置

项目配置了多个 MyBatis-Plus 插件以增强功能和安全性:

blog-application/src/main/java/com/blog/config/MybatisPlusConfig.java
@Slf4j
@Configuration
public class MybatisPlusConfig {

@Value("${spring.profiles.active:dev}")
private String activeProfile;

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

// 1. 防全表更新删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

// 2. 分页插件(完整配置)
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(500L); // 单页最大数量,防止恶意查询
paginationInterceptor.setOverflow(false); // 溢出总页数后是否处理(false=返回空)
paginationInterceptor.setOptimizeJoin(true); // 优化JOIN的COUNT SQL
interceptor.addInnerInterceptor(paginationInterceptor);

// 3. 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

// 开发/测试环境输出插件配置信息
if ("dev".equals(activeProfile) || "test".equals(activeProfile)) {
log.info("════════════════════════════════════════════════════════");
log.info("MyBatis-Plus插件配置(环境: {})", activeProfile);
log.info(" ✓ 防全表更新删除插件 - 已启用");
log.info(" ✓ 分页插件 - 已启用(最大单页:500条,优化JOIN:true)");
log.info(" ✓ 乐观锁插件 - 已启用");
log.info("════════════════════════════════════════════════════════");
}

return interceptor;
}
}

插件说明:

  • BlockAttackInnerInterceptor: 防止全表更新和删除操作,避免误操作
  • PaginationInnerInterceptor: 物理分页插件,自动处理分页逻辑
    • maxLimit: 限制单页最大数量为500条,防止恶意查询
    • overflow: 页码溢出时返回空结果
    • optimizeJoin: 优化JOIN查询的COUNT SQL性能
  • OptimisticLockerInnerInterceptor: 乐观锁插件,支持 @Version 注解

4. 自动填充配置

项目配置了字段自动填充处理器,自动填充审计字段(创建时间、更新时间、创建人、更新人):

blog-application/src/main/java/com/blog/handler/MyMetaObjectHandler.java
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {

// 字段名常量 - 避免硬编码,便于维护
private static final String FIELD_CREATE_TIME = "createTime";
private static final String FIELD_UPDATE_TIME = "updateTime";
private static final String FIELD_CREATE_BY = "createBy";
private static final String FIELD_UPDATE_BY = "updateBy";

@Override
public void insertFill(MetaObject metaObject) {
if (log.isDebugEnabled()) {
log.debug("自动填充[INSERT] - 实体: {}",
metaObject.getOriginalObject().getClass().getSimpleName());
}

// 填充时间字段(必填)
this.strictInsertFill(metaObject, FIELD_CREATE_TIME, LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, FIELD_UPDATE_TIME, LocalDateTime::now, LocalDateTime.class);

// 填充操作人ID(可选,需登录态)
Long currentUserId = getCurrentUserId();
if (currentUserId != null) {
this.strictInsertFill(metaObject, FIELD_CREATE_BY, () -> currentUserId, Long.class);
this.strictInsertFill(metaObject, FIELD_UPDATE_BY, () -> currentUserId, Long.class);
}
}

@Override
public void updateFill(MetaObject metaObject) {
if (log.isDebugEnabled()) {
log.debug("自动填充[UPDATE] - 实体: {}",
metaObject.getOriginalObject().getClass().getSimpleName());
}

// 填充更新时间(必填)
this.strictUpdateFill(metaObject, FIELD_UPDATE_TIME, LocalDateTime::now, LocalDateTime.class);

// 填充更新人ID(可选,需登录态)
Long currentUserId = getCurrentUserId();
if (currentUserId != null) {
this.strictUpdateFill(metaObject, FIELD_UPDATE_BY, () -> currentUserId, Long.class);
}
}

/**
* 获取当前登录用户ID
* 从Spring Security上下文中获取,失败时返回null
*/
private Long getCurrentUserId() {
try {
return SecurityUtils.getCurrentUserId();
} catch (Exception e) {
log.debug("无法获取当前用户ID: {} - 原因: {}",
e.getClass().getSimpleName(), e.getMessage());
return null;
}
}
}

优化要点:

  • ✅ 使用字段名常量,避免字符串硬编码
  • ✅ 支持createBy/updateBy自动填充(从Security上下文获取当前用户)
  • ✅ DEBUG级别日志,避免生产环境日志泛滥
  • ✅ 异常处理,非Web环境安全

注册配置:

blog-application/src/main/java/com/blog/config/MybatisPlusHandlerConfig.java
@Slf4j
@Configuration
public class MybatisPlusHandlerConfig {

@Bean
public MetaObjectHandler metaObjectHandler() {
log.info("MyBatis-Plus配置: MetaObjectHandler已注册(审计字段自动填充)");
return new MyMetaObjectHandler();
}

@Bean
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
return properties -> {
// 关闭 Banner
properties.getGlobalConfig().setBanner(false);
log.info("MyBatis-Plus配置: 启动Banner已关闭");

// 配置默认枚举处理器
MybatisPlusProperties.CoreConfiguration configuration = properties.getConfiguration();
if (configuration == null) {
configuration = new MybatisPlusProperties.CoreConfiguration();
properties.setConfiguration(configuration);
}
configuration.setDefaultEnumTypeHandler(EnumTypeHandler.class);
log.info("MyBatis-Plus配置: 默认枚举处理器 = EnumTypeHandler(存储name()值)");
};
}
}

📝 实体类定义

1. 基本注解

blog-system-service/src/main/java/com/blog/system/entity/SysUser.java
@Data
@TableName("sys_user") // 指定表名
public class SysUser {

/**
* 主键 - 使用雪花算法自动生成
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;

/**
* 用户名
*/
private String username;

/**
* 密码
*/
private String password;

/**
* 邮箱
*/
private String email;

/**
* 账户状态 (1-正常, 0-禁用)
*/
private Integer status;

/**
* 版本号 - 乐观锁字段
*/
@Version
private Integer version;

/**
* 创建时间 - 自动填充
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

/**
* 更新时间 - 自动填充
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

/**
* 逻辑删除标记 (0-未删, 1-已删)
*/
@TableLogic
private Integer isDeleted;

/**
* 备注
*/
private String remark;
}

2. 常用注解说明

注解作用示例
@TableName指定表名@TableName("sys_user")
@TableId指定主键及策略@TableId(type = IdType.ASSIGN_ID)
@TableField指定字段属性@TableField(fill = FieldFill.INSERT)
@Version乐观锁字段@Version
@TableLogic逻辑删除字段@TableLogic

主键策略 (IdType):

  • ASSIGN_ID: 雪花算法生成 Long 类型 ID(项目默认)
  • ASSIGN_UUID: UUID 生成
  • AUTO: 数据库自增
  • INPUT: 手动输入

🔨 Mapper 层开发

1. 基础 Mapper 定义

blog-system-service/src/main/java/com/blog/system/mapper/UserMapper.java
@Mapper
public interface UserMapper extends BaseMapper<SysUser> {

/**
* 根据用户ID查询用户的角色列表(自定义方法)
*/
List<SysRole> selectRolesByUserId(@Param("userId") Long userId);

/**
* 根据用户名查询用户
*/
SysUser selectByUsername(@Param("username") String username);

/**
* 批量查询用户
*/
List<SysUser> selectByIds(@Param("ids") List<Long> ids);

/**
* 检查用户名是否存在
*/
Boolean existsByUsername(@Param("username") String username);
}

2. BaseMapper 内置方法

继承 BaseMapper<T> 后自动拥有以下方法:

插入:

int insert(T entity);                    // 插入一条记录

删除:

int deleteById(Serializable id);         // 根据ID删除
int deleteByMap(Map<String, Object> columnMap); // 根据条件删除
int delete(Wrapper<T> wrapper); // 根据Wrapper删除
int deleteBatchIds(Collection<? extends Serializable> idList); // 批量删除

更新:

int updateById(T entity);                // 根据ID更新
int update(T entity, Wrapper<T> updateWrapper); // 根据Wrapper更新

查询:

T selectById(Serializable id);           // 根据ID查询
List<T> selectBatchIds(Collection<? extends Serializable> idList); // 批量查询
T selectOne(Wrapper<T> queryWrapper); // 查询一条记录
Long selectCount(Wrapper<T> queryWrapper); // 查询总记录数
List<T> selectList(Wrapper<T> queryWrapper); // 查询列表
List<Map<String, Object>> selectMaps(Wrapper<T> queryWrapper); // 查询列表(Map)
Page<T> selectPage(Page<T> page, Wrapper<T> queryWrapper); // 分页查询

💼 Service 层开发

1. 使用 IService 接口

public interface IUserService extends IService<SysUser> {
// 自定义业务方法
SysUser getUserByUsername(String username);
}

2. 实现类

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, SysUser>
implements IUserService {

@Override
public SysUser getUserByUsername(String username) {
return baseMapper.selectByUsername(username);
}
}

3. IService 常用方法

保存:

boolean save(T entity);                  // 插入一条记录
boolean saveBatch(Collection<T> entityList); // 批量插入
boolean saveOrUpdate(T entity); // 存在则更新,否则插入

删除:

boolean removeById(Serializable id);     // 根据ID删除
boolean removeByIds(Collection<? extends Serializable> idList); // 批量删除
boolean remove(Wrapper<T> queryWrapper); // 根据条件删除

更新:

boolean updateById(T entity);            // 根据ID更新
boolean updateBatchById(Collection<T> entityList); // 批量更新
boolean update(Wrapper<T> updateWrapper); // 根据条件更新

查询:

T getById(Serializable id);              // 根据ID查询
List<T> listByIds(Collection<? extends Serializable> idList); // 批量查询
List<T> list(Wrapper<T> queryWrapper); // 查询列表
Page<T> page(Page<T> page, Wrapper<T> queryWrapper); // 分页查询
long count(Wrapper<T> queryWrapper); // 查询总数

🔍 条件构造器

1. QueryWrapper 示例

// 查询用户名为"admin"且状态为1的用户
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", "admin")
.eq("status", 1);
List<SysUser> users = userMapper.selectList(queryWrapper);

// 模糊查询
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.like("username", "test")
.or()
.like("email", "test");
List<SysUser> list = userMapper.selectList(wrapper);

2. LambdaQueryWrapper 示例(推荐)

// Lambda方式 - 类型安全,避免字符串硬编码
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUsername, "admin")
.eq(SysUser::getStatus, 1);
List<SysUser> users = userMapper.selectList(wrapper);

// 动态条件
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(username), SysUser::getUsername, username)
.eq(status != null, SysUser::getStatus, status)
.ge(startTime != null, SysUser::getCreateTime, startTime);

3. UpdateWrapper 示例

// 更新用户状态
UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", userId)
.set("status", 0);
userMapper.update(null, updateWrapper);

// Lambda方式
LambdaUpdateWrapper<SysUser> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysUser::getId, userId)
.set(SysUser::getStatus, 0);
userMapper.update(null, wrapper);

📄 分页查询

1. 基本分页

// 创建分页对象
Page<SysUser> page = new Page<>(1, 10); // 第1页,每页10条

// 执行分页查询
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getStatus, 1);
Page<SysUser> resultPage = userMapper.selectPage(page, wrapper);

// 获取分页结果
List<SysUser> records = resultPage.getRecords(); // 数据列表
long total = resultPage.getTotal(); // 总记录数
long pages = resultPage.getPages(); // 总页数

2. Service层分页

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, SysUser>
implements IUserService {

public Page<SysUser> getUserPage(int current, int size, String keyword) {
Page<SysUser> page = new Page<>(current, size);

LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(keyword), SysUser::getUsername, keyword)
.eq(SysUser::getStatus, 1)
.orderByDesc(SysUser::getCreateTime);

return this.page(page, wrapper);
}
}

⚠️ 最佳实践

1. 优先使用 Lambda 方式

// ✅ 推荐:使用 Lambda,类型安全
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUsername, "admin");

// ❌ 不推荐:字符串硬编码,容易拼写错误
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.eq("username", "admin");

2. 合理使用逻辑删除

// 配置了 @TableLogic 后,以下操作自动变为逻辑删除
userMapper.deleteById(1L); // UPDATE sys_user SET is_deleted=1 WHERE id=1

// 查询时自动过滤已删除数据
userMapper.selectList(null); // SELECT * FROM sys_user WHERE is_deleted=0

3. 利用乐观锁防止并发问题

// 实体类中添加 @Version 注解
@Version
private Integer version;

// 更新时会自动比对版本号
SysUser user = userMapper.selectById(1L); // version=1
user.setUsername("newName");
userMapper.updateById(user);
// UPDATE sys_user SET username='newName', version=2 WHERE id=1 AND version=1

4. 防止全表更新和删除

// 配置了 BlockAttackInnerInterceptor 后,以下操作会被拦截
userMapper.delete(null); // ❌ 抛出异常,禁止全表删除
userMapper.update(entity, null); // ❌ 抛出异常,禁止全表更新

// 必须带条件
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getStatus, 0);
user Mapper.delete(wrapper); // ✅ 允许

5. 自动填充字段

// 实体类配置自动填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

// 插入和更新时自动填充,无需手动设置
SysUser user = new SysUser();
user.setUsername("test");
userMapper.insert(user); // createTime 和 updateTime 自动填充

📚 参考资源


提示: MyBatis-Plus 只做增强不做改变,可以无缝集成到现有项目中。