本文配项目源码,下载地址为:springhibernateatomikos.zip
附官方提供Atomikos与tomcat的整合:http://www.atomikos.com/Documentation/Tomcat7Integration35
Spring重要的一个优点就是可以帮我们管理事务。Spring可以托管的事务分两类,
本地数据库事务;
JTA事务。
本地事务只能管理一个数据源,不能跨数据源。如果想跨数据源,则必须使用JTA事务。
若想使用JTA服务,只有两种选择:
J2EE容器。所有的J2EE容器都支持JTA规范。
独立于容器的第三方软件。比如JTOM, Atomikos, JbossTS等。独立JTS可以脱离容器运行。
因为tomcat不是一个完整的J2EE容器,不支持JTA,所以必须结合第三方软件实现JTA。
App.java为程序的入口。里面提供了两个测试方法,一个运行本地事务,一个运行JTA事务。
dao和hb与模型不多说。
JtaService运行的是JTA事务,里面会对两个数据源进行操作。
Service运行的是本地事务。
applicationContext.xml定义了本地事务下的容器。
applicationContext.xml定义了JTA事务下的容器,它依赖ds1.xml和ds2.xml。ds1定义了数据源A,ds2定义了数据源B。
ds1.sql创建了数据库A,ds2.sql创建了数据库B。
创建maven项目,然后编辑pom.xml,引入我们需要的资源:
其中c3p0只是为本地事务服务的。如果用JTA事务,则必须配置XADatasourse。XADatasource由J2EE容器提供,或者向本项目,由Atomikos提供。
除了最后一个资源,其它资源都一目了然,很容易找到。最后一个资源是由com.automikos提供的:
<dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-hibernate3</artifactId> <version>3.9.3</version> </dependency>
请直接查看源码。
public class JtaService { private CustomerDao customerDaoA; private CustomerDao customerDaoB; @Transactional(propagation=Propagation.REQUIRED, readOnly=false, isolation=Isolation.READ_COMMITTED) public void createCustomer(Customer c, Customer c2) { this.customerDaoA.saveCustomer(c); this.customerDaoB.saveCustomer(c2); } // getters and setters }
在JtaService中,使用来自两个数据源的customerDao。虽然两个数据源的数据库schema完全一样,但它们的确是跨数据库。customerDaoA和customerDaoB将在IOC容器中装配。
<!-- ds1.xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:s="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName"><value>dataSource1</value></property> <property name="xaDataSourceClassName"> <value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value> </property> <property name="xaProperties"> <props> <prop key="user">mysql</prop> <prop key="password">mysql</prop> <prop key="URL">jdbc:mysql://localhost:3306/test1</prop> </props> </property> <property name="poolSize" value="3"/> </bean> <bean id="sessionFactory1" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" destroy-method="destroy"> <property name="dataSource" ref="dataSource1" /> <property name="mappingResources"> <list> <value>com/xpbug/hb/Customer.hbm.xml</value> <value>com/xpbug/hb/Order.hbm.xml</value> <value>com/xpbug/hb/Item.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.show_sql=true hibernate.format_sql=false </value> </property> </bean> <bean id="customerDaoA" class="com.xpbug.dao.CustomerDao"> <property name="sessionFactory" ref="sessionFactory1"/> </bean> </beans>
略,与A类似,只是不同的名称和指向不同的DB。
<!-- applicationContext-jta.xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:s="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <import resource="ds1.xml" /> <import resource="ds2.xml" /> <bean id="service" class="com.xpbug.service.JtaService"> <property name="customerDaoA" ref="customerDaoA"></property> <property name="customerDaoB" ref="customerDaoB"></property> </bean> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown" value="false" /> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.J2eeUserTransaction"> <property name="transactionTimeout" value="300" /> </bean> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" depends-on="atomikosTransactionManager,atomikosUserTransaction"> <property name="transactionManager" ref="atomikosTransactionManager" /> <property name="userTransaction" ref="atomikosUserTransaction" /> <property name="allowCustomIsolationLevels" value="true" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
至此,装配完成。
public static void main( String[] args ) { //new App().testLocalTransaction(); new App().testJtaTransaction(); } private void testJtaTransaction() { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-jta.xml"); JtaService s = (JtaService) context.getBean("service"); Customer customer = new Customer(); customer.setName("A"); Order order = new Order(); order.setName("o1"); order.setCustomer(customer); customer.getOrders().add(order); Customer c1 = customer; customer = new Customer(); customer.setName("B"); order = new Order(); order.setName("o2"); order.setCustomer(customer); customer.getOrders().add(order); Customer c2 = customer; s.createCustomer(c1, c2); context.close(); }
查看两个数据库A和B是否插入了数据。
为了测试原子性,修改JtaService,人为破坏程序执行:
@Transactional(propagation=Propagation.REQUIRED, readOnly=false, isolation=Isolation.READ_COMMITTED) public void createCustomer(Customer c, Customer c2) { this.customerDaoA.saveCustomer(c); this.customerDaoB.saveCustomer(c2); throw new RuntimeException(); }
让方法createCustomer最后抛出异常。运行App,查看两个数据库是否插入数据。