使用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);
}
}
最后运行单元测试,查看数据变化。
下一篇: 《MFC程序开发参考大全》学习笔记_3