使用atomikos在spring3、jpa2/hibernate4中实现JTA

公司使用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);

	}
}


最后运行单元测试,查看数据变化。

 

你可能感兴趣的:(spring,Hibernate,jpa,jta,atomikos)