1.定义数据库配置文件
server.port=8081
spring.datasource.jdbcUrl=jdbc:mysql://******:3306/spring-db-2019?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=*****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
y2018.spring.datasource.jdbcUrl=jdbc:mysql://******:3306/spring-db-2018?useUnicode=true&characterEncoding=utf8
y2018.spring.datasource.username=root
y2018.spring.datasource.password=*****
y2018.spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
logging.level.com.ijianghu.dynamic.web=debug
2.配置多数据源、sessionFactory和事务管理器
package com.ijianghu.dynamic.web.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
public class BaseDataSourceConfig {
/**
* 配置默认数据源,并设置高优先级
* @return
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置多数据源
* @return
*/
@Bean("y2018DataSource")
@ConfigurationProperties(prefix = "y2018.spring.datasource")
public DataSource y2018DataSource(){
return DataSourceBuilder.create().build();
}
/**
* 设置多数据源,配置动态路由数据源
* @return
*/
@Bean("dynamincDataSource")
public DataSource dynamincDataSource(){
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
HashMap<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("dataSource",dataSource());
dataSourceMap.put("y2018DataSource",y2018DataSource());
//配置默认目标数据源
dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource());
//设置目标数据源集合,供路由选择
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
/**
* 设置会话工厂
* @return
* @throws Exception
*/
@Bean(name="sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//配置数据源为多数据源
factoryBean.setDataSource(dynamincDataSource());
//设置mybaits的xml文件路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
return factoryBean.getObject();
}
/**
* 配置事务管理器
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dynamincDataSource());
}
}
3.继承AbstractRoutingDataSource类,实现选择目标数据源
package com.ijianghu.dynamic.web.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* Multiple DataSource Configurer
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected Object determineCurrentLookupKey() {
logger.info("Current DataSource is {}",DynamicDataSourceContextHolder.getDataSourceKey());
//从动态数据源上下文持有者里面获取
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
4.配置多数据源上下文持有者
package com.ijianghu.dynamic.web.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Multiple DataSource Context Holder
*/
public class DynamicDataSourceContextHolder {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Maintain variable for every thread, to avoid effect other thread
*/
private static ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.dataSource::name);;
/**
* All DataSource List
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* Get current DataSource
*
* @return data source key
*/
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
/**
* To switch DataSource
*
* @param key the key
*/
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
/**
* To set DataSource as default
*/
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
/**
* Check if give DataSource is in current DataSource list
*
* @param key the key
* @return boolean boolean
*/
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
/**
* Use slave data source.
*/
public static void useSlaveDataSource(String ds) {
DataSourceKey dataSourceKey = DataSourceKey.valueOf(ds);
//if there is no suitable enum,then use default dataSource
if(dataSourceKey == null){
setDataSourceKey(DataSourceKey.dataSource.dataSourceName);
}
setDataSourceKey(dataSourceKey.dataSourceName);
}
/**
* Use master data source.
*/
public static void useMasterDataSource() {
CONTEXT_HOLDER.set(DataSourceKey.dataSource.dataSourceName);
}
}
5.根据数据源名称,配置多数据源枚举类,根据参数获取对应的数据源名称
package com.ijianghu.dynamic.web.config;
public enum DataSourceKey {
dataSource("dataSource","dataSource"),
y2018("key","y2018DataSource");
public String key;
public String dataSourceName;
DataSourceKey(String key, String dataSourceName){
this.key = key;
this.dataSourceName = dataSourceName;
}
}
6.配置AOP切面,根据参数设置对应的数据源名称
package com.ijianghu.dynamic.web.aop;
import com.ijianghu.dynamic.web.config.DynamicDataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)||" +
"@annotation(org.springframework.web.bind.annotation.GetMapping)||" +
"@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void daoAspect(){ }
@Before("daoAspect()")
public void switchDataSource(JoinPoint point){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ds = request.getHeader("ds");
if(ds != null){
DynamicDataSourceContextHolder.useSlaveDataSource(ds);
}else{
DynamicDataSourceContextHolder.useMasterDataSource();
}
}
}
7.关键点
1、数据源是如何切换的
根据参数获取到对应的数据源名称,然后动态数据源路由根据获取到的name,从数据源集合里面获取对应的dataSource