前言
项目中我们经常会遇到的问题,尤其是数据同步或定时任务等项目更是如此。多数据源让人最头痛的,不是配置多个数据源,而是如何能灵活动态的切换数据源。例如在一个spring和hibernate的框架的项目中,我们在spring配置中往往是配置一个dataSource来连接数据库,然后绑定给sessionFactory,在dao层代码中再指定sessionFactory来进行数据库操作。
正如上图所示,每一块都是指定绑死的,如果是多个数据源,也只能是下图中那种方式。
可看出在Dao层代码中写死了两个SessionFactory,这样日后如果再多一个数据源,还要改代码添加一个SessionFactory,显然这并不符合开闭原则。
那么正确的做法应该是
(引用:http://blog.csdn.net/wangpeng047/article/details/8866239博客的图片):
实现原理
1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
从AbstractRoutingDataSource的源码中:
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
获取连接的方法中,重点是determineTargetDataSource()方法,看源码:
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
DataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static final String TEST_DB="testDataSource";
public static final String DEV_DB="devDataSource";
/**
* @Description: 设置数据源类型
* @param dataSourceType 数据库类型
* @return void
* @throws
*/
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
/**
* @Description: 获取数据源类型
* @param
* @return String
* @throws
*/
public static String getDataSourceType() {
return contextHolder.get();
}
/**
* @Description: 清除数据源类型
* @param
* @return void
* @throws
*/
public static void clearDataSourceType() {
contextHolder.remove();
}
}
2、setDataSource这方法是要在什么时候执行呢?手动在代码中调用写死吗?这是多蠢的方法,当然要让它动态咯。所以可以应用spring aop来设置,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源(就跟设置事务一样):
@DataSource(name=DataSource.slave1)
public List getProducts(){
3.在方法设置数据源仍然很麻烦,而且增大代码量,所以使用aop来达到切换数据源,每个数据源相应的dao层代码放在不同的包里,通过aop的@Before 在实现不同的包使用不同的数据源
@Before("execution(* org.vergil.dao.dev.*.*(..))")
public void changeDevDataSource(){
DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DEV_DB);
}
@Before("execution(* org.vergil.dao.test.*.*(..))")
public void changeTestDataSource(){
DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.TEST_DB);
}
4.下面是java方式配置动态数据源
@Bean
public DynamicDataSource dataSource(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("devDataSource", devDataSource());
dataSourceMap.put("testDataSource", testDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(devDataSource());
return dynamicDataSource;
}
@Bean
public DataSource devDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.username"));
dataSource.setPassword(env.getProperty("db.password"));
return dataSource;
}
@Bean
public DataSource testDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("test.driver"));
dataSource.setUrl(env.getProperty("test.url"));
dataSource.setUsername(env.getProperty("test.username"));
dataSource.setPassword(env.getProperty("test.password"));
return dataSource;
}