diff --git a/pom.xml b/pom.xml index f82caa9..ff07845 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ ym-websocket ym-packing ym-s7 + ym-dynamic-datasource pom @@ -241,4 +242,4 @@ - \ No newline at end of file + diff --git a/ym-dynamic-datasource/pom.xml b/ym-dynamic-datasource/pom.xml new file mode 100644 index 0000000..0ee5918 --- /dev/null +++ b/ym-dynamic-datasource/pom.xml @@ -0,0 +1,26 @@ + + + + ym-pass + com.cnbm + 1.0-SNAPSHOT + + 4.0.0 + + ym-dynamic-datasource + + + 8 + 8 + UTF-8 + + + + + ${project.artifactId} + + + + \ No newline at end of file diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/annotation/DataSource.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/annotation/DataSource.java new file mode 100644 index 0000000..8adef9a --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/annotation/DataSource.java @@ -0,0 +1,11 @@ +package com.cnbm.dynamic.datasource.annotation; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource { + String value() default ""; +} diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/aspect/DataSourceAspect.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/aspect/DataSourceAspect.java new file mode 100644 index 0000000..9534233 --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/aspect/DataSourceAspect.java @@ -0,0 +1,57 @@ +package com.cnbm.dynamic.datasource.aspect; + +import com.cnbm.dynamic.datasource.annotation.DataSource; +import com.cnbm.dynamic.datasource.config.DynamicContextHolder; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +@Aspect +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class DataSourceAspect { + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.cnbm.dynamic.datasource.annotation.DataSource) " + + "|| @within(com.cnbm.dynamic.datasource.annotation.DataSource)") + public void dataSourcePointCut() { + + } + + @Around("dataSourcePointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + MethodSignature signature = (MethodSignature) point.getSignature(); + Class targetClass = point.getTarget().getClass(); + Method method = signature.getMethod(); + + DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class); + DataSource methodDataSource = method.getAnnotation(DataSource.class); + if(targetDataSource != null || methodDataSource != null){ + String value; + if(methodDataSource != null){ + value = methodDataSource.value(); + }else { + value = targetDataSource.value(); + } + + DynamicContextHolder.push(value); + logger.debug("set datasource is {}", value); + } + + try { + return point.proceed(); + } finally { + DynamicContextHolder.poll(); + logger.debug("clean datasource"); + } + } +} diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicContextHolder.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicContextHolder.java new file mode 100644 index 0000000..b3b2ca5 --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicContextHolder.java @@ -0,0 +1,44 @@ +package com.cnbm.dynamic.datasource.config; + +import java.util.ArrayDeque; +import java.util.Deque; + +public class DynamicContextHolder { + @SuppressWarnings("unchecked") + private static final ThreadLocal> CONTEXT_HOLDER = new ThreadLocal() { + @Override + protected Object initialValue() { + return new ArrayDeque(); + } + }; + + /** + * 获得当前线程数据源 + * + * @return 数据源名称 + */ + public static String peek() { + return CONTEXT_HOLDER.get().peek(); + } + + /** + * 设置当前线程数据源 + * + * @param dataSource 数据源名称 + */ + public static void push(String dataSource) { + CONTEXT_HOLDER.get().push(dataSource); + } + + /** + * 清空当前线程数据源 + */ + public static void poll() { + Deque deque = CONTEXT_HOLDER.get(); + deque.poll(); + if (deque.isEmpty()) { + CONTEXT_HOLDER.remove(); + } + } + +} diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSource.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSource.java new file mode 100644 index 0000000..ee30a34 --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSource.java @@ -0,0 +1,12 @@ +package com.cnbm.dynamic.datasource.config; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +public class DynamicDataSource extends AbstractRoutingDataSource { + + @Override + protected Object determineCurrentLookupKey() { + return DynamicContextHolder.peek(); + } + +} diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSourceConfig.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSourceConfig.java new file mode 100644 index 0000000..4e55ef6 --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSourceConfig.java @@ -0,0 +1,50 @@ +package com.cnbm.dynamic.datasource.config; + +import com.alibaba.druid.pool.DruidDataSource; +import com.cnbm.dynamic.datasource.properties.DataSourceProperties; +import com.cnbm.dynamic.datasource.properties.DynamicDataSourceProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +@EnableConfigurationProperties(DynamicDataSourceProperties.class) +public class DynamicDataSourceConfig { + @Autowired + private DynamicDataSourceProperties properties; + + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + @Bean + public DynamicDataSource dynamicDataSource(DataSourceProperties dataSourceProperties) { + DynamicDataSource dynamicDataSource = new DynamicDataSource(); + dynamicDataSource.setTargetDataSources(getDynamicDataSource()); + + //默认数据源 + DruidDataSource defaultDataSource = DynamicDataSourceFactory.buildDruidDataSource(dataSourceProperties); + dynamicDataSource.setDefaultTargetDataSource(defaultDataSource); + + return dynamicDataSource; + } + + private Map getDynamicDataSource(){ + Map dataSourcePropertiesMap = properties.getDatasource(); + Map targetDataSources = new HashMap<>(dataSourcePropertiesMap.size()); + dataSourcePropertiesMap.forEach((k, v) -> { + DruidDataSource druidDataSource = DynamicDataSourceFactory.buildDruidDataSource(v); + targetDataSources.put(k, druidDataSource); + }); + + return targetDataSources; + } + +} diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSourceFactory.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSourceFactory.java new file mode 100644 index 0000000..507f14d --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/config/DynamicDataSourceFactory.java @@ -0,0 +1,40 @@ +package com.cnbm.dynamic.datasource.config; + +import com.alibaba.druid.pool.DruidDataSource; +import com.cnbm.dynamic.datasource.properties.DataSourceProperties; + +import java.sql.SQLException; + +public class DynamicDataSourceFactory { + + public static DruidDataSource buildDruidDataSource(DataSourceProperties properties) { + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setDriverClassName(properties.getDriverClassName()); + druidDataSource.setUrl(properties.getUrl()); + druidDataSource.setUsername(properties.getUsername()); + druidDataSource.setPassword(properties.getPassword()); + + 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()); + druidDataSource.init(); + } catch (SQLException e) { + e.printStackTrace(); + } + return druidDataSource; + } +} diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/properties/DataSourceProperties.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/properties/DataSourceProperties.java new file mode 100644 index 0000000..08fa59a --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/properties/DataSourceProperties.java @@ -0,0 +1,188 @@ +package com.cnbm.dynamic.datasource.properties; + +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"; + + public String getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getInitialSize() { + return initialSize; + } + + public void setInitialSize(int initialSize) { + this.initialSize = initialSize; + } + + public int getMaxActive() { + return maxActive; + } + + public void setMaxActive(int maxActive) { + this.maxActive = maxActive; + } + + public int getMinIdle() { + return minIdle; + } + + public void setMinIdle(int minIdle) { + this.minIdle = minIdle; + } + + public long getMaxWait() { + return maxWait; + } + + public void setMaxWait(long maxWait) { + this.maxWait = maxWait; + } + + public long getTimeBetweenEvictionRunsMillis() { + return timeBetweenEvictionRunsMillis; + } + + public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + public long getMinEvictableIdleTimeMillis() { + return minEvictableIdleTimeMillis; + } + + public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + public long getMaxEvictableIdleTimeMillis() { + return maxEvictableIdleTimeMillis; + } + + public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) { + this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis; + } + + public String getValidationQuery() { + return validationQuery; + } + + public void setValidationQuery(String validationQuery) { + this.validationQuery = validationQuery; + } + + public int getValidationQueryTimeout() { + return validationQueryTimeout; + } + + public void setValidationQueryTimeout(int validationQueryTimeout) { + this.validationQueryTimeout = validationQueryTimeout; + } + + public boolean isTestOnBorrow() { + return testOnBorrow; + } + + public void setTestOnBorrow(boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + } + + public boolean isTestOnReturn() { + return testOnReturn; + } + + public void setTestOnReturn(boolean testOnReturn) { + this.testOnReturn = testOnReturn; + } + + public boolean isTestWhileIdle() { + return testWhileIdle; + } + + public void setTestWhileIdle(boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + } + + public boolean isPoolPreparedStatements() { + return poolPreparedStatements; + } + + public void setPoolPreparedStatements(boolean poolPreparedStatements) { + this.poolPreparedStatements = poolPreparedStatements; + } + + public int getMaxOpenPreparedStatements() { + return maxOpenPreparedStatements; + } + + public void setMaxOpenPreparedStatements(int maxOpenPreparedStatements) { + this.maxOpenPreparedStatements = maxOpenPreparedStatements; + } + + public boolean isSharePreparedStatements() { + return sharePreparedStatements; + } + + public void setSharePreparedStatements(boolean sharePreparedStatements) { + this.sharePreparedStatements = sharePreparedStatements; + } + + public String getFilters() { + return filters; + } + + public void setFilters(String filters) { + this.filters = filters; + } +} diff --git a/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/properties/DynamicDataSourceProperties.java b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/properties/DynamicDataSourceProperties.java new file mode 100644 index 0000000..4f6e7bc --- /dev/null +++ b/ym-dynamic-datasource/src/main/java/com/cnbm/dynamic/datasource/properties/DynamicDataSourceProperties.java @@ -0,0 +1,20 @@ +package com.cnbm.dynamic.datasource.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.LinkedHashMap; +import java.util.Map; + +@ConfigurationProperties(prefix = "dynamic") +public class DynamicDataSourceProperties { + private Map datasource = new LinkedHashMap<>(); + + public Map getDatasource() { + return datasource; + } + + public void setDatasource(Map datasource) { + this.datasource = datasource; + } +} + diff --git a/ym-gateway/pom.xml b/ym-gateway/pom.xml index 455a248..df6fdee 100644 --- a/ym-gateway/pom.xml +++ b/ym-gateway/pom.xml @@ -67,6 +67,11 @@ ym-websocket 1.0-SNAPSHOT + + com.cnbm + ym-dynamic-datasource + 1.0-SNAPSHOT + io.springfox springfox-boot-starter