springboot Dynamic multiple data sources, and data switching configuration (AOP implementation)

1.POM dependence

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid 连接池 -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
</dependency>
 <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.16.18</version>
 </dependency>

2. Profiles

@Data
@Component
@ConfigurationProperties("spring.datasource.druid")
public class DruidDataSourceProperties {
   private String username;
   private String password;
   private String url;
   private String driverClassName;
}
这里注意,yml中存放在默认的一个数据源,其他数据源是存放在数据库的下面会说明表结构以及例子
/**
 * @author lengleng
 * @date 2019-03-31
 * <p>
 * 动态数据源切换配置
 */
@Slf4j
@Configuration
@AllArgsConstructor
public class DynamicDataSourceConfig  {
   private final Map<Object, Object> dataSourceMap = new HashMap<>(8);
   private final DruidDataSourceProperties dataSourceProperties;
   private final StringEncryptor stringEncryptor;

   @Bean("dynamicDataSource")
   public DynamicDataSource dataSource() {
      DynamicDataSource ds = new DynamicDataSource();
      DruidDataSource cads = new DruidDataSource();
      cads.setUrl(dataSourceProperties.getUrl());
      cads.setDriverClassName(dataSourceProperties.getDriverClassName());
      cads.setUsername(dataSourceProperties.getUsername());
      cads.setPassword(dataSourceProperties.getPassword());
      ds.setDefaultTargetDataSource(cads);
      dataSourceMap.put(0, cads);
      ds.setTargetDataSources(dataSourceMap);
      return ds;
   }

   /**
    * 组装默认配置的数据源,查询数据库配置
    */
   @PostConstruct
   public void init() {
      DriverManagerDataSource dds = new DriverManagerDataSource();
      dds.setUrl(dataSourceProperties.getUrl());
      dds.setDriverClassName(dataSourceProperties.getDriverClassName());
      dds.setUsername(dataSourceProperties.getUsername());
      dds.setPassword(dataSourceProperties.getPassword());

      List<Map<String, Object>> dbList = new JdbcTemplate(dds).queryForList(DataSourceConstants.QUERY_DS_SQL);
      log.info("开始 -> 初始化动态数据源");
      Optional.of(dbList).ifPresent(list -> list.forEach(db -> {
         log.info("数据源:{}", db.get(DataSourceConstants.DS_NAME));
         DruidDataSource ds = new DruidDataSource();
         ds.setUrl(String.valueOf(db.get(DataSourceConstants.DS_JDBC_URL)));
         ds.setDriverClassName(Driver.class.getName());
         ds.setUsername((String) db.get(DataSourceConstants.DS_USER_NAME));
         String decPwd = stringEncryptor.decrypt((String)db.get(DataSourceConstants.DS_USER_PWD));
         ds.setPassword(decPwd);
         dataSourceMap.put(db.get(DataSourceConstants.DS_ROUTE_KEY), ds);
      }));

      log.info("完毕 -> 初始化动态数据源,共计 {} 条", dataSourceMap.size());
   }

   /**
    * 重新加载数据源配置
    */
   public Boolean reload() {
      init();
      DynamicDataSource dataSource = dataSource();
      dataSource.setTargetDataSources(dataSourceMap);
      dataSource.afterPropertiesSet();
      return Boolean.FALSE;
   }
}
/**
 * @author lengleng
 * @date 2019-04-01
 * <p>
 * 数据源相关常量
 */
public interface DataSourceConstants {
   /**
    * 查询数据源的SQL
    */
   String QUERY_DS_SQL = "select * from gen_datasource_conf where del_flag = 0";

   /**
    * 数据源名称
    */
   String DS_NAME = "name";

   /**
    * jdbcurl
    */
   String DS_JDBC_URL = "url";

   /**
    * 用户名
    */
   String DS_USER_NAME = "username";

   /**
    * 密码
    */
   String DS_USER_PWD = "password";

}
/*
 * @Date : 2019/10/18 11:29
 * @auhtor : bei
 * @description : 数据源常量
 */
