博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring JTA multiple resource transactions in Tomcat with Atomikos example
阅读量:2439 次
发布时间:2019-05-10

本文共 15822 字,大约阅读时间需要 52 分钟。

一篇关于分布式事务的文章 转自 

In this tutorial you will learn how to configure a JTA transaction manager outside an enterprise container - using the Spring framework and Atomikos, both deployed in Tomcat - in order to implement distributed multiple resource (or XA) transactions.

Introduction

Distributed multiple resource transactions in Java are usually accomplished by resorting to the Java Transaction API (JTA). One usually delegates the task of distributed transaction coordination to an entity called the Transaction Manager.

The transaction manager is then responsible for coordinating the distributed transaction by interacting with each resource's own Resource Manager. Fully fledged enterprise containers include a JTA implementation but what if we need JTA outside an enterprise container, ie. a servlet container like Tomcat?

In this case we must use a 3rd party JTA implementation. In this tutorial we will use Atomikos, a quality JTA implementation which is also available as an open source distribution, along with the Spring framework in order to implement JTA transactions.

This tutorial considers the following environment:

  1. Ubuntu 12.04
  2. JDK 1.7.0.21
  3. Spring 3.2.3
  4. Atomikos 3.8.0
  5. Tomcat 7.0.35

The following Maven dependencies are required:

Required Maven dependencies
UTF-8
3.2.3.RELEASE
4.1.9.Final
3.8.0
org.springframework
spring-core
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-orm
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.hibernate
hibernate-entitymanager
${hibernate.version}
cglib
cglib
dom4j
dom4j
javax.servlet
javax.servlet-api
3.0.1
provided
com.atomikos
transactions-jta
${atomikos.version}
com.atomikos
transactions-jdbc
${atomikos.version}
com.atomikos
transactions-hibernate3
${atomikos.version}
hibernate
org.hibernate
dom4j
dom4j
1.6.1
log4j
log4j
1.2.16
mysql
mysql-connector-java
5.1.25

Datasources

In this tutorial we will use two datasources each one of them referencing a distinct MySQL database. We could have also included a JMS message queue to participate in the distributed transaction as this is also a very common scenario, but for simplicity we will keep up with the two MySQL datasources:

The datasources used in this example
Datasource 1

Database: DATABASE1

Username: user1

Database: passwd1


Datasource 2

Database: DATABASE2

Username: user2

Database: passwd2


We will use a couple of tables in this example. Datasource 1 will contain TABLE_ONE and Datasource 2 will contain TABLE_TWO:

Tables used in this example:
Datasource 1

CREATE TABLE TABLE_ONE (

TABLE_ONE_ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,

VALUE VARCHAR(32) NOT NULL

); 


Datasource 2

CREATE TABLE TABLE_TWO (

TABLE_TWO_ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,

VALUE VARCHAR(32) NOT NULL

);

JPA entities

Now we define a couple of JPA entities to map the two tables mentioned in the previous section.

TableOne.java
package com.byteslounge.spring.tx.entity;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name = "TABLE_ONE")public class TableOne {
@Id @Column(name = "TABLE_ONE_ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private int tableOneId; @Column(name = "VALUE", nullable = false) private String value; public int getTableOneId() {
return tableOneId; } public void setTableOneId(int tableOneId) {
this.tableOneId = tableOneId; } public String getValue() {
return value; } public void setValue(String value) {
this.value = value; }}

TableTwo.java
package com.byteslounge.spring.tx.entity;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name = "TABLE_TWO")public class TableTwo {
@Id @Column(name = "TABLE_TWO_ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private int tableTwoId; @Column(name = "VALUE", nullable = false) private String value; public int getTableTwoId() {
return tableTwoId; } public void setTableTwoId(int tableTwoId) {
this.tableTwoId = tableTwoId; } public String getValue() {
return value; } public void setValue(String value) {
this.value = value; }}

DAO definition

We will also use a couple of Spring services as DAO's where each one is used to interact with the respective datasource. Following next are the DAO's interfaces and implementations:

