本文的几个关键词,分布式数据源,数据源的动态寻找,分布式事务JTA实现。
对于一些较大规模的应用,单个数据源是无法支撑起庞大的用户量,需要引入多数据源,水平层面进行分库分表,降低单个DB的负载。接下来,我们程序里里面需 要管理不同数据源之前的程序调用,保证功能是WORK的。另外,跨库就意味着之前单DB的事务就失效了,所以J2EE提出了JTA,分布式的事务管理,往 简单了说,就是2步提交(two phase),比单步提交更苛刻。实际上他有两个容器来管理,一个是资源管理器,一个是事务管理。小伙伴们可以发现,这是一个环环相扣的过程。想解决一个 问题,你就得解决这几个相关的问题。以下代码,我也是参考了前辈们的思想,进行了改造。
第一步:XA数据源定义
选定义一个抽象的父类源,这样子类可以直接继承
1 <!-- 两个数据源的功用配置,方便下面直接引用 -->
2 <bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init"
3 destroy-method="close" abstract="true">
4 <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
5 <property name="poolSize" value="10" />
6 <property name="minPoolSize" value="10"/>
7 <property name="maxPoolSize" value="30"/>
8 <property name="borrowConnectionTimeout" value="60"/>
9 <property name="reapTimeout" value="20"/>
10 <!-- 最大空闲时间 -->
11 <property name="maxIdleTime" value="60"/>
12 <property name="maintenanceInterval" value="60" />
13 <property name="loginTimeout" value="60"/>
14 <property name="logWriter" value="60"/>
15 <property name="testQuery">
16 <value>select 1</value>
17 </property>
18
19 </bean>
20
A源
1 <!-- 配置第一个数据源 -->
2 <bean id="dataSource_a" parent="abstractXADataSource">
3 <!-- value只要两个数据源不同就行,随便取名 -->
4 <property name="uniqueResourceName" value="mysql/sitestone" />
5 <property name="xaDataSourceClassName"
6 value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
7 <property name="xaProperties">
8 <props>
9 <prop key="URL">${jdbc.url.spider}</prop>
10 <prop key="user">${jdbc.username}</prop>
11 <prop key="password">${jdbc.password}</prop>
12 </props>
13 </property>
14 </bean>
B 源
1 <!-- 配置第二个数据源-->
2 <bean id="dataSource_b" parent="abstractXADataSource">
3 <!-- value只要两个数据源不同就行,随便取名 -->
4 <property name="uniqueResourceName" value="mysql/sitesttwo" />
5 <property name="xaDataSourceClassName"
6 value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
7 <property name="xaProperties">
8 <props>
9 <prop key="URL">${jdbc_tb.url.spider}</prop>
10 <prop key="user">${jdbc_tb.username}</prop>
11 <prop key="password">${jdbc_tb.password}</prop>
12 </props>
13 </property>
14 </bean>
基于SPRING的AbstractRoutingDataSource动态数据路由定义
1 <bean name="dynamicDatasource" class="com.***.spring.datasource.CustomerDatasource">
2 <property name="targetDataSources">
3 <map>
4 <entry key="ds_1" value-ref="dataSource_a"/>
5 <entry key="ds_2" value-ref="dataSource_b"/>
6 </map>
7 </property>
8 <property name="defaultTargetDataSource" ref="dataSource_a" />
9 </bean>
我这里是使用MYBATIS来进行ORM映射,配置如下
1 <bean id="sqlSessionFactorya" class="org.mybatis.spring.SqlSessionFactoryBean">
2 <property name="dataSource" ref="dataSource_a"/>
3 <property name="typeAliasesPackage" value="com.****.spring.dschange.bean" />
4 <!-- mapper和resultmap配置路径 -->
5 <property name="mapperLocations">
6 <list>
7 <!-- 表示在com.**目录下的任意包下的resultmap包目录中,以-resultmap.xml或-mapper.xml结尾所有文件 -->
8 <value>classpath:com/***/spring/dschange/mapper/ShopMapper.xml</value>
9 </list>
10 </property>
11 </bean>
12 <bean id="sqlSessionFactoryb" class="org.mybatis.spring.SqlSessionFactoryBean">
13 <property name="dataSource" ref="dataSource_b"/>
14 <property name="typeAliasesPackage" value="com.****.spring.dschange.bean" />
15 <!-- mapper和resultmap配置路径 -->
16 <property name="mapperLocations">
17 <list>
18 <!-- 表示在com.***目录下的任意包下的resultmap包目录中,以-resultmap.xml或-mapper.xml结尾所有文件 -->
19 <value>classpath:com/***/spring/dschange/mapper/ShopMapper.xml</value>
20 </list>
21 </property>
22 </bean>
接下来,一个比较关键的地方是对MYBATIS的CustomSqlSessionTemplate的重写,主要是引入动态数据源sqlSessionFactory。针对不同的数据库,调用其对应的会话工厂,这对JTA是否启用,比较重要。
1 @Override
2 public SqlSessionFactory getSqlSessionFactory() {
3
4 SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(DataSourceKeyHolder.getDataSourceKey());
5 if (targetSqlSessionFactory != null) {
6 return targetSqlSessionFactory;
7 } else if (defaultTargetSqlSessionFactory != null) {
8 return defaultTargetSqlSessionFactory;
9 } else {
10 Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");
11 Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");
12 }
13 return this.sqlSessionFactory;
14 }
XML配置
1 <!-- 配置自定义的SqlSessionTemplate模板,注入相关配置 -->
2 <bean id="sqlSessionTemplate" class="com.amos.spring.mybatis.CustomSqlSessionTemplate" scope="prototype">
3 <constructor-arg ref="sqlSessionFactorya" />
4 <property name="targetSqlSessionFactorys">
5 <map>
6 <entry value-ref="sqlSessionFactorya" key="ds_1"/>
7 <entry value-ref="sqlSessionFactoryb" key="ds_2"/>
8 </map>
9 </property>
10 </bean>
扫描配置
1 <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
1 <!-- jta -->
2 <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
3 init-method="init" destroy-method="close">
4 <property name="forceShutdown">
5 <value>true</value>
6 </property>
7 </bean>
8
9 <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
10 <property name="transactionTimeout" value="300" />
11 </bean>
12
13 <bean id="springTransactionManager"
14 class="org.springframework.transaction.jta.JtaTransactionManager">
15 <property name="transactionManager">
16 <ref bean="atomikosTransactionManager" />
17 </property>
18 <property name="userTransaction">
19 <ref bean="atomikosUserTransaction" />
20 </property>
21 </bean>
22 <tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true" />
2 <property name="basePackage" value="com.****.spring.dschange.mapper" />
3 <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
4 <property name="markerInterface" value="com.*****.spring.dschange.mapper.SqlMapper"/>
5 </bean>
最后就是JTA的实现配置
1 <!-- jta -->
2 <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
3 init-method="init" destroy-method="close">
4 <property name="forceShutdown">
5 <value>true</value>
6 </property>
7 </bean>
8
9 <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
10 <property name="transactionTimeout" value="300" />
11 </bean>
12
13 <bean id="springTransactionManager"
14 class="org.springframework.transaction.jta.JtaTransactionManager">
15 <property name="transactionManager">
16 <ref bean="atomikosTransactionManager" />
17 </property>
18 <property name="userTransaction">
19 <ref bean="atomikosUserTransaction" />
20 </property>
21 </bean>
22 <tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true" />
以上,XML配置相关的东西已经完成。
具体的代码,请点击GITHUB查看
https://github.com/igool/spring-jta-mybatis