zoukankan      html  css  js  c++  java
  • spring 多数据源的使用

    spring 多数据源的使用

    • 在同一个项目中需要使用多个数据源,这就需要根据不同的场景进行切换数据源,spring给我们提供一种很方便的方式,那就是使用 AbstractRoutingDataSource 进行切换数据源。

    • 首先来看 AbstractRoutingDataSource 这个类,下面是这个类源码。

      public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
          // 需要存储的所有数据源,需要在 bean 生成后进行注入,key 为获得数据源名称,value 为数据源
          private Map<Object, Object> targetDataSources;
      
          private Object defaultTargetDataSource;  // 初始设计的默认数据源
      
          // 如果在查找数据源时,找不到相应的数据源时是否使用默认数据源
          private boolean lenientFallback = true;
      
          // 通过 JNDI 方式的来获取一个数据源
          private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
      
          // 将 targetDataSources 通过转化得到的数据源的集合。(如果不使用 JNDI 方式,它和 targetDataSources 是一样的)
          private Map<Object, DataSource> resolvedDataSources;
      
          // 转化后的默认数据源,如果在数据源的 key 为 null 时会使用,默认数据源,当然还有其他情况,下面会介绍
          private DataSource resolvedDefaultDataSource;
      
          public void setTargetDataSources(Map<Object, Object> targetDataSources) {
              this.targetDataSources = targetDataSources;
          }
      
          public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
              this.defaultTargetDataSource = defaultTargetDataSource;
          }
      
          public void setLenientFallback(boolean lenientFallback) {
              this.lenientFallback = lenientFallback;
          }
      
          public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
              this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
          }
      
      
          /**
           * 因为该类实现了 InitializingBean,所以会在 bean 生成之后(@Bean 注解执行之后,或者 xml 解析完之后)执行该方法
           */
          @Override
          public void afterPropertiesSet() {
              if (this.targetDataSources == null) {  // 必须在 Bean 生成后注入数据源
                  throw new IllegalArgumentException("Property 'targetDataSources' is required");
              }
              this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
              for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
                  Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());  // 对数据源名称进行重定义
                  DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());    // 获取数据源
                  this.resolvedDataSources.put(lookupKey, dataSource);
              }
              if (this.defaultTargetDataSource != null) {  // 设置默认数据源
                  this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
              }
          }
      
          /**
           * 变换 lookupKey,由子类实现, 默认是 targetDataSources 的原始 key
           */
          protected Object resolveSpecifiedLookupKey(Object lookupKey) {
              return lookupKey;
          }
      
          /**
           * 获取数据,直接获取,或者是通过 JNDI 的方法进行获取
           */
          protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
              if (dataSource instanceof DataSource) {  // targetDataSources 中直接存储的就是数据源
                  return (DataSource) dataSource;
              }
              else if (dataSource instanceof String) {  // 通过 JNDI 的方法获取数据源
                  return this.dataSourceLookup.getDataSource((String) dataSource);
              }
              else {
                  throw new IllegalArgumentException(
                          "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
              }
          }
      
      
          /**
           * 获取数据源的数据库连接
           */
          @Override
          public Connection getConnection() throws SQLException {
              return determineTargetDataSource().getConnection();
          }
      
          /**
           * 通过用户名和密码的方式获取数据库连接
           */
          @Override
          public Connection getConnection(String username, String password) throws SQLException {
              return determineTargetDataSource().getConnection(username, password);
          }
      
          @Override
          @SuppressWarnings("unchecked")
          public <T> T unwrap(Class<T> iface) throws SQLException {
              if (iface.isInstance(this)) {
                  return (T) this;
              }
              return determineTargetDataSource().unwrap(iface);
          }
      
          @Override
          public boolean isWrapperFor(Class<?> iface) throws SQLException {
              return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
          }
      
          /**
           * 通过 lookupKey 来获取数据源
           */
          protected DataSource determineTargetDataSource() {
              Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
              Object lookupKey = determineCurrentLookupKey();  // 获取需要数据源的 lookupKey
              // 从已经转化过的数据源查找相应的数据源
              DataSource dataSource = this.resolvedDataSources.get(lookupKey);
      
              // 如果没有找到数据源,或者可以使用默认数据源,或者 lookupKey 为 null,则使用默认数据源
              if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                  dataSource = this.resolvedDefaultDataSource;
              }
              if (dataSource == null) {
                  throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
              }
              return dataSource;
          }
      
          /**
           * 由子类实现,返回需要获取的数据源的 lookupKey
           */
          protected abstract Object determineCurrentLookupKey();
      
      }
      
      
    • 如果要实现多数据源的方式就必须要继承类,然后实现其抽象方法,下面给出一种我的实现方式:

    • 多数据源的的实现:

      public class MultipleDataSource extends AbstractRoutingDataSource {
      
          // 通过 ThreadLocal 的方式来为每个线程一存储个不同的数据源名称
          private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<>();
      
          /**
           * 设置数据源,可以考虑使用注解 aop 的方式,在每个方法执行进行拦截,然后将数据源名称(lookupKey) 设置进行去
           */
          public static void setDataSourceKey(String dataSource) {
              dataSourceKey.set(dataSource);
          }
      
          /**
           * 实现父类的获取数据名称的方法
           */
          @Override
          protected Object determineCurrentLookupKey() {
              String dsName = dataSourceKey.get();
              dataSourceKey.remove();  // 注意要删除,否则可能内存泄漏
              return dsName;
          }
      }
      
      
    • 多数据源的注入 spring 容器:

      @Bean("master")
      public DataSource masterDataSource(){
          return new C3p0SingleDataSource();
      }
      
      @Bean("slaver")
      public DataSource slaverDataSource(){
          return new BladeDataSource();
      }
      
      @Bean
      public DataSource dataSource(@Qualifier("master") DataSource master, @Qualifier("slaver") DataSource slaver){
          Map<Object, Object> targetDataSources = new HashMap<>();
          targetDataSources.put("master", master);
          targetDataSources.put("slaver", slaver);
      
          MultipleDataSource multipleDataSource = new MultipleDataSource();
          multipleDataSource.setTargetDataSources(targetDataSources);  // 注入
          multipleDataSource.setDefaultTargetDataSource(master);  // 设置默认数据源
          return multipleDataSource;
      }
      
    • 注意上面的 master 和 slaver 数据源都是随便 new 的,具体使用时还需要自己设置数据库的相应属性。

    • 再考虑写一个多数据源的注解和 aop,可以加在方法和类上面,可以进行多数据源的使用:

      @Target({ElementType.METHOD, ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface DataSource {
          String value() default "";
      }
      
      @Aspect
      @Component
      public class DataSourceAspect {
          public DataSourceAspect() {
          }
      
          @Around("@within(dataSource) || @annotation(dataSource)")
          public Object around(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
              if (null == dataSource) {
                  dataSource = (DataSource)extractClassLevelAnnotation(joinPoint, DataSource.class);
              }
              
              MultipleDataSource.setDataSourceKey(dataSource.value());
              return joinPoint.proceed();
          }
          
          public static Annotation extractClassLevelAnnotation(JoinPoint joinPoint, Class clazz) {
              for(Annotation annotation : joinPoint.getTarget().getClass().getAnnotations()) {
                  if (annotation.annotationType() == clazz) {
                      return annotation;
                  }
              }
              throw new RuntimeException("类:" + joinPoint.getTarget().getClass().getSimpleName() + "没有找到" + clazz.getName() + "注解");
          }
      }
      
      
    • DataSource 注解可以类上面,表示类中的所有方法都使用该数据源,也可以加在方法上面,如果都加了,则会使用方法上面的那个。

      @DataSource("slave")
      public interface UserDao {
      
          @DataSource("master")
          int insert(UserPO userPO);
      }
      
  • 相关阅读:
    【一致性检验指标】Kappa(cappa)系数
    仅需4步,轻松升级K3s集群!
    使用Kubernetes、K3s和Traefik2进行本地开发
    超强教程!在树莓派上构建多节点K8S集群!
    使用容器化块存储OpenEBS在K3s中实现持久化存储
    仅需60秒,使用k3sup快速部署高可用K3s集群
    在本地运行Kubernetes的3种主流方式
    为什么Kubernetes在边缘计算中如此关键?
    这4件事,让你了解边缘计算的真实面貌
    极简教程!教你快速将K3s与Cloud Controller集成
  • 原文地址:https://www.cnblogs.com/dwtfukgv/p/14848407.html
Copyright © 2011-2022 走看看