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

SpringBoot實(shí)現(xiàn)整合多數(shù)據(jù)源的全攻略

 更新時(shí)間:2026年03月13日 08:55:46   作者:我可能在學(xué)習(xí)如何寫bug  
在實(shí)際開發(fā)中,單數(shù)據(jù)源往往無法滿足復(fù)雜的業(yè)務(wù)場景,本文將從實(shí)際業(yè)務(wù)需求出發(fā),拆解?SpringBoot?中多數(shù)據(jù)源的核心實(shí)現(xiàn)方式,并附上可直接運(yùn)行的代碼示例,需要的可以了解下

在實(shí)際開發(fā)中,單數(shù)據(jù)源往往無法滿足復(fù)雜的業(yè)務(wù)場景 —— 比如讀寫分離、分庫分表、不同業(yè)務(wù)模塊對接不同數(shù)據(jù)庫等。SpringBoot 作為主流的開發(fā)框架,提供了多種多數(shù)據(jù)源整合方案,從簡單的靜態(tài)切換到靈活的動態(tài)路由,每種方案都有其適用場景。本文將從實(shí)際業(yè)務(wù)需求出發(fā),拆解 SpringBoot 中多數(shù)據(jù)源的核心實(shí)現(xiàn)方式,并附上可直接運(yùn)行的代碼示例。

一、多數(shù)據(jù)源核心場景與技術(shù)選型

先明確多數(shù)據(jù)源的常見使用場景,避免盲目選型:

  • 靜態(tài)多數(shù)據(jù)源:不同業(yè)務(wù)模塊固定對接不同數(shù)據(jù)庫(如訂單庫、用戶庫),啟動時(shí)加載,運(yùn)行時(shí)不切換;
  • 動態(tài)切換數(shù)據(jù)源:運(yùn)行時(shí)根據(jù)條件(如用戶 ID、業(yè)務(wù)標(biāo)識)動態(tài)選擇數(shù)據(jù)源(如讀寫分離、分庫);
  • 分布式事務(wù):多數(shù)據(jù)源操作需保證事務(wù)一致性(本文暫不展開,后續(xù)單獨(dú)講解)。

核心依賴(基于 SpringBoot 2.7.x):

<dependencies>
    <!-- SpringBoot核心 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 數(shù)據(jù)庫連接 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    <!-- 數(shù)據(jù)庫驅(qū)動(以MySQL為例) -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- 連接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.16</version>
    </dependency>
    <!-- 測試 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

二、方案 1:靜態(tài)多數(shù)據(jù)源(基于配置類分離)

適用場景

不同業(yè)務(wù)模塊完全隔離,比如「用戶模塊」對接 user_db,「訂單模塊」對接 order_db,運(yùn)行時(shí)無需切換數(shù)據(jù)源。

實(shí)現(xiàn)步驟

配置文件(application.yml)

spring:
  datasource:
    # 數(shù)據(jù)源1:用戶庫
    user:
      url: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
    # 數(shù)據(jù)源2:訂單庫
    order:
      url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.demo.entity
  configuration:
    map-underscore-to-camel-case: true

數(shù)據(jù)源配置類

分別配置兩個(gè)數(shù)據(jù)源的 Bean,指定不同的掃描路徑:

用戶數(shù)據(jù)源配置(UserDataSourceConfig.java):

package com.example.demo.config;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
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.boot.jdbc.DataSourceBuilder;
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 javax.sql.DataSource;
/**
 * 用戶庫數(shù)據(jù)源配置
 * 掃描user模塊的mapper
 */
