Commit 915cc5f0 by qiuweili123

GX-1845 自定义数据源开发

parent 21fd52c4
package com.secoo.mall.datasource.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 选择使用的数据源
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SelectDataSource {
String value();
}
package com.secoo.mall.datasource.aop;
import com.secoo.mall.datasource.annotation.SelectDataSource;
import com.secoo.mall.datasource.holder.DataSourceContextHolder;
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.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Order(Byte.MIN_VALUE)
@Component
public class DataSourceAop {
@Pointcut("@annotation(com.secoo.mall.datasource.annotation.SelectDataSource) || @within(com.secoo.mall.datasource.annotation.SelectDataSource)")
public void dataSoucePointcut() {
}
@Around("com.secoo.mall.datasource.aop.DataSourceAop.dataSoucePointcut()")
public Object lookupKeyAround(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
SelectDataSource dsName = AnnotationUtils.getAnnotation(method, SelectDataSource.class);
if (dsName == null) {
Class clazz = signature.getDeclaringType();
dsName = AnnotationUtils.findAnnotation(clazz, SelectDataSource.class);
}
try {
DataSourceContextHolder.setDs(dsName.value());
return pjp.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
}
package com.secoo.mall.datasource.bean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public abstract class AbsDynamicDataSource extends AbstractDataSource {
private String dsName;
public AbsDynamicDataSource(String dsName) {
this.dsName = dsName;
}
public String getDsName() {
return dsName;
}
@Override
public Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getDataSource().getConnection(username, password);
}
protected abstract DataSource getDataSource();
}
package com.secoo.mall.datasource.bean;
import com.secoo.mall.datasource.config.MatrixDataSourceConfig;
import com.secoo.mall.datasource.constant.DataSourceTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.MethodUtils;
import javax.sql.DataSource;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
@Slf4j
public class MatrixDataSource implements DataSource, Closeable {
private DataSource dataSource;
public MatrixDataSource() {
}
public MatrixDataSource(DataSourceTypeEnum dataSourceTypeEnum, MatrixDataSourceConfig config) {
dataSource = dataSourceTypeEnum.getDataSourceFactory().createDataSouce(config);
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return dataSource.getConnection(username, password);
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return dataSource.unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return dataSource.isWrapperFor(iface);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return dataSource.getLogWriter();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
dataSource.setLogWriter(out);
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
dataSource.setLoginTimeout(seconds);
}
@Override
public int getLoginTimeout() throws SQLException {
return dataSource.getLoginTimeout();
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return dataSource.getParentLogger();
}
@Override
public void close() throws IOException {
try {
MethodUtils.invokeExactMethod(dataSource, "close");
} catch (Exception e) {
log.error("datasource close error ", e);
}
}
}
package com.secoo.mall.datasource.bean;
import com.secoo.mall.common.core.exception.BusinessException;
import com.secoo.mall.common.util.string.StringUtil;
import com.secoo.mall.datasource.errorCode.DataSourceError;
import com.secoo.mall.datasource.holder.DataSourceContextHolder;
import com.secoo.mall.datasource.provider.DataSourceProvider;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@Data
public class MatrixDynamicDataSource extends AbsDynamicDataSource implements InitializingBean, DisposableBean {
private Map<String, DataSource> dataSourceMap = new HashMap<>();
private static Lock lock = new ReentrantLock();
@Resource
private DataSourceProvider provider;
public MatrixDynamicDataSource(String dsName) {
super(dsName);
}
@Override
/**
* 获取数据源有两种途径:
* 1.由构造参数直接指定
* 2.使用了selectDataSource注解
*/
protected DataSource getDataSource() {
String dsName = StringUtil.isEmpty(getDsName()) ? DataSourceContextHolder.getDs() : getDsName();
log.info("cur ds is {}", dsName);
return getTargetDataSource(dsName);
}
/**
* 获取目的数据原
*
* @param dsName
* @return
*/
private DataSource getTargetDataSource(String dsName) {
//如果数据源名字为空默认取一个,此种情况可以允许不使用@SelectDataSource指定数据源
/* if (dsName == null) {
dsName = dataSourceMap.keySet().iterator().next();
}*/
if (dataSourceMap.containsKey(dsName)) {
return dataSourceMap.get(dsName);
}
throw new BusinessException(DataSourceError.DATA_SOURCE_NOT_EXIST, dsName);
}
/**
* bean销毁
*
* @throws Exception
*/
@Override
public void destroy() throws Exception {
log.info("datasource start closing ....");
for (Map.Entry<String, DataSource> item : dataSourceMap.entrySet()) {
DataSource dataSource = item.getValue();
Class<? extends DataSource> clazz = dataSource.getClass();
try {
Method closeMethod = clazz.getDeclaredMethod("close");
closeMethod.invoke(dataSource);
} catch (NoSuchMethodException e) {
log.warn("datasource close the datasource named [{}] failed,", item.getKey());
}
}
log.info("datasource all closed success");
}
/**
* 数据加载
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
Map<String, DataSource> dataSourceMap = this.provider.loadDataSources();
for (Map.Entry<String, DataSource> dataSourceEntry : dataSourceMap.entrySet()) {
addDataSource(dataSourceEntry.getKey(), dataSourceEntry.getValue());
}
}
public void addDataSource(String dsName, DataSource dataSource) {
lock.lock();
log.info("load dataSources lock {}", dsName);
if (!dataSourceMap.containsKey(dsName)) {
dataSourceMap.put(dsName, dataSource);
}
log.info("load dataSources unlock {}", dsName);
lock.unlock();
}
/**
* todo:动态更新数据源
*
* @param dsName
*/
public void removeDataSource(String dsName) {
lock.lock();
log.info("remove dataSources lock {}", dsName);
if (dataSourceMap.containsKey(dsName)) {
dataSourceMap.remove(dsName);
}
log.info("remove dataSources unlock {}", dsName);
lock.unlock();
}
}
package com.secoo.mall.datasource.config;
import java.util.concurrent.TimeUnit;
public interface DataSourceConfig {
String POOL_NAME_PRIFIX = "matrix-dataSource-";
int DEFAULT_MIN_IDLE = 5;
int DEFAULT_POOL_SIZE = 50;
long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(30L);
long VALIDATION_TIMEOUT = TimeUnit.SECONDS.toMillis(5L);
long IDLE_TIMEOUT = TimeUnit.MINUTES.toMillis(10L);
long MAX_LIFETIME = TimeUnit.MINUTES.toMillis(30L);
/**
* Get the maximum number of milliseconds that a client will wait for a connection from the pool. If this
* time is exceeded without a connection becoming available, a SQLException will be thrown from
* {@link javax.sql.DataSource#getConnection()}.
*
* @return the connection timeout in milliseconds
*/
long getConnectionTimeout();
/**
* Set the maximum number of milliseconds that a client will wait for a connection from the pool. If this
* time is exceeded without a connection becoming available, a SQLException will be thrown from
* {@link javax.sql.DataSource#getConnection()}.
*
* @param connectionTimeoutMs the connection timeout in milliseconds
*/
void setConnectionTimeout(long connectionTimeoutMs);
/**
* This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit
* idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30
* seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout.
* A value of 0 means that idle connections are never removed from the pool.
*
* @return the idle timeout in milliseconds
*/
long getIdleTimeout();
/**
* This property controls the maximum amount of time (in milliseconds) that a connection is allowed to sit
* idle in the pool. Whether a connection is retired as idle or not is subject to a maximum variation of +30
* seconds, and average variation of +15 seconds. A connection will never be retired as idle before this timeout.
* A value of 0 means that idle connections are never removed from the pool.
*
* @param idleTimeoutMs the idle timeout in milliseconds
*/
void setIdleTimeout(long idleTimeoutMs);
/**
* This property controls the maximum lifetime of a connection in the pool. When a connection reaches this
* timeout, even if recently used, it will be retired from the pool. An in-use connection will never be
* retired, only when it is idle will it be removed.
*
* @return the maximum connection lifetime in milliseconds
*/
long getMaxLifetime();
/**
* This property controls the maximum lifetime of a connection in the pool. When a connection reaches this
* timeout, even if recently used, it will be retired from the pool. An in-use connection will never be
* retired, only when it is idle will it be removed.
*
* @param maxLifetimeMs the maximum connection lifetime in milliseconds
*/
void setMaxLifetime(long maxLifetimeMs);
/**
* The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool,
* including both idle and in-use connections. If the idle connections dip below this value, HikariCP will
* make a best effort to restore them quickly and efficiently.
*
* @return the minimum number of connections in the pool
*/
int getMinimumIdle();
/**
* The property controls the minimum number of idle connections that HikariCP tries to maintain in the pool,
* including both idle and in-use connections. If the idle connections dip below this value, HikariCP will
* make a best effort to restore them quickly and efficiently.
*
* @param minIdle the minimum number of idle connections in the pool to maintain
*/
void setMinimumIdle(int minIdle);
/**
* The property controls the maximum number of connections that HikariCP will keep in the pool,
* including both idle and in-use connections.
*
* @return the maximum number of connections in the pool
*/
int getMaximumPoolSize();
/**
* The property controls the maximum size that the pool is allowed to reach, including both idle and in-use
* connections. Basically this value will determine the maximum number of actual connections to the database
* backend.
* <p>
* When the pool reaches this size, and no idle connections are available, calls to getConnection() will
* block for up to connectionTimeout milliseconds before timing out.
*
* @param maxPoolSize the maximum number of connections in the pool
*/
void setMaximumPoolSize(int maxPoolSize);
/**
* Set the password used for authentication. Changing this at runtime will apply to new connections only.
* Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based
* connections.
*
* @param password the database password
*/
void setPassword(String password);
/**
* Set the username used for authentication. Changing this at runtime will apply to new connections only.
* Altering this at runtime only works for DataSource-based connections, not Driver-class or JDBC URL-based
* connections.
*
* @param username the database username
*/
void setUsername(String username);
/**
* The name of the data source.
*
* @return the name of the connection pool
*/
String getName();
}
package com.secoo.mall.datasource.config;
import lombok.Data;
@Data
public class MatrixDataSourceConfig implements DataSourceConfig {
private String name;
private String username;
private String password;
/**
* Connection url
*/
private String url;
private int minimumIdle = DEFAULT_MIN_IDLE;
private int maxPoolSize = DEFAULT_POOL_SIZE;
/**
* Unit:Millis
*/
private long connectionTimeout = CONNECTION_TIMEOUT;
private long idleTimeout = IDLE_TIMEOUT;
private long maxLifetime = MAX_LIFETIME;
public void setName(String name) {
this.name = POOL_NAME_PRIFIX + name;
}
@Override
public long getConnectionTimeout() {
return connectionTimeout;
}
@Override
public void setConnectionTimeout(long connectionTimeoutMs) {
this.connectionTimeout = connectionTimeoutMs;
}
@Override
public long getIdleTimeout() {
return idleTimeout;
}
@Override
public void setIdleTimeout(long idleTimeoutMs) {
this.idleTimeout = idleTimeoutMs;
}
@Override
public long getMaxLifetime() {
return maxLifetime;
}
@Override
public void setMaxLifetime(long maxLifetimeMs) {
this.maxLifetime = maxLifetimeMs;
}
@Override
public int getMinimumIdle() {
return this.minimumIdle;
}
@Override
public void setMinimumIdle(int minIdle) {
this.minimumIdle = minIdle;
}
@Override
public int getMaximumPoolSize() {
return maxPoolSize;
}
@Override
public void setMaximumPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
}
package com.secoo.mall.datasource.constant;
import java.util.concurrent.TimeUnit;
public interface DataSourceConstant {
/**
* 解密服务
*/
String APP_SECURITY_SERVER_HOST = "http://localhost:9080";
/**
* 解密文件
*/
String SECURITY_FILE_JAR_PATH = APP_SECURITY_SERVER_HOST + "/file/getSecurityFile.jar";
/**
* 获取私钥
*/
String APP_SALT_KEY_PATH = APP_SECURITY_SERVER_HOST + "/config/getPrivateKeyByAppId?appId=%s";
/**
* 阿波罗数据库的配置命名空间
*/
String DB_NAMESPACE = "db.config";
/**
* 属性配置分割符
*/
String PROPETY_SPLIT_CHAR = "\\.";
/**
* 加密标识
*/
String ENCRY_FLAG = "###";
/**
* 应用唯一标识
*/
String APP_ID = "app.id";
/**
* http访问超时时间。10S
*/
Long HTTP_CONNETION_TIME_COUT = TimeUnit.SECONDS.toMillis(10L);
}
package com.secoo.mall.datasource.constant;
import com.secoo.mall.datasource.factory.DataSourceFactory;
import com.secoo.mall.datasource.factory.DruidDataSourceFactory;
import com.secoo.mall.datasource.factory.HikariDataSourceFactory;
public enum DataSourceTypeEnum {
DRUID("druid", new DruidDataSourceFactory()), HIKARI("hikari", new HikariDataSourceFactory());
private String name;
private DataSourceFactory dataSourceFactory;
DataSourceTypeEnum(String name, DataSourceFactory dataSourceFactory) {
this.name = name;
this.dataSourceFactory = dataSourceFactory;
}
public String getName() {
return name;
}
public DataSourceFactory getDataSourceFactory() {
return dataSourceFactory;
}
public static DataSourceTypeEnum getByName(String name) {
for (DataSourceTypeEnum typeEnum : DataSourceTypeEnum.values()) {
if (typeEnum.getName().equals(name)) {
return typeEnum;
}
}
return null;
}
}
package com.secoo.mall.datasource.errorCode;
import com.secoo.mall.common.core.errorcode.ErrorCode;
public interface DataSourceError {
ErrorCode APOLLO_CONFIG_NOT_EXIST = new ErrorCode(1, "appollo config not exits");
ErrorCode PRIVATE_KEY_NOT_EXIST = new ErrorCode(2, "http get privatekey fail");
ErrorCode APP_ID_NOT_EXIST = new ErrorCode(3, "please config -Dappp.id");
ErrorCode DATA_SOURCE_NOT_EXIST = new ErrorCode(4, "could not find a datasource named %s");
}
package com.secoo.mall.datasource.factory;
import com.secoo.mall.datasource.config.MatrixDataSourceConfig;
import javax.sql.DataSource;
public abstract class AbsDataSourceFactory<T> implements DataSourceFactory<T> {
@Override
public <T extends DataSource> T createDataSouce(MatrixDataSourceConfig config) {
return convertAndCreate(config);
}
protected abstract <T extends DataSource> T convertAndCreate(MatrixDataSourceConfig config);
}
package com.secoo.mall.datasource.factory;
import com.secoo.mall.datasource.config.MatrixDataSourceConfig;
import javax.sql.DataSource;
public interface DataSourceFactory<T> {
<T extends DataSource> T createDataSouce(MatrixDataSourceConfig config);
}
package com.secoo.mall.datasource.factory;
import com.alibaba.druid.pool.DruidDataSource;
import com.secoo.mall.datasource.config.MatrixDataSourceConfig;
import java.util.Properties;
public class DruidDataSourceFactory extends AbsDataSourceFactory<DruidDataSource> {
private final String duridPreFix = "druid.";
@Override
protected DruidDataSource convertAndCreate(MatrixDataSourceConfig config) {
Properties properties = new Properties();
properties.setProperty(duridPreFix + "name", config.getName());
properties.setProperty(duridPreFix + "url", config.getUrl());
properties.setProperty(duridPreFix + "username", config.getUsername());
properties.setProperty(duridPreFix + "password", config.getPassword());
//连接相关信息
properties.setProperty(duridPreFix + "minIdle", String.valueOf(config.getMinimumIdle()));
properties.setProperty(duridPreFix + "maxActive", String.valueOf(config.getMaximumPoolSize()));
properties.setProperty(duridPreFix + "phyTimeoutMillis", String.valueOf(config.getConnectionTimeout()));
properties.setProperty(duridPreFix + "timeBetweenEvictionRunsMillis", String.valueOf(config.getIdleTimeout()));
properties.setProperty(duridPreFix + "maxEvictableIdleTimeMillis", String.valueOf(config.getMaxLifetime()));
DruidDataSource dataSource = new DruidDataSource();
dataSource.configFromPropety(properties);
return dataSource;
}
}
package com.secoo.mall.datasource.factory;
import com.secoo.mall.app.security.encryptor.Encryptor;
import com.secoo.mall.common.constant.CharConstant;
import com.secoo.mall.common.core.classloader.MatrixUrlClassLoader;
import com.secoo.mall.common.core.exception.BusinessException;
import com.secoo.mall.common.util.file.IOUtil;
import com.secoo.mall.common.util.net.IpUtil;
import com.secoo.mall.common.util.string.StringUtil;
import com.secoo.mall.datasource.constant.DataSourceConstant;
import com.secoo.mall.datasource.errorCode.DataSourceError;
import com.secoo.mall.datasource.util.SysUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Objects;
@Slf4j
public class EncryptorFactory<T> {
private volatile static EncryptorFactory instance;
private Encryptor<T> encryptor;
private final String privateKey = initPrivateKey();
/**
* 初始化是进行远程加载加密文件
*/
private EncryptorFactory() {
createEncryptor();
}
public static EncryptorFactory getInstance() {
if (instance == null) {
synchronized (EncryptorFactory.class) {
if (instance == null)
instance = new EncryptorFactory();
}
}
return instance;
}
public Encryptor<T> getEncryptor() {
return encryptor;
}
public String getPrivateKey() {
return privateKey;
}
/**
* 同步方式获取数据
*
* @return
*/
private String initPrivateKey() {
String appId = System.getProperty(DataSourceConstant.APP_ID);
if (StringUtil.isEmpty(appId)) {
throw new BusinessException(DataSourceError.APP_ID_NOT_EXIST);
}
HttpURLConnection connection = null;
try {
URL url = new URL(String.format(DataSourceConstant.APP_SALT_KEY_PATH, appId));
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.addRequestProperty(DataSourceConstant.APP_ID, appId);
connection.addRequestProperty("accessToken", SysUtil.getProperty("accessToken"));
connection.addRequestProperty("hostIP", IpUtil.getHostIp());
connection.addRequestProperty("hostName", IpUtil.getHostName());
connection.setConnectTimeout(DataSourceConstant.HTTP_CONNETION_TIME_COUT.intValue());
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = connection.getInputStream();
List<String> strings = IOUtil.readLines(inputStream, CharConstant.CHARSET_NAME);
log.info("get key secucess");
return String.join("", strings);
}
} catch (Exception e) {
log.error("connetion server error", e);
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
private void createEncryptor() {
if (StringUtil.isNotEmpty(this.privateKey)) {
MatrixUrlClassLoader.getInstance().loadByUrl(DataSourceConstant.SECURITY_FILE_JAR_PATH);
String[] args = this.privateKey.split(DataSourceConstant.ENCRY_FLAG);
encryptor = MatrixUrlClassLoader.getInstance().loadClassByFullName("com.secoo.mall.app.security.encryptor.StringEncryptor", Encryptor.class, new Class[]{String.class, String.class}, args);
}
/**
* 如果无法从远程创建解密对象,则进行降级处理
*/
if (Objects.isNull(encryptor)) {
encryptor = new Encryptor<T>() {
@Override
public T encrypt(T value) {
return value;
}
@Override
public T decrypt(T encryptedValue) {
return encryptedValue;
}
};
}
}
public static void main(String[] args) {
Encryptor encryptor = EncryptorFactory.getInstance().getEncryptor();
String key = "8d5862bcc9d076f20d2788cd731e4aa1cc7a0d187b1e9b9245d35f0f18f51968";
System.out.println(encryptor.decrypt(key));
}
}
package com.secoo.mall.datasource.factory;
import com.secoo.mall.datasource.config.MatrixDataSourceConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class HikariDataSourceFactory extends AbsDataSourceFactory<HikariDataSource> {
@Override
protected HikariDataSource convertAndCreate(MatrixDataSourceConfig config) {
//基础配置
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(config.getUrl());
hikariConfig.setPoolName(config.getName());
hikariConfig.setUsername(config.getUsername());
hikariConfig.setPassword(config.getPassword());
//连接相关配置
hikariConfig.setMinimumIdle(config.getMinimumIdle());
hikariConfig.setMinimumIdle(config.getMinimumIdle());
hikariConfig.setMaximumPoolSize(config.getMaximumPoolSize());
hikariConfig.setIdleTimeout(config.getIdleTimeout());
hikariConfig.setMaxLifetime(config.getMaxLifetime());
hikariConfig.setConnectionTimeout(config.getConnectionTimeout());
return new HikariDataSource(hikariConfig);
}
}
package com.secoo.mall.datasource.holder;
import com.secoo.mall.common.util.string.StringUtil;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* 数据源管理工具
*/
public final class DataSourceContextHolder {
private static final ThreadLocal<Deque<String>> DATA_SOURCE_HOLDER = new ThreadLocal() {
@Override
protected Object initialValue() {
return new ArrayDeque();
}
};
private DataSourceContextHolder() {
}
/**
* 获取数据源
*
* @return 数据源名臣
*/
public static String getDs() {
return DATA_SOURCE_HOLDER.get().peek();
}
/**
* 设置数据源
*
* @param ds
*/
public static void setDs(String ds) {
DATA_SOURCE_HOLDER.get().push(StringUtil.isNotEmpty(ds) ? ds : "");
}
/**
* 如果当前线程是连续切换数据源 只会移除掉当前方法的数据源名称
*/
public static void clear() {
Deque<String> deque = DATA_SOURCE_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
forceClear();
}
}
/**
* 强制清空线程中的数据源
* <p>手动清空数据源,防止OOM</p>
*/
public static void forceClear() {
DATA_SOURCE_HOLDER.remove();
}
}
package com.secoo.mall.datasource.properties;
import com.secoo.mall.datasource.config.MatrixDataSourceConfig;
import com.secoo.mall.datasource.constant.DataSourceTypeEnum;
import lombok.Data;
@Data
public class MatrixDataSourceProperties extends MatrixDataSourceConfig {
public static final String PREFIX = "spring.datasource.matrix";
public static final DataSourceTypeEnum DEFAULT_DATASOURCE_TYPE = DataSourceTypeEnum.HIKARI;
public MatrixDataSourceProperties() {
}
/**
* SourceType DRUID or HIKARI.
* Default value HIKARI.
*/
private DataSourceTypeEnum type = DEFAULT_DATASOURCE_TYPE;
public void setType(String type) {
this.type = DataSourceTypeEnum.getByName(type);
}
}
package com.secoo.mall.datasource.provider;
import com.google.common.collect.Maps;
import com.secoo.mall.app.security.encryptor.Encryptor;
import com.secoo.mall.datasource.bean.MatrixDataSource;
import com.secoo.mall.datasource.config.DataSourceConfig;
import com.secoo.mall.datasource.constant.DataSourceConstant;
import com.secoo.mall.datasource.factory.EncryptorFactory;
import com.secoo.mall.datasource.properties.MatrixDataSourceProperties;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
public abstract class AbsDataSourceProvider<T extends MatrixDataSourceProperties> implements DataSourceProvider {
private Encryptor<String> encryptor = EncryptorFactory.getInstance().getEncryptor();
@Override
public Map<String, DataSource> loadDataSources() {
List<T> list = getDataSourceProperties();
Map<String, DataSource> dataSourceMap = Maps.newHashMapWithExpectedSize(list.size());
for (T properties : list) {
DataSource dataSource = new MatrixDataSource(properties.getType(), properties);
//取得原数据源名称
String dsName = properties.getName().replace(DataSourceConfig.POOL_NAME_PRIFIX, "");
dataSourceMap.put(dsName, dataSource);
}
return dataSourceMap;
}
/**
* 解密相关加密属性项项
*/
public String decryptPropertyItemValue(String value) {
if (value.contains(DataSourceConstant.ENCRY_FLAG)) {
value = encryptor.decrypt(value);
}
return value;
}
protected abstract List<T> getDataSourceProperties();
}
package com.secoo.mall.datasource.provider;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.google.common.collect.Lists;
import com.secoo.mall.common.core.exception.BusinessException;
import com.secoo.mall.common.util.colletion.CollectionUtil;
import com.secoo.mall.common.util.string.StringUtil;
import com.secoo.mall.datasource.constant.DataSourceConstant;
import com.secoo.mall.datasource.errorCode.DataSourceError;
import com.secoo.mall.datasource.properties.MatrixDataSourceProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.MethodUtils;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 从Apollo配置中心进行加载
*/
@Slf4j
public class ApolloDataSourceProvider extends AbsDataSourceProvider<MatrixDataSourceProperties> {
/**
* 从appollo加载配置信息
*
* @return
*/
@Override
protected List<MatrixDataSourceProperties> getDataSourceProperties() {
Config appConfig = ConfigService.getConfig(DataSourceConstant.DB_NAMESPACE);
Set<String> propertyNames = appConfig.getPropertyNames();
List<MatrixDataSourceProperties> list = Lists.newArrayList();
Map<String, DataSource> dataSourceMap = new HashMap<>();
if (CollectionUtil.isEmpty(propertyNames)) {
throw new BusinessException(DataSourceError.APOLLO_CONFIG_NOT_EXIST);
}
//用数据源名字为key,进行分组
Map<String, List<String>> propertyMap = propertyNames.stream().filter(pro -> pro.contains(MatrixDataSourceProperties.PREFIX)).map(pro -> pro.replace(MatrixDataSourceProperties.PREFIX, ""))
.collect(Collectors.groupingBy(str -> str.split(DataSourceConstant.PROPETY_SPLIT_CHAR)[1]));
try {
for (Map.Entry<String, List<String>> entry : propertyMap.entrySet()) {
String dsName = entry.getKey();
MatrixDataSourceProperties matrixDataSourceProperties = new MatrixDataSourceProperties();
matrixDataSourceProperties.setName(dsName);
for (String property : entry.getValue()) {
String propertyFullPath = MatrixDataSourceProperties.PREFIX + property;
String value = decryptPropertyItemValue(appConfig.getProperty(propertyFullPath, ""));
//得到实际的属性,首字母大写
String beanProperty = StringUtil.capitalize(property.split(DataSourceConstant.PROPETY_SPLIT_CHAR)[2]);
MethodUtils.invokeExactMethod(matrixDataSourceProperties, "set" + beanProperty, value);
}
log.info("init datasource name:{}", dsName);
list.add(matrixDataSourceProperties);
}
} catch (Exception e) {
log.error("e:", e);
throw new BusinessException(DataSourceError.APOLLO_CONFIG_NOT_EXIST);
}
return list;
}
}
package com.secoo.mall.datasource.provider;
import javax.sql.DataSource;
import java.util.Map;
public interface DataSourceProvider {
/**
* 加载定义的数据源
*
* @return
*/
Map<String, DataSource> loadDataSources();
}
package com.secoo.mall.datasource.provider;
import com.secoo.mall.datasource.properties.MatrixDataSourceProperties;
import lombok.Setter;
import java.util.List;
/**
* 默认的,基于Spring的配置的加载方式
*/
public class DefaultDataSourceProvider extends AbsDataSourceProvider<MatrixDataSourceProperties> {
/**
* 注入配置
*/
@Setter
private List<MatrixDataSourceProperties> dataSourceConfigs;
@Override
protected List<MatrixDataSourceProperties> getDataSourceProperties() {
return this.dataSourceConfigs;
}
}
package com.secoo.mall.mybatis.datasouce;
import com.secoo.mall.datasource.bean.MatrixDataSource;
import com.secoo.mall.datasource.config.MatrixDataSourceConfig;
import com.secoo.mall.datasource.constant.DataSourceTypeEnum;
import org.junit.Test;
public class DataSourceTest {
@Test
public void testCreateDataSource() {
MatrixDataSourceConfig config = new MatrixDataSourceConfig();
config.setName("01");
// config.setType(DataSourceType.HIKARI);
config.setUrl("jdbc:mysql://127.0.0.1:3306/appmsdb?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false");
config.setPassword("mysql");
config.setUsername("root");
MatrixDataSource dataSource = new MatrixDataSource(DataSourceTypeEnum.HIKARI, config);
System.out.println(dataSource);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment