在网上搜集了一波资料发现,关于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进行拦截,并设置主库):