SpringCloud之基於SpringCloud+MybatisPlus+Config實現多個數據源的動態切換
阿新 • • 發佈:2018-11-09
一、首先,專案基於SpringCloud,配置檔案在Git上(包括資料來源的配置資訊)。
二、開始基於原有專案進行重構
1、寫一個動態資料來源上下文.程式碼如下:
/** * 動態資料來源上下文 */ public class DbContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal<>(); /* * 管理所有的資料來源id; * 主要是為了判斷資料來源是否存在; */ public static List<String> dataSourceIds = new ArrayList<String>(); /** * 設定資料來源 * @param dbTypeEnum */ public static void setDbType(String dbTypeEnum) { contextHolder.set(dbTypeEnum); } /** * 取得當前資料來源 * @return */ public static String getDbType() { return (String)contextHolder.get(); } /** * 清除上下文資料 */ public static void clearDbType() { contextHolder.remove(); } /** * 判斷指定DataSrouce當前是否存在 * @param dataSourceId * @return */ public static boolean containsDataSource(String dataSourceId){ return dataSourceIds.contains(dataSourceId); } }
2、再寫一個數據源路由類,程式碼如下:
/**
* 資料來源路由類
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 取得當前使用哪個資料來源
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
3、寫一個自定義註解,用來傳遞需要哪個資料來源:
/**
* 在方法上使用,用於指定使用哪個資料來源
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
4、寫一個AOP,只要有註解的就根據引數切換資料來源:
@Component @Order(-10)//保證該AOP在@Transactional之前執行 @Aspect public class DBTypeAOP { @Pointcut("@annotation(TargetDataSource)") public void dbType() { } @Before("dbType()") public void before(JoinPoint joinPoint) throws Throwable { //獲取當前的指定的資料來源; System.out.println("---------------before---------------"); TargetDataSource targetDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(TargetDataSource.class); String dsId = targetDataSource.value(); //如果不在我們注入的所有的資料來源範圍之內,那麼輸出警告資訊,系統自動使用預設的資料來源。 if (!DbContextHolder.containsDataSource(dsId)) { System.err.println("資料來源[{}]不存在,使用預設資料來源 > {}"+targetDataSource.value()+joinPoint.getSignature()); } else { System.out.println("Use DataSource : {} > {}"+targetDataSource.value()+joinPoint.getSignature()); //找到的話,那麼設定到動態資料來源上下文中。 DbContextHolder.setDbType(targetDataSource.value()); } } @After("dbType()") public void after(JoinPoint joinPoint) { System.out.println("--------------after----------------"); TargetDataSource targetDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(TargetDataSource.class); System.out.println("Revert DataSource : {} > {}"+targetDataSource.value()+joinPoint.getSignature()); //方法執行完畢之後,銷燬當前資料來源資訊,進行垃圾回收。 DbContextHolder.clearDbType(); } }
5、使用ImportBeanDefinitionRegistrar進行註冊我們的多資料來源:
/**
* 動態資料來源註冊;
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
//如配置檔案中未指定資料來源型別,使用該預設值
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 預設資料來源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
/**
* 載入多資料來源配置
*/
@Override
public void setEnvironment(Environment environment) {
System.out.println("DynamicDataSourceRegister.setEnvironment()");
initDefaultDataSource(environment);
initCustomDataSources(environment);
}
/**
* 載入主資料來源配置.
* @param env
*/
private void initDefaultDataSource(Environment env){
// 讀取主資料來源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<String, Object>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
//建立資料來源;
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
/**
* 初始化更多資料來源
*/
private void initCustomDataSources(Environment env) {
// 讀取配置檔案獲取更多資料來源,也可以通過defaultDataSource讀取資料庫獲取更多資料來源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {// 多個數據源
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
/**
* 建立datasource.
* @param dsMap
* @return
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null){
type = DATASOURCE_TYPE_DEFAULT;// 預設DataSource
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 為DataSource繫結更多資料
* @param dataSource
* @param env
*/
private void dataBinder(DataSource dataSource, Environment env){
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);//false
dataBinder.setIgnoreInvalidFields(false);//false
dataBinder.setIgnoreUnknownFields(true);//true
if(dataSourcePropertyValues == null){
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> values = new HashMap<>(rpr);
// 排除已經設定的屬性
values.remove("type");
values.remove("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 將主資料來源新增到更多資料來源中
targetDataSources.put("dataSource", defaultDataSource);
DbContextHolder.dataSourceIds.add("dataSource");
// 新增更多資料來源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DbContextHolder.dataSourceIds.add(key);
}
// 建立DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//新增屬性:AbstractRoutingDataSource.defaultTargetDataSource
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
}
三、最後,我們在Application.java這個啟動類中,使用@import註冊DynamicDataSourceRegister:
@EnableDiscoveryClient
@SpringBootApplication
//註冊
@Import({DynamicDataSourceRegister.class})
public class CircleOfFriendsApplication {
public static void main(String[] args) {
SpringApplication.run(CircleOfFriendsApplication.class, args);
}
}
四、執行:
執行的話,只需在你原有的方法上加上我們第3步寫的自定義註解@TargetDataSource("資料來源1")
例如:
五、效果:
---------------before---------------
Use DataSource : {} > {}ds1 com.luda.springcloud.client.controller.CircleOfFriendsController.queryCircleOfFriends(String,String,String)
2018-08-03 12:19:06.942 INFO [spring-cloud-circleOfFriends,e1c88ebca78edb0d,f27d8e29d1a698a3,false] 6708 --- [ XNIO-2 task-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} inited
--------------after----------------
Time:5 ms - ID:com.luda.springcloud.client.mapper.CircleOfFriendsMapper.selectPage
Execute SQL:SELECT id,`createUser`,content,imgUrl,createTime,ip FROM circle_of_friends ORDER BY createTime DESC LIMIT 0,10
Revert DataSource : {} > {}ds1 com.luda.springcloud.client.controller.CircleOfFriendsController.queryCircleOfFriends(String,String,String)
進入了AOP,並且獲取我需要的資料來源,在方法完畢後,進行清除操作。
給我點個贊吧!拜託了^-^