MyBatis-Plus

MyBatis-Plus(MP)是 MyBatis 的增强工具,在不修改原有代码的基础上提供:内置 CRUD条件构造器代码生成器分页插件乐观锁逻辑删除等能力,大幅减少样板代码。


依赖配置

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.7</version>
</dependency>
# application.yml
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.domain
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 开发时打印 SQL
  global-config:
    db-config:
      id-type: auto               # 主键策略:auto / assign_id / input
      logic-delete-field: deleted # 逻辑删除字段名
      logic-delete-value: 1
      logic-not-delete-value: 0
      update-strategy: not_null   # 更新时忽略 null 字段

一、实体定义

@Data
@TableName("orders")
public class Order {
 
    @TableId(type = IdType.AUTO)
    private Long id;
 
    @TableField("order_no")
    private String orderNo;
 
    private BigDecimal totalAmount;
 
    private OrderStatus status;
 
    private Long userId;
 
    @TableField(fill = FieldFill.INSERT)          // 插入时自动填充
    private LocalDateTime createdAt;
 
    @TableField(fill = FieldFill.INSERT_UPDATE)   // 插入和更新时自动填充
    private LocalDateTime updatedAt;
 
    @Version                                       // 乐观锁版本号
    private Integer version;
 
    @TableLogic                                    // 逻辑删除标记
    private Integer deleted;
 
    @TableField(exist = false)                     // 非数据库字段
    private String userNickname;
}

自动填充处理器

@Component
public class MetaObjectHandler implements com.baomidou.mybatisplus.core.handlers.MetaObjectHandler {
 
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
    }
 
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
    }
}

二、内置 CRUD

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
    // 继承 BaseMapper 即获得全套 CRUD,无需编写任何方法
}
 
@Service
@RequiredArgsConstructor
public class OrderService extends ServiceImpl<OrderMapper, Order> implements IService<Order> {
 
    // ServiceImpl 提供更丰富的批量操作和事务方法
    // 也可以不继承,直接注入 OrderMapper 使用
 
    public void demo() {
        // 插入
        Order order = new Order();
        order.setOrderNo("ORD-001");
        save(order);                // INSERT,回填 id
 
        // 查询
        Order found = getById(1L);
        List<Order> all = list();
 
        // 更新(只更新非 null 字段)
        Order patch = new Order();
        patch.setId(1L);
        patch.setStatus(OrderStatus.CONFIRMED);
        updateById(patch);
 
        // 删除(逻辑删除时更新 deleted=1,物理删除时 DELETE)
        removeById(1L);
 
        // 批量操作(分批执行,默认 1000 条/批)
        saveBatch(orders);
        updateBatchById(orders);
    }
}

三、条件构造器

QueryWrapper

@Service
@RequiredArgsConstructor
public class OrderQueryService {
 
    private final OrderMapper orderMapper;
 
    public List<Order> findConfirmedOrders(Long userId, LocalDateTime since) {
        QueryWrapper<Order> wrapper = new QueryWrapper<Order>()
            .eq("user_id", userId)
            .eq("status", "CONFIRMED")
            .ge("created_at", since)
            .orderByDesc("created_at")
            .last("LIMIT 100");   // 直接拼接 SQL 末尾(谨慎使用)
 
        return orderMapper.selectList(wrapper);
    }
 
    public Long countPending() {
        return orderMapper.selectCount(
            new QueryWrapper<Order>().eq("status", "PENDING")
        );
    }
}

LambdaQueryWrapper(推荐,避免硬编码列名)

public Page<Order> searchOrders(OrderQuery query, int pageNum, int pageSize) {
 
    LambdaQueryWrapper<Order> wrapper = Wrappers.<Order>lambdaQuery()
        // 条件仅在参数非空时拼入(第一个参数为 condition)
        .eq(query.getUserId() != null,   Order::getUserId,    query.getUserId())
        .eq(query.getStatus() != null,   Order::getStatus,    query.getStatus())
        .ge(query.getStartDate() != null, Order::getCreatedAt, query.getStartDate())
        .le(query.getEndDate() != null,   Order::getCreatedAt, query.getEndDate())
        .like(StringUtils.hasText(query.getOrderNo()), Order::getOrderNo, query.getOrderNo())
        .orderByDesc(Order::getCreatedAt);
 
    return orderMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
}

LambdaUpdateWrapper

// 批量更新:将指定用户的所有待支付订单标记为已取消
int count = orderMapper.update(null,
    Wrappers.<Order>lambdaUpdate()
        .set(Order::getStatus,    OrderStatus.CANCELLED)
        .set(Order::getUpdatedAt, LocalDateTime.now())
        .eq(Order::getUserId,     userId)
        .eq(Order::getStatus,     OrderStatus.PENDING)
);

