Spring Boot 多数据源配置

打印 上一主题 下一主题

主题 546|帖子 546|积分 1638

第一种方式: 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<Object, Object> 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. }
复制代码
好了,配置完以后,在操作数据库之前,先设置用哪个数据源即可,就像下面这样:
  1. 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. 自动切换数据源
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj
給刚才的代码升个级,利用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
功能很强大,支持 数据源分组 ,适用于多种场景 纯粹多库  读写分离  一主多从  混合模式
https://github.com/baomidou/dynamic-datasource-spring-boot-starter
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<OrderMapper, 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<StockMapper, 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<AccountMapper, Account> implements IAccountService {
  11. }
复制代码
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

欢乐狗

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表