从XML到注解:MyBatis-Plus如何用‘偷懒’哲学重构你的数据层代码?
·
从XML到注解:MyBatis-Plus如何用‘偷懒’哲学重构你的数据层代码?
第一次在项目中引入MyBatis-Plus时,我盯着屏幕上不到10行的CRUD代码发了半小时呆——这和我过去五年用原生MyBatis写XML配置的体验形成了魔幻对比。当同事演示用Lambda表达式构造动态查询时,我甚至怀疑这是不是某种黑魔法。这种冲击不亚于当年从JDBC过渡到Hibernate时的认知颠覆。
1. 两种哲学的对撞:工匠精神 vs 效率革命
传统MyBatis像瑞士钟表匠,追求每个齿轮的精确咬合。在电商订单模块中,一个带多表关联的分页查询往往需要:
<!-- OrderMapper.xml -->
<select id="selectOrderWithDetails" resultMap="orderResultMap">
SELECT o.*, d.*
FROM orders o
LEFT JOIN order_details d ON o.id = d.order_id
<where>
<if test="userId != null">
o.user_id = #{userId}
</if>
<if test="status != null">
AND o.status = #{status}
</if>
</where>
LIMIT #{offset}, #{pageSize}
</select>
<resultMap id="orderResultMap" type="Order">
<id property="id" column="id"/>
<collection property="details" ofType="OrderDetail">
<id property="id" column="d_id"/>
</collection>
</resultMap>
而MyBatis-Plus则像现代工厂的自动化流水线。同样的功能,现在只需要:
// OrderMapper.java
public interface OrderMapper extends BaseMapper<Order> {}
// 业务代码
Page<Order> page = new Page<>(1, 10);
LambdaQueryWrapper<Order> wrapper = Wrappers.lambdaQuery(Order.class)
.eq(Order::getUserId, 123)
.eq(status != null, Order::getStatus, status)
.leftJoin(OrderDetail.class, OrderDetail::getOrderId, Order::getId);
orderMapper.selectJoinPage(page, Order.class, wrapper);
关键差异对比 :
| 维度 | MyBatis | MyBatis-Plus |
|---|---|---|
| 基础CRUD | 需手写XML/注解 | BaseMapper内置17种通用方法 |
| 动态SQL | XML标签或@Provider注解 | Wrapper条件构造器 |
| 分页实现 | 手动计算limit/offset | 内置分页插件 |
| 代码量 | 平均多3-5倍 | 极简接口继承 |
| 心智负担 | 需维护SQL与对象映射关系 | 专注业务逻辑 |
2. 偷懒的艺术:MyBatis-Plus核心武器库
2.1 BaseMapper:CRUD的终极抽象
继承这个接口就自动获得全套数据库操作能力,就像给Mapper插上翅膀:
User user = userMapper.selectById(1); // 替代:SELECT * FROM user WHERE id=1
List<User> users = userMapper.selectBatchIds(Arrays.asList(1,2,3));
userMapper.delete(Wrappers.<User>query()
.lambda().eq(User::getAge, 18)); // 替代:DELETE FROM user WHERE age=18
注意:虽然方法名是selectById,但实际支持任意主键字段名,无需额外配置
2.2 Wrapper条件构造器:动态SQL的优雅解法
告别XML中的 标签,用Java8的Lambda表达式构建类型安全的查询条件:
// 多条件动态查询
List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery()
.like(StringUtils.isNotBlank(name), User::getName, name)
.between(ageStart != null, User::getAge, ageStart, ageEnd)
.orderByDesc(User::getCreateTime));
// 联表查询示例
wrapper.inSql(User::getDepartmentId,
"SELECT id FROM department WHERE parent_id = 2");
2.3 代码生成器:消灭重复劳动
用这个工具可以一键生成:
- 实体类(含Lombok注解)
- Mapper接口(继承BaseMapper)
- Service层接口及实现
- Controller基础CRUD
// 代码生成器配置示例
AutoGenerator generator = new AutoGenerator();
generator.setGlobalConfig(buildGlobalConfig());
generator.setDataSource(buildDataSourceConfig());
generator.setPackageInfo(buildPackageConfig());
generator.setStrategy(buildStrategyConfig());
generator.execute(); // 执行生成
3. 实战进阶:当"偷懒"遇到复杂场景
3.1 多租户架构的优雅实现
通过TenantLineInnerInterceptor插件,只需配置租户ID字段和过滤条件:
public class MyTenantLineHandler implements TenantLineHandler {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new LongValue(1L); // 实际从上下文中获取
}
}
// 配置拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLineHandler()));
3.2 逻辑删除的自动化处理
全局配置逻辑删除字段后,所有delete操作自动变为update:
# application.yml
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 实体类字段名
logic-delete-value: 1 # 删除值
logic-not-delete-value: 0 # 未删除值
3.3 敏感字段加解密
通过TypeHandler实现数据落地前的自动加密:
@TableField(typeHandler = AESEncryptHandler.class)
private String mobile;
// 自定义TypeHandler
public class AESEncryptHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(...) {
// 加密逻辑
}
@Override
public String getNullableResult(...) {
// 解密逻辑
}
}
4. 理性看待:不是银弹但有惊喜
在微服务架构下,我们团队的实际度量数据:
- 开发效率 :简单CRUD提升300%,复杂查询提升50%
- 代码质量 :SQL错误减少70%(类型安全的Wrapper)
- 维护成本 :XML文件减少90%,新人上手时间缩短2/3
但需要注意:
- 极端复杂的SQL仍需XML辅助(约占5%场景)
- 批量操作需注意性能(推荐使用executeBatch)
- 多表join查询要合理设计索引
最近在重构库存系统时,原本需要2周的数据层开发,用MyBatis-Plus配合代码生成器3天就完成了核心模块。当看到分页查询从原来的200行XML变成现在的10行Java代码时,我确信这已不仅是工具升级,更是开发范式的进化。
更多推荐


所有评论(0)