公司使用ssh框架,近期因为项目使用到多数据源,web服务器为tomcat,为了数据的一致性,需要使用jta。在spring中使用jta现有两个主要的开源项目:jotm、atomikos。spring3中己移除了对jotm支持,所以只能使用atomikos,按照网上资料学习,将实践过程记录下来,以备参考。
1、atomikos需要的jar
transactions-3.9.0.M1.jar transactions-api-3.9.0.M1.jar transactions-hibernate3-3.9.0.M1.jar transactions-jta-3.9.0.M1.jar transactions-jdbc-3.9.0.M1.jar atomikos-util-3.9.0.M1.jar
2、persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <!-- Keep this file empty, Spring will scan the @Entity classes --> <persistence-unit name="orcalePU" transaction-type="JTA"> </persistence-unit> <persistence-unit name="mysqlPU" transaction-type="JTA"> </persistence-unit> </persistence>
persistence.xml很简单,定义两个持久化单元,类型为JTA
3、application.properties
mysql.databasePlatform=org.hibernate.dialect.MySQL5InnoDBDialect mysql.jdbc.driver=com.mysql.jdbc.Driver mysql.jdbc.url=jdbc:mysql://localhost:3306/post?useUnicode=true&characterEncoding=utf-8 mysql.jdbc.username=root mysql.jdbc.password=kfs #Oracle oracle.databasePlatform=org.hibernate.dialect.Oracle10gDialect oracle.jdbc.driver=oracle.jdbc.driver.OracleDriver oracle.jdbc.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl oracle.jdbc.username=test oracle.jdbc.password=kfs
application.properties中配置两个数据,oralce和mysql
4、applicationContext.xml
<description>多数源JTA配置例子,本例子同时使用oracle、mysql数据同时提交、回滚事务</description> <!-- 使用annotation 自动注册bean,并检查@Required,@Autowired的属性已被注入 --> <context:component-scan base-package="com.techstar"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> <context:property-placeholder ignore-resource-not-found="true" location="classpath*:/application.properties" /> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="300" /> </bean> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown" value="true" /> </bean> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager" ref="atomikosTransactionManager" /> <property name="userTransaction" ref="atomikosUserTransaction" /> <property name="allowCustomIsolationLevels" value="true" /> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> <!-- 定义aspectj --> <aop:aspectj-autoproxy proxy-target-class="true" /> <!-- oracle 数据源配置 --> <bean id="oracleDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="oracleXADBMS" /> <property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource" /> <property name="xaProperties"> <props> <prop key="user">${oracle.jdbc.username}</prop> <prop key="password">${oracle.jdbc.password}</prop> <prop key="URL">${oracle.jdbc.url}</prop> </props> </property> <property name="poolSize" value="2" /> <property name="minPoolSize" value="1" /> <property name="maxPoolSize" value="5" /> <property name="testQuery" value="select 1 from dual" /> </bean> <!-- AbstractTransactionalJUnit4SpringContextTests测试类需要一个名为 dataSource的数据源--> <alias name="oracleDataSource" alias="dataSource"/> <!-- Jpa Entity Manager 配置 --> <bean id="orcaleEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml" /> <property name="persistenceUnitName" value="orcalePU" /> <property name="dataSource" ref="oracleDataSource" /> <property name="jpaVendorAdapter" ref="oralceHibernateJpaVendorAdapter" /> <property name="jpaProperties"> <props> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.connection.driver_class">oracle.jdbc.xa.client.OracleXADataSource</prop> <prop key="hibernate.current_session_context_class">jta</prop> <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup </prop> </props> </property> </bean> <bean id="oralceHibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="databasePlatform"> <value>${oracle.databasePlatform}</value> </property> <property name="showSql"> <value>true</value> </property> <property name="generateDdl"> <value>true</value> </property> </bean> <!-- Spring Data Jpa配置, 扫描base-package下所有继承于Repository<T,ID>的接口 --> <jpa:repositories base-package="com.techstar.topic.**.repository.jpa" transaction-manager-ref="transactionManager" entity-manager-factory-ref="orcaleEntityManagerFactory" /> <!-- mysql 数据源配置 --> <bean id="mysqlDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="mysqlXADBMS" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="user">${mysql.jdbc.username}</prop> <prop key="password">${mysql.jdbc.password}</prop> <prop key="URL">${mysql.jdbc.url}</prop> </props> </property> <property name="poolSize" value="2" /> <property name="minPoolSize" value="1" /> <property name="maxPoolSize" value="5" /> <property name="testQuery" value="select 1" /> </bean> <!-- Jpa Entity Manager 配置 --> <bean id="mysqlEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml" /> <property name="persistenceUnitName" value="mysqlPU" /> <property name="dataSource" ref="mysqlDataSource" /> <property name="jpaVendorAdapter" ref="mysqlHibernateJpaVendorAdapter" /> <property name="jpaProperties"> <props> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.connection.driver_class">com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</prop> <prop key="hibernate.current_session_context_class">jta</prop> <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup </prop> </props> </property> </bean> <bean id="mysqlHibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="databasePlatform"> <value>${mysql.databasePlatform}</value> </property> <property name="showSql"> <value>true</value> </property> <property name="generateDdl"> <value>true</value> </property> </bean> <!-- Spring Data Jpa配置, 扫描base-package下所有继承于Repository<T,ID>的接口 --> <jpa:repositories base-package="com.techstar.post.**.repository.jpa" transaction-manager-ref="transactionManager" entity-manager-factory-ref="mysqlEntityManagerFactory" />
5、测试
oracle数据库实体类和dao类:
package com.techstar.topic.entity; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.hibernate.validator.constraints.NotBlank; @Entity @Table(name = "XA_TOPIC") public class Topic { private String id; private String forumId; private String topicTitle; private String userId; private Date topicTime; private Integer topicViews; private Integer topicReplies; @Id @GeneratedValue(generator = "system-uuid")// 主键自动生成策略:UUID @GenericGenerator(name = "system-uuid", strategy = "uuid") @Column(length=32) public String getId() { return id; } public void setId(String id) { this.id = id; } @NotBlank @Column(length = 100) public String getForumId() { return forumId; } public void setForumId(String forumId) { this.forumId = forumId; } @NotBlank @Column(length = 200) public String getTopicTitle() { return topicTitle; } public void setTopicTitle(String topicTitle) { this.topicTitle = topicTitle; } @NotBlank @Column(length = 50) public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } @Temporal(TemporalType.TIMESTAMP) public Date getTopicTime() { return topicTime; } public void setTopicTime(Date topicTime) { this.topicTime = topicTime; } public Integer getTopicViews() { return topicViews; } public void setTopicViews(Integer topicViews) { this.topicViews = topicViews; } public Integer getTopicReplies() { return topicReplies; } public void setTopicReplies(Integer topicReplies) { this.topicReplies = topicReplies; } }
package com.techstar.topic.repository.jpa; import org.springframework.data.jpa.repository.JpaRepository; import com.techstar.topic.entity.Topic; public interface TopicDao extends JpaRepository<Topic, String>{ }
项目使用了spring data jpa,dao类很简单,定义接口即可。
mysql对应的实体类和dao类
package com.techstar.post.entity; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Lob; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.hibernate.validator.constraints.NotBlank; @Entity @Table(name = "XA_POST") public class Post { private String id; private String topic; private String forumId; private String userId; private byte[] postAttach; private Date postTime; @Id @GeneratedValue(generator = "system-uuid")// 主键自动生成策略:UUID @GenericGenerator(name = "system-uuid", strategy = "uuid") @Column(length=32) public String getId() { return id; } public void setId(String id) { this.id = id; } @NotBlank @Column(name = "TOPIC", length = 32) public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } @NotBlank @Column(length = 100) public String getForumId() { return forumId; } public void setForumId(String forumId) { this.forumId = forumId; } @NotBlank @Column(length = 50) public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } @Lob public byte[] getPostAttach() { return postAttach; } public void setPostAttach(byte[] postAttach) { this.postAttach = postAttach; } @Temporal(TemporalType.TIMESTAMP) public Date getPostTime() { return postTime; } public void setPostTime(Date postTime) { this.postTime = postTime; } }
package com.techstar.post.repository.jpa; import org.springframework.data.jpa.repository.JpaRepository; import com.techstar.post.entity.Post; public interface PostDao extends JpaRepository<Post, String>{ }
junit单元测试类
package com.techstar.topic.service; import java.util.Date; import junit.framework.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Transactional; import com.techstar.modules.utils.Identities; import com.techstar.post.entity.Post; import com.techstar.post.repository.jpa.PostDao; import com.techstar.topic.entity.Topic; import com.techstar.topic.repository.jpa.TopicDao; @ContextConfiguration(locations = { "classpath:spring/applicationContext.xml" }) @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false) @Transactional public class JTAServiceTest extends AbstractTransactionalJUnit4SpringContextTests { @Autowired private TopicDao topicDao;// oralce数据源 @Autowired private PostDao postDao;// mysql数据源 /** * 单独俣存Topic到oracle数据库 */ @Test public void saveTopic() { Topic topic = new Topic(); topic.setForumId(Identities.uuid2()); topic.setTopicReplies(5); topic.setTopicTime(new Date()); topic.setTopicTitle(Identities.uuid2()); topic.setTopicViews(2); topic.setUserId(Identities.uuid2()); topicDao.save(topic); Assert.assertNotNull(topic.getId()); } /** * 单独俣存Post到mydql数据库 */ @Test public void savePost() { Post post = new Post(); post.setForumId(Identities.uuid2()); post.setUserId(Identities.uuid2()); post.setPostAttach(new String(Identities.uuid2()).getBytes()); post.setPostTime(new Date()); post.setTopic(Identities.uuid2()); postDao.save(post); Assert.assertNotNull(post.getId()); } /** * 同时保存Topic到oracle数据库、Post到mydql数据库 */ @Test public void saveTopicAndPostCommit() { Topic topic = new Topic(); topic.setForumId(Identities.uuid2()); topic.setTopicReplies(5); topic.setTopicTime(new Date()); topic.setTopicTitle(Identities.uuid2()); topic.setTopicViews(2); topic.setUserId(Identities.uuid2()); topicDao.save(topic); Post post = new Post(); post.setForumId(Identities.uuid2()); post.setUserId(Identities.uuid2()); post.setPostAttach(new String(Identities.uuid2()).getBytes()); post.setPostTime(new Date()); post.setTopic(topic.getId()); postDao.save(post); Assert.assertNotNull(topic.getId()); Assert.assertNotNull(post.getId()); } /** * Post回滚、Topic一起回滚 */ @Test public void saveTopicAndPostRollback() { Topic topic = new Topic(); topic.setForumId(Identities.uuid2()); topic.setTopicReplies(5); topic.setTopicTime(new Date()); topic.setTopicTitle(Identities.uuid2()); topic.setTopicViews(2); topic.setUserId(Identities.uuid2()); topicDao.save(topic); Post post = new Post(); post.setForumId(null);// ForumId在数据设置不能为空,在这里设置为null,测试数据一起回滚 post.setUserId(Identities.uuid2()); post.setPostAttach(new String(Identities.uuid2()).getBytes()); post.setPostTime(new Date()); post.setTopic(topic.getId()); postDao.save(post); } /** * Topic回滚、Post一起回滚 */ @Test(expected = ArithmeticException.class) public void saveTopicAndPostRollback2() { Post post = new Post(); post.setForumId(Identities.uuid2()); post.setUserId(Identities.uuid2()); post.setPostAttach(new String(Identities.uuid2()).getBytes()); post.setPostTime(new Date()); post.setTopic(Identities.uuid2()); postDao.save(post); Topic topic = new Topic(); topic.setForumId(Identities.uuid2()); topic.setTopicReplies(5); topic.setTopicTime(new Date()); topic.setTopicTitle(Identities.uuid2()); topic.setTopicViews(2); topic.setUserId(null);// UserId在数据设置不能为空,在这里设置为null,测试数据一起回滚 topicDao.save(topic); } }
最后运行单元测试,查看数据变化。