SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源配置的四種方案
數(shù)據(jù)庫(kù)環(huán)境:本地 MySQL / PostgreSQL / 阿里云 RDS MySQL
方案一:手動(dòng)配置多 DataSource + Druid
適用場(chǎng)景:固定 2~3 個(gè)數(shù)據(jù)源,按 Mapper 包路徑隔離
1.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<groupId>com.example</groupId>
<artifactId>multi-ds-manual</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<druid.version>1.2.21</druid.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- Druid(Spring Boot 3 用這個(gè)) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- MySQL 驅(qū)動(dòng) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- PostgreSQL 驅(qū)動(dòng) -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>1.2 application.yml
server:
port: 8080
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# ============ 數(shù)據(jù)源1:本地 MySQL ============
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_primary?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root123456
initial-size: 5
min-idle: 5
max-active: 30
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,slf4j
# ============ 數(shù)據(jù)源2:PostgreSQL ============
secondary:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/db_secondary?currentSchema=public&stringtype=unspecified
username: postgres
password: pg123456
initial-size: 3
min-idle: 3
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,slf4j
# ============ 數(shù)據(jù)源3:阿里云 RDS MySQL ============
third:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://rm-bp1xxxxxxxxxxxx.mysql.rds.aliyuncs.com:3306/db_cloud?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
username: cloud_user
password: Cloud@2024!
initial-size: 10
min-idle: 10
max-active: 50
max-wait: 30000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,slf4j
keep-alive: true
phy-timeout-millis: 1800000
logging:
level:
com.example.mapper: debug1.3 啟動(dòng)類(lèi)
package com.example;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 關(guān)鍵:排除 Druid 自動(dòng)配置,否則會(huì)和手動(dòng)配置沖突
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
public class ManualDsApplication {
public static void main(String[] args) {
SpringApplication.run(ManualDsApplication.class, args);
}
}
1.4 數(shù)據(jù)源配置類(lèi)
PrimaryDataSourceConfig.java
package com.example.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(
basePackages = "com.example.mapper.primary",
sqlSessionFactoryRef = "primarySqlSessionFactory",
sqlSessionTemplateRef = "primarySqlSessionTemplate"
)
public class PrimaryDataSourceConfig {
@Primary
@Bean("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Primary
@Bean("primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource ds) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
org.apache.ibatis.session.Configuration cfg =
new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
cfg.setLogImpl(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
bean.setConfiguration(cfg);
return bean.getObject();
}
@Primary
@Bean("primaryTransactionManager")
public PlatformTransactionManager primaryTxManager(
@Qualifier("primaryDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Primary
@Bean("primarySqlSessionTemplate")
public SqlSessionTemplate primarySqlSessionTemplate(
@Qualifier("primarySqlSessionFactory") SqlSessionFactory sf) {
return new SqlSessionTemplate(sf);
}
}
SecondaryDataSourceConfig.java
package com.example.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(
basePackages = "com.example.mapper.secondary",
sqlSessionFactoryRef = "secondarySqlSessionFactory",
sqlSessionTemplateRef = "secondarySqlSessionTemplate"
)
public class SecondaryDataSourceConfig {
@Bean("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(
@Qualifier("secondaryDataSource") DataSource ds) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/secondary/*.xml"));
org.apache.ibatis.session.Configuration cfg =
new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
cfg.setLogImpl(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
bean.setConfiguration(cfg);
return bean.getObject();
}
@Bean("secondaryTransactionManager")
public PlatformTransactionManager secondaryTxManager(
@Qualifier("secondaryDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Bean("secondarySqlSessionTemplate")
public SqlSessionTemplate secondarySqlSessionTemplate(
@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sf) {
return new SqlSessionTemplate(sf);
}
}
ThirdDataSourceConfig.java
package com.example.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(
basePackages = "com.example.mapper.third",
sqlSessionFactoryRef = "thirdSqlSessionFactory",
sqlSessionTemplateRef = "thirdSqlSessionTemplate"
)
public class ThirdDataSourceConfig {
@Bean("thirdDataSource")
@ConfigurationProperties(prefix = "spring.datasource.third")
public DataSource thirdDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("thirdSqlSessionFactory")
public SqlSessionFactory thirdSqlSessionFactory(
@Qualifier("thirdDataSource") DataSource ds) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/third/*.xml"));
org.apache.ibatis.session.Configuration cfg =
new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
cfg.setLogImpl(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
bean.setConfiguration(cfg);
return bean.getObject();
}
@Bean("thirdTransactionManager")
public PlatformTransactionManager thirdTxManager(
@Qualifier("thirdDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Bean("thirdSqlSessionTemplate")
public SqlSessionTemplate thirdSqlSessionTemplate(
@Qualifier("thirdSqlSessionFactory") SqlSessionFactory sf) {
return new SqlSessionTemplate(sf);
}
}
1.5 Druid 監(jiān)控配置
package com.example.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DruidMonitorConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
Map<String, String> params = new HashMap<>();
params.put("loginUsername", "admin");
params.put("loginPassword", "admin123");
params.put("allow", "");
params.put("resetEnable", "false");
bean.setInitParameters(params);
return bean;
}
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
FilterRegistrationBean<WebStatFilter> bean =
new FilterRegistrationBean<>(new WebStatFilter());
bean.addUrlPatterns("/*");
bean.addInitParameter("exclusions", ".js,.gif,.jpg,.png,.css,.ico,/druid/*");
return bean;
}
}
1.6 Entity / Mapper / XML
Entity
package com.example.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String username;
private String email;
private LocalDateTime createTime;
}
@Data
public class Order {
private Long id;
private Long userId;
private String orderNo;
private java.math.BigDecimal amount;
private Integer status;
private LocalDateTime createTime;
}
@Data
public class Product {
private Long id;
private String productName;
private java.math.BigDecimal price;
private Integer stock;
}
Mapper 接口
package com.example.mapper.primary;
import com.example.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
List<User> selectAll();
User selectById(@Param("id") Long id);
int insert(User user);
}
package com.example.mapper.secondary;
import com.example.entity.Order;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface OrderMapper {
List<Order> selectAll();
List<Order> selectByUserId(@Param("userId") Long userId);
int insert(Order order);
}
package com.example.mapper.third;
import com.example.entity.Product;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ProductMapper {
List<Product> selectAll();
Product selectById(@Param("id") Long id);
int insert(Product product);
}
Mapper XML
resources/mapper/primary/UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.primary.UserMapper">
<select id="selectAll" resultType="com.example.entity.User">
SELECT id, username, email, create_time FROM t_user
</select>
<select id="selectById" resultType="com.example.entity.User">
SELECT id, username, email, create_time FROM t_user WHERE id = #{id}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user(username, email, create_time)
VALUES(#{username}, #{email}, NOW())
</insert>
</mapper>resources/mapper/secondary/OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.secondary.OrderMapper">
<select id="selectAll" resultType="com.example.entity.Order">
SELECT id, user_id, order_no, amount, status, create_time FROM t_order
</select>
<select id="selectByUserId" resultType="com.example.entity.Order">
SELECT id, user_id, order_no, amount, status, create_time
FROM t_order WHERE user_id = #{userId}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_order(user_id, order_no, amount, status, create_time)
VALUES(#{userId}, #{orderNo}, #{amount}, #{status}, NOW())
</insert>
</mapper>resources/mapper/third/ProductMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.third.ProductMapper">
<select id="selectAll" resultType="com.example.entity.Product">
SELECT id, product_name, price, stock FROM t_product
</select>
<select id="selectById" resultType="com.example.entity.Product">
SELECT id, product_name, price, stock FROM t_product WHERE id = #{id}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_product(product_name, price, stock)
VALUES(#{productName}, #{price}, #{stock})
</insert>
</mapper>1.7 Service & Controller
package com.example.service;
import com.example.entity.;
import com.example.mapper.primary.UserMapper;
import com.example.mapper.secondary.OrderMapper;
import com.example.mapper.third.ProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class BusinessService {
@Autowired
private UserMapper userMapper;
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
public List<User> getUsers() { return userMapper.selectAll(); }
public List<Order> getOrders() { return orderMapper.selectAll(); }
public List<Product> getProducts() { return productMapper.selectAll(); }
@Transactional(transactionManager = "primaryTransactionManager")
public void addUser(User user) { userMapper.insert(user); }
@Transactional(transactionManager = "secondaryTransactionManager")
public void addOrder(Order order) { orderMapper.insert(order); }
@Transactional(transactionManager = "thirdTransactionManager")
public void addProduct(Product product) { productMapper.insert(product); }
}
package com.example.controller;
import com.example.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.;
import java.util.;
@RestController
@RequestMapping("/api")
public class TestController {
@Autowired
private BusinessService service;
@GetMapping("/test")
public Map<String, Object> test() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("本地MySQL_用戶", service.getUsers());
result.put("PostgreSQL_訂單", service.getOrders());
result.put("阿里云RDS_商品", service.getProducts());
return result;
}
}
1.8 建表 SQL
-- ========== 本地 MySQL: db_primary ==========
CREATE DATABASE IF NOT EXISTS db_primary DEFAULT CHARSET utf8mb4;
USE db_primary;
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO t_user(username, email) VALUES('張三','zhangsan@test.com');
INSERT INTO t_user(username, email) VALUES('李四','lisi@test.com');
-- ========== PostgreSQL: db_secondary ==========
-- CREATE DATABASE db_secondary;
CREATE TABLE t_order (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
order_no VARCHAR(64) NOT NULL,
amount NUMERIC(10,2) DEFAULT 0,
status INT DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO t_order(user_id, order_no, amount, status)
VALUES(1, 'ORD20240101001', 99.90, 1);
INSERT INTO t_order(user_id, order_no, amount, status)
VALUES(2, 'ORD20240101002', 199.00, 0);
-- ========== 阿里云 RDS MySQL: db_cloud ==========
CREATE DATABASE IF NOT EXISTS db_cloud DEFAULT CHARSET utf8mb4;
USE db_cloud;
CREATE TABLE t_product (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) DEFAULT 0,
stock INT DEFAULT 0
);
INSERT INTO t_product(product_name, price, stock) VALUES('iPhone 15', 7999.00, 100);
INSERT INTO t_product(product_name, price, stock) VALUES('MacBook Pro', 14999.00, 50);1.9 驗(yàn)證
啟動(dòng)后訪問(wèn)
curl http://localhost:8080/api/test
Druid 監(jiān)控
瀏覽器打開(kāi) http://localhost:8080/druid/
賬號(hào): admin 密碼: admin123
方案二:AbstractRoutingDataSource + Druid(動(dòng)態(tài)切換)
適用場(chǎng)景:運(yùn)行時(shí)根據(jù)請(qǐng)求參數(shù)/注解動(dòng)態(tài)切換數(shù)據(jù)源(多租戶)
2.1 pom.xml
與方案一完全相同,不再重復(fù)。
2.2 application.yml
server:
port: 8081
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_primary?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root123456
initial-size: 5
min-idle: 5
max-active: 30
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
filters: stat,wall,slf4j
secondary:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/db_secondary?currentSchema=public
username: postgres
password: pg123456
initial-size: 3
min-idle: 3
max-active: 20
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
filters: stat,wall,slf4j
third:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://rm-bp1xxxxxxxxxxxx.mysql.rds.aliyuncs.com:3306/db_cloud?useSSL=true&serverTimezone=Asia/Shanghai
username: cloud_user
password: Cloud@2024!
initial-size: 5
min-idle: 5
max-active: 40
max-wait: 30000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
filters: stat,wall,slf4j
keep-alive: true
logging:
level:
com.example: debug2.3 啟動(dòng)類(lèi)
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
public class DynamicDsApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDsApplication.class, args);
}
}
2.4 數(shù)據(jù)源上下文持有者
package com.example.dynamic;
public class DataSourceContextHolder {
public static final String PRIMARY = "primary";
public static final String SECONDARY = "secondary";
public static final String THIRD = "third";
private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();
public static void set(String ds) {
HOLDER.set(ds);
}
public static String get() {
return HOLDER.get();
}
public static void clear() {
HOLDER.remove();
}
}
2.5 動(dòng)態(tài)路由數(shù)據(jù)源
package com.example.dynamic;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.get();
}
}
2.6 數(shù)據(jù)源配置類(lèi)
package com.example.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import com.example.dynamic.DataSourceContextHolder;
import com.example.dynamic.DynamicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DynamicDataSourceConfig {
@Bean("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("thirdDataSource")
@ConfigurationProperties(prefix = "spring.datasource.third")
public DataSource thirdDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Primary
@Bean("dynamicDataSource")
public DataSource dynamicDataSource(
@Qualifier("primaryDataSource") DataSource primary,
@Qualifier("secondaryDataSource") DataSource secondary,
@Qualifier("thirdDataSource") DataSource third) {
DynamicDataSource dynamicDs = new DynamicDataSource();
Map<Object, Object> targetMap = new HashMap<>();
targetMap.put(DataSourceContextHolder.PRIMARY, primary);
targetMap.put(DataSourceContextHolder.SECONDARY, secondary);
targetMap.put(DataSourceContextHolder.THIRD, third);
dynamicDs.setTargetDataSources(targetMap);
dynamicDs.setDefaultTargetDataSource(primary); // 默認(rèn)走 primary
return dynamicDs;
}
}
2.7 MyBatis 配置(只需一個(gè) SqlSessionFactory)
package com.example.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(
@Qualifier("dynamicDataSource") DataSource ds) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*/*.xml"));
org.apache.ibatis.session.Configuration cfg =
new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
cfg.setLogImpl(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
bean.setConfiguration(cfg);
return bean.getObject();
}
@Bean
public PlatformTransactionManager transactionManager(
@Qualifier("dynamicDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}
2.8 自定義注解 @DS
package com.example.dynamic;
import java.lang.annotation.;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
String value() default DataSourceContextHolder.PRIMARY;
}
2.9 AOP 切面
package com.example.dynamic;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
@Order(-1) // 必須在 @Transactional 之前執(zhí)行
public class DataSourceAspect {
@Around("@annotation(com.example.dynamic.DS) || @within(com.example.dynamic.DS)")
public Object around(ProceedingJoinPoint point) throws Throwable {
String dsKey = determineDatasource(point);
try {
DataSourceContextHolder.set(dsKey);
return point.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
private String determineDatasource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 優(yōu)先取方法上的注解
DS ds = method.getAnnotation(DS.class);
if (ds != null) {
return ds.value();
}
// 再取類(lèi)上的注解
ds = point.getTarget().getClass().getAnnotation(DS.class);
if (ds != null) {
return ds.value();
}
return DataSourceContextHolder.PRIMARY;
}
}
2.10 Mapper(所有放同一個(gè)包)
package com.example.mapper;
import com.example.entity.User;
import java.util.List;
public interface UserMapper {
List<User> selectAll();
}
package com.example.mapper;
import com.example.entity.Order;
import java.util.List;
public interface OrderMapper {
List<Order> selectAll();
}
package com.example.mapper;
import com.example.entity.Product;
import java.util.List;
public interface ProductMapper {
List<Product> selectAll();
}
2.11 Service 使用
package com.example.service;
import com.example.dynamic.DS;
import com.example.dynamic.DataSourceContextHolder;
import com.example.entity.;
import com.example.mapper.;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DynamicService {
@Autowired
private UserMapper userMapper;
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
// 方式1:注解切換
@DS(DataSourceContextHolder.PRIMARY)
public List<User> getUsers() {
return userMapper.selectAll();
}
@DS(DataSourceContextHolder.SECONDARY)
public List<Order> getOrders() {
return orderMapper.selectAll();
}
@DS(DataSourceContextHolder.THIRD)
public List<Product> getProducts() {
return productMapper.selectAll();
}
// 方式2:手動(dòng)切換(適合復(fù)雜邏輯)
public Object queryBySource(String source) {
try {
DataSourceContextHolder.set(source);
if ("primary".equals(source)) return userMapper.selectAll();
if ("secondary".equals(source)) return orderMapper.selectAll();
if ("third".equals(source)) return productMapper.selectAll();
return "未知數(shù)據(jù)源";
} finally {
DataSourceContextHolder.clear();
}
}
}
2.12 Controller
package com.example.controller;
import com.example.service.DynamicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.;
import java.util.;
@RestController
@RequestMapping("/api")
public class DynamicController {
@Autowired
private DynamicService service;
@GetMapping("/all")
public Map<String, Object> all() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("primary_用戶", service.getUsers());
map.put("secondary_訂單", service.getOrders());
map.put("third_商品", service.getProducts());
return map;
}
@GetMapping("/switch")
public Object switchDs(@RequestParam String source) {
return service.queryBySource(source);
}
}
2.13 驗(yàn)證
curl http://localhost:8081/api/all
curl http://localhost:8081/api/switch?source=primary
curl http://localhost:8081/api/switch?source=secondary
curl http://localhost:8081/api/switch?source=third
方案三:dynamic-datasource(苞米豆)+ Druid
適用場(chǎng)景:快速集成,注解驅(qū)動(dòng),最省心
3.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<groupId>com.example</groupId>
<artifactId>multi-ds-dynamic</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus(dynamic-datasource 最佳搭檔) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 多數(shù)據(jù)源核心 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.3.0</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>3.2 application.yml
server:
port: 8082
spring:
datasource:
dynamic:
# 默認(rèn)數(shù)據(jù)源
primary: master
# 嚴(yán)格模式(未匹配到數(shù)據(jù)源直接報(bào)錯(cuò))
strict: false
# 懶加載(按需初始化數(shù)據(jù)源)
lazy: true
# ========== 全局 Druid 配置 ==========
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,slf4j
# 監(jiān)控
stat-view-servlet:
enabled: true
url-pattern: /druid/
login-username: admin
login-password: admin123
allow: ""
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: ".js,.gif,.jpg,.png,.css,.ico,/druid/*"
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 2000
merge-sql: true
wall:
enabled: true
config:
multi-statement-allow: true
# ========== 數(shù)據(jù)源定義 ==========
datasource:
# 本地 MySQL
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_primary?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root123456
druid:
initial-size: 10
max-active: 50
# PostgreSQL
postgres:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/db_secondary?currentSchema=public
username: postgres
password: pg123456
druid:
initial-size: 3
max-active: 15
# 阿里云 RDS MySQL
aliyun:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://rm-bp1xxxxxxxxxxxx.mysql.rds.aliyuncs.com:3306/db_cloud?useSSL=true&serverTimezone=Asia/Shanghai
username: cloud_user
password: Cloud@2024!
druid:
initial-size: 10
max-active: 50
test-on-borrow: true
keep-alive: true
MyBatis-Plus
mybatis-plus:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
com.baomidou.dynamic: debug
3.3 啟動(dòng)類(lèi)
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mapper")
public class DynamicDsApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDsApplication.class, args);
}
}
3.4 Entity(MyBatis-Plus 風(fēng)格)
package com.example.entity;
import com.baomidou.mybatisplus.annotation.;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String email;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
@Data
@TableName("t_order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private String orderNo;
private java.math.BigDecimal amount;
private Integer status;
private LocalDateTime createTime;
}
@Data
@TableName("t_product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String productName;
private java.math.BigDecimal price;
private Integer stock;
}
3.5 Mapper
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
public interface UserMapper extends BaseMapper<User> {
}
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.Order;
public interface OrderMapper extends BaseMapper<Order> {
}
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.Product;
public interface ProductMapper extends BaseMapper<Product> {
}
3.6 Service(核心:@DS 注解)
package com.example.service;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.;
import com.example.mapper.;
import org.springframework.stereotype.Service;
import java.util.List;
// ========== 用戶服務(wù) → master(本地MySQL) ==========
@Service
@DS("master")
public class UserService extends ServiceImpl<UserMapper, User> {
public List<User> listAll() {
return this.list();
}
}
// ========== 訂單服務(wù) → postgres ==========
@Service
@DS("postgres")
public class OrderService extends ServiceImpl<OrderMapper, Order> {
public List<Order> listAll() {
return this.list();
}
public List<Order> listByUserId(Long userId) {
return this.lambdaQuery().eq(Order::getUserId, userId).list();
}
}
// ========== 商品服務(wù) → aliyun ==========
@Service
@DS("aliyun")
public class ProductService extends ServiceImpl<ProductMapper, Product> {
public List<Product> listAll() {
return this.list();
}
}
方法級(jí)別切換(更細(xì)粒度)
package com.example.service;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MixedService {
@Autowired
private UserMapper userMapper;
// 默認(rèn)走 master
public List<User> getUsers() {
return userMapper.selectList(null);
}
// 方法級(jí)別切換到 postgres
@DS("postgres")
public List<User> getUsersFromPostgres() {
return userMapper.selectList(null);
}
// 方法級(jí)別切換到 aliyun
@DS("aliyun")
public List<User> getUsersFromAliyun() {
return userMapper.selectList(null);
}
}
3.7 Controller
package com.example.controller;
import com.example.service.;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.;
import java.util.;
@RestController
@RequestMapping("/api")
public class TestController {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
@Autowired
private ProductService productService;
@GetMapping("/all")
public Map<String, Object> all() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("master_本地MySQL", userService.listAll());
map.put("postgres_PG", orderService.listAll());
map.put("aliyun_阿里云", productService.listAll());
return map;
}
@PostMapping("/user")
public boolean addUser(@RequestBody com.example.entity.User user) {
return userService.save(user);
}
@GetMapping("/orders/{userId}")
public Object orders(@PathVariable Long userId) {
return orderService.listByUserId(userId);
}
}
3.8 驗(yàn)證
curl http://localhost:8082/api/all
curl -X POST http://localhost:8082/api/user
-H “Content-Type: application/json”
-d ‘{“username”:“王五”,“email”:“wangwu@test.com”}’
Druid 監(jiān)控
http://localhost:8082/druid/
方案四:JPA 多數(shù)據(jù)源 + Druid
適用場(chǎng)景:使用 JPA/Hibernate 的項(xiàng)目
4.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<groupId>com.example</groupId>
<artifactId>multi-ds-jpa</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>4.2 application.yml
server:
port: 8083
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# 本地 MySQL
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_primary?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root123456
initial-size: 5
min-idle: 5
max-active: 30
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
filters: stat,wall,slf4j
# PostgreSQL
secondary:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/db_secondary
username: postgres
password: pg123456
initial-size: 3
min-idle: 3
max-active: 20
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
filters: stat,wall,slf4j
# 阿里云 RDS
third:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://rm-bp1xxxxxxxxxxxx.mysql.rds.aliyuncs.com:3306/db_cloud?useSSL=true&serverTimezone=Asia/Shanghai
username: cloud_user
password: Cloud@2024!
initial-size: 5
min-idle: 5
max-active: 40
max-wait: 30000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
filters: stat,wall,slf4j
keep-alive: true
jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate:
format_sql: true
logging:
level:
org.hibernate.SQL: debug4.3 啟動(dòng)類(lèi)
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
public class JpaMultiDsApplication {
public static void main(String[] args) {
SpringApplication.run(JpaMultiDsApplication.class, args);
}
}
4.4 JPA 數(shù)據(jù)源配置
PrimaryJpaConfig.java(本地 MySQL)
package com.example.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.repository.primary",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryJpaConfig {
@Primary
@Bean("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Primary
@Bean("primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("primaryDataSource") DataSource ds) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
return builder
.dataSource(ds)
.packages("com.example.entity.primary") // Entity 掃描包
.persistenceUnit("primary")
.properties(properties)
.build();
}
@Primary
@Bean("primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
SecondaryJpaConfig.java(PostgreSQL)
package com.example.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.repository.secondary",
entityManagerFactoryRef = "secondaryEntityManagerFactory",
transactionManagerRef = "secondaryTransactionManager"
)
public class SecondaryJpaConfig {
@Bean("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("secondaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("secondaryDataSource") DataSource ds) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.show_sql", "true");
return builder
.dataSource(ds)
.packages("com.example.entity.secondary")
.persistenceUnit("secondary")
.properties(properties)
.build();
}
@Bean("secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(
@Qualifier("secondaryEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
ThirdJpaConfig.java(阿里云 RDS)
package com.example.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.repository.third",
entityManagerFactoryRef = "thirdEntityManagerFactory",
transactionManagerRef = "thirdTransactionManager"
)
public class ThirdJpaConfig {
@Bean("thirdDataSource")
@ConfigurationProperties(prefix = "spring.datasource.third")
public DataSource thirdDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean("thirdEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean thirdEntityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("thirdDataSource") DataSource ds) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.show_sql", "true");
return builder
.dataSource(ds)
.packages("com.example.entity.third")
.persistenceUnit("third")
.properties(properties)
.build();
}
@Bean("thirdTransactionManager")
public PlatformTransactionManager thirdTransactionManager(
@Qualifier("thirdEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
4.5 Entity(按包隔離)
com.example.entity.primary.User
package com.example.entity.primary;
import jakarta.persistence.;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String username;
@Column(length = 100)
private String email;
@Column(name = "create_time")
private LocalDateTime createTime;
@PrePersist
public void prePersist() {
this.createTime = LocalDateTime.now();
}
}
com.example.entity.secondary.Order
package com.example.entity.secondary;
import jakarta.persistence.;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "order_no", nullable = false, length = 64)
private String orderNo;
@Column(precision = 10, scale = 2)
private BigDecimal amount;
private Integer status;
@Column(name = "create_time")
private LocalDateTime createTime;
@PrePersist
public void prePersist() {
this.createTime = LocalDateTime.now();
}
}
com.example.entity.third.Product
package com.example.entity.third;
import jakarta.persistence.;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Entity
@Table(name = "t_product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "product_name", nullable = false, length = 200)
private String productName;
@Column(precision = 10, scale = 2)
private BigDecimal price;
private Integer stock;
}
4.6 Repository(按包隔離)
com.example.repository.primary.UserRepository
package com.example.repository.primary;
import com.example.entity.primary.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByUsernameContaining(String keyword);
}
com.example.repository.secondary.OrderRepository
package com.example.repository.secondary;
import com.example.entity.secondary.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByUserId(Long userId);
List<Order> findByStatus(Integer status);
}
com.example.repository.third.ProductRepository
package com.example.repository.third;
import com.example.entity.third.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Modifying
@Query("UPDATE Product p SET p.stock = :stock WHERE p.id = :id")
int updateStock(@Param("id") Long id, @Param("stock") Integer stock);
}
4.7 Service
package com.example.service;
import com.example.entity.primary.User;
import com.example.entity.secondary.Order;
import com.example.entity.third.Product;
import com.example.repository.primary.UserRepository;
import com.example.repository.secondary.OrderRepository;
import com.example.repository.third.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class JpaBusinessService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductRepository productRepository;
// ===== 本地 MySQL =====
public List<User> getAllUsers() {
return userRepository.findAll();
}
@Transactional(transactionManager = "primaryTransactionManager")
public User saveUser(User user) {
return userRepository.save(user);
}
// ===== PostgreSQL =====
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
@Transactional(transactionManager = "secondaryTransactionManager")
public Order saveOrder(Order order) {
return orderRepository.save(order);
}
// ===== 阿里云 RDS =====
public List<Product> getAllProducts() {
return productRepository.findAll();
}
@Transactional(transactionManager = "thirdTransactionManager")
public void updateStock(Long productId, Integer stock) {
productRepository.updateStock(productId, stock);
}
}
4.8 Controller
package com.example.controller;
import com.example.entity.primary.User;
import com.example.service.JpaBusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.;
import java.util.*;
@RestController
@RequestMapping("/api")
public class JpaTestController {
@Autowired
private JpaBusinessService service;
@GetMapping("/all")
public Map<String, Object> all() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("primary_MySQL用戶", service.getAllUsers());
map.put("secondary_PG訂單", service.getOrdersByUserId(1L));
map.put("third_阿里云商品", service.getAllProducts());
return map;
}
@PostMapping("/user")
public User addUser(@RequestBody User user) {
return service.saveUser(user);
}
}
4.9 驗(yàn)證
curl http://localhost:8083/api/all
curl -X POST http://localhost:8083/api/user
-H “Content-Type: application/json”
-d ‘{“username”:“趙六”,“email”:“zhaoliu@test.com”}’
四方案對(duì)比總結(jié)
| 維度 | 方案一:手動(dòng)配置 | 方案二:動(dòng)態(tài)路由 | 方案三:dynamic-datasource | 方案四:JPA |
|---|---|---|---|---|
| 切換方式 | 包路徑隔離 | 注解/手動(dòng) ThreadLocal | @DS 注解 | 包路徑隔離 |
| 數(shù)據(jù)源數(shù)量 | 固定,編譯時(shí)確定 | 可動(dòng)態(tài)增減 | 可動(dòng)態(tài)增減 | 固定 |
| 代碼侵入 | 低 | 中(需寫(xiě)AOP) | 極低 | 低 |
| 事務(wù)管理 | 每個(gè)數(shù)據(jù)源獨(dú)立 TM | 單個(gè) TM | 自動(dòng)處理 | 每個(gè)數(shù)據(jù)源獨(dú)立 TM |
| Druid 監(jiān)控 | 手動(dòng)注冊(cè) | 自動(dòng) | 自動(dòng)(內(nèi)置) | 手動(dòng)注冊(cè) |
| 學(xué)習(xí)成本 | ?? | ??? | ? | ??? |
| 推薦場(chǎng)景 | 固定2~3個(gè)庫(kù) | 多租戶/SaaS | 通用(首選)? | JPA 技術(shù)棧 |
通用注意事項(xiàng)
1. 排除自動(dòng)配置
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
多數(shù)據(jù)源時(shí)必須排除,否則 Druid 會(huì)嘗試自動(dòng)創(chuàng)建單個(gè)數(shù)據(jù)源
2. 事務(wù)不能跨庫(kù)
一個(gè) @Transactional 只能管一個(gè)數(shù)據(jù)源
跨庫(kù)一致性 → Seata / 消息最終一致性
3. 阿里云 RDS 注意
- 開(kāi)啟 SSL:useSSL=true
- 開(kāi)啟 test-on-borrow(網(wǎng)絡(luò)抖動(dòng)多)
- 開(kāi)啟 keep-alive(防止連接被 RDS 防火墻斷開(kāi))
- 白名單配置你的服務(wù)器 IP
4. PostgreSQL 注意
- validation-query 用 SELECT 1
- 注意 schema 配置:currentSchema=public
- 自增用 BIGSERIAL / IDENTITY
5. Druid 監(jiān)控安全
- 生產(chǎn)環(huán)境限制 allow IP
- 修改默認(rèn)賬號(hào)密碼
- 關(guān)閉 resetEnable
以上就是SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源配置的四種方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot多數(shù)據(jù)源配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中關(guān)于http請(qǐng)求獲取FlexManager某設(shè)備分組監(jiān)控點(diǎn)
這篇文章主要介紹了Java中關(guān)于http請(qǐng)求獲取FlexManager某設(shè)備分組監(jiān)控點(diǎn),本文僅僅介紹了使用http請(qǐng)求獲取FlexManager平臺(tái)某個(gè)FBox盒子即某設(shè)備的監(jiān)控點(diǎn)分組的分組下的所有監(jiān)控點(diǎn)信息,需要的朋友可以參考下2022-10-10
idea中運(yùn)行Application時(shí)報(bào)錯(cuò),命令行過(guò)長(zhǎng)問(wèn)題及解決
本文主要描述了在IntelliJIDEA中編譯并運(yùn)行Java項(xiàng)目時(shí),因命令行長(zhǎng)度限制導(dǎo)致的“命令行行長(zhǎng)”錯(cuò)誤,并給出了具體解決方案,即修改IDEA的運(yùn)行配置文件,選擇“縮短命令行”并選擇“jar清單”選項(xiàng),最后保存設(shè)置重新運(yùn)行2026-05-05
SpringBoot集成ENC對(duì)配置文件進(jìn)行加密的流程步驟
Spring Boot Encoder,即Spring Boot加密模塊,它提供了一種簡(jiǎn)單的方式來(lái)集成安全編碼功能到Spring Boot應(yīng)用程序中,它是Spring Security框架的一部分,旨在幫助開(kāi)發(fā)者輕松地處理數(shù)據(jù)加密,本文給大家介紹了SpringBoot集成ENC對(duì)配置文件進(jìn)行加密的流程步驟2024-12-12
IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫(kù)
這篇文章介紹了IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫(kù)的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-01-01
Java添加事件監(jiān)聽(tīng)的四種方法代碼實(shí)例
這篇文章主要介紹了Java添加事件監(jiān)聽(tīng)的四種方法代碼實(shí)例,本文直接給出代碼示例,并用注釋說(shuō)明,需要的朋友可以參考下2014-09-09
值得收藏的2017年Java開(kāi)發(fā)崗位面試題
這篇文章主要為大家推薦一份值得收藏的2017年Java開(kāi)發(fā)崗位面試題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
淺談java反射和自定義注解的綜合應(yīng)用實(shí)例
本篇文章主要介紹了java反射和自定義注解的綜合應(yīng)用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
使用Jenkins來(lái)構(gòu)建GIT+Maven項(xiàng)目的方法步驟
這篇文章主要介紹了使用Jenkins來(lái)構(gòu)建GIT+Maven項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01

