当一个项目有多个数据源时,我们可以通过spring 切面优雅的实现数据源切换
1.定义切面
public DataSourceAspect(){
logger.debug("选择数据源---");
}
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
@Pointcut("@within(com.some.web.db.DataSource) || @annotation(com.some.web.db.DataSource)")
public void pointCut(){
logger.debug("选择数据源---");
}
@Before("pointCut() && @annotation(dataSource)")
public void doBefore(DataSource dataSource){
logger.debug("选择数据源---"+dataSource.value().getValue());
DataSourceContextHolder.setDataSource(dataSource.value().getValue());
}
@After("pointCut()&& @annotation(dataSource)")
public void doAfter(DataSource dataSource){
logger.debug("清除数据源---"+dataSource.value().getValue());
DataSourceContextHolder.remove();
}
2.切面自动把切换的数据源入栈
public class DataSourceContextHolder {
private static final Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);
private static final ThreadLocal<Stack<String>> contextHolder = new InheritableThreadLocal<>();
/**
* 设置数据源
* @param db
*/
public static void setDataSource(String db){
Stack<String> st = contextHolder.get();
if(st==null){
st = new Stack<String>();
contextHolder.set(st);
}
st.push(db);
}
/**
* 取得当前数据源
* @return
*/
public static String getDataSource(){
Stack<String> st = contextHolder.get();
//不移除
return st==null||st.isEmpty()?null:st.peek();
}
/**
* 清除上下文数据
*/
public static void clear(){
contextHolder.remove();
}
/**
* 清除当前db
*/
public static void remove(){
Stack<String> st = contextHolder.get();
if(st==null||st.isEmpty()){
logger.warn("数据源切换异常");
}else {
st.pop();
}
}
}
3. 定义多数据源路由的数据源 这个设置为Primary
public class MultipleDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
4.注册数据源
@Configuration
public class DataSourceConfig {
@Bean(name = "someDataSource1")
@Qualifier("someDataSource1")
@ConfigurationProperties(prefix = "spring.datasource.some1")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
}
@Bean(name = "someDataSource2")
@Qualifier("someDataSource2")
@ConfigurationProperties(prefix = "spring.datasource.some2")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
}
@Bean(name = "multipleDataSource")
@Qualifier("multipleDataSource")
@Primary
public DataSource multipleDataSource(@Qualifier("someDataSource1") DataSource some1, @Qualifier("someDataSource2") DataSource some2) {
MultipleDataSource multipleDataSource = new MultipleDataSource();
Map< Object, Object > targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceEnum.SOME1.getValue(), some1);
targetDataSources.put(DataSourceEnum.SOME2.getValue(), some2);
//添加数据源
multipleDataSource.setTargetDataSources(targetDataSources);
//设置默认数据源
multipleDataSource.setDefaultTargetDataSource(some1);
return multipleDataSource;
}
}
5.通过在方法加注解切换
@DataSource(DataSourceEnum.SOME1)
public void getMaxVip(){
}
public enum DataSourceEnum {
SOME1("some1"),SOME2("some2")
private String value;
DataSourceEnum(String value){this.value=value;}
public String getValue() {
return value;
}
}
spring 配置
spring:
datasource:
some1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url:
username:
password:
initialize: false
sqlScriptEncoding: UTF-8
initialSize: 5
minIdle: 5
maxActive: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 600000
minEvictableIdleTimeMillis: 3000000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true #在检查闲置连接时同时检查连接可用性
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
some2:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url:
username:
password:
initialize: false
sqlScriptEncoding: UTF-8
initialSize: 5
minIdle: 5
maxActive: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 600000
minEvictableIdleTimeMillis: 3000000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true #在检查闲置连接时同时检查连接可用性
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
备注:spring boot 默认开启open-in-view,所以在controller层同时调用两个带事务service时(service注解了不同数据源),切换会失效,因为事务在controller方法只会获取一次数据源连接,第二次不会获取,一般建议如果不需要懒加载功能则把open-in-view关闭,配置
spring:
jpa:
open-in-view: false