最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源配置的四種方案

 更新時(shí)間:2026年07月26日 14:33:40   作者:夢(mèng)境之冢  
還在為Spring?Boot多數(shù)據(jù)源配置發(fā)愁,本文詳細(xì)對(duì)比四種主流方案:手動(dòng)配置、動(dòng)態(tài)路由、苞米豆dynamic-datasource和JPA多數(shù)據(jù)源,涵蓋Druid監(jiān)控集成,無(wú)論你是固定數(shù)據(jù)源還是多租戶動(dòng)態(tài)切換,都能找到最適合的實(shí)踐方法,需要的朋友可以參考下

數(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: debug

1.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: debug

2.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: debug

4.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)

    這篇文章主要介紹了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)題及解決

    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)行加密的流程步驟

    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
  • java枚舉enum和Enum類(lèi)的使用

    java枚舉enum和Enum類(lèi)的使用

    本文主要介紹了java枚舉enum和Enum類(lèi)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • IDEA使用JDBC安裝配置jar包連接MySQL數(shù)據(jù)庫(kù)

    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í)例

    這篇文章主要介紹了Java添加事件監(jiān)聽(tīng)的四種方法代碼實(shí)例,本文直接給出代碼示例,并用注釋說(shuō)明,需要的朋友可以參考下
    2014-09-09
  • 值得收藏的2017年Java開(kāi)發(fā)崗位面試題

    值得收藏的2017年Java開(kāi)發(fā)崗位面試題

    這篇文章主要為大家推薦一份值得收藏的2017年Java開(kāi)發(fā)崗位面試題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • 淺談java反射和自定義注解的綜合應(yīng)用實(shí)例

    淺談java反射和自定義注解的綜合應(yīng)用實(shí)例

    本篇文章主要介紹了java反射和自定義注解的綜合應(yīng)用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • Java使用二分法進(jìn)行查找和排序的示例

    Java使用二分法進(jìn)行查找和排序的示例

    這篇文章主要介紹了Java使用二分法進(jìn)行查找和排序的示例,二分插入排序和二分查找是基礎(chǔ)的算法,需要的朋友可以參考下
    2016-04-04
  • 使用Jenkins來(lái)構(gòu)建GIT+Maven項(xiàng)目的方法步驟

    使用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

最新評(píng)論

桂阳县| 时尚| 亳州市| 东宁县| 高邮市| 高要市| 建瓯市| 塔城市| 庆安县| 嘉荫县| 博兴县| 阿克| 辽阳市| 阳泉市| 桦南县| 慈溪市| 固原市| 建始县| 曲沃县| 梓潼县| 屏南县| 临夏县| 闸北区| 大化| 上蔡县| 固原市| 禹城市| 奈曼旗| 葵青区| 桐城市| 石屏县| 海原县| 健康| 盘锦市| 高淳县| 德保县| 西充县| 江津市| 安塞县| 岐山县| 临城县|