SpringBoot+Hirika 实现动态数据源
2019-02-12 本文已影响83人
yellow_han
1、实现原理
AbstractRoutingDataSource中,determineTargetDataSource 方法通过数据源的标识获取当前数据源;determineCurrentLookupKey方法则是获取数据源标识,实现动态切换数据源,需要实现determineCurrentLookupKey方法,动态提供数据源标识即可。这边使用AOP识别方法上的注解进行数据源切换。没用注解使用默认数据源。
2、需要的类
实现AbstractRoutingDataSource的方法,使用DataSourceContextHolder统一数据源管理
DynamicDataSource
package com.hsshy.beam.common.mutidatasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源
*
* @author hs
* @date 2019年2月12日
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
DataSourceContextHolder
package com.hsshy.beam.common.mutidatasource;
/**
* datasource的上下文
*
* @author hs
* @date 2019年2月12日
*/
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 设置数据源类型
*
* @param dataSourceType 数据库类型
*/
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
/**
* 获取数据源类型
*/
public static String getDataSourceType() {
return contextHolder.get();
}
/**
* 清除数据源类型
*/
public static void clearDataSourceType() {
contextHolder.remove();
}
}
数据库数据源配置
a、默认数据源 HikariProperties配置
package com.hsshy.beam.common.config;
import com.zaxxer.hikari.HikariDataSource;
/**
* <p>数据库数据源配置</p>
* @author hs
* @date 2019年2月12日
*/
public class HikariProperties {
private String url = "jdbc:mysql://127.0.0.1:3306/beam?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";
private String username = "root";
private String password = "root";
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private long connectionTimeout = 60000L;
private long idleTimeout = 60000L;
private long validationTimeout = 3000L;
private long maxLifetime = 60000L;
private int maximumPoolSize = 60;
private int minimumIdle = 10;
public void config(HikariDataSource dataSource) {
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
dataSource.setDriverClassName(driverClassName);
dataSource.setConnectionTimeout(connectionTimeout);
dataSource.setIdleTimeout(idleTimeout);
dataSource.setValidationTimeout(validationTimeout);
dataSource.setMaxLifetime(maxLifetime);
dataSource.setMaximumPoolSize(maximumPoolSize);
dataSource.setMinimumIdle(minimumIdle);
dataSource.setReadOnly(false);
}
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 String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public long getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(long connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public long getIdleTimeout() {
return idleTimeout;
}
public void setIdleTimeout(long idleTimeout) {
this.idleTimeout = idleTimeout;
}
public long getValidationTimeout() {
return validationTimeout;
}
public void setValidationTimeout(long validationTimeout) {
this.validationTimeout = validationTimeout;
}
public long getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(long maxLifetime) {
this.maxLifetime = maxLifetime;
}
public int getMaximumPoolSize() {
return maximumPoolSize;
}
public void setMaximumPoolSize(int maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
}
public int getMinimumIdle() {
return minimumIdle;
}
public void setMinimumIdle(int minimumIdle) {
this.minimumIdle = minimumIdle;
}
}
b、第二个数据源配置 MutiDataSourceProperties
package com.hsshy.beam.common.config;
import com.zaxxer.hikari.HikariDataSource;
/**
* 默认多数据源配置
*
* @author fengshuonan
* @date 2017-08-16 10:02
*/
public class MutiDataSourceProperties {
private String url = "jdbc:mysql://127.0.0.1:3306/biz?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";
private String username = "root";
private String password = "root";
private long connectionTimeout = 60000L;
private long idleTimeout = 60000L;
private long validationTimeout = 3000L;
private long maxLifetime = 60000L;
private int maximumPoolSize = 60;
private int minimumIdle = 10;
private String filters = "log4j,wall,mergeStat";
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private String[] dataSourceNames = {"dataSourceBeam", "dataSourceBiz"};
public void config(HikariDataSource dataSource) {
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
dataSource.setConnectionTimeout(connectionTimeout);
dataSource.setIdleTimeout(idleTimeout);
dataSource.setValidationTimeout(validationTimeout);
dataSource.setMaxLifetime(maxLifetime);
dataSource.setMaximumPoolSize(maximumPoolSize);
dataSource.setMinimumIdle(minimumIdle);
dataSource.setReadOnly(false);
}
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 long getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(long connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public long getIdleTimeout() {
return idleTimeout;
}
public void setIdleTimeout(long idleTimeout) {
this.idleTimeout = idleTimeout;
}
public long getValidationTimeout() {
return validationTimeout;
}
public void setValidationTimeout(long validationTimeout) {
this.validationTimeout = validationTimeout;
}
public long getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(long maxLifetime) {
this.maxLifetime = maxLifetime;
}
public int getMaximumPoolSize() {
return maximumPoolSize;
}
public void setMaximumPoolSize(int maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
}
public int getMinimumIdle() {
return minimumIdle;
}
public void setMinimumIdle(int minimumIdle) {
this.minimumIdle = minimumIdle;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getFilters() {
return filters;
}
public void setFilters(String filters) {
this.filters = filters;
}
public String[] getDataSourceNames() {
return dataSourceNames;
}
public void setDataSourceNames(String[] dataSourceNames) {
this.dataSourceNames = dataSourceNames;
}
}
多数据源配置 MultiDataSourceConfig
package com.hsshy.beam.common.config;
import com.hsshy.beam.common.mutidatasource.DynamicDataSource;
import com.hsshy.beam.common.mutidatasource.aop.MultiSourceExAop;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.sql.SQLException;
import java.util.HashMap;
/**
* 多数据源配置<br/>
* <p>
* 注:由于引入多数据源,所以让spring事务的aop要在多数据源切换aop的后面
*
* @author hs
* @Date 2019/2/12 21:58
*/
@Configuration
@ConditionalOnProperty(prefix = "beam.muti-datasource", name = "open", havingValue = "true")
@EnableTransactionManagement(order = 2)
public class MultiDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "beam.muti-datasource")
public MutiDataSourceProperties mutiDataSourceProperties() {
return new MutiDataSourceProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public HikariProperties hikariProperties() {
return new HikariProperties();
}
@Bean
public MultiSourceExAop multiSourceExAop() {
return new MultiSourceExAop();
}
/**
* guns的数据源
*/
private HikariDataSource dataSource(HikariProperties druidProperties) {
HikariDataSource dataSource = new HikariDataSource();
druidProperties.config(dataSource);
return dataSource;
}
/**
* 多数据源,第二个数据源
*/
private HikariDataSource bizDataSource(HikariProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {
HikariDataSource dataSource = new HikariDataSource();
druidProperties.config(dataSource);
mutiDataSourceProperties.config(dataSource);
return dataSource;
}
/**
* 多数据源连接池配置
*/
@Bean
public DynamicDataSource mutiDataSource(HikariProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {
HikariDataSource dataSourceBeam = dataSource(druidProperties);
HikariDataSource bizDataSource = bizDataSource(druidProperties, mutiDataSourceProperties);
try {
dataSourceBeam.getConnection();
bizDataSource.getConnection();
} catch (SQLException sql) {
sql.printStackTrace();
}
DynamicDataSource dynamicDataSource = new DynamicDataSource();
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(mutiDataSourceProperties.getDataSourceNames()[0], dataSourceBeam);
hashMap.put(mutiDataSourceProperties.getDataSourceNames()[1], bizDataSource);
dynamicDataSource.setTargetDataSources(hashMap);
dynamicDataSource.setDefaultTargetDataSource(dataSourceBeam);
return dynamicDataSource;
}
}
数据源标识 DataSource
package com.hsshy.beam.common.mutidatasource.annotion;
import java.lang.annotation.*;
/**
*
* 多数据源标识
*
* @author hs
* @date 2019年2月12日
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface DataSource {
String name() default "";
}
AOP动态切换数据源 MultiSourceExAop
package com.hsshy.beam.common.mutidatasource.aop;
import com.hsshy.beam.common.config.MutiDataSourceProperties;
import com.hsshy.beam.common.mutidatasource.DataSourceContextHolder;
import com.hsshy.beam.common.mutidatasource.annotion.DataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import java.lang.reflect.Method;
/**
* 多数据源切换的aop
*
* @author hs
* @date 2019年2月12日
*/
@Aspect
public class MultiSourceExAop implements Ordered {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
MutiDataSourceProperties mutiDataSourceProperties;
@Pointcut(value = "@annotation(com.hsshy.beam.common.mutidatasource.annotion.DataSource)")
private void cut() {
}
@Around("cut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Signature signature = point.getSignature();
MethodSignature methodSignature = null;
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
methodSignature = (MethodSignature) signature;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
DataSource datasource = currentMethod.getAnnotation(DataSource.class);
if (datasource != null) {
DataSourceContextHolder.setDataSourceType(datasource.name());
log.debug("设置数据源为:" + datasource.name());
} else {
DataSourceContextHolder.setDataSourceType(mutiDataSourceProperties.getDataSourceNames()[0]);
log.debug("设置数据源为:dataSourceCurrent");
}
try {
return point.proceed();
} finally {
log.debug("清空数据源信息!");
DataSourceContextHolder.clearDataSourceType();
}
}
/**
* aop的顺序要早于spring的事务
*/
@Override
public int getOrder() {
return 1;
}
}
多数据源枚举 DatasourceEnum
package com.hsshy.beam.common.constant;
/**
*
* 多数据源的枚举
*
* @author hs
* @date 2019年2月12日
*/
public interface DatasourceEnum {
String DATA_SOURCE_GUNS = "dataSourceBeam"; //beam数据源
String DATA_SOURCE_BIZ = "dataSourceBiz"; //其他业务的数据源
}
配置文件 application.yml
server:
port: 8081
spring:
profiles: local
datasource:
url: jdbc:mysql://127.0.0.1:3306/beam?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
filters: log4j,wall,mergeStat
hikari:
readOnly: false
connectionTimeout: 60000
idleTimeout: 60000
validationTimeout: 3000
maxLifetime: 60000
loginTimeout: 5
maximumPoolSize: 60
minimumIdle: 10
redis:
database: 0
host: 127.0.0.1
port: 6379
password: # 密码(默认为空)
timeout: 6000ms # 连接超时时长(毫秒)
##多数据源情况的配置
beam:
muti-datasource:
open: true
url: jdbc:mysql://127.0.0.1:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
dataSourceNames:
- dataSourceBeam
- dataSourceBiz
只有一个数据源可以使用以下配置,即可使用Hikari数据库连接池
spring:
profiles: local
datasource:
url: jdbc:mysql://127.0.0.1:3306/beam?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
filters: log4j,wall,mergeStat
hikari:
readOnly: false
connectionTimeout: 60000
idleTimeout: 60000
validationTimeout: 3000
maxLifetime: 60000
loginTimeout: 5
maximumPoolSize: 60
minimumIdle: 10