@Configuration
@MapperScan(basePackages = "com.example.demo.mapper.user", sqlSessionTemplateRef = "userSqlSessionTemplate")
public class UserDataSourceConfig {
    /**
     * 配置用戶庫數(shù)據(jù)源
     */
    @Bean(name = "userDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.user")
    @Primary // 主數(shù)據(jù)源(必須指定一個(gè)主數(shù)據(jù)源)
    public DataSource userDataSource() {
        return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
    }
    /**
     * 配置用戶庫SqlSessionFactory
     */
    @Bean(name = "userSqlSessionFactory")
    @Primary
    public SqlSessionFactory userSqlSessionFactory(@Qualifier("userDataSource") DataSource dataSource,
                                                   MybatisPlusInterceptor interceptor) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        // 配置MyBatis-Plus
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        sqlSessionFactory.setConfiguration(configuration);
        // 分頁插件(可選)
        sqlSessionFactory.setPlugins(interceptor);
        // Mapper文件路徑
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/user/*.xml"));
        return sqlSessionFactory.getObject();
    }
    /**
     * 配置用戶庫SqlSessionTemplate
     */
    @Bean(name = "userSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate userSqlSessionTemplate(@Qualifier("userSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

訂單數(shù)據(jù)源配置(OrderDataSourceConfig.java):

package com.example.demo.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
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.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/**
 * 訂單庫數(shù)據(jù)源配置
 * 掃描order模塊的mapper
 */
@Configuration
@MapperScan(basePackages = "com.example.demo.mapper.order", sqlSessionTemplateRef = "orderSqlSessionTemplate")
public class OrderDataSourceConfig {

    @Bean(name = "orderDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.order")
    public DataSource orderDataSource() {
        return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
    }

    @Bean(name = "orderSqlSessionFactory")
    public SqlSessionFactory orderSqlSessionFactory(@Qualifier("orderDataSource") DataSource dataSource,
                                                   MybatisPlusInterceptor interceptor) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setPlugins(interceptor);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/order/*.xml"));
        return sqlSessionFactory.getObject();
    }

    @Bean(name = "orderSqlSessionTemplate")
    public SqlSessionTemplate orderSqlSessionTemplate(@Qualifier("orderSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

業(yè)務(wù)代碼示例

用戶 Mapper(UserMapper.java):放在com.example.demo.mapper.user包下

package com.example.demo.mapper.user;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

訂單 Mapper(OrderMapper.java):放在com.example.demo.mapper.order包下

package com.example.demo.mapper.order;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.Order;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}

業(yè)務(wù)層調(diào)用:

package com.example.demo.service.impl;

import com.example.demo.entity.Order;
import com.example.demo.entity.User;
import com.example.demo.mapper.order.OrderMapper;
import com.example.demo.mapper.user.UserMapper;
import com.example.demo.service.DataSourceService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class DataSourceServiceImpl implements DataSourceService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private OrderMapper orderMapper;

    @Override
    public User getUserById(Long id) {
        // 自動使用用戶庫數(shù)據(jù)源
        return userMapper.selectById(id);
    }

    @Override
    public Order getOrderById(Long id) {
        // 自動使用訂單庫數(shù)據(jù)源
        return orderMapper.selectById(id);
    }
}

優(yōu)缺點(diǎn)

優(yōu)點(diǎn):配置簡單、無侵入性、性能高,適合模塊隔離的場景;

缺點(diǎn):無法動態(tài)切換,新增數(shù)據(jù)源需新增配置類,靈活性低。

三、方案 2:動態(tài)切換數(shù)據(jù)源(基于 AbstractRoutingDataSource)

適用場景

需要根據(jù)業(yè)務(wù)邏輯動態(tài)切換數(shù)據(jù)源,比如「讀寫分離」(讀庫 / 寫庫切換)、「分庫」(按用戶 ID 路由到不同庫)。

核心思路:繼承 Spring 提供的AbstractRoutingDataSource,重寫determineCurrentLookupKey方法,通過 ThreadLocal 存儲當(dāng)前線程的數(shù)據(jù)源標(biāo)識,實(shí)現(xiàn)動態(tài)路由。

實(shí)現(xiàn)步驟

配置文件(application.yml)

spring:
  datasource:
    # 主庫(寫)
    master:
      url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    # 從庫(讀)
    slave:
      url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

核心工具類

數(shù)據(jù)源上下文 Holder(DynamicDataSourceContextHolder.java):

package com.example.demo.config.dynamic;

/**
 * 數(shù)據(jù)源上下文 Holder,基于ThreadLocal存儲當(dāng)前線程的數(shù)據(jù)源標(biāo)識
 */
public class DynamicDataSourceContextHolder {

    /**
     * 線程本地變量:存儲當(dāng)前線程使用的數(shù)據(jù)源標(biāo)識
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 設(shè)置數(shù)據(jù)源標(biāo)識
     */
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    /**
     * 獲取數(shù)據(jù)源標(biāo)識
     */
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清除數(shù)據(jù)源標(biāo)識
     */
    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }
}

動態(tài)數(shù)據(jù)源路由類(DynamicRoutingDataSource.java):

package com.example.demo.config.dynamic;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 動態(tài)數(shù)據(jù)源路由:重寫determineCurrentLookupKey方法,返回當(dāng)前線程的數(shù)據(jù)源標(biāo)識
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // 從ThreadLocal中獲取當(dāng)前線程的數(shù)據(jù)源標(biāo)識
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

數(shù)據(jù)源配置類

package com.example.demo.config.dynamic;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 動態(tài)數(shù)據(jù)源配置
 */
@Configuration
@MapperScan(basePackages = "com.example.demo.mapper", sqlSessionFactoryRef = "dynamicSqlSessionFactory")
public class DynamicDataSourceConfig {

    /**
     * 配置主庫數(shù)據(jù)源
     */
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
    }

    /**
     * 配置從庫數(shù)據(jù)源
     */
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
    }