TableOneDao.java
package com.byteslounge.spring.tx.dao;import com.byteslounge.spring.tx.entity.TableOne;public interface TableOneDao {
void save(TableOne tableOne); }

TableOneDaoImpl.java
package com.byteslounge.spring.tx.dao.impl;import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;import org.springframework.stereotype.Service;import com.byteslounge.spring.tx.dao.TableOneDao;import com.byteslounge.spring.tx.entity.TableOne;@Servicepublic class TableOneDaoImpl implements TableOneDao {
private EntityManager entityManager; @PersistenceContext(unitName="PersistenceUnit1") public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager; } @Override public void save(TableOne tableOne) {
entityManager.persist(tableOne); }}

TableTwoDao.java
package com.byteslounge.spring.tx.dao;import com.byteslounge.spring.tx.entity.TableTwo;public interface TableTwoDao {
void save(TableTwo tableTwo) throws Exception; }

TableTwoDaoImpl.java
package com.byteslounge.spring.tx.dao.impl;import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;import org.springframework.stereotype.Service;import com.byteslounge.spring.tx.dao.TableTwoDao;import com.byteslounge.spring.tx.entity.TableTwo;@Servicepublic class TableTwoDaoImpl implements TableTwoDao {
private EntityManager entityManager; @PersistenceContext(unitName="PersistenceUnit2") public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager; } @Override public void save(TableTwo tableTwo) throws Exception {
entityManager.persist(tableTwo); throw new Exception("Force transaction rollback"); }}

There are a couple of things to note in this DAO's. The first one is that each DAO is associated with a distinct PersistenceUnit(we will see how to configure them later in this tutorial).

The second one is that the save method of TableTwoDaoImpl is explicitly throwing an exception. This exception will be used to force the global transaction to rollback. We will also see this in detail in the following tutorial sections.

The transaction service

Finally we need a service to implement our global container managed transaction. Following next is a possible service interface and implementation:

TransactionalService.java
package com.byteslounge.spring.tx.service;import com.byteslounge.spring.tx.entity.TableOne;import com.byteslounge.spring.tx.entity.TableTwo;public interface TransactionalService {
void persist(TableOne tableOne, TableTwo tableTwo) throws Exception; }

TransactionalServiceImpl.java
package com.byteslounge.spring.tx.service.impl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import com.byteslounge.spring.tx.dao.TableOneDao;import com.byteslounge.spring.tx.dao.TableTwoDao;import com.byteslounge.spring.tx.entity.TableOne;import com.byteslounge.spring.tx.entity.TableTwo;import com.byteslounge.spring.tx.service.TransactionalService;@Servicepublic class TransactionalServiceImpl          implements TransactionalService {
@Autowired private TableOneDao tableOneDao; @Autowired private TableTwoDao tableTwoDao; @Override @Transactional(rollbackFor=Exception.class) public void persist(TableOne tableOne, TableTwo tableTwo) throws Exception {
tableOneDao.save(tableOne); tableTwoDao.save(tableTwo); }}

Thing to note in this service implementation: Method persist is annotated with @Transactional so the method will be executed in a transactional fashion by the Spring container. This method will call the data persistence methods for both DAO's we defined earlier.

Transactional annotation has the rollbackFor attribute defined with Exception.class value. This means that the transaction will rollback if an exception of type Exception occurs inside persist method execution.

You can refine the exception types that cause a transaction rollback but for the simplicity of this example we will keep withException.

Persistence Context configuration

Now the Persistence Context configuration (persistence.xml):

persistence.xml
com.byteslounge.spring.tx.entity.TableOne
com.byteslounge.spring.tx.entity.TableTwo

Here we configure the Persistence Units we are injecting in the DAO's we defined earlier. Note that transaction-type attribute of each persistence unit is defined as JTA.

We also define where Hibernate should look for a Transaction Manager. Since we are using Atomikos we will define it as the Hibernate TransactionManagerLookup provided by Atomikos (we are using Hibernate as JPA implementation).

Spring configuration

Now we define the necessary Spring beans and also the Spring container configuration:

