How to Build a Conditional Multi‑DataSource Spring Boot Starter to Eliminate Dubbo Bottlenecks
This article walks through creating a custom Spring Boot starter that uses conditional auto‑configuration to provide primary and secondary data sources, DAOs, and services, enabling high‑frequency Dubbo calls to be replaced with direct database access and dramatically improving performance.
Hello, I am Peng Lei.
Introduction: The Auto‑Configuration We Learned but Rarely Practiced
When I first studied Spring Boot, the auto‑configuration mechanism fascinated me with its "convention over configuration" principle, and I even tried writing my own starter using @EnableAutoConfiguration and spring.factories. However, real‑world projects rarely require custom auto‑configuration; most teams use official or third‑party starters. A recent performance issue with Dubbo finally gave me a chance to apply this knowledge.
Background: Dubbo Calls Became a Performance Bottleneck
In a large micro‑service project, services communicate via Dubbo. Some high‑frequency data‑query operations incurred noticeable latency when called through Dubbo, especially under high concurrency. The database itself was not the bottleneck; the Dubbo service provider could not be horizontally scaled, leading to frequent alerts and risk of crashes.
Design Idea: Conditional Auto‑Configuration Multi‑DataSource SDK
The goal is to develop an "intelligent" SDK that automatically configures the required data source, DAO, and service based on properties. Business teams only need to add the dependency and configure the data source.
SDK Project Structure
sdk-multi-datasource/
├── src/main/java/com/example/sdk/
│ ├── config/
│ │ ├── condition/AnySdkDataSourceCondition.java
│ │ ├── datasource/SdkPrimaryDataConfig.java
│ │ ├── datasource/SdkSecondaryDataConfig.java
│ │ └── SdkAutoConfiguration.java
│ ├── dao/primary/SdkAppInfoDao.java
│ ├── dao/secondary/SdkOtherDataDao.java
│ ├── service/SdkAppInfoService.java
│ └── service/SdkOtherDataService.java
├── src/main/resources/META-INF/spring.factories
└── pom.xmlCore Code Implementation
1. Condition Class: Detect SDK Data‑Source Configuration
public class AnySdkDataSourceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
// Return true if either primary or secondary SDK datasource property is present
return env.containsProperty("spring.datasource.sdk-primary.jdbc-url") ||
env.containsProperty("spring.datasource.sdk-secondary.jdbc-url");
}
}Why use conditional annotations? They allow beans to be created only when the required properties exist, preventing unnecessary bean loading and avoiding name conflicts.
2. Primary DataSource Configuration
@Configuration
@ConditionalOnProperty(prefix = "spring.datasource.sdk-primary", name = "jdbc-url")
@MapperScan(basePackages = "com.example.sdk.dao.primary", sqlSessionFactoryRef = "sdkPrimarySqlSessionFactory")
public class SdkPrimaryDataConfig {
@Bean(name = "sdkPrimaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sdk-primary")
public DataSource sdkPrimaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "sdkPrimarySqlSessionFactory")
public SqlSessionFactory sdkPrimarySqlSessionFactory(@Qualifier("sdkPrimaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/primary/*.xml"));
return bean.getObject();
}
@Bean(name = "sdkPrimarySqlSessionTemplate")
public SqlSessionTemplate sdkPrimarySqlSessionTemplate(@Qualifier("sdkPrimarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(name = "sdkPrimaryTransactionManager")
public DataSourceTransactionManager sdkPrimaryTransactionManager(@Qualifier("sdkPrimaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}3. Secondary DataSource Configuration
The secondary configuration mirrors the primary one, with bean names, package paths, and property prefixes changed from primary to secondary.
4. DAO Interfaces
@Mapper
public interface SdkAppInfoDao {
AppInfo getByBusinessId(String businessId);
}5. Service Implementation (Setter Injection)
public class SdkAppInfoService {
private SdkAppInfoDao sdkAppInfoDao;
public void setSdkAppInfoDao(SdkAppInfoDao dao) {
this.sdkAppInfoDao = dao;
}
public AppInfo getByBusinessId(String businessId) {
// Business logic such as caching or logging can be added here
return sdkAppInfoDao.getByBusinessId(businessId);
}
}6. Auto‑Configuration Class
@Configuration
@Conditional(AnySdkDataSourceCondition.class)
@Import({SdkPrimaryDataConfig.class, SdkSecondaryDataConfig.class})
public class SdkAutoConfiguration {
@Bean
@Lazy
@ConditionalOnProperty(prefix = "spring.datasource.sdk-primary", name = "jdbc-url")
public SdkAppInfoService sdkAppInfoService(SdkAppInfoDao sdkAppInfoDao) {
SdkAppInfoService service = new SdkAppInfoService();
service.setSdkAppInfoDao(sdkAppInfoDao);
return service;
}
@Bean
@Lazy
@ConditionalOnProperty(prefix = "spring.datasource.sdk-secondary", name = "jdbc-url")
public SdkOtherDataService sdkOtherDataService(SdkOtherDataDao sdkOtherDataDao) {
SdkOtherDataService service = new SdkOtherDataService();
service.setSdkOtherDataDao(sdkOtherDataDao);
return service;
}
}Benefits of @Lazy – Delays bean initialization until first use, guaranteeing that dependent DAO beans are already created and avoiding null‑injection errors.
Registering the Auto‑Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.sdk.config.SdkAutoConfigurationHow Business Teams Use the SDK
Add the Maven dependency:
<dependency>
<groupId>com.example</groupId>
<artifactId>sdk-multi-datasource</artifactId>
<version>1.0.0</version>
</dependency>Configure the data sources in application.yml (or application.properties) following Spring Boot conventions:
spring:
datasource:
sdk-primary:
jdbc-url: jdbc:mysql://primary-db-host:3306/primary_db
username: db_user
password: db_password
driver-class-name: com.mysql.jdbc.Driver
sdk-secondary:
jdbc-url: jdbc:mysql://secondary-db-host:3306/secondary_db
username: db_user
password: db_password
driver-class-name: com.mysql.jdbc.DriverInject and use the service in your code:
@RestController
public class BusinessController {
@Autowired
private SdkAppInfoService sdkAppInfoService;
@GetMapping("/app-info/{businessId}")
public AppInfo getAppInfo(@PathVariable String businessId) {
return sdkAppInfoService.getByBusinessId(businessId);
}
}Results and Reflection
By replacing high‑frequency Dubbo calls with direct database access through the SDK, latency dropped dramatically and system load decreased. Teams appreciated the "out‑of‑the‑box" experience.
Conditional annotations make the SDK intelligent and flexible:
Beans load only when the corresponding datasource is configured.
Name prefixes avoid conflicts with existing beans.
Memory usage is reduced by avoiding unnecessary beans.
Runtime errors caused by missing configuration are prevented.
Architectural Thought: Balancing Microservices and Monoliths
Microservice architecture offers clear service boundaries and independent scalability, but introduces network overhead and distributed‑system complexity. The multi‑datasource SDK provides a compromise: it retains microservice benefits while achieving near‑monolith performance for specific high‑traffic paths.
The key is to choose the right solution for the actual scenario; occasionally reverting to "partial monolith" thinking can yield a more mature architecture.
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.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
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.