四、分页插件

@Configuration
public class MybatisPlusConfig {
 
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件(必须指定数据库类型)
        interceptor.addInnerInterceptor(
            new PaginationInnerInterceptor(DbType.POSTGRESQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 防全表更新/删除(生产安全措施)
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}
// Service 层分页查询
public IPage<OrderVO> pageOrders(int pageNum, int pageSize) {
    Page<Order> page = new Page<>(pageNum, pageSize);
    // 第二个参数可传 Wrapper 做条件过滤
    IPage<Order> orderPage = orderMapper.selectPage(page, null);
 
    // 转换为 VO
    return orderPage.convert(order -> {
        OrderVO vo = new OrderVO();
        BeanUtils.copyProperties(order, vo);
        return vo;
    });
}

五、乐观锁

// 实体类加 @Version(见实体定义)
// 更新时 MP 自动追加 AND version = #{version} 并将 version+1
 
Order order = orderMapper.selectById(id);
order.setStatus(OrderStatus.CONFIRMED);
int rows = orderMapper.updateById(order);
if (rows == 0) {
    throw new BusinessException("订单已被其他操作修改,请刷新重试");
}

六、逻辑删除

配置 logic-delete-field 后,deleteById 自动改为 UPDATE deleted=1selectList 自动追加 WHERE deleted=0

// 逻辑删除(实际执行 UPDATE orders SET deleted=1 WHERE id=1)
orderMapper.deleteById(1L);
 
// 真实物理删除(绕过逻辑删除):需在 Mapper 中自定义 SQL
@Delete("DELETE FROM orders WHERE id = #{id}")
int physicalDelete(Long id);
 
// 查询时包含已删除记录
QueryWrapper<Order> wrapper = new QueryWrapper<Order>()
    .eq("id", id);
wrapper.isNotNull("deleted");   // 不自动过滤
// 或直接用 baseMapper.selectMaps(wrapper) + 自定义 SQL

七、代码生成器

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.7</version>
    <scope>test</scope>   <!-- 仅开发时使用 -->
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
    <scope>test</scope>
</dependency>
@Test
void generate() {
    FastAutoGenerator.create("jdbc:postgresql://localhost:5432/mydb", "postgres", "secret")
        .globalConfig(builder -> builder
            .author("dev")
            .outputDir("src/main/java")
            .disableOpenDir())
        .packageConfig(builder -> builder
            .parent("com.example")
            .entity("domain")
            .mapper("mapper")
            .service("service")
            .serviceImpl("service.impl")
            .xml("mapper"))
        .strategyConfig(builder -> builder
            .addInclude("orders", "order_items", "users")  // 指定表
            .entityBuilder()
                .enableLombok()
                .enableTableFieldAnnotation()
                .logicDeleteColumnName("deleted")
                .versionColumnName("version")
            .mapperBuilder()
                .enableBaseResultMap()
            .serviceBuilder()
                .formatServiceFileName("%sService"))
        .execute();
}

八、多租户插件

@Configuration
public class MybatisPlusConfig {
 
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(
            new TenantLineHandler() {
                @Override
                public Expression getTenantId() {
                    // 从 ThreadLocal / SecurityContext 获取当前租户 ID
                    Long tenantId = TenantContext.getCurrentTenantId();
                    return new LongValue(tenantId);
                }
 
                @Override
                public String getTenantIdColumn() {
                    return "tenant_id";
                }
 
                // 不需要租户隔离的表
                @Override
                public boolean ignoreTable(String tableName) {
                    return Set.of("sys_config", "sys_dict").contains(tableName);
                }
            }
        ));
        return interceptor;
    }
}

九、自定义 SQL + Wrapper

在 XML 中使用 Wrapper 传入动态条件,同时手写复杂 JOIN:

// Mapper 接口
List<OrderVO> findWithUser(@Param(Constants.WRAPPER) Wrapper<Order> wrapper);
<!-- OrderMapper.xml -->
<select id="findWithUser" resultType="OrderVO">
    SELECT o.*, u.name AS user_name, u.email AS user_email
    FROM orders o
    LEFT JOIN users u ON o.user_id = u.id
    ${ew.customSqlSegment}
</select>
// 调用
List<OrderVO> list = orderMapper.findWithUser(
    Wrappers.<Order>lambdaQuery()
        .eq(Order::getStatus, OrderStatus.CONFIRMED)
        .ge(Order::getCreatedAt, since)
);

MP vs MyBatis 选择

场景推荐
简单 CRUD、单表操作MyBatis-Plus
多表复杂 JOIN、报表 SQLMyBatis(XML)
动态条件查询LambdaQueryWrapper
存储过程、批量 ETLMyBatis 原生
多租户、乐观锁、逻辑删除MyBatis-Plus 插件

相关链接