SuperAPI AI Directory

mybatis-plus-patterns

General & other

View repository

Updated 10-15 21:48

About this skill

MyBatis-Plus enhanced ORM

Source:https://baomidou.com/
GitHub:https://github.com/baomidou/mybatis-plus

Capability Boundaries

✅ Strong Suits

  1. LambdaQueryWrapper — type-safe queries (no need to write string field names)
  2. Pagination plugin — PaginationInnerInterceptor configuration and usage
  3. Optimistic locking — @Version + OptimisticLockerInnerInterceptor
  4. Logical deletion — @TableLogic global configuration vs single-table configuration
  5. Auto-fill — @TableField(fill) + MetaObjectHandler implementation
  6. ActiveRecord vs Mapper — Entity extends Model vs standalone Mapper interface selection
  7. Full-table operation prevention — BlockAttackInnerInterceptor
  8. Generic pagination encapsulation — PageQuery → PageDTO generic pattern

❌ Out of Scope

  1. MyBatis XML/ResultMap/dynamic SQL → mybatis-patterns
  2. MyBatis-Plus Generator code generation → existing mybatis-plus-generator skill
  3. 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

  1. LambdaQueryWrapper requires an annotation processor to generate metadata — otherwise Lambda expressions cannot resolve field names
  2. Optimistic locking @Version field types support int/Integer/long/Long/Date/LocalDateTime/Timestamp
  3. Optimistic locking must query first, then update — selectById → setXxx → updateById, otherwise version is null and has no effect
  4. @TableLogic affects all delete operations globally — deleteById/deleteBatchIds all become UPDATE
  5. Custom SQL does not automatically add logical deletion conditions — handwritten SQL requires manual concatenation of AND deleted = 0
  6. BlockAttackInnerInterceptor blocks full-table updates — recommended to enable during development, as needed in production
  7. The pagination plugin may conflict when PageHelper is present — choose one, MyBatis-Plus pagination is recommended
  8. In ActiveRecord mode the Entity must extends Model — otherwise there are no insert/updateById and other methods
  9. Queries after logical deletion automatically include deleted=0 — If you need to query deleted data, manually eq(User::getDeleted, 1)
  10. The auto-fill MetaObjectHandler must be a Spring Bean (@Component) — otherwise it will not take effect
  11. strictInsertFill/strictUpdateFill require exact field type matching — newer MyBatis-Plus versions recommend using the strict methods
  12. 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.