一般在实际开发中,可能需要牵涉到多个数据源,这个时候就需要在使用service方法的时候,动态注入要使用的数据源,下面就简单分析一下如何实现
1 提取数据源的公用属性
一般如下所示
@Configuration
@Slf4j
@Data
public class CommonDataSourceProperties {
/**
* 自动提交从池中返回的连接
*/
@Value("${jdbc.datasource.autoCommit}")
private boolean autoCommit;
/**
* 等待来自池的连接的最大毫秒数
*/
@Value("${jdbc.datasource.connectionTimeout}")
private long connectionTimeout;
/**
* 接允许在池中闲置的最长时间
*/
@Value("${jdbc.datasource.idleTimeout}")
private long idleTimeout;
/**
* 池中连接最长生命周期
*/
@Value("${jdbc.datasource.maxLifetime}")
private long maxLifetime;
/**
* 池中维护的最小空闲连接数
*/
@Value("${jdbc.datasource.minimumIdle}")
private int minimumIdle;
/**
* 池中最大连接数,包括闲置和使用中的连接
*/
@Value("${jdbc.datasource.maximumPoolSize}")
private int maximumPoolSize;
/**
* 连接尝试查询
*/
@Value("${jdbc.datasource.connectionTestQuery}")
private String connectionTestQuery;
@PostConstruct
public void print(){
log.info("autocommit:"+autoCommit+",connectionTimeout:"+connectionTimeout+",idleTimeout:"+idleTimeout+"....");
}
}
2 配置多个数据源
通常会把多个数据源存放到一个类里面进行维护
@Configuration
public class DataSourceConfiguration {
@Autowired
private CommonDataSourceProperties commonDataSourceProperties;
/**
* 数据中心数据源
* @return
*/
@Bean(name = "dataCenterDataSource")
@ConfigurationProperties(prefix="spring.datasource.datacenter")
public DataSource createDatacenterDataSource() {
HikariDataSource hikariDataSource = (HikariDataSource) DataSourceBuilder.create().build();
initDataSourceConfig(hikariDataSource);
return hikariDataSource;
}
/**
* 前置校验数据源
* @return
*/
@Bean(name = "standardDataSource")
@ConfigurationProperties(prefix = "spring.datasource.standard")
public DataSource createStandardDataSource() {
HikariDataSource hikariDataSource = (HikariDataSource) DataSourceBuilder.create().build();
initDataSourceConfig(hikariDataSource);
return hikariDataSource;
}
/**
* 创建科创版数据源
*/
@Bean(name = "kcbDataSource")
@ConfigurationProperties(prefix = "spring.datasource.kcb")
public DataSource createKcbDataSource() {
HikariDataSource hikariDataSource = (HikariDataSource) DataSourceBuilder.create().build();
initDataSourceConfig(hikariDataSource);
return hikariDataSource;
}
/**
* 数据源共用基本属性
* @param dataSource
* @return
*/
private void initDataSourceConfig(HikariDataSource dataSource){
dataSource.setAutoCommit(commonDataSourceProperties.isAutoCommit());
dataSource.setConnectionTimeout(commonDataSourceProperties.getConnectionTimeout());
dataSource.setIdleTimeout(commonDataSourceProperties.getIdleTimeout());
dataSource.setMaxLifetime(commonDataSourceProperties.getMaxLifetime());
dataSource.setMinimumIdle(commonDataSourceProperties.getMinimumIdle());
dataSource.setMaximumPoolSize(commonDataSourceProperties.getMaximumPoolSize());
dataSource.setConnectionTestQuery(commonDataSourceProperties.getConnectionTestQuery());
}
/**
* 创建动态数据源
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(createStandardDataSource());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap<>();
dsMap.put(DataSourceContextHolder.DATA_CENTER_SOURCE, createDatacenterDataSource());
dsMap.put(DataSourceContextHolder.DATA_STANDARD_SOURCE, createStandardDataSource());
dsMap.put(DataSourceContextHolder.DATA_KCB_SOURCE,createKcbDataSource());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 注入动态数据源到事务
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
@Bean(name = "dynamicSqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("dynamicDataSource") DataSource eccsDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(eccsDataSource);
Resource[] checkResources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml");
bean.setMapperLocations(ArrayUtils.addAll(checkResources));
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
}
@Bean(name = "dynamicSqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("dynamicSqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
3 保存多个数据源的标识,存放到线程上下文中
考虑到线程共享,把标识每种数据源的标识存放到ThreadLocal
public class DataSourceContextHolder {
public static final String DATA_CENTER_SOURCE ="DATA_CENTER_SOURCE";
public static final String DATA_STANDARD_SOURCE = "DATA_STANDARD_SOURCE";
public static final String DATA_KCB_SOURCE = "DATA_KCB_SOURCE";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 设置指定的数据源类型
* @param dbType
*/
public static void setDbType(String dbType) {
contextHolder.set(dbType);
}
/**
* 获取指定的数据源类型
* @return
*/
public static String getDbType() {
return ((String) contextHolder.get());
}
/**
* 清除指定的数据源
*/
public static void clearDbType() {
contextHolder.remove();
}
/**
* 判断是否包含数据源
* @param dsId
* @return
*/
public static boolean containsDataSource(String dsId) {
if (null == dsId) {
return false;
} else if (!DATA_CENTER_SOURCE.equals(dsId) && !DATA_STANDARD_SOURCE.equals(dsId) && !DATA_KCB_SOURCE.equals(dsId)) {
return false;
} else {
return true;
}
}
}
4 实现多数据源的路由接口
通过实现spring里面自带的数据源路由接口,来达到动态路由机制
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDbType();
}
}
5 实现数据源动态注入到切面
像这种一般和业务无关的代码,都是通过切面的机制进行实现,在执行方法之前进行数据源的注入
,在执行完方法之后,要把指定的数据源再次给清除掉
@Aspect
@Order(-15) //保证该AOP在@Transactional之前执行
@Component
@Log4j2
public class DataSourceAspect {
/**
* 进入方法执行之前 先设置指定的数据源
* @param point
* @param dataSource
* @throws Throwable
*/
@Before(value = "@annotation(dataSource)")
public void afterReturning(JoinPoint point, DataSource dataSource) throws Throwable {
// 获取当前的指定的数据源;
String dsId = dataSource.value();
if (!DataSourceContextHolder.containsDataSource(dsId)) {
log.warn("数据源[" + dataSource.value() + "]不存在,使用默认数据源 > " + DataSourceContextHolder.DATA_CENTER_SOURCE);
} else {
log.info("使用数据源 > " + dataSource.value());
DataSourceContextHolder.setDbType(dataSource.value());
}
}
/**
* 方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
* @param point
* @param dataSource
*/
@After(value = "@annotation(dataSource)")
public void restoreDataSource(JoinPoint point, DataSource dataSource) {
log.info("Destory DataSource > " + dataSource.value() + " >" + point.getSignature());
DataSourceContextHolder.clearDbType();
}
}
6 添加数据源的动态注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSource {
/**
* 默认返回数据中心的数据源
* @return
*/
String value() default DataSourceContextHolder.DATA_CENTER_SOURCE;
}
注意,数据源的插入一定要在事务之前获取到,否则就会出现事务连接问题了.,事务都是和特定的Connection绑定的,而Connection的获取依赖于DataSource.