spring.xml

Things to note in Spring configuration:

We are instructing Spring to use a JTA transaction manager (jta-transaction-manager configuration element).

We define a couple of beans to represent the both database connections (beans dataBase1 and dataBase2). The datasource connections are using com.mysql.jdbc.jdbc2.optional.MysqlXADataSource class.

We are defining two Atomikos datasources (dataSource1 and dataSource2) that will be coupled with the respective database connections we just defined.

We are also defining a couple of Entity Manager factories each one associated with the respective Atomikos datasource and the Persistence Units we defined in the previous section.

Finally we define the Atomikos JTA Transaction Manager and the Atomikos JTA User Transaction that will be both used by The Spring JTA Transaction Manager.

If you need more information on the JTA Transaction Manager and JTA User Transaction roles in a JTA distributed transaction you should search the Internet for additional documentation as there is plenty available.

Testing the example

Now we define a simple servlet to test the configuration:

Testing servlet
package com.byteslounge.spring.tx.servlet;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.HttpRequestHandler;import com.byteslounge.spring.tx.entity.TableOne;import com.byteslounge.spring.tx.entity.TableTwo;import com.byteslounge.spring.tx.service.TransactionalService;@Component("testServlet")public class TestServlet implements HttpRequestHandler {
@Autowired private TransactionalService transactionalService; @Override public void handleRequest( HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
TableOne tableOne = new TableOne(); tableOne.setValue("value1"); TableTwo tableTwo = new TableTwo(); tableTwo.setValue("value2"); try {
transactionalService.persist(tableOne, tableTwo); } catch (Exception e) {
e.printStackTrace(); } } }

Note that we are implementing Spring HttpRequestHandler interface so we are able to use Spring dependency injection in the servlet itself, but we will not detail this subject in the current tutorial.

After the servlet execution we will observe that neither of the records are inserted in TABLE_ONE and TABLE_TWO. This is because the second transaction is explicitly throwing an Exception as we configured it in the previous sections and so the global transaction is rollback.

Make the transaction commit

In order to make the transaction to successfully commit we just need to remove the explicit exception throwing in Datasource 2DAO:

Modified TableTwoDaoImpl.java
package com.byteslounge.spring.tx.dao.impl;import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;import org.springframework.stereotype.Service;import com.byteslounge.spring.tx.dao.TableTwoDao;import com.byteslounge.spring.tx.entity.TableTwo;@Servicepublic class TableTwoDaoImpl implements TableTwoDao {
private EntityManager entityManager; @PersistenceContext(unitName="PersistenceUnit2") public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager; } @Override public void save(TableTwo tableTwo) throws Exception {
// Exception throwing is removed entityManager.persist(tableTwo); }}

Downloadable sample

You may find the complete example source code as a downloadable resource at the end of this page. The downloadable sampleis explicitly throwing the exception so you may observe the behaviour of global transaction rollback. Change it in order to suit your needs.

Download source code from this article

Download link: 

转载地址:http://rsgmb.baihongyu.com/

你可能感兴趣的文章
Mybatis常见异常类型Could not set parameters for mapping离不开这个原因!
查看>>
JAVA如何实现短信验证码--阿里云接口,新手式图文教学,个人项目有这一篇就够了
查看>>
UVa 10917 Dijkstra
查看>>
CF403B/CF402D
查看>>
CF402E / 403C
查看>>
cf404c
查看>>
武大网络预赛 Problem 1545 - I - Twenty-four
查看>>
ZOJ Problem Set - 3768 Continuous Login
查看>>
某山面试 3、实现如下函数:
查看>>
malloc的小知识
查看>>
UVALive 6755 - Swyper Keyboard
查看>>
uva10023 手算开方的方法
查看>>
第一个JSP程序(JSP入门)
查看>>
JSP语法简介
查看>>
JSP中EL表达式入门与简介
查看>>
Spring自动装配
查看>>
Hibernate入门与实例
查看>>
Jython入门学习
查看>>
Hiberate基础用法实例
查看>>
Maven编译时指定JDK版本
查看>>