配置类
package com.nuzar.fcms.common.core.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.convert.DurationUnit;
import javax.sql.DataSource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
/**
* 数据源配置信息,使用 Hikari
*
* @author tangang
*/
@Getter
@Setter
public class HikariDataSourceProperties {
private String poolName;
private String driverClassName;
private String url;
private String username;
private String password;
private int maxPoolSize = 100;
private int minIdle = 1;
@DurationUnit(ChronoUnit.MINUTES)
private Duration idleTimeout = Duration.ofMinutes(10);
@DurationUnit(ChronoUnit.MINUTES)
private Duration validationTimeout = Duration.ofMinutes(10);
@DurationUnit(ChronoUnit.MINUTES)
private Duration maxLifetime = Duration.ofMinutes(30);
@DurationUnit(ChronoUnit.MINUTES)
private Duration connectionTimeout = Duration.ofMinutes(30);
/**
* 拓展配置
*/
private Map<String, String> dataSourceProperty = new HashMap<>();
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setPoolName(getPoolName());
config.setDriverClassName(getDriverClassName());
config.setJdbcUrl(getUrl());
config.setUsername(getUsername());
config.setPassword(getPassword());
config.setMinimumIdle(getMinIdle());
config.setMaximumPoolSize(getMaxPoolSize());
config.setAutoCommit(true);
config.setIdleTimeout(getIdleTimeout().toMillis());
config.setMaxLifetime(getMaxLifetime().toMillis());
config.setConnectionTimeout(getConnectionTimeout().toMillis());
config.addDataSourceProperty("cachePrepStmts", "true"); // 是否自定义配置,为true时下面两个参数才生效
config.addDataSourceProperty("prepStmtCacheSize", "250"); // 连接池大小默认25,官方推荐250-500
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); // 单条语句最大长度默认256,官方推荐2048
for (Map.Entry<String, String> entry : dataSourceProperty.entrySet()) {
String key = entry.getKey(), value = entry.getValue();
config.addDataSourceProperty(key, value);
}
return new HikariDataSource(config);
}
}
package com.nuzar.fcms.common.core.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import java.util.HashMap;
import java.util.Map;
/**
* 默认数据源与多数据源配置文件
*
* @author tangang
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "spring.datasource")
public class CompositeDataSourceProperties extends HikariDataSourceProperties {
@NestedConfigurationProperty
private Map<String, HikariDataSourceProperties> multi = new HashMap<>();
}
注入bean
package com.nuzar.fcms.common.core.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* 数据源配置 bean
*
* @author tangang
*/
@Configuration
@EnableConfigurationProperties(CompositeDataSourceProperties.class)
public class DataSourceConfiguration {
@Autowired
private CompositeDataSourceProperties compositeDataSourceProperties;
/**
* 多数据源
*/
@Bean
public DataSource dataSource() {
return new MultiRoutingDataSource(compositeDataSourceProperties);
}
}
多数据源实现
package com.nuzar.fcms.common.core.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 支持路由多数据源的 datasource
*
* @author tangang
*/
public class MultiRoutingDataSource extends AbstractRoutingDataSource {
/**
* 数据源配置
*/
private CompositeDataSourceProperties compositeDataSourceProperties;
public MultiRoutingDataSource(CompositeDataSourceProperties compositeDataSourceProperties) {
this.compositeDataSourceProperties = compositeDataSourceProperties;
init();
}
/**
* 初始化数据源
*/
private void init() {
//设置默认数据源
setDefaultTargetDataSource(compositeDataSourceProperties.dataSource());
Map<Object, Object> multiDataSource = new HashMap<>();
for (Map.Entry<String, HikariDataSourceProperties> entry : compositeDataSourceProperties.getMulti().entrySet()) {
multiDataSource.put(entry.getKey(), entry.getValue().dataSource());
}
//设置路由目标数据源
setTargetDataSources(multiDataSource);
}
/**
* 确定当前数据源路由key
*/
@Override
protected Object determineCurrentLookupKey() {
return MultiRoutingDataSourceContextHolder.getRoutingKey();
}
}
多数据源路由 key 的线程上下文
package com.nuzar.fcms.common.core.config;
/**
* 多数据源路由 key 的线程上下文,通过 @MultiDataSource 的切面变化,不建议直接调用
*
* @author tangang
*/
class MultiRoutingDataSourceContextHolder {
private static final ThreadLocal<String> routingKeyThreadLocal = new ThreadLocal<>();
public static void setRoutingKey(String routingKey) {
routingKeyThreadLocal.set(routingKey);
}
public static String getRoutingKey() {
return routingKeyThreadLocal.get();
}
public static void clear() {
routingKeyThreadLocal.remove();
}
}
多数据源切面
package com.nuzar.fcms.common.core.config;
import com.nuzar.fcms.common.core.annotations.MultiDataSource;
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.AnnotatedElementUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* 多数据源切面
*/
@Aspect
@Component
public class MultiRoutingDataSourceAspectConfig {
private ExpressionParser parser = new SpelExpressionParser();
@Pointcut("@annotation(com.nuzar.fcms.common.core.annotations.MultiDataSource)")
public void multiDataSource() {
}
/**
* 围绕 @MultiDataSource 注解标识的方法设置数据源
*/
@Around("multiDataSource()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String routingKey = getRoutingKey(methodSignature, joinPoint.getArgs());
//设置当前线程上下文的数据源路由key
MultiRoutingDataSourceContextHolder.setRoutingKey(routingKey);
try {
return joinPoint.proceed();
} finally {
//清除当前线程上下文的数据源路由key
MultiRoutingDataSourceContextHolder.clear();
}
}
/**
* 根据 @MultiDataSource 注解的属性获取路由 key
*
* @param methodSignature
* @param args
* @return
*/
private String getRoutingKey(MethodSignature methodSignature, Object[] args) {
if (methodSignature == null) {
return null;
}
MultiDataSource multiDataSource = AnnotatedElementUtils.getMergedAnnotation(methodSignature.getMethod(), MultiDataSource.class);
if (multiDataSource == null) {
multiDataSource = AnnotatedElementUtils.getMergedAnnotation(methodSignature.getDeclaringType(), MultiDataSource.class);
}
if (multiDataSource == null) {
return null;
}
//优先使用 name 硬编码的 key
if (StringUtils.hasText(multiDataSource.name())) {
return multiDataSource.name();
}
if (StringUtils.hasText(multiDataSource.key())) {
return parseSpel(methodSignature.getMethod(), args, multiDataSource.key());
}
return null;
}
/**
* 解析 spel 表达式
*
* @param method 方法
* @param arguments 参数
* @param spel 表达式
* @return 执行spel表达式后的结果
*/
private String parseSpel(Method method, Object[] arguments, String spel) {
Parameter[] parameters = method.getParameters();
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < parameters.length; len++) {
context.setVariable(parameters[len].getName(), arguments[len]);
}
Expression expression = parser.parseExpression(spel);
return expression.getValue(context, String.class);
}
}
注解用法
/**
* 在场箱汇总列表
*/
@MultiDataSource(name = "ph")
List<PFYardContainerSumVw> yardSummaryList(String iycCstCoperc);
yml配置
spring:
#oracle数据源
datasource:
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@//10.166.0.161:1521/hftest
username: hfpttest
password: HFpttt#0923#Ss
max-pool-size: 100
idle-timeout: 60000
validation-timeout: 60000
max-lifetime: 360000
multi:
ph:
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@//10.166.0.34:1521/hftest
username: hftops
password: Hftops#220110
max-pool-size: 100
idle-timeout: 60000
validation-timeout: 60000
max-lifetime: 360000