使用spring 实现真正多数据源的动态加载及动态切换

来源:互联网 发布:c语言实验报告答案 编辑:程序博客网 时间:2024/06/05 07:36
内容提示:数据的统计分析操作,项目是以spring为基础框架搭建的.收集现在网上的所有关于多数据源配置的方式,并没有自己十分满意的,例如我有N个数据源,按照现网可以搜索到的配置方式,都是在spring配置文件中配置N个datasource,并通过实现AbstractRoutingDataSource抽象类的子类进行多...
1 前言:
  公司需要做一个分析统计系统,该系统需要连接N台服务器结点,进行数据的统计分析操作,
项目是以spring为基础框架搭建的.收集现在网上的所有关于多数据源配置的方式,并没有自己十分满意的,例如我有N个数据源,按照现网可以搜索到的配置方式,都是在spring配置文件中配置N个datasource,并通过实现AbstractRoutingDataSource抽象类的子类进行多数据源的管理.这种情况个人认为很不合理,一来维护起来困难,二来,数据源的基本信息基本都一致的情况下,会造成配置文件重复性的文字.(比如:初始化连接数,最小连接数,最大连接数,等等通用的信息.)
  
  而配置AbstractRoutingDataSource的子类必须进行targetDataSources属性的初始化,这也决定了如上所说的情况,如果有N个数据源的情况,会让配置文件显得非常冗长,也容易侵染其他业务bean配置.原因请看代码:
AbstractRoutingDataSource.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void afterPropertiesSet() {
        if(this.targetDataSources ==null) {
            thrownew 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配置文件去配置.
3 AbstractRoutingDataSource的子类无需配置datasource集合,只需要简单的通过bean标签,声明在配置文件里,或者仅仅需要一个@Component注解.
4 实现动态切换数据源.

2 实现:
   spring为多数据源的支持提供了AbstractRoutingDataSource.java类.
该类通过运行时指定当前的datasourcename来进行数据源的动态切换.您应该根据需要
重写AbstractRoutingDataSource的几个方法
AbstractRoutingDataSource.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//查找当前用户上下文变量中设置的数据源.
@Override
    protectedObject determineCurrentLookupKey() {
        DataSourceType dataSourceType= DataSourceContextHolder.getDataSourceType();
         
        returndataSourceType;
    }
 
//设置默认的数据源
    @Override
    publicvoid setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
    }
//设置数据源集合.
    @Override
    publicvoid setTargetDataSources(Map targetDataSources) {
        super.setTargetDataSources(targetDataSources);
    }



DataSourceContextHolder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
*数据源线程上下文对象.
*
*/
public class DataSourceContextHolder {
 
    privatestatic final ThreadLocal contextHolder=new ThreadLocal();
     
    publicstatic void setDataSourceType(DataSourceType dataSourceType){
        contextHolder.set(dataSourceType);
    }
     
    publicstatic DataSourceType getDataSourceType(){
        return(DataSourceType) contextHolder.get();
    }
     
    publicstatic void clearDataSourceType(){
        contextHolder.remove();
    }
     
}


有了以上两个基础类之后,我们还需要解决一个问题,如何让我们的数据源管理器(AbstractRoutingDataSource的子类)实现声明的时候零配置,即无需对targetDataSources(数据源集合)进行配置,而是采取系统初始化时加载的方式.

思路:
1 必须在afterPropertiesSet方法之前,将必须属性的值进行填充.
2 必须把动态加载的数据源注册为spring容器内的bean.

因此,为了实现以上2点需求,我们必须继承 AbstractRoutingDataSource类,并且实线 ApplicationContextAware 接口.
MutiDataSourceBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
 * 初始化动态数据源
 * @author Administrator
 *
 */
/**
 * 初始化动态数据源
 * @author Administrator
 *
 */
@Component("mutiGameDs")
public class MutiDataSourceBean extendsAbstractRoutingDataSource implementsApplicationContextAware{
 
    privatestatic Logger log = Logger.getLogger("InistailizeMutiDataSourceBean");
    privatestatic ApplicationContext ac ;
 
    @Override
    publicvoid afterPropertiesSet() {
         
        log.info("初始化多数据源");
        try{
            initailizeMutiDataSource();
        }catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        log.info("多数据源加入spring容器中成功!");
 
        super.afterPropertiesSet();
    }
 
    @Override
    publicvoid setApplicationContext(ApplicationContext ctx)
            throwsBeansException {
        // 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
    protectedObject determineCurrentLookupKey() {
        DataSourceType dataSourceType= DataSourceContextHolder.getDataSourceType();
         
        returndataSourceType;
    }
 
    @Override
    publicvoid setDataSourceLookup(DataSourceLookup dataSourceLookup) {
        super.setDataSourceLookup(dataSourceLookup);
    }
 
    @Override
    publicvoid setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
    }
 
    @Override
    publicvoid setTargetDataSources(Map targetDataSources) {
        super.setTargetDataSources(targetDataSources);
    }
 
}


xml


<bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">false
                </prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9iDialect
                </prop>
                <prop key="hibernate.show_sql">true
                </prop>
                <prop key="hibernate.query.substitutions">true 1, false 0, yes 'Y', no 'N'
                </prop>
                <prop key="hibernate.bytecode.use_reflection_optimizer">true
                </prop>
                <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
                </prop>
                <prop key="hibernate.cache.use_query_cache">true
                </prop>
                <prop key="hibernate.cache.use_structured_entries">false
                </prop>
                <prop key="hibernate.max_fetch_depth">0
                </prop>
                <prop key="hibernate.jdbc.fetch_size">50
                </prop>
                <prop key="hibernate.jdbc.batch_size">20
                </prop>
            </props>
        </property>
         <property name="dataSource">
            <ref bean="mutiDataSourceBean" />
        </property>
    </bean>
    <bean id="jdbcExceptionTranslator"
        class="org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator">
         <property name="dataSource">
            <ref bean="mutiDataSourceBean" />
        </property>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
        <property name="jdbcExceptionTranslator">
            <ref bean="jdbcExceptionTranslator" />
        </property>
    </bean>
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref local="sessionFactory" />
        </property>
    </bean>

 <bean id="mutiDataSourceBean" class="com.mshop.common.dataSource.MutiDataSourceBean">  
    </bean>


OK,大公告成.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestMutiDataSource  extendsSpringTxTestCase{
 
    @Autowired
    @Qualifier("jdbcTemplate4Game")
    JdbcTemplate  jt;
     
    @Test
    publicvoid 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);
        }
    }
 
}
0 1