public class DataSourceConstant {
    // 商家数据源id
    public static Integer BUS_DATASOURCE_ID = 1;

    // 平台方数据源id
    public static Integer PLA_DATASOURCE_ID = 2;
}
/*
 * @Date : 2019/10/18 11:29
 * @auhtor : bei
 * @description : 数据源常量
 */
public enum DataSourceEnums {
    // 商家数据源
    BUS_DATASOURCE_ID(DataSourceConstant.BUS_DATASOURCE_ID),

    // 平台方数据源id
    PLA_DATASOURCE_ID(DataSourceConstant.PLA_DATASOURCE_ID);

   // 数据源存放在数据库的id
    private int value;
   DataSourceEnums(int value){
      this.value = value;
   }
   public int getValue(){
      return this.value;
   }
}
/**
 * 多数据源处理aop
 *
 * @author bei
 */
@Aspect
@Order(1)
@AllArgsConstructor
public class DataSourceAspect
{

    @SneakyThrows
    @Around("@annotation(dataSource)")
    public Object around(ProceedingJoinPoint point, DataSource dataSource)
    {
        if (ObjectUtil.isNotNull(dataSource.value()))
        {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().getValue());
        }
        try
        {
            return point.proceed();
        }
        finally
        {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}
/**
 * @author lengleng
 * @date 2019-05-18
 * <p>
 * 根据当前线程来选择具体的数据源
 */
@UtilityClass
public class DynamicDataSourceContextHolder {
   private final ThreadLocal<Integer> CONTEXT_HOLDER = new ThreadLocal<>();

   /**
    * 提供给AOP去设置当前的线程的数据源的信息
    *
    * @param dataSourceType
    */
   public void setDataSourceType(Integer dataSourceType) {
      CONTEXT_HOLDER.set(dataSourceType);
   }

   /**
    * 使用默认的数据源
    *
    */
   public void clearDataSourceType() {
      CONTEXT_HOLDER.remove();
   }
}
/*
 * @Date : 2019/10/18 1:06
 * @auhtor : bei
 * @description : 切换数据源注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    DataSourceEnums value() default DataSourceEnums.BUS_DATASOURCE_ID;
}
/**
 * @author Lucky
 * @date 2019-05-18
 * <p>
 * 开启动态数据源
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({DynamicDataSourceConfig.class})
public @interface EnableDynamicDataSource {
}
/**
 * @author Lucky
 * @date 2019-05-18
 * <p>
 * 自动配置类
 */
@AllArgsConstructor
@EnableConfigurationProperties({DruidDataSourceProperties.class})
public class DataSourceAutoConfiguration {
    @Bean
    public DataSourceAspect dataSourceAspect() {
        return new DataSourceAspect();
    }
}

3. Table Structure

Note that under this code, stringEncryptor.encrypt ( "Password") to use the database

4.yml

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    druid:
      url: jdbc:mysql://123:3306/123?useUnicode=true&characterEncoding=utf-8&useServerPstmts=true&cachePrepStmts=true&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&cacheCallableStmts=true&connectTimeout=1000&maxReconnects=5&zeroDateTimeBehavior=convertToNull&useSSL=true
      username: 123
      password: 123
      # 初始连接数
      initialSize: 10
      # 最大连接池数量
      maxActive: 50
      # 最小连接池数量
      minIdle: 10
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        url-pattern: ${adminPath}/sys/druid/*
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: false
        wall:
          config:
            multi-statement-allow: true

5. Use

1. annotations on application startup items of written springboot

2. Where you want to switch data sources together, here to pay attention to the next, a transaction can only support a data source, and transaction propagation behavior

Propagation.REQUIRES_NEW集合使用

5. achieve results:

If you perform this method appears logs, notes and handover success!

Published 13 original articles · won praise 7 · views 10000 +

Guess you like

Origin blog.csdn.net/weixin_39701913/article/details/103936231