Backend Development 18 min read

Integrating Spring Boot with MyBatis and Multi‑DataSource (Dynamic DataSource) – A Complete Guide

This article explains how to integrate Spring Boot with MyBatis, configure a single Druid data source, implement multi‑data‑source support using Spring's AbstractRoutingDataSource, create a dynamic data source with ThreadLocal, and wire everything together with custom annotations, aspects, and transaction management for backend Java applications.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Integrating Spring Boot with MyBatis and Multi‑DataSource (Dynamic DataSource) – A Complete Guide

Purpose of This Article

The article demonstrates how to integrate Spring Boot with MyBatis and a database, focusing on multi‑data‑source scenarios often encountered in medical information systems that need to interact with a HIS (Hospital Information System).

What Is a Multi‑DataSource?

In a single‑application context, a Datasource represents one database. A multi‑data‑source configuration means the application works with two or more databases, each represented by its own Datasource bean.

When to Use Multi‑DataSource?

Typical HIS integration requires either reading data from HIS view tables or calling HIS web services. The view‑based approach inevitably involves at least two databases – the HIS database and the application’s own database – thus requiring data‑source switching.

Integrating a Single DataSource (Druid)

We use Alibaba's Druid connection pool. Add the dependency:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.9</version>
</dependency>

The auto‑configuration class DruidDataSourceAutoConfigure is enabled by @EnableConfigurationProperties for DruidStatProperties (prefix spring.datasource.druid ) and DataSourceProperties (prefix spring.datasource ).

Configure the datasource in application.properties (example):

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.remove-abandoned=true
spring.datasource.druid.remove-abandoned-timeout=1800
spring.datasource.druid.log-abandoned=true
spring.datasource.druid.filters=mergeStat

Integrating MyBatis

Add the starter:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

MyBatis auto‑configuration is enabled with @EnableConfigurationProperties(MybatisProperties.class) . Global properties are prefixed with mybatis . You can also use @MapperScan to scan mapper interfaces.

Typical SqlSessionFactory bean (single datasource):

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/**/*.xml"));
    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();
}

Dynamic DataSource Concept

Spring provides AbstractRoutingDataSource , which holds a Map<Object, Object> targetDataSources . Subclasses override determineCurrentLookupKey() to return a key (usually stored in a ThreadLocal ) that selects the actual datasource at runtime.

Thread‑Local Helper

public class DataSourceHolder {
    private static final ThreadLocal
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(); }
}

DynamicDataSource Implementation

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();
    }
}

Custom 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 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]");
    }
}

Wiring Everything into the IOC Container

Define two concrete datasources (default and HIS):

@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();
}

Create the dynamic datasource bean:

@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);
}

MyBatis SqlSessionFactory Using DynamicDataSource

@Primary
@Bean("sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dynamicDataSource);
    factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/**/*.xml"));
    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();
}

Transaction Manager for DynamicDataSource

@Primary
@Bean("transactionManager2")
public PlatformTransactionManager annotationDrivenTransactionManager(DynamicDataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

Demonstration

Apply the custom annotation on a service method to switch to the HIS datasource:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
@SwitchSource
@Override
public List
list() {
    return hisDeptInfoMapper.listDept();
}

When the method executes, the aspect sets the ThreadLocal key, the dynamic datasource routes to the HIS database, and after the method finishes the key is cleared, reverting to the default datasource.

Conclusion

The article covered the complete process of integrating Spring Boot with a single datasource, MyBatis, and a dynamic multi‑datasource solution, including connection‑pool configuration, MyBatis setup, dynamic routing, custom annotations, AOP handling, and transaction management, providing a solid foundation for backend Java projects that need to switch between multiple databases.

backendSpring BootMyBatismulti-datasourceDynamic DataSource
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.