    /**
     * 配置動態(tài)數(shù)據(jù)源(核心)
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                       @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        // 1. 配置默認(rèn)數(shù)據(jù)源(主庫)
        dynamicRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        // 2. 配置所有數(shù)據(jù)源(key為數(shù)據(jù)源標(biāo)識,value為數(shù)據(jù)源)
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", masterDataSource);
        dataSourceMap.put("slave", slaveDataSource);
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
        return dynamicRoutingDataSource;
    }

    /**
     * 配置SqlSessionFactory
     */
    @Bean(name = "dynamicSqlSessionFactory")
    public SqlSessionFactory dynamicSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource,
                                                     MybatisPlusInterceptor interceptor) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/**/*.xml"));
        sqlSessionFactory.setPlugins(interceptor);
        return sqlSessionFactory.getObject();
    }
}

自定義注解 + AOP 實(shí)現(xiàn)自動切換

數(shù)據(jù)源注解(DataSource.java):

package com.example.demo.annotation;

import java.lang.annotation.*;

/**
 * 自定義數(shù)據(jù)源注解:標(biāo)注在方法/類上,指定使用的數(shù)據(jù)源
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
     * 數(shù)據(jù)源標(biāo)識,對應(yīng)dynamicDataSource中的key
     */
    String value() default "master";
}

AOP 切面(DataSourceAspect.java):

package com.example.demo.aspect;

import com.example.demo.annotation.DataSource;
import com.example.demo.config.dynamic.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 數(shù)據(jù)源切換切面:攔截@DataSource注解,設(shè)置對應(yīng)的數(shù)據(jù)源標(biāo)識
 */
@Aspect
@Component
@Order(-1) // 保證切面優(yōu)先級高于事務(wù)
public class DataSourceAspect {

    /**
     * 切入點(diǎn):攔截所有標(biāo)注@DataSource的方法/類
     */
    @Pointcut("@annotation(com.example.demo.annotation.DataSource) || @within(com.example.demo.annotation.DataSource)")
    public void dataSourcePointCut() {}

    /**
     * 環(huán)繞通知:切換數(shù)據(jù)源
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 1. 獲取注解中的數(shù)據(jù)源標(biāo)識
        String dataSourceKey = getDataSourceKey(point);
        // 2. 設(shè)置數(shù)據(jù)源標(biāo)識到ThreadLocal
        DynamicDataSourceContextHolder.setDataSourceKey(dataSourceKey);
        try {
            // 3. 執(zhí)行目標(biāo)方法
            return point.proceed();
        } finally {
            // 4. 清除數(shù)據(jù)源標(biāo)識,避免線程復(fù)用導(dǎo)致的問題
            DynamicDataSourceContextHolder.clearDataSourceKey();
        }
    }

    /**
     * 獲取方法/類上的數(shù)據(jù)源標(biāo)識
     */
    private String getDataSourceKey(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 優(yōu)先獲取方法上的注解
        DataSource methodAnnotation = method.getAnnotation(DataSource.class);
        if (methodAnnotation != null) {
            return methodAnnotation.value();
        }
        // 方法上沒有則獲取類上的注解
        Class<?> targetClass = point.getTarget().getClass();
        DataSource classAnnotation = targetClass.getAnnotation(DataSource.class);
        if (classAnnotation != null) {
            return classAnnotation.value();
        }
        // 默認(rèn)使用主庫
        return "master";
    }
}

業(yè)務(wù)代碼示例

package com.example.demo.service.impl;

import com.example.demo.annotation.DataSource;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    /**
     * 寫操作:使用主庫
     */
    @Override
    @DataSource("master")
    public void saveUser(User user) {
        userMapper.insert(user);
    }

    /**
     * 讀操作:使用從庫
     */
    @Override
    @DataSource("slave")
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}

優(yōu)缺點(diǎn)

優(yōu)點(diǎn):靈活性高,支持運(yùn)行時(shí)動態(tài)切換,可擴(kuò)展至 N 個(gè)數(shù)據(jù)源;

