Mastering Spring Boot: How to Integrate Multiple Data Sources with MyBatis
This tutorial explains how to configure Spring Boot with a single Druid data source, extend it to multiple dynamic data sources using AbstractRoutingDataSource, and seamlessly integrate MyBatis, including custom annotations, AOP switching, thread‑local isolation, and transaction management for robust multi‑database applications.
Overview
This guide shows how to configure a Spring Boot application to use multiple databases with MyBatis. It covers a single Druid data source, creation of a dynamic routing data source, a custom annotation for switching, an AOP aspect, MyBatis integration, and a matching transaction manager.
Single DataSource Configuration
Add the Druid starter dependency:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>Define the connection properties in application.properties (replace the placeholder values with your own):
spring.datasource.url=jdbc:mysql://HOST:3306/DB_NAME?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
spring.datasource.username=YOUR_USER
spring.datasource.password=YOUR_PASSWORD
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# Druid pool settings
spring.datasource.druid.initial-size=0
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=0
spring.datasource.druid.max-wait=6000
spring.datasource.druid.validation-query=SELECT 1
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=25200000
spring.datasource.druid.remove-abandoned=true
spring.datasource.druid.remove-abandoned-timeout=1800
spring.datasource.druid.log-abandoned=true
spring.datasource.druid.filters=mergeStatTwo ways to expose the bean are possible:
Let Spring Boot create the bean automatically from the properties.
Define a @Configuration class with a @Bean method that returns a DruidDataSource and is annotated with @ConfigurationProperties(prefix="spring.datasource").
MyBatis Integration
Add the MyBatis starter:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>Typical global properties (in application.properties) are:
mybatis.type-handlers-package=com.demo.typehandler
mybatis.configuration.map-underscore-to-camel-case=trueWhen only one DataSource bean exists, MyBatis auto‑configuration is activated because of the condition @ConditionalOnSingleCandidate(DataSource.class). To work with multiple data sources we will replace the default data source with a custom routing data source.
Dynamic Routing DataSource
Extend AbstractRoutingDataSource and override determineCurrentLookupKey() so that the key is taken from a thread‑local holder.
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSource();
}
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
}The holder stores the current key per thread:
public class DataSourceHolder {
private static final ThreadLocal<String> dataSources = new InheritableThreadLocal<>();
public static void setDataSource(String ds) { dataSources.set(ds); }
public static String getDataSource() { return dataSources.get(); }
public static void clearDataSource() { dataSources.remove(); }
}Switch Annotation
Create a method‑level annotation that specifies which data source key to use. The default key is "hisDataSource".
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SwitchSource {
String DEFAULT_NAME = "hisDataSource";
String value() default DEFAULT_NAME;
}Aspect for Automatic Switching
An AOP aspect intercepts methods annotated with @SwitchSource, sets the key before execution and clears it afterwards.
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
@Slf4j
public class DataSourceAspect {
@Pointcut("@annotation(SwitchSource)")
public void pointcut() {}
@Before("pointcut()")
public void beforeOpt(JoinPoint jp) {
Method method = ((MethodSignature) jp.getSignature()).getMethod();
SwitchSource ss = method.getAnnotation(SwitchSource.class);
log.info("[Switch DataSource]: {}", ss.value());
DataSourceHolder.setDataSource(ss.value());
}
@After("pointcut()")
public void afterOpt() {
DataSourceHolder.clearDataSource();
log.info("[Switch Default DataSource]");
}
}MyBatis Configuration with the Dynamic DataSource
Declare the routing data source and the two concrete data sources in a configuration class.
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean(name = SwitchSource.DEFAULT_NAME)
@ConfigurationProperties(prefix = "spring.datasource.his")
public DataSource hisDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DynamicDataSource dynamicDataSource(@Qualifier("dataSource") DataSource defaultDs,
@Qualifier(SwitchSource.DEFAULT_NAME) DataSource hisDs) {
Map<Object, Object> target = new HashMap<>();
target.put(SwitchSource.DEFAULT_NAME, hisDs);
return new DynamicDataSource(defaultDs, target);
}
@Primary
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dynamicDataSource);
org.apache.ibatis.session.Configuration cfg = new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
cfg.setDefaultFetchSize(100);
cfg.setDefaultStatementTimeout(30);
factory.setConfiguration(cfg);
return factory.getObject();
}
@Primary
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}Usage Example
Apply @SwitchSource (and optionally disable transaction propagation) on a service method that needs to read from the HIS database.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@SwitchSource
public List<DeptInfo> list() {
return hisDeptInfoMapper.listDept();
}When the method starts, the aspect sets the thread‑local key to "hisDataSource", causing DynamicDataSource to route all JDBC calls to the HIS data source. After the method returns, the aspect clears the key, and subsequent calls use the default data source again.
Key Points
Only one bean of type DataSource should be marked as @Primary; the routing bean takes that role.
The routing logic is completely decoupled from business code via the @SwitchSource annotation.
Thread safety is guaranteed by storing the lookup key in an InheritableThreadLocal, ensuring that parallel requests do not interfere.
The same approach works for any number of target data sources—just add more entries to the targetDataSources map and define corresponding @ConfigurationProperties prefixes.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
