1. 程式人生 > >SpringBoot(十一)-- 動態數據源

SpringBoot(十一)-- 動態數據源

eterm 屬性 this runt ids 指定 之前 exception factory

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(十一)-- 動態數據源