本文的几个关键词,分布式数据源,数据源的动态寻找,分布式事务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源
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
1
2 < bean id ="dataSource_a" parent ="abstractXADataSource" >
3
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 >
2 < bean id ="dataSource_a" parent ="abstractXADataSource" >
3
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
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 >
2 < bean id ="dataSource_b" parent ="abstractXADataSource" >
3
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 >
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
5 < property name ="mapperLocations" >
6 < list >
7
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
16 < property name ="mapperLocations" >
17 < list >
18
19 < value > classpath:com/***/spring/dschange/mapper/ShopMapper.xml value >
20 list >
21 property >
22 bean >
2 < property name ="dataSource" ref ="dataSource_a" />
3 < property name ="typeAliasesPackage" value ="com.****.spring.dschange.bean" />
4
5 < property name ="mapperLocations" >
6 < list >
7
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
16 < property name ="mapperLocations" >
17 < list >
18
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配置
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
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 >
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"
>
3 < property name ="sqlSessionTemplateBeanName" value ="sqlSessionTemplate" />
4 < property name ="markerInterface" value ="com.*****.spring.dschange.mapper.SqlMapper" />
5 bean >
1
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"
/>
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" />
3 < property name ="sqlSessionTemplateBeanName" value ="sqlSessionTemplate" />
4 < property name ="markerInterface" value ="com.*****.spring.dschange.mapper.SqlMapper" />
5 bean >
最后就是JTA的实现配置
1
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 < 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