缺點(diǎn):需要手動管理 ThreadLocal,切面優(yōu)先級需高于事務(wù),否則切換失效。

四、方案 3:基于 MyBatis-Plus 插件(dynamic-datasource-spring-boot-starter)

適用場景

追求極簡配置,快速實(shí)現(xiàn)多數(shù)據(jù)源切換(推薦生產(chǎn)環(huán)境使用)。

dynamic-datasource-spring-boot-starter 是 MyBatis-Plus 團(tuán)隊(duì)提供的多數(shù)據(jù)源插件,封裝了 AbstractRoutingDataSource 的底層邏輯,支持注解、spel 表達(dá)式、分布式場景等,無需手動編寫切面和路由類。

實(shí)現(xiàn)步驟

引入依賴

<!-- 動態(tài)數(shù)據(jù)源插件(核心) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.6.1</version>
</dependency>

配置文件(application.yml)

spring:
  # 動態(tài)數(shù)據(jù)源配置
  dynamic:
    datasource:
      # 主庫(默認(rèn))
      master:
        url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      # 從庫1
      slave1:
        url: jdbc:mysql://localhost:3306/slave1_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      # 從庫2
      slave2:
        url: jdbc:mysql://localhost:3306/slave2_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
    # 配置默認(rèn)數(shù)據(jù)源
    primary: master
    # 配置Druid連接池
    type: com.alibaba.druid.pool.DruidDataSource

業(yè)務(wù)代碼示例

直接使用插件提供的@DS注解切換數(shù)據(jù)源:

package com.example.demo.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    /**
     * 默認(rèn)使用主庫(可不加注解)
     */
    @Override
    public void saveUser(User user) {
        userMapper.insert(user);
    }

    /**
     * 使用從庫1
     */
    @Override
    @DS("slave1")
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }

    /**
     * 使用從庫2
     */
    @Override
    @DS("slave2")
    public User getUserByPhone(String phone) {
        return userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
    }
}

進(jìn)階用法:spel 表達(dá)式動態(tài)路由

支持根據(jù)方法參數(shù)動態(tài)選擇數(shù)據(jù)源(比如按用戶 ID 取模分庫):

/**
 * 根據(jù)用戶ID取模,路由到不同從庫
 */
@Override
@DS("#{id % 2 == 0 ? 'slave1' : 'slave2'}")
public User getUserById(Long id) {
    return userMapper.selectById(id);
}

優(yōu)缺點(diǎn)

優(yōu)點(diǎn):零配置成本、功能豐富(支持讀寫分離、負(fù)載均衡、分布式鎖)、官方維護(hù);

缺點(diǎn):依賴第三方插件,深度定制化場景需二次開發(fā)。

五、方案對比與選型建議

方案優(yōu)點(diǎn)缺點(diǎn)適用場景
靜態(tài)多數(shù)據(jù)源(配置類)配置簡單、性能高、無侵入無法動態(tài)切換、擴(kuò)展性差模塊隔離的固定多數(shù)據(jù)源場景
自定義動態(tài)數(shù)據(jù)源高度自定義、靈活性高需手動編寫代碼、易出問題特殊定制化的動態(tài)切換場景
dynamic-datasource極簡配置、功能豐富、穩(wěn)定性高依賴第三方插件大部分生產(chǎn)環(huán)境(推薦)

六、注意事項(xiàng)

  • 事務(wù)問題:動態(tài)數(shù)據(jù)源切換需保證切面優(yōu)先級高于事務(wù)(@Order (-1)),否則事務(wù)內(nèi)切換數(shù)據(jù)源失效;
  • 線程安全:ThreadLocal 需在方法執(zhí)行完畢后清除,避免線程池復(fù)用導(dǎo)致數(shù)據(jù)源串用;
  • 連接池配置:多數(shù)據(jù)源場景下需合理配置連接池大小,避免連接耗盡;
  • 讀寫分離:從庫建議設(shè)置為只讀,避免寫入數(shù)據(jù)導(dǎo)致主從同步異常。

總結(jié)

SpringBoot 整合多數(shù)據(jù)源的核心思路是「數(shù)據(jù)源路由」,不同方案只是封裝程度不同。對于大部分開發(fā)者來說,優(yōu)先選擇 dynamic-datasource 插件,兼顧效率和穩(wěn)定性;特殊定制化場景可基于 AbstractRoutingDataSource 手動實(shí)現(xiàn);模塊隔離場景則用靜態(tài)多數(shù)據(jù)源即可。

