Master Spring Boot Multi‑DataSource Integration with MyBatis
This tutorial explains how to combine Spring Boot, MyBatis, and multiple databases using Druid connection pools, dynamic routing data sources, custom annotations, and AOP, providing complete configuration examples, code snippets, and best‑practice guidance for backend developers.
What Is a Multi‑DataSource?
The most common single‑application scenario uses one Datasource. A multi‑dataSource setup involves two or more databases within the same application, defined by configuring multiple Datasource beans.
@Bean(name = "dataSource")
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setPassword(password);
return druidDataSource;
}url , username and password uniquely identify a database; configuring multiple such Datasource objects creates a multi‑dataSource environment.
When to Use Multiple DataSources?
In healthcare systems, integration with a Hospital Information System (HIS) often requires either periodic view synchronization or direct API calls. Both approaches involve at least two databases – the HIS database and the application’s own database – thus necessitating data‑source switching.
Integrating a Single DataSource
We use Alibaba’s Druid connection pool. Add the dependency:
<!--druid connection pool-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>Configure the pool and database properties in application.properties (prefixes spring.datasource and spring.datasource.druid).
spring.datasource.url=jdbc:mysql://120.26.101.xxx:3306/xxx?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=xxxx
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 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.removeAbandoned=true
spring.datasource.druid.remove-abandoned-timeout=1800
spring.datasource.druid.log-abandoned=true
spring.datasource.druid.filters=mergeStatThe @EnableConfigurationProperties annotation on DruidDataSourceAutoConfigure binds these properties to DruidStatProperties (prefix spring.datasource.druid) and DataSourceProperties (prefix spring.datasource).
Integrating MyBatis
Add the MyBatis starter:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>MyBatis auto‑configuration class MybatisAutoConfiguration is enabled with @EnableConfigurationProperties(MybatisProperties.class). Global properties prefixed with mybatis map to this class.
Typical configuration:
mybatis.type-handlers-package=com.demo.typehandler
mybatis.configuration.map-underscore-to-camel-case=trueUse @MapperScan to specify mapper packages.
Dynamic DataSource Concept
Spring provides AbstractRoutingDataSource, which holds a Map<Object, Object> targetDataSources. Subclasses must implement determineCurrentLookupKey() to return the key that selects the actual DataSource from the map.
protected abstract Object determineCurrentLookupKey();ThreadLocal stores the key per thread, ensuring isolation.
public class DataSourceHolder {
private static final ThreadLocal<String> dataSources = new InheritableThreadLocal<>();
public static void setDataSource(String datasource) { dataSources.set(datasource); }
public static String getDataSource() { return dataSources.get(); }
public static void clearDataSource() { dataSources.remove(); }
}Building a DynamicDataSource
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();
}
}Annotation for Switching
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SwitchSource {
String DEFAULT_NAME = "hisDataSource";
String value() default DEFAULT_NAME;
}Aspect to Apply the Annotation
@Aspect
@Order(1)
@Component
@Slf4j
public class DataSourceAspect {
@Pointcut("@annotation(SwitchSource)")
public void pointcut() {}
@Before("pointcut()")
public void beforeOpt(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
SwitchSource switchSource = method.getAnnotation(SwitchSource.class);
log.info("[Switch DataSource]:" + switchSource.value());
DataSourceHolder.setDataSource(switchSource.value());
}
@After("pointcut()")
public void afterOpt() {
DataSourceHolder.clearDataSource();
log.info("[Switch Default DataSource]");
}
}Integrating DynamicDataSource with MyBatis
Define a primary SqlSessionFactory that uses the dynamic data source:
@Primary
@Bean("sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/**/*.xml"));
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultFetchSize(100);
configuration.setDefaultStatementTimeout(30);
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean.getObject();
}Transaction manager must also manage the dynamic data source:
@Primary
@Bean("transactionManager2")
public PlatformTransactionManager annotationDrivenTransactionManager(DynamicDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}Injecting the Real DataSources
@ConfigurationProperties(prefix = "spring.datasource")
@Bean("dataSource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean(name = SwitchSource.DEFAULT_NAME)
@ConfigurationProperties(prefix = "spring.datasource.his")
public DataSource hisDataSource() {
return DataSourceBuilder.create().build();
}Using the Annotation
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@SwitchSource
@Override
public List<DeptInfo> list() {
return hisDeptInfoMapper.listDept();
}When the method executes, the aspect sets the thread‑local key to hisDataSource, causing MyBatis to route queries to the HIS database; after the method returns, the key is cleared and the default data source is restored.
Thus, integrating Spring Boot, MyBatis, and multiple data sources is achieved by defining a dynamic routing data source, a ThreadLocal holder, a custom annotation, and an AOP aspect, together with proper bean configuration for the data sources, SqlSessionFactory, and transaction manager.
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.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.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.
