nacos或项目中配置文件中定义多数据源配置,通用配置
配置多数据源和通用配置
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://xxx:3306/master库名?autoReconnect=true&useCompression=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai
username: super_user
password: 123456
# message库
message:
# 从数据源开关/默认关闭
enabled: true
url: jdbc:mysql://xxx:3306/message库名?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&useTimezone=true&serverTimezone=Asia/Shanghai
username: super_admin
password: 123456
jackson:
time-zone: GMT+8
# user库
user:
# 从数据源开关/默认关闭
enabled: true
url: jdbc:mysql://xxx:3306/user库名?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&useTimezone=true&serverTimezone=Asia/Shanghai
username: super_admin
password: 123456
jackson:
time-zone: GMT+8
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username:
login-password:
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
数据库通用属性配置类
配置数据库连接的通用配置,不管是哪个数据源都是用这些通用的配置,使用@Value注解给属性注入值
提供dataSource(DruidDataSource datasource)方法,传入的datasource对象设置通用属性后返回
@Configuration
public class DruidProperties
{
@Value("${spring.datasource.druid.initialSize}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
private int maxWait;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
private boolean testOnReturn;
public DruidDataSource dataSource(DruidDataSource datasource)
{
/** 配置初始化大小、最小、最大 */
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
/** 配置获取连接等待超时的时间 */
datasource.setMaxWait(maxWait);
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}
多数据源配置类
注解说明:
bean说明
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
/**
* message数据库
*
* @param druidProperties
* @return
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.message")
@ConditionalOnProperty(prefix = "spring.datasource.druid.message", name = "enabled", havingValue = "true")
public DataSource messageDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
/**
* user数据库
*
* @param druidProperties
* @return
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.user")
@ConditionalOnProperty(prefix = "spring.datasource.druid.user", name = "enabled", havingValue = "true")
public DataSource userDataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource, DataSource messageDataSource, DataSource userDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
//DataSourceType见下文多数据源枚举类
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourceType.MESSAGE.name(), messageDataSource);
targetDataSources.put(DataSourceType.USER.name(), userDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
多数据源枚举类
/**
* 数据源枚举类
*
* @author csgosp
*/
public enum DataSourceType
{
/**
* 主库
*/
MASTER,
/**
* message库
*/
MESSAGE,
/**
* user库
*/
USER
}
定义自定义注解@interface DataSource
自定义注解中一个属性,就是数据源名称,默认值是MASTER数据源名称
需要指定数据源的方法上添加该注解,注解中指定数据源名称,方法中就可以使用指定的数据源
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}
定义切面类,处理方法上的@DataSource(value = DataSourceType.MESSAGE)注解
方法上添加了注解,方法中怎么才能使用到注解中指定的数据源呢?
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.ms.common.util.annotation.DataSource)"
+ "|| @within(com.ms.common.util.annotation.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (StringUtils.isNotNull(dataSource)) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Class<? extends Object> targetClass = point.getTarget().getClass();
DataSource targetDataSource = targetClass.getAnnotation(DataSource.class);
if (StringUtils.isNotNull(targetDataSource)) {
return targetDataSource;
} else {
Method method = signature.getMethod();
return method.getAnnotation(DataSource.class);
}
}
}
DynamicDataSourceContextHolder类
上文中已经说到,数据源名称的添加,获取,删除都是通过这个类的不同方法来实现的
数据源名称添加到哪里?从哪里获取获取,从哪里删除?
A方法通过@DataSource注解指定A数据源名称,B方法通过@DataSource注解指定B数据源名称
如何解决目标方法执行完成后,原方法找不到数据源问题?
public class DynamicDataSourceContextHolder
{
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<Stack<String>> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static synchronized void setDataSourceType(String dsType)
{
Stack<String> stack = CONTEXT_HOLDER.get() == null ? new Stack<>() : CONTEXT_HOLDER.get();
stack.push(dsType);
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(stack);
}
/**
* 获得数据源的变量
*/
public static synchronized String getDataSourceType()
{
Stack<String> stack = CONTEXT_HOLDER.get();
if (stack == null || stack.empty()) {
return null;
}
return stack.peek();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType()
{
Stack<String> stack = CONTEXT_HOLDER.get();
String pop = stack.pop();
log.info("释放{}数据源", pop);
}
}
动态数据源实现类
动态数据源类,继承AbstractRoutingDataSource类,重写determineCurrentLookupKey方法;
连接数据库的时候会根据determineCurrentLookupKey方法返回的数据源名称来创建指定的数据源连接
public class DynamicDataSource extends AbstractRoutingDataSource
{
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
{
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey()
{
return DynamicDataSourceContextHolder.getDataSourceType();
}
}