使用spring 实现真正多数据源的动态加载及动态切换
1前言:
公司需要做一个分析统计系统,该系统需要连接N台服务器结点,进行数据的统计分析操作,
项目是以spring为基础框架搭建的.收集现在网上的所有关于多数据源配置的方式,并没有自己十分满意的,例如我有N个数据源,按照现网可以搜索到的配置方式,都是在spring配置文件中配置N个datasource,并通过实现AbstractRoutingDataSource抽象类的子类进行多数据源的管理.这种情况个人认为很不合理,一来维护起来困难,二来,数据源的基本信息基本都一致的情况下,会造成配置文件重复性的文字.(比如:初始化连接数,最小连接数,最大连接数,等等通用的信息.)
而配置AbstractRoutingDataSource的子类必须进行targetDataSources属性的初始化,这也决定了如上所说的情况,如果有N个数据源的情况,会让配置文件显得非常冗长,也容易侵染其他业务bean配置.原因请看代码:
AbstractRoutingDataSource.java
public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } }
如代码所示,AbstractRoutingDataSource实现了InitializingBean类,实现了afterPropertiesSet方法.afterPropertiesSet方法在bean的属性赋值之后执行,
并检查targetDataSources是否有值,如果有值就将targetDataSources转换成
resolvedDataSources.也就是说,如果你要使用AbstractRoutingDataSource,就必须在方法afterPropertiesSet执行之前,进行targetDataSources属性的初始化.这也就是目前网上的配置方式,在配置文件里配置N个数据源的由来.
笔者认为很不爽快,按着自己想法,多数据源管理应该满足如下条件:
1多数据源配置信息应该独立出来
2实现动态加载,即通过自定义配置文件,让系统动态加载数据源,而不是通过spring配置文件去配置.
3AbstractRoutingDataSource的子类无需配置datasource集合,只需要简单的通过bean标签,声明在配置文件里,或者仅仅需要一个@Component注解.
4实现动态切换数据源.
2实现:
spring为多数据源的支持提供了AbstractRoutingDataSource.java类.
该类通过运行时指定当前的datasourcename来进行数据源的动态切换.您应该根据需要
重写AbstractRoutingDataSource的几个方法
AbstractRoutingDataSource.java
//查找当前用户上下文变量中设置的数据源. @Override protected Object determineCurrentLookupKey() { DataSourceType dataSourceType= DataSourceContextHolder.getDataSourceType(); return dataSourceType; } //设置默认的数据源 @Override public void setDefaultTargetDataSource(Object defaultTargetDataSource) { super.setDefaultTargetDataSource(defaultTargetDataSource); } //设置数据源集合. @Override public void setTargetDataSources(Map targetDataSources) { super.setTargetDataSources(targetDataSources); }
DataSourceContextHolder.java
/** *数据源线程上下文对象. * */ public class DataSourceContextHolder { private static final ThreadLocal contextHolder=new ThreadLocal(); public static void setDataSourceType(DataSourceType dataSourceType){ contextHolder.set(dataSourceType); } public static DataSourceType getDataSourceType(){ return (DataSourceType) contextHolder.get(); } public static void clearDataSourceType(){ contextHolder.remove(); } }
有了以上两个基础类之后,我们还需要解决一个问题,如何让我们的数据源管理器(AbstractRoutingDataSource的子类)实现声明的时候零配置,即无需对targetDataSources(数据源集合)进行配置,而是采取系统初始化时加载的方式.
思路:
1必须在afterPropertiesSet方法之前,将必须属性的值进行填充.
2必须把动态加载的数据源注册为spring容器内的bean.
因此,为了实现以上2点需求,我们必须继承AbstractRoutingDataSource类,并且实线ApplicationContextAware接口.
MutiDataSourceBean.java
/** * 初始化动态数据源 * @author Administrator * */ /** * 初始化动态数据源 * @author Administrator * */ @Component("mutiGameDs") public class MutiDataSourceBean extends AbstractRoutingDataSource implements ApplicationContextAware{ private static Logger log = Logger.getLogger("InistailizeMutiDataSourceBean"); private static ApplicationContext ac ; @Override public void afterPropertiesSet() { log.info("初始化多数据源"); try { initailizeMutiDataSource(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } log.info("多数据源加入spring容器中成功!"); super.afterPropertiesSet(); } @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { // TODO Auto-generated method stub ac=ctx; } private void initailizeMutiDataSource() throws Exception{ Document doc = XmlUtils.loadXMLClassPath("game-ds.xml"); List servers = doc.selectNodes("//server"); DruidDataSource ds = null; ..... .... .. . DefaultListableBeanFactory acf = (DefaultListableBeanFactory)ac.getAutowireCapableBeanFactory(); Map<Object,DruidDataSource> dsMap = new HashMap<Object, DruidDataSource>(); for (Object object : servers) { Element el =(Element)object; ds = new DruidDataSource(); String id = el.attributeValue("id"); String username = el.attributeValue("username"); String url = el.attributeValue("url"); String pwd = el.attributeValue("pwd"); ds.setUsername(username); ds.setUrl(url); ds.setPassword(pwd); ds.setInitialSize( Integer.valueOf(initialSize)); ds.setMaxActive(Integer.valueOf(maxActive)); ds.setMinIdle(Integer.valueOf(minIdle)); ds.setMaxWait(Integer.valueOf(maxWait)); ds.setTestOnBorrow(testOnBorrow.equals("true")?true:false); ds.setTestOnReturn(testOnReturn.equals("true")?true:false); ds.setTestWhileIdle(testWhileIdle.equals("true")?true:false); ds.setTimeBetweenEvictionRunsMillis(Long.valueOf(timeBetweenEvictionRunsMillis)); ds.setMinEvictableIdleTimeMillis(Long.valueOf(minEvictableIdleTimeMillis)); ds.setRemoveAbandoned(removeAbandoned.equals("true")?true:false); ds.setRemoveAbandonedTimeout(Integer.valueOf(removeAbandonedTimeout)); ds.setLogAbandoned(logAbandoned.equals("true")?true:false); ds.setFilters(filters); acf.registerSingleton(id, ds); dsMap.put(DataSourceType.valueOf(id), ds); } this.setTargetDataSources(dsMap); setDefaultTargetDataSource(dsMap.get("game_server_1"));//设置默认数据源 } @Override protected Object determineCurrentLookupKey() { DataSourceType dataSourceType= DataSourceContextHolder.getDataSourceType(); return dataSourceType; } @Override public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { super.setDataSourceLookup(dataSourceLookup); } @Override public void setDefaultTargetDataSource(Object defaultTargetDataSource) { super.setDefaultTargetDataSource(defaultTargetDataSource); } @Override public void setTargetDataSources(Map targetDataSources) { super.setTargetDataSources(targetDataSources); } }
OK,大公告成.
public class TestMutiDataSource extends SpringTxTestCase{ @Autowired @Qualifier("jdbcTemplate4Game") JdbcTemplate jt; @Test public void test() { DataSourceContextHolder.setDataSourceType(DataSourceType.game_server_1); List<Map<String, Object>> list = jt.queryForList("select* from fs_accountlog.t_accountlogin"); for (Map<String, Object> map : list) { System.out.println(map); } } }