本文所有代碼均可直接復(fù)制運(yùn)行,建議根據(jù)實(shí)際業(yè)務(wù)場景調(diào)整數(shù)據(jù)源配置和切換邏輯。

以上就是SpringBoot實(shí)現(xiàn)整合多數(shù)據(jù)源的全攻略的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot整合多數(shù)據(jù)源的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • IDEA插件之mybatisx?插件使用教程

    IDEA插件之mybatisx?插件使用教程

    這篇文章主要介紹了mybatisx?插件使用教程,包括插件安裝自動生成代碼的相關(guān)知識,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • Java中Cookie和Session詳解及區(qū)別總結(jié)

    Java中Cookie和Session詳解及區(qū)別總結(jié)

    這篇文章主要介紹了Java中Cookie和Session詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下
    2022-06-06
  • Java中實(shí)現(xiàn)文件上傳下載的三種解決方案(推薦)

    Java中實(shí)現(xiàn)文件上傳下載的三種解決方案(推薦)

    這篇文章主要介紹了Java中實(shí)現(xiàn)文件上傳下載的三種解決方案的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-07-07
  • Java線程池執(zhí)行過程中遇到異常的原因和解決辦法

    Java線程池執(zhí)行過程中遇到異常的原因和解決辦法

    當(dāng)線程池中線程頻繁出現(xiàn)未捕獲的異常,那線程的復(fù)用率就大大降低了,需要不斷地創(chuàng)建新線程,所以本文通過詳細(xì)的代碼給大家介紹了Java線程池執(zhí)行過程中遇到異常的解決辦法,需要的朋友可以參考下
    2025-10-10
  • spring集成redis cluster詳解

    spring集成redis cluster詳解

    這篇文章主要介紹了spring集成redis cluster詳解,分享了maven依賴,Spring配置,增加connect-redis.properties 配置文件等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • Spring?Cloud?Eureka基礎(chǔ)應(yīng)用及原理

    Spring?Cloud?Eureka基礎(chǔ)應(yīng)用及原理

    這篇文章主要介紹了Spring?Cloud?Eureka基礎(chǔ)應(yīng)用,Eureka?Client中內(nèi)置一個(gè)負(fù)載均衡器,用來進(jìn)行基本的負(fù)載均衡,下面我們將通過搭建一個(gè)簡單的Eureka例子來了解Eureka的運(yùn)作原理,感興趣的朋友一起看看吧
    2022-05-05
  • 解決redisTemplate中l(wèi)eftPushAll隱性bug的問題

    解決redisTemplate中l(wèi)eftPushAll隱性bug的問題

    這篇文章主要介紹了解決redisTemplate中l(wèi)eftPushAll隱性bug的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Java this關(guān)鍵字的引用詳解

    Java this關(guān)鍵字的引用詳解

    每個(gè)類中都有一個(gè)本類的this引用,類似:類名 this;實(shí)例對象時(shí),this引用指向?qū)ο蟊旧恚渥钪饕淖饔檬亲岊愔械姆椒梢栽L問本類中的另外一個(gè)方法或?qū)傩?,因?yàn)檎{(diào)用一個(gè)方法或?qū)傩?非類方法/屬性)必須要有實(shí)例
    2022-03-03
  • java設(shè)計(jì)優(yōu)化之代理模式

    java設(shè)計(jì)優(yōu)化之代理模式

    這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)優(yōu)化中的代理模式,感興趣的朋友可以參考一下
    2016-03-03
  • Java?實(shí)戰(zhàn)項(xiàng)目之家政服務(wù)平臺系統(tǒng)的實(shí)現(xiàn)流程

    Java?實(shí)戰(zhàn)項(xiàng)目之家政服務(wù)平臺系統(tǒng)的實(shí)現(xiàn)流程

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實(shí)現(xiàn)家政服務(wù)平臺系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平
    2021-11-11

最新評論

鹿邑县| 河曲县| 绥江县| 曲阜市| 建始县| 澎湖县| 时尚| 容城县| 繁峙县| 论坛| 宁夏| 鲜城| 江达县| 枣强县| 杂多县| 连山| 措美县| 永兴县| 焦作市| 龙井市| 长阳| 天门市| 淅川县| 永吉县| 望奎县| 班玛县| 上犹县| 禄劝| 吉隆县| 通榆县| 望谟县| 巨野县| 洪江市| 东至县| 祥云县| 浙江省| 韩城市| 沙河市| 万载县| 稻城县| 麦盖提县|