springboot动态创建多数据源

本文实现案例场景:
       某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。

       为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。

  配置文件配置内容为:
(不包括项目中的其他配置,这里只是数据源相关的)

# 主数据源,默认的
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456

# 更多数据源
custom.datasource.names=ds1,ds2
custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=123456

custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2
custom.datasource.ds2.username=root
custom.datasource.ds2.password=123456

   注册数据源 :

  1. /**
  2.  * 自定义注解
  3.  * @author meng
  4.  *
  5.  */
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Target({
  8.         ElementType.METHOD
  9. })
  10. public @interface DS {
  11.     DataSourceType  value() default DataSourceContextHolder.DataSourceType.base;
  1. /**
  2.  *初始化主数据源
  3.  * @author meng
  4.  *
  5.  */
  6. @Configuration
  7. public class DataSourceConfiguration {
  8.     @Bean(name = "base")
  9.     @Primary
  10.     @ConfigurationProperties(prefix = "spring.datasource")
  11.     public DataSource dataSource1() {
  12.         return DataSourceBuilder.create().build();
  13.     }
  14.     
  15.     @Bean(name = "jdbc2")
  16.     @ConfigurationProperties(prefix = "custom.datasource.ds1")
  17.     public DataSource dataSource2() {
  18.         return DataSourceBuilder.create().build();
  19.     }
  20.     
  21.     @Bean(name = "hive")
  22.     @ConfigurationProperties(prefix = "custom.datasource.ds2")
  23.     public DataSource dataSource2hive() {
  24.         return DataSourceBuilder.create().build();
  25.     }
  26.     
  27. }

       创建一个线程保存当前线程使用的Database标识:

  1. /**
  2.  * 保存一个线程安全的DataBaseType容器
  3.  * @author meng
  4.  *
  5.  */
  6. public class DataSourceContextHolder {
  7.     
  8.     /** 使用枚举标识数据源 */
  9.     public enum DataSourceType{
  10.         base,jdbc2,hive
  11.     }
  12.     /** 创建线程保存数据源 */
  13.     private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<DataSourceType>();
  14.     /**
  15.      * 设置数据源
  16.      * @param dbType
  17.      */
  18.     public static void setDataSourceType(DataSourceType dataSourceType){
  19.         // 如果数据源为空,抛异常
  20.         if(dataSourceType == null)throw new NullPointerException();
  21.         contextHolder.set(dataSourceType);
  22.     }
  23.     /**
  24.      * 获得数据源
  25.      * @return
  26.      */
  27.     public static DataSourceType getDataSourceType(){
  28.         return contextHolder.get()==null? DataSourceType.base:contextHolder.get();
  29.     }
  30.     /**
  31.      * 清空数据源
  32.      */
  33.     public static void clearDataSourceType(){
  34.         contextHolder.remove();
  35.     }
  36. }

         获取当前线程的Database标识

  1. /**
  2.  * 动态获取数据源(需要继承AbstractRoutingDataSource)
  3.  * @author meng
  4.  *
  5.  */
  6. public class DynamicDataSource extends AbstractRoutingDataSource{
  7.     @Override
  8.     protected Object determineCurrentLookupKey() {
  9.         // 使用DatabaseContextHolder获取当前线程的DatabaseType
  10.         return DataSourceContextHolder.getDataSourceType();
  11.     }
  12. }

         使用aop动态切换数据源:

  1. /**
  2.  * 使用aop动态切换数据源
  3.  * @author meng
  4.  *
  5.  */
  6. @Aspect
  7. @Component
  8. public class DynamicDataSourceAspect{
  9.     public static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
  10.     @SuppressWarnings("rawtypes")
  11.     @Before("@annotation(DS)")
  12.     public void beforeSwitchDS(JoinPoint point){
  13.         //获得当前访问的class
  14.         Class<?> className = point.getTarget().getClass();
  15.         //获得访问的方法名
  16.         String methodName = point.getSignature().getName();
  17.         MethodSignature methodSignature = (MethodSignature) point.getSignature();
  18.         //得到方法的参数的类型
  19.         Class[] argClass = methodSignature.getParameterTypes();
  20.         DataSourceType dataSource = DataSourceContextHolder.DataSourceType.base;
  21.         try {
  22.             // 得到访问的方法对象
  23.             Method method = className.getMethod(methodName, argClass);
  24.             // 判断是否存在@DS注解
  25.             if (method.isAnnotationPresent(DS.class)) {
  26.                 DS annotation = method.getAnnotation(DS.class);
  27.                 // 取出注解中的数据源名
  28.                 dataSource = annotation.value();
  29.             }
  30.         } catch (Exception e) {
  31.             e.printStackTrace();
  32.             logger.error("数据源切换异常",e.getMessage());
  33.         }
  34.         // 切换数据源
  35.         DataSourceContextHolder.setDataSourceType(dataSource);
  36.         logger.info("切换到数据源:"+dataSource);
  37.     }
  38.     /**
  39.      * 清空数据源
  40.      * @param point
  41.      */
  42.     @After("@annotation(DS)")
  43.     public void afterSwitchDS(JoinPoint point){
  44.         DataSourceContextHolder.clearDataSourceType();
  45.         logger.info("执行完成,数据源清空");
  46.     }
  47. }

           动态切换数据源并注册事务:

  1. /**
  2.  * 数据源切换
  3.  * @author meng
  4.  *
  5.  */
  6. @Configuration
  7. public class MybatisConfiguration {
  8.     /** 注入数据源 */
  9.     @Resource(name = "base")
  10.     private DataSource base;
  11.     @Resource(name = "jdbc2")
  12.     private DataSource jdbc2;
  13.     @Resource(name = "hive")
  14.     private DataSource hive;
  15.     /**
  16.      * 将动态数据源添加到SqlSessionFactory中
  17.      * @return
  18.      * @throws Exception
  19.      */
  20.     @Bean(name="sqlSessionFactoryBean")
  21.     public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSouceProxy")
  22.         DataSource dataSource) throws Exception {
  23.         SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  24.         sqlSessionFactoryBean.setDataSource(dataSource);
  25.         sqlSessionFactoryBean.setTypeAliasesPackage(Constants.TYPE_ALIASES_PACKAGE);
  26.         // 添加XML目录
  27.         ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  28.         org.springframework.core.io.Resource[] resources = resolver.getResources(Constants.MAPPER_XML_LOCATION);
  29.         org.springframework.core.io.Resource[] re = resolver.getResources(Constants.MAPPER_XML_LOCATION_OTHER);
  30.         sqlSessionFactoryBean.setMapperLocations(resources);
  31.         sqlSessionFactoryBean.setMapperLocations(re);
  32.         
  33.         // 分页插件
  34.         PageHelper pageHelper = new PageHelper();
  35.         Properties properties = new Properties();
  36.         // reasonable:分页合理化参数,默认值为false。
  37.         // 当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
  38.         properties.setProperty("reasonable", "true");
  39.         // supportMethodsArguments:默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
  40.         properties.setProperty("supportMethodsArguments", "true");
  41.         properties.setProperty("returnPageInfo", "check");
  42.         // params:用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值,
  43.         // 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
  44.         properties.setProperty("params", "count=countSql");
  45.         pageHelper.setProperties(properties);
  46.         sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageHelper});
  47.         return sqlSessionFactoryBean.getObject();
  48.     }
  49.     @Bean(name="dataSouceProxy")
  50.     public AbstractRoutingDataSource dataSouceProxy(){
  51.         // 动态数据源对象
  52.         DynamicDataSource proxy = new DynamicDataSource();
  53.         Map<Object,Object> targetDataResources = new HashMap<>();
  54.         targetDataResources.put(DataSourceContextHolder.DataSourceType.base,base);
  55.         targetDataResources.put(DataSourceContextHolder.DataSourceType.jdbc2,jdbc2);
  56.         targetDataResources.put(DataSourceContextHolder.DataSourceType.hive,hive);
  57.         // 默认数据源
  58.         proxy.setDefaultTargetDataSource(base);
  59.         proxy.setTargetDataSources(targetDataResources);
  60.         return proxy;
  61.     }
  62.     
  63.    @Bean
  64.     public PlatformTransactionManager baseTransactionManager(
  65.             @Qualifier("dataSouceProxy") DataSource dataSource) {
  66.         return new DataSourceTransactionManager(dataSource);
  67.     }
  68.     
  69. }

猜你喜欢

转载自blog.csdn.net/weixin_39330443/article/details/81179548