ShardingSphere系列04:MybatisPlus动态数据源与ShardingJdbc分表策略的深度整合实践
1. 为什么需要整合动态数据源与分表策略
在实际业务开发中,我们经常会遇到两个典型场景:一是需要根据业务模块访问不同的数据库(多数据源),二是单个数据表数据量过大需要分表存储(分库分表)。MybatisPlus的动态数据源和ShardingJdbc分别擅长解决这两个问题,但将它们整合在一起使用时却会遇到不少坑。
我最近在一个电商项目中就遇到了这样的需求:订单数据需要按用户ID分表存储,同时又要能灵活切换到日志库、商品库等其他数据源。刚开始尝试时,发现直接用MybatisPlus的@DS注解切换数据源会导致ShardingJdbc的分表规则失效,反之亦然。经过多次调试才找到正确的整合方式。
这种整合的核心价值在于:
- 业务代码无需关心底层是单表还是分表
- 可以透明地使用@DS注解切换不同业务数据源
- 分表策略对上层完全透明,就像操作单表一样简单
2. 环境准备与依赖配置
2.1 基础环境搭建
首先确保你的Spring Boot项目版本在2.3.x以上,我这里用的是2.7.12版本。数据库准备两个实例:
- 主库:table_sharding(用于订单分表)
- 从库:product_db(模拟商品数据)
创建订单表结构(分4个表):
CREATE TABLE `order_info_0` ( `id` bigint NOT NULL, `user_id` bigint NOT NULL, `product_id` bigint DEFAULT NULL, `amount` decimal(10,2) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;记得同时创建order_info_1到order_info_3共4个相同结构的表。
2.2 关键依赖管理
pom.xml中需要特别注意这些依赖的版本兼容性:
<!-- MybatisPlus核心 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- 动态数据源 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.6.1</version> </dependency> <!-- ShardingSphere --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> </dependency> <!-- 数据源驱动 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.16</version> </dependency>特别提醒:ShardingSphere 5.x版本与MybatisPlus 3.5+存在一些SPI加载冲突,需要额外添加以下配置类:
@Configuration public class ShardingConfig { @PostConstruct void init() { System.setProperty("org.apache.shardingsphere.sql.parser.sql.cache.enabled", "false"); } }3. 核心配置实战
3.1 动态数据源桥接配置
这是整个整合最关键的环节,我们需要创建一个DataSourceConfiguration来桥接两者:
@Configuration @AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class}) public class DataSourceConfig { private static final String SHARDING_DS_NAME = "sharding"; @Autowired private DynamicDataSourceProperties properties; @Lazy @Resource private DataSource shardingDataSource; @Bean public DynamicDataSourceProvider dynamicDataSourceProvider() { return new AbstractDataSourceProvider() { @Override public Map<String, DataSource> loadDataSources() { Map<String, DataSource> dataSourceMap = createDataSourceMap(properties.getDatasource()); // 将ShardingSphere管理的数据源纳入动态数据源体系 dataSourceMap.put(SHARDING_DS_NAME, shardingDataSource); return dataSourceMap; } }; } @Primary @Bean public DataSource dataSource() { DynamicRoutingDataSource ds = new DynamicRoutingDataSource(); ds.setPrimary(properties.getPrimary()); ds.setProvider(dynamicDataSourceProvider()); return ds; } }这个配置类做了三件重要的事情:
- 加载MybatisPlus动态数据源的常规配置
- 获取ShardingSphere创建的分片数据源
- 将分片数据源注册为动态数据源的一个特殊数据源
3.2 YAML配置详解
application.yml需要分层配置:
spring: shardingsphere: datasource: names: sharding sharding: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/table_sharding username: root password: 123456 rules: sharding: tables: order_info: actual-data-nodes: sharding.order_info_$->{0..3} table-strategy: standard: sharding-column: user_id precise-algorithm-class-name: com.example.config.OrderShardingAlgorithm key-generators: snowflake: type: SNOWFLAKE datasource: dynamic: primary: master datasource: master: url: jdbc:mysql://localhost:3306/product_db username: root password: 123456 log: url: jdbc:mysql://192.168.1.100:3306/log_db username: log_user password: log@123注意几个关键点:
- ShardingSphere的数据源名称必须与桥接配置中的SHARDING_DS_NAME一致
- 动态数据源的primary设置会影响@DS注解的默认行为
- 分片算法建议使用自定义类实现更灵活的分片逻辑
4. 业务层实现技巧
4.1 实体与Mapper设计
对于分表实体,需要特别注意两点:
@Data @TableName("order_info") // 逻辑表名 public class OrderInfo { @TableId(type = IdType.ASSIGN_ID) private Long id; private Long userId; // 分片键 private Long productId; private BigDecimal amount; private Date createTime; }Mapper接口保持常规写法即可:
@Mapper public interface OrderMapper extends BaseMapper<OrderInfo> { @DS("sharding") List<OrderInfo> selectByUser(@Param("userId") Long userId); }4.2 服务层的最佳实践
在Service层使用时,推荐这种写法:
@Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> { // 分表操作统一使用sharding数据源 @DS("sharding") public void processOrder(OrderInfo order) { save(order); // 会自动根据userId分片 // 切换到商品库查询 Product product = productService.getById(order.getProductId()); } // 多数据源混合操作 public void fullProcess(OrderInfo order) { // 默认使用master数据源 logService.record(order); // 切换到分片数据源 processOrder(order); } }4.3 自定义分片算法示例
对于复杂分片逻辑,建议实现标准分片算法:
public class OrderShardingAlgorithm implements StandardShardingAlgorithm<Long> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) { // 按userId的哈希值取模分片 int size = availableTargetNames.size(); long mod = shardingValue.getValue() % size; return "order_info_" + Math.abs(mod); } }5. 常见问题排查指南
5.1 典型错误与解决方案
问题一:分表规则不生效现象:数据总是插入到第一个物理表 排查步骤:
- 检查sharding-column是否与实体字段一致
- 确认分片算法返回的表名后缀在actual-data-nodes范围内
- 在yaml中添加sql-show: true查看实际路由
问题二:动态切换失效现象:@DS注解切换后仍使用默认数据源 解决方案:
- 检查DataSourceConfiguration是否加了@Primary
- 确认没有在多个地方重复创建DataSource bean
- 在启动日志中搜索"DynamicDataSource"确认初始化顺序
5.3 性能优化建议
- 分片键选择:尽量选择离散度高的字段,避免热点问题
- 连接池配置:建议为分片数据源设置更大的maxPoolSize
- 批量操作:使用ShardingSphere提供的批量插入API提升性能
- 分布式事务:对于跨分片操作,考虑使用Seata集成
我在实际项目中还发现一个有用的技巧:对于需要频繁跨库查询的场景,可以创建一个视图数据库,通过ETL工具定期同步需要关联查询的数据,这样既能保持分片的优势,又能解决关联查询的性能问题。
