欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-23 12:56:31
...

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

	}
}


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