在網上搜集了一波資料發現,關於spring boot動態數據源的文章少之甚少。大部分都是已知數據源,增一套配置,寫一個bean。java
通過筆者日以徹夜的研究,終於得出了方案。mysql
一、遍歷配置,得到數據源配置spring
二、建立數據源鏈接池sql
三、使用AbstractRoutingDataSource接管數據源apache
===============================================mybatis
yml配置: app
spring: datasource: master-source: jdbcUrl: jdbc:mysql://xxx:3306/db?autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8 username: user password: password minimumIdle: 16 maximumPoolSize: 1024 connectionTestQuery: SELECT 1 FROM DUAL driverClassName: com.mysql.jdbc.Driver dataSource: cachePrepStmts: true prepStmtCacheSize: 1024 prepStmtCacheSqlLimit: 4096 slave-a-source: jdbcUrl: jdbc:mysql://xxx:3306/db?autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8 username: user password: password minimumIdle: 16 maximumPoolSize: 1024 connectionTestQuery: SELECT 1 FROM DUAL driverClassName: com.mysql.jdbc.Driver dataSource: cachePrepStmts: true prepStmtCacheSize: 1024 prepStmtCacheSqlLimit: 4096
Bean配置:dom
package com.jiujun.voice.common.jdbc.source; import java.lang.reflect.Field; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.util.ConcurrentReferenceHashMap; import com.jiujun.voice.common.jdbc.source.DynamicDataSource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @Configuration public class DataSourceConfig { private static final String PREFIX = "spring.datasource"; @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dynamicDataSources(ConfigurablePropertyResolver environment) throws SQLException, InterruptedException { // 抽取配置容器 PropertySources propertySources = getFieldValue(environment, "propertySources"); List<PropertySource<?>> list = getFieldValue(propertySources, "propertySourceList"); PropertySource<?> propertySource = getByCollections(list, "name", "configurationProperties"); ConcurrentReferenceHashMap<PropertySource<?>, ConfigurationPropertySource> propertySourceMap = getFieldValue( propertySource.getSource(), "cache"); // 篩選yml配置 List<ConfigurationPropertySource> ymlPropertys = new ArrayList<ConfigurationPropertySource>(); for (PropertySource<?> key : propertySourceMap.keySet()) { if (isNullOrEmpty(propertySourceMap.get(key))) { continue; } ConfigurationPropertySource value = propertySourceMap.get(key); if (key.getName().startsWith("applicationConfig:")) { ymlPropertys.add(value); } } Map<String, Object> propertyMap = new HashMap<String, Object>(); // 合併配置 for (ConfigurationPropertySource source : ymlPropertys) { MapPropertySource underlyingSource = (MapPropertySource) source.getUnderlyingSource(); for (String propertyName : underlyingSource.getPropertyNames()) { propertyMap.put(propertyName, underlyingSource.getProperty(propertyName)); } } // 整合數據源配置 Map<String, Properties> dataSourceConfigs = new HashMap<String, Properties>(); for (String propertyName : propertyMap.keySet()) { if (!propertyName.startsWith(PREFIX)) { continue; } String dataSourceName = getDatasourceNameByPropertyName(propertyName); String fieldName = getFieldNameByPropertyName(propertyName); Object fieldValue = propertyMap.get(propertyName); if (!dataSourceConfigs.containsKey(dataSourceName)) { Properties properties = new Properties(); dataSourceConfigs.put(dataSourceName, properties); } dataSourceConfigs.get(dataSourceName).put(fieldName, fieldValue); } // 建立數據源 Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); for (String dataSourceName : dataSourceConfigs.keySet()) { Properties properties = dataSourceConfigs.get(dataSourceName); HikariConfig configuration = new HikariConfig(properties); HikariDataSource dataSource = new HikariDataSource(configuration); targetDataSources.put(dataSourceName, dataSource); } DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.afterPropertiesSet(); return dynamicDataSource; } @Bean(name = "jdbcTemplate") public JdbcTemplate instanceJdbcTemplate(DynamicDataSource dynamicDataSource) { return new JdbcTemplate(dynamicDataSource); } @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception { return new DataSourceTransactionManager(dataSource); } private static String getFieldNameByPropertyName(String propertyName) { propertyName = propertyName.substring(PREFIX.length() + 1); String datasourceName = propertyName.substring(0, propertyName.indexOf(".")); String fieldName = propertyName.substring(datasourceName.length() + 1); return fieldName; } private static String getDatasourceNameByPropertyName(String propertyName) { propertyName = propertyName.substring(PREFIX.length() + 1); String datasourceName = propertyName.substring(0, propertyName.indexOf(".")); return datasourceName; } @SuppressWarnings("unchecked") private static <T> T getFieldValue(Object object, String fieldName) { if (isNullOrEmpty(object)) { return null; } Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { if (!fieldName.equals(field.getName())) { continue; } try { field.setAccessible(true); return (T) field.get(object); } catch (Exception e) { e.printStackTrace(); return null; } } return null; } public static boolean isNullOrEmpty(Object obj) { try { if (obj == null) { return true; } if (obj instanceof CharSequence) { return ((CharSequence) obj).length() == 0; } if (obj instanceof Collection) { return ((Collection<?>) obj).isEmpty(); } if (obj instanceof Map) { return ((Map<?, ?>) obj).isEmpty(); } if (obj instanceof Object[]) { Object[] object = (Object[]) obj; if (object.length == 0) { return true; } boolean empty = true; for (int i = 0; i < object.length; i++) { if (!isNullOrEmpty(object[i])) { empty = false; break; } } return empty; } return false; } catch (Exception e) { return true; } } @SuppressWarnings("unchecked") private static <T> T getByCollections(Collection<?> collections, String fieldName, Object fieldValue) { if (isNullOrEmpty(collections)) { return null; } for (Object object : collections) { Object value = getFieldValue(object, fieldName); if (fieldValue == null && value == null) { return (T) object; } if (fieldValue == value || fieldValue.equals(value)) { return (T) object; } } return null; } }
動態數據源對象:ide
package com.jiujun.voice.common.jdbc.source; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.log4j.Logger; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 動態數據源 * * @author Coody * */ public class DynamicDataSource extends AbstractRoutingDataSource { static Logger logger=Logger.getLogger(DynamicDataSource.class); private static final ThreadLocal<String> CURRENT_SOURCE = new ThreadLocal<String>(); public static final String MASTER_FLAG = "master"; public static final String SLAVE_FLAG = "slave"; private static List<String> masters = new ArrayList<String>(); private static List<String> slaves = new ArrayList<String>(); @Override public void setTargetDataSources(Map<Object, Object> targetDataSources) { for (Object key : targetDataSources.keySet()) { String dataSourceName = key.toString(); if (dataSourceName.startsWith(MASTER_FLAG)) { masters.add(dataSourceName); this.setDefaultTargetDataSource(targetDataSources.get(dataSourceName)); continue; } if (dataSourceName.startsWith(SLAVE_FLAG)) { slaves.add(dataSourceName); continue; } throw new RuntimeException("未知的數據源類型,數據源命名只能以" + MASTER_FLAG + "、" + SLAVE_FLAG + "開頭"); } if (masters==null||masters.isEmpty()) { throw new RuntimeException("缺乏主庫數據源"); } super.setTargetDataSources(targetDataSources); } @Override protected Object determineCurrentLookupKey() { String dataSourceName = getDataSourceName(); logger.debug("使用數據源>>" + dataSourceName); return dataSourceName; } public static void setDataSourceFlag(String sourceFlag) { CURRENT_SOURCE.set(sourceFlag); } public static String getDataSourceName() { String flag = CURRENT_SOURCE.get(); if (flag == null) { flag = MASTER_FLAG; } if (flag.equals(MASTER_FLAG)) { if (masters.size() == 1) { return masters.get(0); } return masters.get(new Random().nextInt(masters.size())); } if (flag.equals(SLAVE_FLAG)) { if (slaves.size() == 1) { return slaves.get(0); } return slaves.get(new Random().nextInt(slaves.size())); } logger.error("多數據源警告:指定數據源標記錯誤,只能指定master、slave相關數值"); return masters.get(0); } public static void clearDataSourceFlag() { CURRENT_SOURCE.remove(); } }
數據源切面(默認使用從庫,當執行碰到update則自動切換爲主庫):ui
package com.jiujun.voice.common.jdbc.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; import com.jiujun.voice.common.jdbc.source.DynamicDataSource; /** * 多數據源控制 * @author Coody * @date 2018年9月21日 */ @Aspect @Component public class DataSourceAspect { /** * 爲全部CMD指定從庫 * * @param pjp * @return * @throws Throwable */ @Around("@annotation(com.jiujun.voice.common.cmd.anntation.CmdAction)") public Object buildCmdDataSource(ProceedingJoinPoint pjp) throws Throwable { StopWatch sw = new StopWatch(getClass().getSimpleName()); try { // AOP啓動監聽 sw.start(pjp.getSignature().toShortString()); //默認指定從庫 DynamicDataSource.setDataSourceFlag(DynamicDataSource.SLAVE_FLAG); return pjp.proceed(); } finally { //釋放線程數據源 DynamicDataSource.clearDataSourceFlag(); sw.stop(); } } }
sql語句更新處(mybatis和hibernate可採用攔截器對sql進行攔截,並設置主庫):