Maven Dependencies
Include the required starters in your pom.xml:
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
Connection Pool Settings
Configure distinct connection pools in application.yml:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
primary:
jdbc-url: jdbc:mysql://127.0.0.1:3306/main_db?useSSL=false&serverTimezone=UTC
username: app_user
password: app_pass
driver-class-name: com.mysql.cj.jdbc.Driver
auxiliary:
jdbc-url: jdbc:mysql://127.0.0.1:3306/analytics_db?useSSL=false&serverTimezone=UTC
username: readonly_user
password: readonly_pass
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
initial-size: 5
max-active: 20
min-idle: 5
max-wait: 60000
DataSource Routing Infrastructure
Implement the routing mechanism using Spring's AbstractRoutingDataSource:
public enum DataSourceKey {
PRIMARY, AUXILIARY
}
public class RoutingContext {
private static final ThreadLocal<DataSourceKey> contextHolder = new ThreadLocal<>();
public static void set(DataSourceKey key) {
contextHolder.set(key);
}
public static DataSourceKey get() {
return contextHolder.get();
}
public static void clear() {
contextHolder.remove();
}
}
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return RoutingContext.get();
}
}
Bean Configuration
Register the data sources and MyBatis-Plus components:
@Configuration
@MapperScan(basePackages = "com.example.persistence.mapper",
sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceArchitecture {
@Bean(name = "primaryPool")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryPool() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean(name = "auxiliaryPool")
@ConfigurationProperties(prefix = "spring.datasource.auxiliary")
public DataSource auxiliaryPool() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean(name = "routingDataSource")
@Primary
public DataSource routingDataSource(
@Qualifier("primaryPool") DataSource primary,
@Qualifier("auxiliaryPool") DataSource auxiliary) {
DynamicRoutingDataSource router = new DynamicRoutingDataSource();
Map<Object, Object> pools = new HashMap<>();
pools.put(DataSourceKey.PRIMARY, primary);
pools.put(DataSourceKey.AUXILIARY, auxiliary);
router.setTargetDataSources(pools);
router.setDefaultTargetDataSource(primary);
return router;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(
@Qualifier("routingDataSource") DataSource routingDS) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(routingDS);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/**/*.xml"));
return factoryBean.getObject();
}
}
Switching Data Sources at Runtime
Utilize the routing context within your service layer:
@Service
public class OrderProcessingService {
@Autowired
private OrderMapper orderMapper;
public List<Order> fetchFromPrimary() {
RoutingContext.set(DataSourceKey.PRIMARY);
try {
return orderMapper.selectActiveOrders();
} finally {
RoutingContext.clear();
}
}
public List<Report> fetchAnalytics() {
RoutingContext.set(DataSourceKey.AUXILIARY);
try {
return orderMapper.selectHistoricalData();
} finally {
RoutingContext.clear();
}
}
}
Alternatively, implement AOP-based switching using custom annotations:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
DataSourceKey value() default DataSourceKey.PRIMARY;
}
@Aspect
@Component
public class DataSourceInterceptor {
@Around("@annotation(targetDataSource)")
public Object route(ProceedingJoinPoint point, TargetDataSource targetDataSource)
throws Throwable {
RoutingContext.set(targetDataSource.value());
try {
return point.proceed();
} finally {
RoutingContext.clear();
}
}
}
Usage Example
Apply the annotation to repository methods:
public interface InventoryRepository {
@TargetDataSource(DataSourceKey.PRIMARY)
List<Stock> getCurrentStock();
@TargetDataSource(DataSourceKey.AUXILIARY)
List<StockTrend> getStockTrends();
}