服务提供动态的指标计算,可查不同库配置不同sql或者代码,实时计算指标,本来数据源是需要重启配置的,想了下可以做成不重启就新增。
设计:在mongodb中保存数据源信息,项目启动时加载到内存初始化数据源,运行时可动态的新增或者删除数据源不需要重启服务,没测过,暂时需求不明,先做个记录,后面可能会用到。
动态数据源工厂类
主要作用就是初始化数据源
import com.alibaba.druid.pool.DruidDataSource; import com.google.common.collect.Maps; import com.google.gson.reflect.TypeToken; import com.linzi.risk.indicator.enums.YesOrNoEnum; import com.linzi.risk.indicator.utils.GsonUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.sql.DataSource; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.Set; import static com.linzi.risk.indicator.feign.IndicatorConfigFeignImpl.INDICATOR_CONFIG; /** * @author chentiefeng * @date 2019/11/12 09:49 */ @Slf4j @Component("dynamicDataSourceFactory") public class DynamicDataSourceFactory { public final static String DYNAMIC_DATA_SOURCE = "dynamic_data_source"; /** * 运行时数据源 */ private static Map<String, DataSource> runtimeDataSourceMap = Maps.newConcurrentMap(); private static MongoTemplate mongoTemplate; private static DataSourceProperties dataSourceProperties; private static DataSource defaultDataSource; private static String defaultDataSourceKey; /** * 所有数据源 * * @return */ public static List<DataSourceConfig> list() { Set<String> keys = runtimeDataSourceMap.keySet(); Query query = Query.query(Criteria.where("key").is("dataSource")); Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG); List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() { }.getType()); for (DataSourceConfig dataSourceConfig : list) { if (keys.contains(dataSourceConfig.getKey())) { dataSourceConfig.setRuntime(YesOrNoEnum.YES.getInitValue()); } } return list; } public static Map<String, DataSource> getRuntimeDataSourceMap() { return runtimeDataSourceMap; } public static DataSource get(String key) { return runtimeDataSourceMap.get(key); } @PostConstruct public void init() { Query query = Query.query(Criteria.where("key").is("dataSource")); Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG); List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() { }.getType()); for (DataSourceConfig dataSourceConfig : list) { initDataSource(dataSourceConfig); } if (defaultDataSource == null) { throw new RuntimeException("默认数据源不存在"); } } public static DataSource getDefaultDataSource() { return defaultDataSource; } public static String getDefaultDataSourceKey() { return defaultDataSourceKey; } /** * 初始化数据源 * * @param dataSourceConfig */ private void initDataSource(DataSourceConfig dataSourceConfig) { DruidDataSource druidDataSource = getDruidDataSource(dataSourceProperties); druidDataSource.setDriverClassName(dataSourceConfig.getDriverClassName()); druidDataSource.setUrl(dataSourceConfig.getUrl()); druidDataSource.setUsername(dataSourceConfig.getUsername()); druidDataSource.setPassword(dataSourceConfig.getPassword()); try { druidDataSource.init(); runtimeDataSourceMap.put(dataSourceConfig.getKey(), druidDataSource); if (YesOrNoEnum.YES.getInitValue().equals(dataSourceConfig.defaultDataSource)) { defaultDataSourceKey = dataSourceConfig.getKey(); defaultDataSource = druidDataSource; } } catch (SQLException e) { log.error(e.getMessage(), e); } } @Data public class DataSourceConfig { private String key; private String desc; private String driverClassName; private String url; private String username; private String password; private Integer defaultDataSource; private Integer runtime; } @Resource public void setMongoTemplate(MongoTemplate mongoTemplate) { DynamicDataSourceFactory.mongoTemplate = mongoTemplate; } @Resource public void setDataSourceProperties(DataSourceProperties dataSourceProperties) { DynamicDataSourceFactory.dataSourceProperties = dataSourceProperties; } public static DruidDataSource getDruidDataSource(DataSourceProperties properties) { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setInitialSize(properties.getInitialSize()); druidDataSource.setMaxActive(properties.getMaxActive()); druidDataSource.setMinIdle(properties.getMinIdle()); druidDataSource.setMaxWait(properties.getMaxWait()); druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis()); druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis()); druidDataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis()); druidDataSource.setValidationQuery(properties.getValidationQuery()); druidDataSource.setValidationQueryTimeout(properties.getValidationQueryTimeout()); druidDataSource.setTestOnBorrow(properties.isTestOnBorrow()); druidDataSource.setTestOnReturn(properties.isTestOnReturn()); druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements()); druidDataSource.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements()); druidDataSource.setSharePreparedStatements(properties.isSharePreparedStatements()); try { druidDataSource.setFilters(properties.getFilters()); } catch (SQLException e) { log.error(e.getMessage(), e); } return druidDataSource; } }
动态数据源路由
主要是druid提供的可以切换数据源
```java package com.linzi.risk.indicator.datasources; import org.springframework.context.annotation.DependsOn; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.sql.DataSource; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * 动态数据源 * * @author chentiefeng * @date 2019/8/19 1:03 */ @Component @DependsOn("dynamicDataSourceFactory") public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public DynamicRoutingDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @PostConstruct public void init() { super.setDefaultTargetDataSource(DynamicDataSourceFactory.getDefaultDataSource()); Map<Object, Object> runtimeDataSourceMap = DynamicDataSourceFactory.getRuntimeDataSourceMap().entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); super.setTargetDataSources(runtimeDataSourceMap); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } @Override protected DataSource determineTargetDataSource() { Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = DynamicDataSourceFactory.getRuntimeDataSourceMap().get(Objects.toString(lookupKey)); if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; } }
数据源注解
在service或者dao方法上注解可以方便切换数据源
package com.linzi.risk.indicator.datasources.annotation; import java.lang.annotation.*; /** * 多数据源注解 * @author chentiefeng * @date 2019/9/16 22:16 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String name(); }
切面
package com.linzi.risk.indicator.datasources.aspect; import com.linzi.risk.indicator.datasources.DynamicRoutingDataSource; import com.linzi.risk.indicator.datasources.DynamicDataSourceFactory; import com.linzi.risk.indicator.datasources.annotation.DataSource; import org.apache.commons.lang.ArrayUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Objects; /** * 多数据源,切面处理类 * * @author chentiefeng * @date 2019/9/16 22:20 */ @Aspect @Component @Order(1) public class DataSourceAspect { @Around("execution(* com.linzi.risk.indicator.dao.Dynamic*Dao.*(..))") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource ds = method.getAnnotation(DataSource.class); if (ds == null) { DynamicRoutingDataSource.setDataSource(DynamicDataSourceFactory.getDefaultDataSourceKey()); } else if (DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE.equals(ds.name())) { //动态参数数据源 String dsKey = DynamicDataSourceFactory.getDefaultDataSourceKey(); Object[] args = point.getArgs(); if (ArrayUtils.isNotEmpty(args)) { dsKey = Objects.toString(args[args.length - 1], DynamicDataSourceFactory.getDefaultDataSourceKey()); } DynamicRoutingDataSource.setDataSource(dsKey); } else { DynamicRoutingDataSource.setDataSource(ds.name()); } try { return point.proceed(); } finally { DynamicRoutingDataSource.clearDataSource(); } } }
调用
/** * 根据动态SQL查询Map结果 * * @param statement 动态sql查询 * @param dataSourceKey * @return */ @DataSource(name = DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE) void execute(@Param("statement") String statement, String dataSourceKey); /** * 指定数据源查询 * * @param statement 动态sql查询 * @return */ @DataSource(name = "risk_biz") void execute(@Param("statement") String statement);
数据源属性类
package com.linzi.risk.indicator.datasources; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * @author chentiefeng * @date 2019/11/13 11:00 */ @Data @Configuration @ConfigurationProperties("spring.datasource.druid") public class DataSourceProperties { private String driverClassName; private String url; private String username; private String password; /** * Druid默认参数 */ private int initialSize = 2; private int maxActive = 10; private int minIdle = -1; private long maxWait = 60 * 1000L; private long timeBetweenEvictionRunsMillis = 60 * 1000L; private long minEvictableIdleTimeMillis = 1000L * 60L * 30L; private long maxEvictableIdleTimeMillis = 1000L * 60L * 60L * 7; private String validationQuery = "select 1"; private int validationQueryTimeout = -1; private boolean testOnBorrow = false; private boolean testOnReturn = false; private boolean testWhileIdle = true; private boolean poolPreparedStatements = false; private int maxOpenPreparedStatements = -1; private boolean sharePreparedStatements = false; private String filters = "stat,wall"; }
服务提供动态的指标计算,可查不同库配置不同sql或者代码,实时计算指标,本来数据源是需要重启配置的,想了下可以做成不重启就新增。设计:在mongodb中保存数据源信息,项目启动时加载到内存初始化数据源,运行时可动态的新增或者删除数据源不需要重启服务,没测过,暂时需求不明,先做个记录,后面可能会用到。
**动态数据源工厂类**主要作用就是初始化数据源```java
import com.alibaba.druid.pool.DruidDataSource;import com.google.common.collect.Maps;import com.google.gson.reflect.TypeToken;import com.linzi.risk.indicator.enums.YesOrNoEnum;import com.linzi.risk.indicator.utils.GsonUtil;import lombok.Data;import lombok.extern.slf4j.Slf4j;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.query.Criteria;import org.springframework.data.mongodb.core.query.Query;import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;import javax.annotation.Resource;import javax.sql.DataSource;import java.sql.SQLException;import java.util.List;import java.util.Map;import java.util.Set;
import static com.linzi.risk.indicator.feign.IndicatorConfigFeignImpl.INDICATOR_CONFIG;
/** * @author chentiefeng * @date 2019/11/12 09:49 */@Slf4j@Component("dynamicDataSourceFactory")public class DynamicDataSourceFactory { public final static String DYNAMIC_DATA_SOURCE = "dynamic_data_source"; /** * 运行时数据源 */ private static Map<String, DataSource> runtimeDataSourceMap = Maps.newConcurrentMap(); private static MongoTemplate mongoTemplate; private static DataSourceProperties dataSourceProperties; private static DataSource defaultDataSource; private static String defaultDataSourceKey;
/** * 所有数据源 * * @return */ public static List<DataSourceConfig> list() { Set<String> keys = runtimeDataSourceMap.keySet(); Query query = Query.query(Criteria.where("key").is("dataSource")); Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG); List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() { }.getType()); for (DataSourceConfig dataSourceConfig : list) { if (keys.contains(dataSourceConfig.getKey())) { dataSourceConfig.setRuntime(YesOrNoEnum.YES.getInitValue()); } } return list; }
public static Map<String, DataSource> getRuntimeDataSourceMap() { return runtimeDataSourceMap; }
public static DataSource get(String key) { return runtimeDataSourceMap.get(key); }
@PostConstruct public void init() { Query query = Query.query(Criteria.where("key").is("dataSource")); Map dsMap = mongoTemplate.findOne(query, Map.class, INDICATOR_CONFIG); List<DataSourceConfig> list = GsonUtil.fromJson(GsonUtil.toJson(dsMap.get("value")), new TypeToken<List<DataSourceConfig>>() { }.getType()); for (DataSourceConfig dataSourceConfig : list) { initDataSource(dataSourceConfig); } if (defaultDataSource == null) { throw new RuntimeException("默认数据源不存在"); } }
public static DataSource getDefaultDataSource() { return defaultDataSource; }
public static String getDefaultDataSourceKey() { return defaultDataSourceKey; }
/** * 初始化数据源 * * @param dataSourceConfig */ private void initDataSource(DataSourceConfig dataSourceConfig) { DruidDataSource druidDataSource = getDruidDataSource(dataSourceProperties); druidDataSource.setDriverClassName(dataSourceConfig.getDriverClassName()); druidDataSource.setUrl(dataSourceConfig.getUrl()); druidDataSource.setUsername(dataSourceConfig.getUsername()); druidDataSource.setPassword(dataSourceConfig.getPassword()); try { druidDataSource.init(); runtimeDataSourceMap.put(dataSourceConfig.getKey(), druidDataSource); if (YesOrNoEnum.YES.getInitValue().equals(dataSourceConfig.defaultDataSource)) { defaultDataSourceKey = dataSourceConfig.getKey(); defaultDataSource = druidDataSource; } } catch (SQLException e) { log.error(e.getMessage(), e); } }
@Data public class DataSourceConfig { private String key; private String desc; private String driverClassName; private String url; private String username; private String password; private Integer defaultDataSource; private Integer runtime; }
@Resource public void setMongoTemplate(MongoTemplate mongoTemplate) { DynamicDataSourceFactory.mongoTemplate = mongoTemplate; }
@Resource public void setDataSourceProperties(DataSourceProperties dataSourceProperties) { DynamicDataSourceFactory.dataSourceProperties = dataSourceProperties; }
public static DruidDataSource getDruidDataSource(DataSourceProperties properties) { DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setInitialSize(properties.getInitialSize()); druidDataSource.setMaxActive(properties.getMaxActive()); druidDataSource.setMinIdle(properties.getMinIdle()); druidDataSource.setMaxWait(properties.getMaxWait()); druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis()); druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis()); druidDataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis()); druidDataSource.setValidationQuery(properties.getValidationQuery()); druidDataSource.setValidationQueryTimeout(properties.getValidationQueryTimeout()); druidDataSource.setTestOnBorrow(properties.isTestOnBorrow()); druidDataSource.setTestOnReturn(properties.isTestOnReturn()); druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements()); druidDataSource.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements()); druidDataSource.setSharePreparedStatements(properties.isSharePreparedStatements()); try { druidDataSource.setFilters(properties.getFilters()); } catch (SQLException e) { log.error(e.getMessage(), e); } return druidDataSource; }}
```
**动态数据源路由**主要是druid提供的可以切换数据源
```javapackage com.linzi.risk.indicator.datasources;
import org.springframework.context.annotation.DependsOn;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;import javax.sql.DataSource;import java.util.Map;import java.util.Objects;import java.util.stream.Collectors;
/** * 动态数据源 * * @author chentiefeng * @date 2019/8/19 1:03 */
@Component@DependsOn("dynamicDataSourceFactory")public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public DynamicRoutingDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); }
@PostConstruct public void init() { super.setDefaultTargetDataSource(DynamicDataSourceFactory.getDefaultDataSource()); Map<Object, Object> runtimeDataSourceMap = DynamicDataSourceFactory.getRuntimeDataSourceMap().entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); super.setTargetDataSources(runtimeDataSourceMap); super.afterPropertiesSet(); }
@Override protected Object determineCurrentLookupKey() { return getDataSource(); }
public static void setDataSource(String dataSource) { contextHolder.set(dataSource); }
public static String getDataSource() { return contextHolder.get(); }
public static void clearDataSource() { contextHolder.remove(); }
@Override protected DataSource determineTargetDataSource() { Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = DynamicDataSourceFactory.getRuntimeDataSourceMap().get(Objects.toString(lookupKey)); if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; }}
```**动态数据源注解**在service或者dao方法上注解可以方便切换数据源
```javapackage com.linzi.risk.indicator.datasources.annotation;
import java.lang.annotation.*;
/** * 多数据源注解 * @author chentiefeng * @date 2019/9/16 22:16 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DataSource { String name();}```**切面**切点可以随便修改```javapackage com.linzi.risk.indicator.datasources.aspect;
import com.linzi.risk.indicator.datasources.DynamicRoutingDataSource;import com.linzi.risk.indicator.datasources.DynamicDataSourceFactory;import com.linzi.risk.indicator.datasources.annotation.DataSource;import org.apache.commons.lang.ArrayUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;
import java.lang.reflect.Method;import java.util.Objects;
/** * 多数据源,切面处理类 * * @author chentiefeng * @date 2019/9/16 22:20 */@Aspect@Component@Order(1)public class DataSourceAspect {
@Around("execution(* com.linzi.risk.indicator.dao.Dynamic*Dao.*(..))") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource ds = method.getAnnotation(DataSource.class); if (ds == null) { DynamicRoutingDataSource.setDataSource(DynamicDataSourceFactory.getDefaultDataSourceKey()); } else if (DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE.equals(ds.name())) { //动态参数数据源 String dsKey = DynamicDataSourceFactory.getDefaultDataSourceKey(); Object[] args = point.getArgs(); if (ArrayUtils.isNotEmpty(args)) { dsKey = Objects.toString(args[args.length - 1], DynamicDataSourceFactory.getDefaultDataSourceKey()); } DynamicRoutingDataSource.setDataSource(dsKey); } else { DynamicRoutingDataSource.setDataSource(ds.name()); } try { return point.proceed(); } finally { DynamicRoutingDataSource.clearDataSource(); } }
}
```**调用**
```java/** * 根据动态SQL查询Map结果 * * @param statement 动态sql查询 * @param dataSourceKey * @return */ @DataSource(name = DynamicDataSourceFactory.DYNAMIC_DATA_SOURCE) void execute(@Param("statement") String statement, String dataSourceKey); /** * 指定数据源查询 * * @param statement 动态sql查询 * @return */ @DataSource(name = "risk_biz") void execute(@Param("statement") String statement);```**数据源属性类**
```javapackage com.linzi.risk.indicator.datasources;
import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;
/** * @author chentiefeng * @date 2019/11/13 11:00 */@Data@Configuration@ConfigurationProperties("spring.datasource.druid")public class DataSourceProperties { private String driverClassName; private String url; private String username; private String password;
/** * Druid默认参数 */ private int initialSize = 2; private int maxActive = 10; private int minIdle = -1; private long maxWait = 60 * 1000L; private long timeBetweenEvictionRunsMillis = 60 * 1000L; private long minEvictableIdleTimeMillis = 1000L * 60L * 30L; private long maxEvictableIdleTimeMillis = 1000L * 60L * 60L * 7; private String validationQuery = "select 1"; private int validationQueryTimeout = -1; private boolean testOnBorrow = false; private boolean testOnReturn = false; private boolean testWhileIdle = true; private boolean poolPreparedStatements = false; private int maxOpenPreparedStatements = -1; private boolean sharePreparedStatements = false; private String filters = "stat,wall";}
```