- 这里的项目是基于 Spring4.X + hibernate4.X架构的。最近的一个项目需要两个数据库,一个Oracle,一个是Sqlserver。业务中有一些需求需要跨库事务的一致,举个例子:合同签订保存到基于Oracle的ERP数据库,紧接着下发到Sqlserver的WMS数据库。
- 以前听说过JTA分布式事务,google到两种分布式框架:JOTM,atomikos。貌似JOTM简便点,就它了。
- 这种方式需要的jar包,首先在JOTM官网下面所有的jar包文件(文末提供下载),下图中选中的jar文件都是需要导入的
首先配置DataSource和SessionFactory
Xml代码
- <!-- 数据库连接池 -->
- <bean id="dataSource1" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource">
- <property name="dataSource">
- <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
- <property name="transactionManager" ref="jotm" />
- <property name="driverName" value="oracle.jdbc.driver.OracleDriver" />
- <property name="url" value="jdbc:oracle:thin:@192.168.1.200:1521:orcl" />
- </bean>
- </property>
- <property name="user" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- </bean>
- <bean id="dataSource2" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"
- >
- <property name="dataSource">
- <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
- <property name="transactionManager" ref="jotm" />
- <property name="driverName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
- <property name="url" value="jdbc:sqlserver://192.168.1.200:1433;DatabaseName=middle_db" />
- </bean>
- </property>
- <property name="user" value="${jdbc2.username}" />
- <property name="password" value="${jdbc2.password}" />
- </bean>
- <bean id="sessionFactory1"
- class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
- <property name="dataSource" ref="dataSource1" />
- <property name="packagesToScan">
- <list>
- <value>com.sy.domain</value>
- </list>
- </property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">
- org.hibernate.dialect.OracleDialect
- </prop>
- <prop key="hibernate.show_sql">true</prop>
- <prop key="hibernate.autoReconnect">true</prop>
- <!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
- </props>
- </property>
- </bean>
- <bean id="sessionFactory2"
- class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
- <property name="dataSource" ref="dataSource2" />
- <property name="packagesToScan">
- <list>
- <value>com.sy.domain</value>
- </list>
- </property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">
- org.hibernate.dialect.SQLServerDialect
- </prop>
- <prop key="hibernate.show_sql">true</prop>
- <prop key="hibernate.autoReconnect">true</prop>
- <!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
- </props>
- </property>
- </bean>
- <!-- erp.dao -->
- <bean id="commonDao" class="com.sy.dao.impl.CommonDaoImpl" >
- <property name="sessionFactory" ref="sessionFactory1"/>
- </bean>
- <!-- wms.dao -->
- <bean id="commonDao2" class="com.sy.dao.impl.CommonDaoImpl" >
- <property name="sessionFactory" ref="sessionFactory2"/>
- </bean>
因为我们要操作两个数据库,所以配置两个datasource,两个sessionfactory。需要注意的是我们这里是JOTM和xapool实现的分布式事务。JOTM实现了TransactionManager的功能,xapool通过使用非XA数据库驱动实现了XA数据库驱动的效果,具体这个以后再写文章。这里连接池的配置按照上面的文档配置即可。
这里DAO层因为两个数据库所以配置了两个。在Service层可以选择一起注入或者单个注入。
下面配置事务
Xml代码
- <bean id="jotm" class="com.sy.utils.JotmFactoryBean"/>
- <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
- <property name="userTransaction" ref="jotm" />
- </bean>
- <tx:advice id="txAdvice" transaction-manager="jtaTransactionManager">
- <tx:attributes>
- <!-- 传播行为 -->
- <tx:method name="save*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="insert*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="add*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="create*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="delete*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="update*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
- <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
- <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
- </tx:attributes>
- </tx:advice>
- <!-- 切面 -->
- <aop:config expose-proxy="true">
- <aop:pointcut id="bussinessService" expression="execution(* com.sy.service.*.*(..))" />
- <aop:advisor pointcut-ref="bussinessService" advice-ref="txAdvice"/>
- </aop:config>
- <tx:annotation-driven transaction-manager="jtaTransactionManager"/>
com.sy.utils.JotmFactoryBean需要手动实现,因为spring4.x不在提供JtaTransactionManager 的默认实现。
到这里JTA分布式事务的配置已经完成,下面我们看看Service层,和junit测试
Java代码
- @Service
- public class CommonServiceImpl implements CommonService{
- /** serialVersionUID*/
- private static final long serialVersionUID = -5991777455696969065L;
- @Resource
- private CommonDao commonDao;
- @Resource
- private CommonDao commonDao2;
- //测试两个数据库的事务
- @Override
- public void saveTest() throws MyException {
- commonDao.update("update Dictionary set name=? where id=? ", new Value().add("货主类型").add(1l).getParams());
- commonDao2.update("delete Member where id>25");
- int i = 1/0; //这里异常,前面配置正确的话事务会回滚
- }
- }
因为这是一个公共的Service层,需要操作两个数据库,所以两个DAO一起注入。下面是Junit测试代码
Java代码
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(locations = {"classpath:spring/applicationContext-*.xml"})
- public class TestSupport extends AbstractJUnit4SpringContextTests {
- @Autowired
- private CommonService commonService;
- @Test
- public void save() throws MyException{
- commonService.saveTest();
- }
- public CommonService getCommonService() {
- return commonService;
- }
- public void setCommonService(CommonService commonService) {
- this.commonService = commonService;
- }
- }
运行结果如我们所料,事务回滚,OK!
下面是直接配置的方式,这种方式是一个老同事那边看到的。当时惊呼不用jta也能实现跨库事务啊。跟普通的单数据库项目配置一样,只是DataSource,SessionFactory,transactionmanager等都是两个。下面是配置
Xml代码
- <!-- 数据库连接池 -->
- <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource"
- destroy-method="close">
- <property name="url" value="${jdbc.url}" />
- <property name="username" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- <property name="driverClassName" value="${jdbc.driver}" />
- <property name="maxActive" value="${jdbc.maxActive}" />
- <property name="minIdle" value="${jdbc.minIdle}" />
- </bean>
- <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource"
- destroy-method="close">
- <property name="url" value="${jdbc2.url}" />
- <property name="username" value="${jdbc2.username}" />
- <property name="password" value="${jdbc2.password}" />
- <property name="driverClassName" value="${jdbc2.driver}" />
- <property name="maxActive" value="${jdbc2.maxActive}" />
- <property name="minIdle" value="${jdbc2.minIdle}" />
- </bean>
- <bean id="sessionFactory1"
- class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
- <property name="dataSource" ref="dataSource1" />
- <property name="packagesToScan">
- <list>
- <value>com.sy.domain</value>
- </list>
- </property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">
- org.hibernate.dialect.OracleDialect
- </prop>
- <prop key="hibernate.show_sql">true</prop>
- <prop key="hibernate.autoReconnect">true</prop>
- <!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
- </props>
- </property>
- </bean>
- <bean id="sessionFactory2"
- class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
- <property name="dataSource" ref="dataSource2" />
- <property name="packagesToScan">
- <list>
- <value>com.sy.domain</value>
- </list>
- </property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">
- org.hibernate.dialect.SQLServerDialect
- </prop>
- <prop key="hibernate.show_sql">true</prop>
- <prop key="hibernate.autoReconnect">true</prop>
- <!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
- </props>
- </property>
- </bean>
- <bean id="commonDao" class="com.sy.dao.impl.CommonDaoImpl" >
- <property name="sessionFactory" ref="sessionFactory1"/>
- </bean>
- <bean id="commonDao2" class="com.sy.dao.impl.CommonDaoImpl" >
- <property name="sessionFactory" ref="sessionFactory2"/>
- </bean>
- <bean id="transactionManager1" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
- <property name="sessionFactory" ref="sessionFactory1" />
- </bean>
- <bean id="transactionManager2" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
- <property name="sessionFactory" ref="sessionFactory2" />
- </bean>
- <tx:advice id="txAdvice1" transaction-manager="transactionManager1">
- <tx:attributes>
- <!-- 传播行为 -->
- <tx:method name="save*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="insert*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="add*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="create*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="delete*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="update*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
- <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
- <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
- </tx:attributes>
- </tx:advice>
- <tx:advice id="txAdvice2" transaction-manager="transactionManager2">
- <tx:attributes>
- <!-- 传播行为 -->
- <tx:method name="save*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="insert*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="add*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="create*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="delete*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="update*" propagation="REQUIRED" rollback-for="com.sy.exceptions.MyException"/>
- <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
- <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
- <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
- </tx:attributes>
- </tx:advice>
- <!-- 切面 -->
- <aop:config expose-proxy="true">
- <aop:pointcut id="bussinessService1" expression="execution(* com.sy.service.*.*(..))" />
- <aop:advisor pointcut-ref="bussinessService1" advice-ref="txAdvice1"/>
- </aop:config>
- <aop:config expose-proxy="true">
- <aop:pointcut id="bussinessService2" expression="execution(* com.sy.service.*.*(..))" />
- <aop:advisor pointcut-ref="bussinessService2" advice-ref="txAdvice2"/>
- </aop:config>
这里配置和普通单库配置基本一样,连接池也是自由选择。但是都是两个配置,spring4对多个事务配置也是支持的。测试代码和上面JTA配置的一样,不再给出。