Mastering Spring Boot Multi‑DataSource Integration with MyBatis

This tutorial explains how to configure Spring Boot with a single DataSource, integrate MyBatis, and extend the setup to a dynamic multi‑DataSource using AbstractRoutingDataSource, custom annotations, and AOP, covering connection pool setup, property mapping, and transaction management.

Programmer DD
Programmer DD
Programmer DD
Mastering Spring Boot Multi‑DataSource Integration with MyBatis

Purpose of this article

This article covers integrating Spring Boot with MyBatis and configuring both single and multiple data sources.

What is a multi‑data source?

In a typical application a single Datasource connects to one database. A multi‑data source means the application uses two or more databases, which can be configured as shown below.

@Bean(name = "dataSource")
public DataSource dataSource() {
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setUrl(url);
    druidDataSource.setUsername(username);
    druidDataSource.setDriverClassName(driverClassName);
    druidDataSource.setPassword(password);
    return druidDataSource;
}
The three properties url , username and password uniquely identify a database; configuring multiple DataSource beans creates a multi‑data source.

When to use multi‑data source?

In medical systems that need to exchange patient, staff and order information with a HIS system, two typical solutions exist:

HIS provides view tables; the application periodically reads those views into its own database.

HIS exposes web‑service or HTTP APIs that the application calls on demand.

The first solution requires at least two databases – the HIS database and the application database – and therefore needs data‑source switching.

Integrating a single data source

The article uses 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>

Druid’s auto‑configuration class DruidDataSourceAutoConfigure is enabled by @EnableConfigurationProperties on DruidStatProperties and DataSourceProperties. The relevant prefixes are spring.datasource.druid and spring.datasource. Example application.properties:

spring.datasource.url=jdbc:mysql://120.26.101.xxx:3306/xxx?useUnicode=true&characterEncoding=UTF-8&...
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
...
Adding the above properties injects a DataSource bean into Spring Boot.

Integrating MyBatis

Add the starter:

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

The auto‑configuration class MybatisAutoConfiguration is activated with @EnableConfigurationProperties(MybatisProperties.class). Global properties prefixed with mybatis configure XML locations, type handlers, etc.

mybatis.type-handlers-package=com.demo.typehandler
mybatis.configuration.map-underscore-to-camel-case=true

For custom configuration, define a SqlSessionFactory bean:

@Bean("sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.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);
    bean.setConfiguration(cfg);
    return bean.getObject();
}
Two configuration styles are shown; the first is sufficient for most cases.

Dynamic (routing) data source

Spring provides AbstractRoutingDataSource to switch among multiple targets at runtime. Subclass it and implement determineCurrentLookupKey() to return a key stored in a ThreadLocal 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();
    }
}

A simple holder:

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

Define an annotation to mark methods that need a specific data source:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SwitchSource {
    String DEFAULT_NAME = "hisDataSource";
    String value() default DEFAULT_NAME;
}

And an aspect that sets and clears the thread‑local key:

@Aspect
@Order(1)
@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]");
    }
}

Wiring the dynamic data source

Define the default and the HIS data source beans:

@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 routing bean:

@Bean
public DynamicDataSource dynamicDataSource(@Qualifier("dataSource") DataSource defaultDs,
                                           @Qualifier(SwitchSource.DEFAULT_NAME) DataSource hisDs) {
    Map<Object, Object> target = new HashMap<>();
    target.put("hisDataSource", hisDs);
    return new DynamicDataSource(defaultDs, target);
}

Inject the routing data source into MyBatis:

@Primary
@Bean("sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dynamicDataSource);
    bean.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);
    bean.setConfiguration(cfg);
    return bean.getObject();
}

Configure a transaction manager that uses the dynamic data source:

@Primary
@Bean("transactionManager2")
public PlatformTransactionManager annotationDrivenTransactionManager(DynamicDataSource ds) {
    return new DataSourceTransactionManager(ds);
}
Now methods annotated with @SwitchSource automatically switch to the specified database, and the thread‑local key is cleared after execution.

Demo

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

Conclusion

The article demonstrates how to configure Spring Boot with a single data source, integrate MyBatis, and extend the setup to a dynamic multi‑data source using AbstractRoutingDataSource, a custom annotation, and an AOP aspect.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootMyBatisdynamic routingMulti-DataSource
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

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.