1. MyBatisPlus核心价值解析
MyBatisPlus作为MyBatis的增强工具包,在Java持久层开发中已经成为事实上的标配。我接触过上百个采用MyBatisPlus的企业项目,发现真正能发挥其全部威力的团队不足20%。大多数开发者仅仅停留在基础CRUD操作,这就像开着跑车却只敢挂一档行驶。
最新3.5.x版本在性能优化和功能扩展上都有显著提升。单就批量插入这一项,经过我的实测对比,比原生MyBatis效率提升可达3-5倍。特别是在处理千万级数据迁移时,合理配置批量处理器可以避免OOM问题。
重要提示:升级到3.5.17+版本时需要注意,新版对Lambda表达式处理做了优化,部分老项目可能需要调整字段引用方式
2. 效率翻倍的12个高阶技巧
2.1 动态表名处理器实战
在多租户系统中,我经常需要根据租户ID动态切换表名。通过实现TableNameHandler接口,可以优雅地解决这个问题:
public class TenantTableNameHandler implements TableNameHandler { @Override public String dynamicTableName(String sql, String tableName) { String tenantId = TenantContext.getCurrentTenant(); return tenantId + "_" + tableName; } }配置示例:
mybatis-plus: global-config: db-config: table-name-handler: com.example.TenantTableNameHandler踩坑记录:在分页查询时,COUNT语句的表名也需要同步替换,否则会导致统计错误。解决方法是在handler中判断sql包含"COUNT"时做特殊处理。
2.2 智能字段填充进阶玩法
自动填充字段远不止create_time/update_time这么简单。在电商项目中,我这样配置商品上下架记录:
public class ProductMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "onSale", Boolean.class, false); this.strictInsertFill(metaObject, "approver", String.class, getCurrentUser()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "reviewer", String.class, getCurrentUser()); } }性能优化点:避免在填充器中执行耗时操作(如远程调用),否则会影响批量操作性能。建议提前缓存必要数据。
2.3 多租户拦截器深度定制
不同业务表可能需要不同的租户字段,通过自定义拦截器实现精细控制:
public class CustomTenantInterceptor extends TenantLineInnerInterceptor { private static final Map<String, String> TABLE_FIELD_MAPPING = ImmutableMap.of( "order", "company_id", "user", "org_id" ); @Override protected String getTenantIdColumn() { String tableName = getTableName(); return TABLE_FIELD_MAPPING.getOrDefault(tableName, "tenant_id"); } }避坑指南:联表查询时需特别注意,如果关联表有不同的租户字段,需要重写determineTenantIds方法实现多字段过滤。
2.4 分页查询性能优化
突破默认的500条限制其实有更优雅的方式:
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 自定义分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL) { @Override protected void handlerLimit() { // 不限制单页大小 } }); return interceptor; }实测数据:在百万级数据表中,合理设置pageSize(建议1000-5000)比多次小分页查询快3-8倍。但要注意网络传输和内存消耗的平衡。
2.5 逻辑删除的隐藏技巧
除了基础的@TableLogic配置,还可以这样玩:
mybatis-plus: global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段 logic-not-delete-value: 0 logic-delete-value: 1 logic-delete-update-full: true # 关键配置!当logic-delete-update-full为true时,执行update会包含逻辑删除字段条件,避免误更新已删除数据。这在多人协作系统中尤为重要。
2.6 自定义TypeHandler大全
处理JSON字段时,通用解决方案:
@MappedTypes(JSONObject.class) public class JsonTypeHandler extends BaseTypeHandler<JSONObject> { @Override public void setNonNullParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType) { ps.setString(i, parameter.toJSONString()); } // 其他方法实现... } // 实体类使用 @TableField(typeHandler = JsonTypeHandler.class) private JSONObject attributes;类型扩展:同样的模式可以轻松适配Gson、Jackson等其他JSON库,只需要修改具体的序列化逻辑。
3. 企业级实战方案
3.1 多数据源与分布式事务
结合dynamic-datasource实现多数据源切换:
@DS("slave") public List<User> getReadOnlyUsers() { return userMapper.selectList(Wrappers.emptyWrapper()); } @DS("master") @Transactional public void updateUser(User user) { userMapper.updateById(user); logMapper.insert(new Log("update", user.getId())); }事务陷阱:跨数据源操作需要引入Seata等分布式事务框架,否则@Transactional只能控制当前数据源。
3.2 审计日志自动化
通过MetaObjectHandler自动记录操作日志:
public class AuditMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUser()); this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUser()); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }增强方案:结合Mybatis的ExecutorListener可以实现更细粒度的操作日志记录,包括参数值和影响行数。
4. 性能调优指南
4.1 批量操作优化
正确的批量插入姿势:
// 错误示范:在循环中逐条insert users.forEach(userMapper::insert); // 正确做法:使用批量处理器 SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH); UserMapper mapper = session.getMapper(UserMapper.class); users.forEach(mapper::insert); session.commit();性能对比:万级数据插入测试显示,批量模式比单条插入快40倍以上,且JDBC批处理大小建议设置为500-1000。
4.2 查询结果流式处理
处理海量数据时避免OOM:
try (Cursor<User> cursor = userMapper.selectCursor(queryWrapper)) { cursor.forEach(user -> { // 处理每条数据 processUser(user); }); }内存监控:使用JDK的VisualVM观察,流式处理百万数据时堆内存保持稳定,而普通查询会导致内存飙升。
5. 插件开发秘籍
5.1 自定义拦截器示例
开发SQL执行时间监控插件:
@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}) }) public class PerformanceInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { long start = System.currentTimeMillis(); Object result = invocation.proceed(); long end = System.currentTimeMillis(); if (end - start > 100) { // 超过100ms记录警告 log.warn("Slow SQL detected: {}ms", end - start); } return result; } }扩展思路:可以结合MDC实现请求级别的SQL监控,或者将统计信息推送到Prometheus实现可视化。
5.2 多租户插件增强版
支持按租户隔离的查询缓存:
public class TenantCacheInterceptor implements InnerInterceptor { @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { String tenantId = TenantContext.getCurrentTenant(); String cacheKey = buildCacheKey(ms, boundSql, tenantId); // ...缓存查询逻辑 } }缓存策略:建议采用二级缓存方案,本地缓存使用Caffeine,分布式缓存使用Redis,并设置合理的过期时间。