MyBatisPlus高阶技巧与性能优化实战 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 MapString, 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 BaseTypeHandlerJSONObject { 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 ListUser 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 查询结果流式处理处理海量数据时避免OOMtry (CursorUser 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并设置合理的过期时间。