We all know that we
commonly use the Java Transaction API and the XA protocol for distributed
transactions in Spring. We have some other options as well whose implementation depends on the types of
resources our application uses and the trade-offs we're willing to make between
performance, safety, reliability, and data integrity.
We have different
options to maintain distributed transaction like 2-phase commit with XA
protocol but there are some performance trade-offs as well with those
transaction management. Here we will discuss about Best Effort 1PC which is
very effective Distributed Transaction Management techmnique.
Introduction-
The basic idea in this technique is to delay the commit of all resources as
late as possible in a transaction so that the only thing that can go wrong is
an infrastructure failure (not a business-processing error). It is often good enough if the participants are
aware of the compromises. Many high-volume, high-throughput
transaction-processing systems are set up this way to improve performance.
Systems that rely on Best Efforts 1PC reason that
infrastructure failures are rare enough that they can afford to take the risk
in return for higher throughput. If business-processing services are also designed
to be idempotent, then little can go wrong in practice.The Best Efforts 1PC
pattern is fairly general but can fail in some circumstances that the developer
must be aware of. This is a non-XA pattern that involves a synchronized
single-phase commit of a number of resources. Because the 2PC is not used, it
can never be as safe as an XA transaction,
To understand this
concept we will go with an example –
For
our example we have two different database transaction resources. So in example
we will update two different databases. One database transaction is started
before the another database one, and they end (either commit or rollback) in
reverse order. So the sequence in the case is:
Here
committing of resources should be in order, why the ordering is important is
technical, but the order itself is determined by business requirements. The
order tells you that one of the transactional resources in this case is
special; it contains instructions about how to carry out the work on the other.
This is a business ordering: the system cannot tell automatically which way
round it is (although if messaging and database are the two resources then it
is often in this order). The reason the ordering is important has to do with
the failure cases. The most common failure case (by far) is a failure of the
business processing (bad data, programming error, and so on). In this case both
transactions can easily be rigged to respond to an exception and rollback. In
that case the integrity of the business data is preserved, with the timeline
being similar to the ideal failure case .
Here
in our example we are using following :
1.
Maven
2.
Spring 3.0
3.
Hibernate 3.0
4.
MySQL
In
our example we will achieve Best Effort 1PC using Chaining of Transaction Manager in Spring.
Our
Project package structure is shown below:
Pom.xml for this project is
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>BestEffort1PC</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>BestEffort1PC Maven
Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.17.1-GA</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.8.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<finalName>BestEffort1PC</finalName>
</build>
</project>
Here we have two database :
1.
Order
2.
ProcessOrder
We will prepare order and store
it in Order database and then insert this into ProcessOrder database after
successful processing the status of Order will be completed.
You can see we have defined two
sessionfactories for two different databases
1
hibernate3AnnotatedSessionFactory and
2
Phibernate3AnnotatedSessionFactory
<bean id="hibernate3AnnotatedSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.test.model</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.current_session_context_class">thread</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="hibernate3AnnotatedPSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="pdataSource" />
<property name="packagesToScan">
<list>
<value>com.test.ordprocessmodel</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<!-- <prop key="hibernate.current_session_context_class">thread</prop>-->
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.test.model</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.current_session_context_class">thread</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="hibernate3AnnotatedPSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="pdataSource" />
<property name="packagesToScan">
<list>
<value>com.test.ordprocessmodel</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<!-- <prop key="hibernate.current_session_context_class">thread</prop>-->
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
As we configure our database
using hibernate annotated session factory now its time to define DAO classes
and Service classes. We will define two different dao classes for two different
databases. Here are those classes:
1.
OrderDao.java and its implementation
OrderDaoImpl.java and,
2.
POrderDao.java and its
implementation POrderDaoImpl.java
Also, there is one service class ProductService.java which will add an
order in Order database and also add it in ProcessOrder database and finally
update the order with Success.
You can see all classes in sample project.
So in our ProcessService class we have processOrder method with annotation "@Transactional" which will make this method to be part of transaction.
After defining Daos and Service here comes our application context
in which we will define our Chained Transaction manager to do our Magic !!! .
Our application will define two different transaction managers otransactionManager and ptransactionManager for different
databases. We will be using org.springframework.data.transaction.ChainedTransactionManager
and pass all transactionmanagers which we need to be involved in one
transaction.
This ChainedTransactionManager will
delay the commit of all transaction managers until any exception occurred or
all things will work successfully.
<bean id="otransactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="hibernate3AnnotatedSessionFactory" />
<bean id="ptransactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="hibernate3AnnotatedPSessionFactory" />
<bean id="myProductServiceTarget" class="com.test.service.ProductService"/>
<bean id="transactionManager" class="org.springframework.data.transaction.ChainedTransactionManager">
<constructor-arg>
<list>
<ref bean="otransactionManager"/>
<ref bean="ptransactionManager"/>
</list>
</constructor-arg>
</bean>
Now its time to run TestChainedTransaction.java which will throw RuntimeException and rollback all DB operations.
You can download the sample project on below link:class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="hibernate3AnnotatedSessionFactory" />
<bean id="ptransactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="hibernate3AnnotatedPSessionFactory" />
<bean id="myProductServiceTarget" class="com.test.service.ProductService"/>
<bean id="transactionManager" class="org.springframework.data.transaction.ChainedTransactionManager">
<constructor-arg>
<list>
<ref bean="otransactionManager"/>
<ref bean="ptransactionManager"/>
</list>
</constructor-arg>
</bean>
Now its time to run TestChainedTransaction.java which will throw RuntimeException and rollback all DB operations.
The precise mechanism for triggering
the rollback is unimportant; several are available. The important point is that
the commit or rollback happens in the reverse order of the business ordering in
the resources. In the sample application, the Second Databse must commit last because the instructions for
the business process are contained in that resource. This is important because
of the (rare) failure case in which the first commit succeeds and the second
one fails. Because by design all business processing has already completed at
this point, the only cause for such a partial failure would be an
infrastructural problem with the Database Connection.
Important Points:
1. The important point here is that
this technique is very efficient and is good in performance when using multiple
resources.
2. Also this technique can be
used where we do not have XA drivers for transactional resources for
Phase Commit.
3. It is not effective in infrastructure failures which are very
rare. Hence application which can survive these types of issues will be very
effective with Best Effort 1 PC
https://drive.google.com/file/d/0BwvELMMMQIaDa2ZZRE1jYUxRdHc/view?usp=sharing