Loading...
Loading...
MyBatis-Plus 增强 ORM 技能。覆盖 LambdaQueryWrapper vs QueryWrapper选择(始终Lambda)、分页插件(PaginationInnerInterceptor)配置、乐观锁(@Version + OptimisticLockerInnerInterceptor)、逻辑删除(@TableLogic 全局/局部配置)、自动填充(@TableField fill + MetaObjectHandler)、ActiveRecord vs Mapper模式选择、防全表更新(BlockAttackInnerInterceptor)、通用PageQuery抽取。 当用户使用 MyBatis-Plus 进行数据库操作、配置分页乐观锁、选择查询方式时使用。 与 mybatis 技能互补:mybatis 侧重XML/ResultMap/SQL写法,mybatis-plus 侧重增强功能。
npx skill4agent add full-stack-skills/java-skills mybatis-plus-patterns@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // ← 推荐
return interceptor;
}
}mybatis-plus:
global-config:
db-config:
id-type: auto # 主键自增
logic-delete-field: deleted # 逻辑删除字段
logic-delete-value: 1
logic-not-delete-value: 0
insert-strategy: not_null
update-strategy: not_null
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境| # | 错误 | 正确做法 |
|---|---|---|
| 1 | | |
| 2 | 只用 PageHelper 不分页 | 用 IPage + PaginationInnerInterceptor |
| 3 | 不配置乐观锁导致并发覆盖 | @Version + OptimisticLockerInnerInterceptor |
| 4 | | @TableLogic 自动转 UPDATE |
| 5 | 不知道 ActiveRecord 模式 | extends Model<T> → entity.insert/updateById/selectById |
| 6 | 不防全表更新/删除 | BlockAttackInnerInterceptor 阻止无 where 的 UPDATE/DELETE |
| 7 | 分页查询手动计算 total/pages | Page 自动封装 total, pages, records |
| 8 | createTime/updateTime 手动设置 | MetaObjectHandler 自动填充 |
| 9 | Wrapper 用字符串字段写死 | Wrappers.lambdaQuery() + LambdaQueryWrapper |
// ✅ 推荐:LambdaQueryWrapper(类型安全)
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.eq(User::getUsername, "张三")
.ge(User::getAge, 18)
.in(User::getStatus, List.of(1, 2))
.orderByDesc(User::getCreateTime)
.last("limit 10"); // 慎用 last——与分页冲突
List<User> list = userMapper.selectList(wrapper);
// ✅ 简化:lambdaQuery() 链式调用
List<User> adults = userService.lambdaQuery()
.ge(User::getAge, 18)
.in(User::getStatus, Arrays.asList(1, 2))
.orderByDesc(User::getCreateTime)
.list();
// ✅ 更新:lambdaUpdate()
userService.lambdaUpdate()
.eq(User::getUsername, "张三")
.set(User::getEmail, "new@test.com")
.update();
// ❌ 避免:QueryWrapper(字符串硬编码,重构时不易发现)
// QueryWrapper<User> wrapper = new QueryWrapper<>();
// wrapper.eq("user_name", "张三"); // 字段名写死,重构时断裂// 通用分页查询参数
@Data
public class PageQuery {
@Min(1) private Integer pageNo = 1;
@Min(1) @Max(5000) private Integer pageSize = 10;
private String sortBy;
private boolean isAsc = true;
public <T> Page<T> toPage() {
Page<T> page = Page.of(pageNo, pageSize);
if (StrUtil.isNotBlank(sortBy)) {
page.addOrder(new OrderItem(sortBy, isAsc));
}
return page;
}
}
// 通用分页返回
@Data
public class PageDTO<T> {
private Long total;
private Long pages;
private List<T> list;
public static <T> PageDTO<T> of(IPage<T> page) {
PageDTO<T> dto = new PageDTO<>();
dto.setTotal(page.getTotal());
dto.setPages(page.getPages());
dto.setList(page.getRecords());
return dto;
}
}
// 使用
public PageDTO<User> queryUsers(PageQuery query) {
Page<User> page = userService.page(query.toPage(),
Wrappers.lambdaQuery<User>().ge(User::getAge, 18));
return PageDTO.of(page);
}@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String email;
@Version private Integer version; // ← 乐观锁(更新时自动+1)
@TableLogic private Integer deleted; // ← 逻辑删除(0=正常, 1=删除)
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime; // ← 插入时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime; // ← 更新时自动填充
}
// 自动填充处理器
@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());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}// ActiveRecord: Entity extends Model<T>
@Data
@EqualsAndHashCode(callSuper = true) // ← 必须加
@TableName("sys_user")
public class User extends Model<User> {
@TableId private Long id;
private String username;
}
// 使用
User user = new User();
user.setUsername("张三");
user.insert(); // 直接操作
User found = user.selectById(1L);
user.setEmail("new@test.com");
user.updateById();
user.deleteById();
// Mapper 模式: 独立 Mapper 接口 + IService
public interface UserMapper extends BaseMapper<User> {}
public interface UserService extends IService<User> {}
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
// 使用
userService.save(user);
userService.getById(1L);
userService.lambdaQuery().eq(User::getUsername, "张三").list();// 配置 BlockAttackInnerInterceptor 后:
userService.update(new User().setEmail("all@test.com")); // ❌ 抛出异常
// 必须加 where 条件
userService.lambdaUpdate()
.eq(User::getStatus, "ACTIVE")
.set(User::getEmail, "all@test.com")
.update(); // ✅ 安全selectById → setXxx → updateByIdAND deleted = 0eq(User::getDeleted, 1)