springboot动态多数据配置以及数据源切换(aop实现)

1.POM依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>


        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.8.6</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.4.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.20</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>

2.配置文件

@Data
@Component
@ConfigurationProperties("spring.datasource.druid")
public class DruidDataSourceProperties {
   private String username;
   private String password;
   private String url;
   private String driverClassName;
}
import com.bei.mybatis.plus.mybatisplus.support.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author bei
 * 动态数据源类
 */
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		return DynamicDataSourceContextHolder.getDataSourceType();
	}
}

这里注意,yml中存放在默认的一个数据源,其他数据源是存放在数据库的下面会说明表结构以及例子
/**
 * @author bei
 * 动态数据源切换配置
 */
@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 bei
 * 数据源相关常量
 */
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.表结构

注意下这里数据库的密码,使用的stringEncryptor.encrypt("密码")

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.使用

1.在springboot的application启动项写上注解

2.在你想要切换数据源的地方加上,这里要注意下,一个事务只能支持一个数据源,可以和事务传播行为的

Propagation.REQUIRES_NEW集合使用

5.实现效果:

如果执行方法出现这个日志,说明以及切换成功!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值