SpringBoot-- 动态数据源

Posted 听歌敲代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot-- 动态数据源相关的知识,希望对你有一定的参考价值。

SpringBoot中使用动态数据源可以实现分布式中的分库技术,比如查询用户 就在用户库中查询,查询订单 就在订单库中查询。

一、配置文件application.properties

# 默认数据源
spring.datasource.url=jdbc:mysql://localhost:3306/consult
spring.datasource.username=myConsult
spring.datasource.password=123456
spring.datasource.driver-class-name=org.gjt.mm.mysql.Driver
# 更多数据源
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

二、使用aop自定义注解,实现动态切换数据源

1.动态数据源注册器

  1 import java.util.HashMap;
  2 import java.util.Map;
  3 import javax.sql.DataSource;
  4 import org.slf4j.Logger;
  5 import org.slf4j.LoggerFactory;
  6 import org.springframework.beans.MutablePropertyValues;
  7 import org.springframework.beans.PropertyValues;
  8 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  9 import org.springframework.beans.factory.support.GenericBeanDefinition;
 10 import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
 11 import org.springframework.boot.bind.RelaxedDataBinder;
 12 import org.springframework.boot.bind.RelaxedPropertyResolver;
 13 import org.springframework.context.EnvironmentAware;
 14 import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
 15 import org.springframework.core.convert.ConversionService;
 16 import org.springframework.core.convert.support.DefaultConversionService;
 17 import org.springframework.core.env.Environment;
 18 import org.springframework.core.type.AnnotationMetadata;
 19 
 20 public class DynamicDataSourceRegister implements
 21         ImportBeanDefinitionRegistrar, EnvironmentAware {
 22     private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
 23     
 24     private ConversionService conversionService = new DefaultConversionService();
 25     
 26     private PropertyValues dataSourcePropertyValues;
 27     
 28     // 如配置文件中未指定数据源类型,使用该默认值
 29     private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
 30     
 31     // private static final Object DATASOURCE_TYPE_DEFAULT =
 32     // "com.zaxxer.hikari.HikariDataSource";
 33     
 34     // 数据源
 35     private DataSource defaultDataSource;
 36     
 37     private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
 38     
 39     public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
 40         Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
 41         // 将主数据源添加到更多数据源中
 42         targetDataSources.put("dataSource", defaultDataSource);
 43         DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
 44         // 添加更多数据源
 45         targetDataSources.putAll(customDataSources);
 46         for (String key : customDataSources.keySet()) {
 47             DynamicDataSourceContextHolder.dataSourceIds.add(key);
 48         }
 49         
 50         // 创建DynamicDataSource
 51         GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
 52         beanDefinition.setBeanClass(DynamicDataSource.class);
 53         beanDefinition.setSynthetic(true);
 54         MutablePropertyValues mpv = beanDefinition.getPropertyValues();
 55         mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
 56         mpv.addPropertyValue("targetDataSources", targetDataSources);
 57         registry.registerBeanDefinition("dataSource", beanDefinition); // 注册到Spring容器中
 58         
 59         logger.info("Dynamic DataSource Registry");
 60     }
 61     
 62     /**
 63      * 创建DataSource
 64      * @param type
 65      * @param driverClassName
 66      * @param url
 67      * @param username
 68      * @param password
 69      * @return
 70      */
 71     @SuppressWarnings("unchecked")
 72     public DataSource buildDataSource(Map<String, Object> dsMap) {
 73         try {
 74             Object type = dsMap.get("type");
 75             if (type == null)
 76                 type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
 77                 
 78             Class<? extends DataSource> dataSourceType;
 79             dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
 80             
 81             String driverClassName = dsMap.get("driver-class-name").toString();
 82             String url = dsMap.get("url").toString();
 83             String username = dsMap.get("username").toString();
 84             String password = dsMap.get("password").toString();
 85             
 86             DataSourceBuilder factory = DataSourceBuilder.create()
 87                     .driverClassName(driverClassName)
 88                     .url(url)
 89                     .username(username)
 90                     .password(password)
 91                     .type(dataSourceType);
 92             return factory.build();
 93         }
 94         catch (ClassNotFoundException e) {
 95             e.printStackTrace();
 96         }
 97         return null;
 98     }
 99     
