Coexisting Multiple DataSources in Spring Boot: No Switching Needed

This guide explains how to configure Spring Boot applications to use multiple coexisting DataSource beans—such as MySQL and TDengine—without runtime switching, covering bean definitions, MyBatis mapper scanning, transaction manager setup, custom @Transactional annotations, and common pitfalls, enabling clean separation of business, log, and time‑series data.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Coexisting Multiple DataSources in Spring Boot: No Switching Needed

Concept of Multiple DataSource Coexistence

In a Spring‑Boot + MyBatis project you can define several DataSource beans, each bound to its own SqlSessionFactory, Mapper package and DataSourceTransactionManager. No AbstractRoutingDataSource, no ThreadLocal routing, and each datasource works independently.

I don’t switch data sources; I simply have several DataSource beans, each clearly linked to its own Mapper/SqlSessionFactory/TransactionManager.

Typical injection:

@Resource(name = "mysqlDataSource")
private DataSource mysqlDataSource;

@Resource(name = "tdengineDataSource")
private DataSource tdengineDataSource;

Multiple DataSource beans exist simultaneously

Each bean has its own configuration

Each bean has its own Mapper/Dao/Service layer

No routing logic, no ThreadLocal switching

Isolation between datasources

Usage pattern is identical for all

When to Use

Different database types (e.g., MySQL + TDengine, MySQL + Oracle, MySQL + MongoDB)

Different responsibilities (business DB, log DB, time‑series DB, analytics DB)

No need for read/write separation, master‑slave, or multi‑tenant switching

Example: a UAV trajectory system stores core business data in MySQL and massive time‑series data in TDengine.

Configuration Steps

1. Define DataSource Properties (application.yml)

spring:
  datasource:
    mysql:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.112.58:3306/uav_safety?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true
      username: root
      password: root
      type: com.alibaba.druid.pool.DruidDataSource
      initialSize: 10
      min-idle: 5
      max-active: 20
      max-wait: 60000
      validation-query: SELECT 1
      test-while-idle: true
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat,wall,slf4j

    tdengine:
      driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
      url: jdbc:TAOS-RS://192.168.112.58:6041/uav_safety?useSSL=false
      username: root
      password: taosdata
      type: com.alibaba.druid.pool.DruidDataSource
      initialSize: 7
      min-idle: 5
      max-active: 20
      max-wait: 60000
      validation-query: SELECT 1
      test-while-idle: true
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 50
      filters: stat

2. MySQL DataSource Configuration

@Configuration
@MapperScan(basePackages = "com.za.uav.mapper.mysql", sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MysqlDataSourceConfig {

    @Bean(name = "mysqlDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.mysql")
    public DataSource mysqlDataSource() {
        return new com.alibaba.druid.pool.DruidDataSource();
    }

    @Bean(name = "mysqlSqlSessionFactory")
    public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    @Bean(name = "mysqlTransactionManager")
    public DataSourceTransactionManager mysqlTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

3. TDengine DataSource Configuration

@Configuration
@MapperScan(basePackages = "com.za.uav.mapper.tdengine", sqlSessionFactoryRef = "tdengineSqlSessionFactory")
public class TdengineDataSourceConfig {

    @Bean(name = "tdengineDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.tdengine")
    public DataSource tdengineDataSource() {
        return new com.alibaba.druid.pool.DruidDataSource();
    }

    @Bean(name = "tdengineSqlSessionFactory")
    public SqlSessionFactory tdengineSqlSessionFactory(@Qualifier("tdengineDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }
}

4. Transaction Management

Each datasource gets its own transaction manager. You can use the full annotation or create semantic aliases:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "mysqlTransactionManager", rollbackFor = Exception.class)
@Documented
public @interface MysqlTx {}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "tdengineTransactionManager", rollbackFor = Exception.class)
@Documented
public @interface TdTx {}

Now service methods can be annotated with @MysqlTx or @TdTx instead of the verbose @Transactional declaration.

5. Operations Involving Both Databases

A single @Transactional can bind only one transaction manager. When a method writes to both MySQL and TDengine you must handle compensation manually:

@Transactional(transactionManager = "mysqlTransactionManager")
public void process() {
    mysqlMapper.insert(order);
    try {
        tdMapper.insert(log);
    } catch (Exception e) {
        // compensate: delete MySQL record or record the failure
        mysqlMapper.deleteById(order.getId());
        throw e;
    }
}

6. MapperScan Registration

Do not place @Mapper on individual interfaces. Register mappers per datasource with @MapperScan to avoid ambiguity.

7. Comparison with Dynamic DataSource

Multiple DataSource Coexistence keeps several datasource beans alive, no runtime switching, and is suited for distinct business modules. Dynamic DataSource exposes a single bean and switches via ThreadLocal, typically used for read/write separation or multi‑tenant scenarios. The former is strongly recommended when each module has its own dedicated database.

8. Usage Example

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Resource
    private TdLogMapper tdLogMapper;

    @MysqlTx
    public void saveUser(User user) {
        userMapper.insert(user);
    }

    @TdTx
    public void saveLog(Log log) {
        tdLogMapper.insert(log);
    }
}

Business code injects the appropriate mapper with @Resource and calls methods in the same way; the underlying datasource is transparent.

Common Pitfalls

Druid monitoring for TDengine : TDengine’s SQL dialect is not compatible with the wall filter. Remove wall from the filter list to avoid NPE.

"Invalid bound statement (not found)" : Ensure the mapper interface is scanned, the XML file is present, and the method name matches the id in the XML.

MyBatis‑Plus integration : Use MybatisSqlSessionFactoryBean instead of the plain SqlSessionFactoryBean when configuring MyBatis‑Plus.

Conclusion

Multiple DataSource coexistence solves the problem of letting each database perform its own responsibilities without any runtime switching logic. Dynamic DataSource solves the problem of switching between databases when needed. Choose the approach that matches your architectural requirements, keep bean names distinct, and avoid unnecessary routing to maintain a clean and stable backend.

backend developmentSpring BootMyBatisTDengineTransaction ManagementMultiple DataSource
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.