• 【超详细demo】Spring Boot 多数据源配置


    第一种方式: AbstractRoutingDataSource

    1.1. 手动切换数据源

    application.properties

    1. # Order
    2. # 如果用Druid作为数据源,应该用url属性,而不是jdbc-url
    3. spring.datasource.order.jdbc-url=jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
    4. spring.datasource.order.username=root
    5. spring.datasource.order.password=123456
    6. spring.datasource.order.driver-class-name=com.mysql.cj.jdbc.Driver
    7. # Stock
    8. spring.datasource.stock.jdbc-url=jdbc:mysql://localhost:3306/stock?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
    9. spring.datasource.stock.username=root
    10. spring.datasource.stock.password=123456
    11. spring.datasource.stock.driver-class-name=com.mysql.cj.jdbc.Driver
    12. # Account
    13. spring.datasource.account.jdbc-url=jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
    14. spring.datasource.account.username=root
    15. spring.datasource.account.password=123456
    16. spring.datasource.account.driver-class-name=com.mysql.cj.jdbc.Driver

    配置数据源

    DataSourceConfig.java

    1. package com.cjs.example.config;
    2. import com.alibaba.druid.pool.DruidDataSource;
    3. import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
    4. import com.zaxxer.hikari.HikariDataSource;
    5. import org.mybatis.spring.SqlSessionFactoryBean;
    6. import org.springframework.beans.factory.annotation.Qualifier;
    7. import org.springframework.boot.context.properties.ConfigurationProperties;
    8. import org.springframework.boot.jdbc.DataSourceBuilder;
    9. import org.springframework.context.annotation.Bean;
    10. import org.springframework.context.annotation.Configuration;
    11. import org.springframework.context.annotation.Primary;
    12. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    13. import javax.sql.DataSource;
    14. import java.util.HashMap;
    15. import java.util.Map;
    16. @Configuration
    17. public class DataSourceConfig {
    18. @Bean("orderDataSource")
    19. @ConfigurationProperties(prefix = "spring.datasource.order")
    20. public DataSource orderDataSource() {
    21. // return new HikariDataSource();
    22. // return new DruidDataSource();
    23. return DataSourceBuilder.create().build();
    24. }
    25. @Bean("accountDataSource")
    26. @ConfigurationProperties(prefix = "spring.datasource.account")
    27. public DataSource accountDataSource() {
    28. // return new HikariDataSource();
    29. // return new DruidDataSource();
    30. return DataSourceBuilder.create().build();
    31. }
    32. @Bean("stockDataSource")
    33. @ConfigurationProperties(prefix = "spring.datasource.stock")
    34. public DataSource stockDataSource() {
    35. // return new HikariDataSource();
    36. // return new DruidDataSource();
    37. return DataSourceBuilder.create().build();
    38. }
    39. @Primary
    40. @Bean("dynamicDataSource")
    41. public DataSource dynamicDataSource(@Qualifier("orderDataSource") DataSource orderDataSource,
    42. @Qualifier("accountDataSource") DataSource accountDataSource,
    43. @Qualifier("stockDataSource") DataSource stockDataSource) {
    44. Map dataSourceMap = new HashMap<>(3);
    45. dataSourceMap.put(DataSourceKey.ORDER.name(), orderDataSource);
    46. dataSourceMap.put(DataSourceKey.STOCK.name(), stockDataSource);
    47. dataSourceMap.put(DataSourceKey.ACCOUNT.name(), accountDataSource);
    48. DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
    49. dynamicRoutingDataSource.setDefaultTargetDataSource(orderDataSource);
    50. dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
    51. return dynamicRoutingDataSource;
    52. }
    53. /* https://baomidou.com/pages/3b5af0/ */
    54. @Bean
    55. public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("dynamicDataSource") DataSource dataSource) {
    56. MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
    57. sqlSessionFactoryBean.setDataSource(dataSource);
    58. // sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml"));
    59. return sqlSessionFactoryBean;
    60. }
    61. }

    由于是MyBatsi-Plus,所以配的是MybatisSqlSessionFactoryBean,如果是MyBatis,则应该是SqlSessionFactoryBean

    DataSourceKey.java

    1. package com.cjs.example.config;
    2. public enum DataSourceKey {
    3. /**
    4. * Order data source key.
    5. */
    6. ORDER,
    7. /**
    8. * Stock data source key.
    9. */
    10. STOCK,
    11. /**
    12. * Account data source key.
    13. */
    14. ACCOUNT
    15. }

    DynamicDataSourceContextHolder.java

    1. package com.cjs.example.config;
    2. public class DynamicDataSourceContextHolder {
    3. private static final ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.ORDER::name);
    4. public static void setDataSourceKey(DataSourceKey key) {
    5. CONTEXT_HOLDER.set(key.name());
    6. }
    7. public static String getDataSourceKey() {
    8. return CONTEXT_HOLDER.get();
    9. }
    10. public static void clearDataSourceKey() {
    11. CONTEXT_HOLDER.remove();
    12. }
    13. }

    DynamicRoutingDataSource.java

    1. package com.cjs.example.config;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    4. @Slf4j
    5. public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    6. @Override
    7. protected Object determineCurrentLookupKey() {
    8. log.info("当前数据源 [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
    9. return DynamicDataSourceContextHolder.getDataSourceKey();
    10. }
    11. }

    好了,配置完以后,在操作数据库之前,先设置用哪个数据源即可,就像下面这样:

    DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ACCOUNT);

    举个例子:

    1. package com.cjs.example;
    2. import com.cjs.example.account.entity.Account;
    3. import com.cjs.example.account.service.IAccountService;
    4. import com.cjs.example.config.DataSourceKey;
    5. import com.cjs.example.config.DynamicDataSourceContextHolder;
    6. import com.cjs.example.order.entity.Order;
    7. import com.cjs.example.order.service.IOrderService;
    8. import com.cjs.example.stock.entity.Stock;
    9. import com.cjs.example.stock.service.IStockService;
    10. import org.junit.jupiter.api.Test;
    11. import org.springframework.beans.factory.annotation.Autowired;
    12. import org.springframework.boot.test.context.SpringBootTest;
    13. import java.math.BigDecimal;
    14. @SpringBootTest
    15. public class Demo1122ApplicationTests {
    16. @Autowired
    17. private IOrderService orderService;
    18. @Autowired
    19. private IAccountService accountService;
    20. @Autowired
    21. private IStockService stockService;
    22. @Test
    23. public void doBusiness() {
    24. DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ORDER);
    25. Order order = new Order();
    26. order.setOrderNo("123");
    27. order.setUserId("1");
    28. order.setCommodityCode("abc");
    29. order.setCount(1);
    30. order.setAmount(new BigDecimal("9.9"));
    31. orderService.save(order);
    32. DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.STOCK);
    33. Stock stock = new Stock();
    34. stock.setId(1);
    35. stock.setCommodityCode("abc");
    36. stock.setName("huawei");
    37. stock.setCount(1);
    38. stockService.updateById(stock);
    39. DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ACCOUNT);
    40. Account account = new Account();
    41. account.setId(1);
    42. account.setUserId("1");
    43. account.setAmount(new BigDecimal(100));
    44. accountService.updateById(account);
    45. }
    46. }

    这样写看起来确实有些麻烦,通常可能不会像这样在一个方法里操作多个数据库,就比如说假设这是一个管理后台,为了图省事把所有业务都写在这一个项目里,这个时候就需要配置多个数据源,各个数据库的业务互相没有关联,只是写在同一个项目中而已,这样的话如果每次都手动设置数据源太麻烦,可以定义一个AOP切面来自动切换数据源。

    1.2. 自动切换数据源

    Core Technologies

    給刚才的代码升个级,利用AOP来拦截目标方法自动切换数据源

    1、添加@EnableAspectJAutoProxy注解

    1. package com.cjs.example;
    2. import org.mybatis.spring.annotation.MapperScan;
    3. import org.springframework.boot.SpringApplication;
    4. import org.springframework.boot.autoconfigure.SpringBootApplication;
    5. import org.springframework.context.annotation.EnableAspectJAutoProxy;
    6. @EnableAspectJAutoProxy
    7. @MapperScan("com.cjs.example.*.mapper")
    8. @SpringBootApplication
    9. public class Demo1122Application {
    10. public static void main(String[] args) {
    11. SpringApplication.run(Demo1122Application.class, args);
    12. }
    13. }

    2、定义切面、切点、通知

    1. package com.cjs.example.aop;
    2. import com.cjs.example.config.DataSourceKey;
    3. import com.cjs.example.config.DynamicDataSourceContextHolder;
    4. import org.aspectj.lang.ProceedingJoinPoint;
    5. import org.aspectj.lang.annotation.Around;
    6. import org.aspectj.lang.annotation.Aspect;
    7. import org.aspectj.lang.annotation.Pointcut;
    8. import org.springframework.stereotype.Component;
    9. @Aspect
    10. @Component
    11. public class DataSourceAdvice {
    12. // @Pointcut("within(com.cjs.example.order..*)")
    13. @Pointcut("execution(* com.cjs.example.order..*.*(..))")
    14. public void orderPointcut() {}
    15. // @Pointcut("within(com.cjs.example.account..*)")
    16. @Pointcut("execution(* com.cjs.example.account..*.*(..))")
    17. public void accountPointcut() {}
    18. // @Pointcut("within(com.cjs.example.stock..*)")
    19. @Pointcut("execution(* com.cjs.example.stock..*.*(..))")
    20. public void stockPointcut() {}
    21. @Around("orderPointcut()")
    22. public Object order(ProceedingJoinPoint pjp) throws Throwable {
    23. DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ORDER);
    24. Object retVal = pjp.proceed();
    25. DynamicDataSourceContextHolder.clearDataSourceKey();
    26. return retVal;
    27. }
    28. @Around("accountPointcut()")
    29. public Object account(ProceedingJoinPoint pjp) throws Throwable {
    30. DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.ACCOUNT);
    31. Object retVal = pjp.proceed();
    32. DynamicDataSourceContextHolder.clearDataSourceKey();
    33. return retVal;
    34. }
    35. @Around("stockPointcut()")
    36. public Object stock(ProceedingJoinPoint pjp) throws Throwable {
    37. DynamicDataSourceContextHolder.setDataSourceKey(DataSourceKey.STOCK);
    38. Object retVal = pjp.proceed();
    39. DynamicDataSourceContextHolder.clearDataSourceKey();
    40. return retVal;
    41. }
    42. }

    现在就不用每次调用service方法前手动设置数据源了

    工程结构

    第二种方式:dynamic-datasource-spring-boot-starter

    功能很强大,支持 数据源分组 ,适用于多种场景 纯粹多库  读写分离  一主多从  混合模式

    GitHub - baomidou/dynamic-datasource-spring-boot-starter: dynamic datasource for springboot 多数据源 动态数据源 主从分离 读写分离 分布式事务

    1、引入dynamic-datasource-spring-boot-starter

    1. <dependency>
    2. <groupId>com.baomidou</groupId>
    3. <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    4. <version>3.5.2</version>
    5. </dependency>

    2、配置数据源

    1. spring:
    2. datasource:
    3. dynamic:
    4. primary: master #设置默认的数据源或者数据源组,默认值即为master
    5. strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
    6. datasource:
    7. master:
    8. url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
    9. username: root
    10. password: 123456
    11. driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
    12. slave_1:
    13. url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
    14. username: root
    15. password: 123456
    16. driver-class-name: com.mysql.jdbc.Driver
    17. slave_2:
    18. url: ENC(xxxxx) # 内置加密,使用请查看详细文档
    19. username: ENC(xxxxx)
    20. password: ENC(xxxxx)
    21. driver-class-name: com.mysql.jdbc.Driver
    22. #......省略
    23. #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

    主从配置,读写分离

    1. # 多主多从 纯粹多库(记得设置primary) 混合配置
    2. spring: spring: spring:
    3. datasource: datasource: datasource:
    4. dynamic: dynamic: dynamic:
    5. datasource: datasource: datasource:
    6. master_1: mysql: master:
    7. master_2: oracle: slave_1:
    8. slave_1: sqlserver: slave_2:
    9. slave_2: postgresql: oracle_1:
    10. slave_3: h2: oracle_2:

    改造一下前面的例子

    1. spring.datasource.dynamic.primary=order
    2. # Order
    3. spring.datasource.dynamic.datasource.order.url=jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
    4. spring.datasource.dynamic.datasource.order.username=root
    5. spring.datasource.dynamic.datasource.order.password=123456
    6. spring.datasource.dynamic.datasource.order.driver-class-name=com.mysql.cj.jdbc.Driver
    7. # Stock
    8. spring.datasource.dynamic.datasource.stock.url=jdbc:mysql://localhost:3306/stock?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
    9. spring.datasource.dynamic.datasource.stock.username=root
    10. spring.datasource.dynamic.datasource.stock.password=123456
    11. spring.datasource.dynamic.datasource.stock.driver-class-name=com.mysql.cj.jdbc.Driver
    12. # Account
    13. spring.datasource.dynamic.datasource.account.url=jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
    14. spring.datasource.dynamic.datasource.account.username=root
    15. spring.datasource.dynamic.datasource.account.password=123456
    16. spring.datasource.dynamic.datasource.account.driver-class-name=com.mysql.cj.jdbc.Driver

    3、使用 @DS 切换数据源

    @DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

    注解结果
    没有@DS默认数据源
    @DS("dsName")  dsName可以为组名也可以为具体某个库的名称
    1. package com.cjs.example.order.service.impl;
    2. import com.baomidou.dynamic.datasource.annotation.DS;
    3. import com.cjs.example.order.entity.Order;
    4. import com.cjs.example.order.mapper.OrderMapper;
    5. import com.cjs.example.order.service.IOrderService;
    6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    7. import org.springframework.stereotype.Service;
    8. @DS("order")
    9. @Service
    10. public class OrderServiceImpl extends ServiceImpl, Order> implements IOrderService {
    11. }
    1. package com.cjs.example.stock.service.impl;
    2. import com.baomidou.dynamic.datasource.annotation.DS;
    3. import com.cjs.example.stock.entity.Stock;
    4. import com.cjs.example.stock.mapper.StockMapper;
    5. import com.cjs.example.stock.service.IStockService;
    6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    7. import org.springframework.stereotype.Service;
    8. @DS("stock")
    9. @Service
    10. public class StockServiceImpl extends ServiceImpl, Stock> implements IStockService {
    11. }
    1. package com.cjs.example.account.service.impl;
    2. import com.baomidou.dynamic.datasource.annotation.DS;
    3. import com.cjs.example.account.entity.Account;
    4. import com.cjs.example.account.mapper.AccountMapper;
    5. import com.cjs.example.account.service.IAccountService;
    6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    7. import org.springframework.stereotype.Service;
    8. @DS("account")
    9. @Service
    10. public class AccountServiceImpl extends ServiceImpl, Account> implements IAccountService {
    11. }

  • 相关阅读:
    前端项目练习(练习-002-NodeJS项目初始化)
    多重视窗管理程序 screen
    关于 某讯QQ群的群文件上传和下载出现错误-134 的解决方法
    idea如何快速找到项目中对应的类(包括源码)
    RPA是什么?怎么成为RPA高手?
    @Bean, @Component, @Configuration简析
    BATJM面试真题文档整理,斩获心仪offer,送给想跳槽的程序猿
    结构体大小的计算(结构体内存对齐)
    92 [递归实现指数型枚举](https://www.acwing.com/problem/content/94/)
    Matlab-resample
  • 原文地址:https://blog.csdn.net/wufaqidong1/article/details/127990507