100     /**
101      * 加载多数据源配置
102      */
103     public void setEnvironment(Environment env) {
104         initDefaultDataSource(env);
105         initCustomDataSources(env);
106     }
107     
108     /**
109      * 初始化主数据源
110      */
111     private void initDefaultDataSource(Environment env) {
112         // 读取主数据源
113         RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(
114                 env, "spring.datasource.");
115         Map<String, Object> dsMap = new HashMap<String, Object>();
116         dsMap.put("type", propertyResolver.getProperty("type"));
117         dsMap.put("driver-class-name",propertyResolver.getProperty("driver-class-name"));
118         dsMap.put("url", propertyResolver.getProperty("url"));
119         dsMap.put("username", propertyResolver.getProperty("username"));
120         dsMap.put("password", propertyResolver.getProperty("password"));
121         defaultDataSource = buildDataSource(dsMap); 
122         dataBinder(defaultDataSource, env);
123     }
124     
125     /**
126      * 为DataSource绑定更多数据
127      * @param dataSource
128      * @param env
129      */
130     private void dataBinder(DataSource dataSource, Environment env) {
131         RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
132         //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
133         dataBinder.setConversionService(conversionService);
134         dataBinder.setIgnoreNestedProperties(false);//false
135         dataBinder.setIgnoreInvalidFields(false);//false
136         dataBinder.setIgnoreUnknownFields(true);//true
137         if (dataSourcePropertyValues == null) {
138             Map<String, Object> rpr = new RelaxedPropertyResolver(env,
139                     "spring.datasource").getSubProperties(".");
140             Map<String, Object> values = new HashMap<String, Object>(rpr);
141             // 排除已经设置的属性
142             values.remove("type");
143             values.remove("driver-class-name");
144             values.remove("url");
145             values.remove("username");
146             values.remove("password");
147             dataSourcePropertyValues = new MutablePropertyValues(values);
148         }
149         dataBinder.bind(dataSourcePropertyValues);
150     }
151     
152     /**
153      * 初始化更多数据源
154      *
155      */
156     private void initCustomDataSources(Environment env) {
157         // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
158         RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(
159                 env, "custom.datasource.");
160         String dsPrefixs = propertyResolver.getProperty("names");
161         for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
162             Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix
163                     + ".");
164             DataSource ds = buildDataSource(dsMap);
165             customDataSources.put(dsPrefix, ds);
166             dataBinder(ds, env);
167         }
168     }
169 }

2.动态数据源适配器

 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 public class DynamicDataSourceContextHolder {
 5     private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
 6     public static List<String> dataSourceIds = new ArrayList<String>();
 7     
 8     public static void setDataSourceType(String dataSourceType) {
 9         contextHolder.set(dataSourceType);
10     }
11     
12     public static String getDataSourceType() {
13         return contextHolder.get();
14     }
15     
16     public static void clearDataSourceType() {
17         contextHolder.remove();
18     }
19     
20     /**
21      * 判断指定DataSrouce当前是否存在
22      *
23      * @param dataSourceId
24      * @return
25      */
26     public static boolean containsDataSource(String dataSourceId){
27         return dataSourceIds.contains(dataSourceId);
28     }
29 }

3.自定义注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String name();
}

4.动态数据源切面

 1 import org.aspectj.lang.JoinPoint;
 2 import org.aspectj.lang.annotation.After;
 3 import org.aspectj.lang.annotation.Aspect;
 4 import org.aspectj.lang.annotation.Before;
 5 import org.slf4j.Logger;
 6 import org.slf4j.LoggerFactory;
 7 import org.springframework.core.annotation.Order;
 8 import org.springframework.stereotype.Component;
 9 
10 @Aspect
11 //保证该AOP在@Transactional之前执行
12 @Order(-1)
13 @Component
14 public class DynamicDataSourceAspect {
15     private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
16     
17     /** 
18      * @Description 在方法执行之前执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的
19      * @param @param point
20      * @param @param ds
21      * @param @throws Throwable 参数 
22      * @return void 返回类型  
23      * @throws 
24      */
25     @Before("@annotation(ds)")
26     public void changeDataSource(JoinPoint point, TargetDataSource ds)
27             throws Throwable {
28         String dsId = ds.name();
29         if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
30             logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
31         }
32         else {
33             logger.debug("Use DataSource : {} > {}", ds.name(),point.getSignature());
34             DynamicDataSourceContextHolder.setDataSourceType(ds.name());
35         }
36     }
37     
38     /** 
39      * @Description 在方法执行之后执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的 
40      * @param @param point
41      * @param @param ds 参数 
42      * @return void 返回类型  
43      * @throws 
44      */
45     @After("@annotation(ds)")
46     public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
47         logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
48         DynamicDataSourceContextHolder.clearDataSourceType();
49     }
50 }

5.继承Spring AbstractRoutingDataSource实现路由切换

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* 继承Spring AbstractRoutingDataSource实现路由切换
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
    
    @Override
    protected Object determineCurrentLookupKey() {
        // TODO Auto-generated method stub
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

三、怎么使用自定义的注解动态的切换数据源

只需要在service实现类中 对需要切换数据源的方法上 加上 自定义的注解即可,如:@TargetDataSource(name = "ds2")

 四、启动类上添加@Import注解

  //注册动态多数据源
  @Import({DynamicDataSourceRegister.class})

 

以上是关于SpringBoot-- 动态数据源的主要内容,如果未能解决你的问题,请参考以下文章

动态 Rstudio 代码片段

是否可以动态编译和执行 C# 代码片段?

支持动态或静态片段的不同屏幕尺寸?

springboot 根据用户ID切换动态数据源代码实现

SpringBoot启动报错“Consider defining a bean of type ‘xxx.mapper.UserMapper‘ in your configuration.“(代码片段

Android studio 动态片段根据日期