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=1,selectList 自动追加 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、报表 SQL | MyBatis(XML) |
| 动态条件查询 | LambdaQueryWrapper |
| 存储过程、批量 ETL | MyBatis 原生 |
| 多租户、乐观锁、逻辑删除 | MyBatis-Plus 插件 |