Source:https://baomidou.com/
GitHub:https://github.com/baomidou/mybatis-plus
Capability Boundaries
✅ Strong Suits
- LambdaQueryWrapper — type-safe queries (no need to write string field names)
- Pagination plugin — PaginationInnerInterceptor configuration and usage
- Optimistic locking — @Version + OptimisticLockerInnerInterceptor
- Logical deletion — @TableLogic global configuration vs single-table configuration
- Auto-fill — @TableField(fill) + MetaObjectHandler implementation
- ActiveRecord vs Mapper — Entity extends Model vs standalone Mapper interface selection
- Full-table operation prevention — BlockAttackInnerInterceptor
- Generic pagination encapsulation — PageQuery → PageDTO generic pattern
❌ Out of Scope
- MyBatis XML/ResultMap/dynamic SQL → mybatis-patterns
- MyBatis-Plus Generator code generation → existing mybatis-plus-generator skill
- MyBatis-Plus data permissions → plugin system, not a common LLM requirement
Minimal configuration
@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 # 开发环境
Most common mistakes LLMs make
| # | Mistake | Correct approach |
|---|---|---|
| 1 | eq("user_name", username) string field names |
eq(User::getUsername, username) Lambda type safety |
| 2 | Using only PageHelper without pagination | Use IPage + PaginationInnerInterceptor |
| 3 | Not configuring optimistic locking causes concurrent overwrites | @Version + OptimisticLockerInnerInterceptor |
| 4 | UPDATE SET is_deleted = 1 manual deletion |
@TableLogic automatically converts to UPDATE |
| 5 | Unaware of ActiveRecord mode | extends Model → entity.insert/updateById/selectById |
| 6 | No protection against full-table update/delete | BlockAttackInnerInterceptor blocks UPDATE/DELETE without where |
| 7 | Manually calculating total/pages for paginated queries | Page automatically encapsulates total, pages, records |
| 8 | Manually setting createTime/updateTime | MetaObjectHandler auto-fill |
| 9 | Wrapper hardcodes string field names | Wrappers.lambdaQuery() + LambdaQueryWrapper |
Core patterns
Pattern 1: LambdaQueryWrapper (always use Lambda)
// ✅ 推荐: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", "张三"); // 字段名写死,重构时断裂
Pattern 2: Generic pagination encapsulation
// 通用分页查询参数
@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);
}
Pattern 3: Optimistic locking + logical deletion + auto-fill Entity
@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());
}
}
Pattern 4: ActiveRecord vs Mapper comparison
// 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();
Pattern 5: Prevent full-table update/delete
// 配置 BlockAttackInnerInterceptor 后:
userService.update(new User().setEmail("all@test.com")); // ❌ 抛出异常
// 必须加 where 条件
userService.lambdaUpdate()
.eq(User::getStatus, "ACTIVE")
.set(User::getEmail, "all@test.com")
.update(); // ✅ 安全
Gotchas
- LambdaQueryWrapper requires an annotation processor to generate metadata — otherwise Lambda expressions cannot resolve field names
- Optimistic locking @Version field types support int/Integer/long/Long/Date/LocalDateTime/Timestamp
- Optimistic locking must query first, then update —
selectById → setXxx → updateById, otherwise version is null and has no effect - @TableLogic affects all delete operations globally — deleteById/deleteBatchIds all become UPDATE
- Custom SQL does not automatically add logical deletion conditions — handwritten SQL requires manual concatenation of
AND deleted = 0 - BlockAttackInnerInterceptor blocks full-table updates — recommended to enable during development, as needed in production
- The pagination plugin may conflict when PageHelper is present — choose one, MyBatis-Plus pagination is recommended
- In ActiveRecord mode the Entity must extends Model — otherwise there are no insert/updateById and other methods
- Queries after logical deletion automatically include deleted=0 — If you need to query deleted data, manually
eq(User::getDeleted, 1) - The auto-fill MetaObjectHandler must be a Spring Bean (@Component) — otherwise it will not take effect
- strictInsertFill/strictUpdateFill require exact field type matching — newer MyBatis-Plus versions recommend using the strict methods
- The Page constructor parameters are pageNo and pageSize — not offset and limit
Data Privacy
This skill does not collect, store, or transmit any user data.