MyBatis-Plus 3.5.x核心功能与分库分表实战

📅 2026/7/21 5:43:03
MyBatis-Plus 3.5.x核心功能与分库分表实战
1. MyBatis-Plus 3.5.x核心价值解析作为Java持久层框架的增强工具MyBatis-Plus 3.5.x版本在简化开发流程方面做出了显著改进。我在实际项目中使用这个版本近半年最直观的感受是它让单表CRUD操作变得异常简单。通过继承BaseMapper接口开发者可以直接获得18种常用数据操作方法无需编写任何XML映射文件。重要提示3.5.x版本对Lambda表达式查询进行了强化现在可以完全避免字段名的硬编码。比如查询年龄大于25的用户可以这样写userMapper.selectList(Wrappers.UserlambdaQuery() .gt(User::getAge, 25));这个版本还引入了全新的SQL注入器机制使得自定义全局方法变得更加灵活。我曾为项目定制过批量插入的通用方法相比原生MyBatis节省了70%的重复代码量。2. 动态表名实战按月分表示例2.1 分表场景构建在处理电商平台的交易流水表时单表数据量在3个月内就突破了2000万条查询性能明显下降。我们最终决定采用按月分表的方案每月生成形如t_order_202301的新表。关键配置步骤如下创建动态表名处理器public class MonthTableNameHandler implements TableNameHandler { private static final ThreadLocalString MONTH_SUFFIX new ThreadLocal(); Override public String dynamicTableName(String sql, String tableName) { return tableName _ Optional.ofNullable(MONTH_SUFFIX.get()) .orElseGet(() - LocalDate.now().format(DateTimeFormatter.ofPattern(yyyyMM))); } public static void setMonth(String month) { MONTH_SUFFIX.set(month); } public static void clear() { MONTH_SUFFIX.remove(); } }2.2 拦截器配置技巧在配置动态表名拦截器时我发现一个容易踩的坑多个拦截器的执行顺序会影响最终效果。正确的配置方式应该是Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 必须先添加动态表名拦截器 DynamicTableNameInnerInterceptor dynamicInterceptor new DynamicTableNameInnerInterceptor(); dynamicInterceptor.setTableNameHandler(new MonthTableNameHandler(t_order)); // 后添加分页拦截器 interceptor.addInnerInterceptor(dynamicInterceptor); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }经验之谈在Spring Boot中建议将拦截器配置单独放在Configuration类中。如果和MybatisPlusConfig混在一起可能会导致某些插件不生效。3. 分库分表深度实践3.1 基因法分库分表示例当单库分表仍无法满足性能需求时我们采用了基因法分库分表方案。以用户订单为例具体实现设计分片键使用用户ID的后4位作为基因片段分库规则库序号 userId后2位 % 库数量分表规则表序号 userId后4位 % 表数量代码实现关键点// 分库策略 public class DbShardingAlgorithm implements PreciseShardingAlgorithmLong { Override public String doSharding(CollectionString dbNames, PreciseShardingValueLong shardingValue) { long userId shardingValue.getValue(); int dbIndex (int) (userId % 100) % dbNames.size(); return ds_ dbIndex; } } // 分表策略 public class TableShardingAlgorithm implements PreciseShardingAlgorithmLong { Override public String doSharding(CollectionString tableNames, PreciseShardingValueLong shardingValue) { long userId shardingValue.getValue(); int tableIndex (int) (userId % 10000) % tableNames.size(); return t_order_ tableIndex; } }3.2 跨库查询解决方案分库分表后最大的挑战是跨库查询。我们采用了以下方案广播表将基础数据表在所有库中冗余存储绑定表将关联密切的表使用相同的分片规则内存聚合先在各分片查询再在应用层聚合结果对于分页查询这种复杂场景建议使用Elasticsearch等搜索引擎作为二级索引。我曾处理过一个需要跨10个分库查询用户订单的需求最终采用ES的方案将响应时间从原来的3秒降到了200毫秒以内。4. 性能优化实战技巧4.1 SQL执行监控MyBatis-Plus 3.5.x提供了强大的SQL分析功能。通过配置如下拦截器可以捕获慢查询Bean public MybatisPlusInterceptor performanceInterceptor() { PerformanceInterceptor interceptor new PerformanceInterceptor(); interceptor.setMaxTime(1000); // 设置SQL执行最大时长(ms) interceptor.setFormat(true); // 格式化SQL return interceptor; }在实际项目中我们发现了几个典型性能问题N1查询问题通过TableField(select false)延迟加载关联字段全表更新使用update(null, wrapper)时忘记加where条件大结果集查询配置分页拦截器限制单次查询条数4.2 二级缓存优化结合Redis实现二级缓存时需要注意缓存一致性问题。我们的解决方案是使用CacheNamespace注解开启Mapper缓存配置自定义的RedisCache实现通过CacheEvict注解保证数据更新时缓存同步失效一个典型的缓存配置示例Configuration EnableCaching public class RedisConfig extends CachingConfigurerSupport { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } }5. 复杂查询的Lambda优化3.5.x版本对Lambda查询进行了重大增强。以下是几个实用技巧条件优先级控制wrapper.nested(w - w.eq(User::getType, 1).or().eq(User::getStatus, 1)) .and(w - w.ge(User::getScore, 60));动态条件构建public LambdaQueryWrapperUser buildQuery(Integer type, String name) { return Wrappers.UserlambdaQuery() .eq(type ! null, User::getType, type) .like(StringUtils.isNotBlank(name), User::getName, name); }子查询支持wrapper.inSql(User::getId, select user_id from user_role where role_id 1);6. 扩展功能开发心得6.1 自定义注入方法在最近的项目中我们需要批量插入时忽略重复记录。通过自定义SQL注入器实现如下public class InsertIgnoreBatch extends AbstractMethod { Override public MappedStatement injectMappedStatement(Class? mapperClass, Class? modelClass, TableInfo tableInfo) { String sql scriptINSERT IGNORE INTO %s %s VALUES %s/script; String fieldSql prepareFieldSql(tableInfo); String valueSql prepareValuesSql(tableInfo); String sqlResult String.format(sql, tableInfo.getTableName(), fieldSql, valueSql); SqlSource sqlSource languageDriver.createSqlSource(configuration, sqlResult, modelClass); return this.addInsertMappedStatement(mapperClass, modelClass, insertIgnoreBatch, sqlSource, new NoKeyGenerator(), null, null); } }6.2 多租户实现方案对于SaaS系统我们基于MyBatis-Plus的多租户插件实现了数据隔离public class TenantInterceptor implements TenantLineHandler { Override public Expression getTenantId() { return new LongValue(SecurityUtils.getTenantId()); } Override public boolean ignoreTable(String tableName) { return !t_order.equals(tableName); // 只有订单表需要租户隔离 } }配置时需要注意interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantInterceptor()));7. 生产环境踩坑记录7.1 枚举类型处理3.5.x对枚举类型的处理方式有所变化。建议采用以下配置EnumValue private StatusEnum status; // 数据库存枚举的ordinal值 // 或者 EnumValue JsonValue private StatusEnum status; // 数据库存枚举的name值7.2 逻辑删除陷阱启用逻辑删除后要注意以下问题连表查询时需要手动添加逻辑删除条件使用wrapper.or()时需要特别注意条件组合唯一索引冲突问题建议将delete字段加入唯一索引7.3 批量操作优化默认的saveBatch方法性能较差我们优化后的版本Transactional public boolean saveBatch(CollectionT list) { String sqlStatement sqlSession.getConfiguration().getMappedStatement(mapperClass.getName() .insert).getSqlSource(); // 使用原生JDBC批量处理 try (Connection conn sqlSession.getConnection()) { conn.setAutoCommit(false); PreparedStatement ps conn.prepareStatement(sqlStatement); for (T entity : list) { // 设置参数... ps.addBatch(); } ps.executeBatch(); conn.commit(); return true; } catch (SQLException e) { throw new RuntimeException(e); } }8. 监控与调优建议8.1 执行SQL分析建议在生产环境开启SQL日志分析mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配合Arthas等工具监控SQL执行情况watch com.baomidou.mybatisplus.core.override.MybatisMapperMethod execute params[0]8.2 连接池配置结合HikariCP的最佳实践配置spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 idle-timeout: 30000 max-lifetime: 1800000 connection-timeout: 30000 connection-test-query: SELECT 18.3 JVM参数优化对于大数据量操作建议调整JVM参数-XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:InitiatingHeapOccupancyPercent35 -Xms2g -Xmx2g