Monday, December 27, 2010

Design and develop a reference J2EE Architecture (Struts 2.0, Spring 3.0, and Hibernate 3.0)

Goal: Design and develop a reference J2EE Architecture using current Java/JEE open source frameworks/DB/Servers which can be used as a quick reference for small to medium size enterprise applications.

Architecture:
               
Clients:
 Client can be
  1. Browser access though Struts 2 interface.
  2. Swing Application through Spring Remote Services
  3. .NET application connected through web services.
  4. Other applications can interact through web services.
Presentation/Interface Layer :
 This layer can have different modules to expose the business services in different modes.
1. Struts 2 can be used by browser client and is main interface.
2. Services can be exposed as DWR services so any UI with JavaScript capability can access business services.
3. Services can be exposed as Remote Service using Sprint Remote and other java client or Swing applications can use this interface.
4. Services can be exposed as web services for other clients.
5. Presentation layer (Struts 2)responsible for Authentication, Authorization, calling business logic through service layer, view preparation and dispatch response.
6. For Authentication and Authorization we can use Spring ACGI security module for Struts 2.
7. Service layer POJO objects will be injected by Spring 3 into Struts 2 Action classes.
Service Layer:
1. Service layer will be responsible to provide one method for each use case for different modules.
2. Handle authentication and authorization when called from other than presentation layer.
3. Transaction management will use Spring 3 transaction strategy. Business and Integration layers will not do any transaction related activities.
4. Hibernate transaction manager will be injected by Spring3 in this layer.
5. Audit logging will be handled by this layer.
6. This layer POJOs can be exposed as web service or remote services using Spring3.
7. This layer will also be responsible for validation of incoming information but only when request is coming from other than presentation layer because presentation layer will have their own validation mechanism using Apache validator framework.
8. All validations will be applied on methods using Spring3 method interceptors.
Business Layer:
1. All business logic will be implemented in this layer without considering transaction management and other concerns.
2. DAO will be used to save the objects states to database or main frames.
3. DAO objects will be injected by Spring 3 into a DAO adoptor which will call DAO interface implementation objects.
4. No Direct call to DB, JMS or web services to get/update/insert the information from this layer.
5. If need to interact with JMS or web service call integration layer.
Integration Layer:
1. This layer will be responsible for all data access related activities.
2. Interaction with Database thorough JPA/JDBC, Web Services and Message broker (JMS). We can use any JPA implementaion like Hibernate, TopLink, Eclipse etc.
Framework/Technologies and servers:
1. Java/J2EE
2. Struts 2.2.1
3. Spring 3.0
4. Hibernate 3.0 /JPA
5. Tomcat 6.0/Glassfish
6. MySQL as Database

Friday, December 24, 2010

Access Multiple Database Using Spring 3, Hibernate 3 and Atomikos

This blog is in continuation with previous blog http://tiwarij2eeblog.blogspot.com/2010/12/handling-transaction-with-multiple.html


In the previous example we had used different transaction managers for different databases. 


Now we will use only one transaction manger to coordinate the transactions using Atomikos. You can download dependent jar files from http://www.atomikos.com/.


All the java code is same from previous blog except applicationContext.xml contents.


Below xml file contents show how to configure data sources and Spring JTA transaction manger to access multiple data sources.


This strategy can be used when you are not having J2EE application server to run your application.


Just replace the applicationContext.xml and run the test again.


Here we are using only one JTA transaction manager.



<?xml version="1.0" encoding="UTF-8"?>
<!--
Application context definition for Online Exam
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://www.springframework.org/schema/jdbc
            http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<context:annotation-config />
<aop:aspectj-autoproxy />


<bean id="mySQLDatasource" 
class="com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean">
<property name="uniqueResourceName"><value>NONXADBMS</value></property>
<property name="url"><value>jdbc:mysql://localhost/pradeep</value></property>
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="user"><value>root</value></property>
<property name="password"><value>root</value></property>
<property name="poolSize"><value>1</value></property>
<property name="borrowConnectionTimeout"><value>60</value></property>
</bean>


<bean id="oracleDatasource" 
class="com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean">
<property name="uniqueResourceName"><value>NONXADBMSORACLE</value></property>
<property name="url"><value>jdbc:oracle:thin:@localhost:1521:ORACLE</value></property>
<property name="driverClassName"><value>oracle.jdbc.driver.OracleDriver</value></property>
<property name="user"><value>pradeep</value></property>
<property name="password"><value>pradeep</value></property>
<property name="poolSize"><value>1</value></property>
<property name="borrowConnectionTimeout"><value>60</value></property>
</bean>


<!-- Hibernate plugin code -->

<bean id="mysqlSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="mySQLDatasource"></property>
<property name="configLocation" value="classpath:mysql_hibernate.cfg.xml" />
</bean>


<bean id="oracleSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="oracleDatasource"></property>
<property name="configLocation" value="classpath:oracle_hibernate.cfg.xml" />
</bean>


<!-- Transaction managers -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<!--  when close is called, should we force transactions to terminate or not?-->
<property name="forceShutdown"><value>true</value></property>
</bean>


<!-- Also use Atomikos UserTransactionImp, needed to configure Spring  --> 
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
     <property name="transactionTimeout"><value>300</value></property>
</bean>


<!-- Configure the Spring framework to use JTA transactions from Atomikos
-->
<bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager"><ref bean="atomikosTransactionManager"  /></property>
<property name="userTransaction"><ref bean="atomikosUserTransaction"  /></property>
</bean>


<aop:config>
<aop:pointcut id="purchaseBeanMethods"
expression="execution(* org.paandav.PurchaseServiceIfc.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="purchaseBeanMethods" />
</aop:config>


<tx:advice id="txAdvice" transaction-manager="springTransactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>


<!-- Service -->
<bean id="purchaseBean" class="org.paandav.PurchaseServiceImpl" />


<!-- DAO -->
<bean id="itemDAO" class="org.paandav.ItemDAOImpl"></bean>


<bean id="purDAO" class="org.paandav.PurchaseDAOImpl"></bean>
</beans>

Handling Transaction with Multiple Database using Spring 3.0 and Hibernate 3.0 Without XA Driver

Problem: We are having two different database like MySQL and ORACLE and we want to access both the database in a single request and want to maintain ACID properties. If there is failure in any of these during request processing all transaction should be rolled back otherwise all transactions should commit. We have to use Spring 3.0 and Hibernate 3.0.

Solution: When I first started to think about this type of transaction management I thought about 2 phase commit which we use in distributed transaction management. In 2 phase commit protocol there will be one resource manager for each database and a single transaction manager who will coordinate with all resource mangers. In first phase Transaction manager will ask to all participating resource manger to be ready and if all reply with OK response then in second phase it ask them to do commit. If during this phase any resource manager reject it the whole transaction will be rolled back. If you need more to dig look at the following e-book.java-transaction-design-strategies.

To implement 2 phase commit protocol in J2EE we need XA database drivers for each database vendor and JTA implementation which will work as transaction manager.

I have tried to implement a transaction strategy without using XA database drivers and JTA. This implementation is not a 2 phase commit protocol. It has tried to take the advantage of Spring declarative transaction mechanism.

In this mechanism if we are using declarative transaction and anything happens wrong Spring roll back all the transaction. 

Design with Spring 3.0 and Hibernate 3.5: In this design we will create 2 session factories and two transaction mangers. This strategy can be applied up to n databases. We will follow the below steps to implement with MySQL and ORACLE.

Step 1: Create two Hibernate session factory one for MySQL and other for ORACLE.

Step 2: Create two transaction manager one for MySQL and other for ORACLE.

Step 3: Create two transaction advice and apply these two advices on a single point cut.

Implementation Example: In this example there are two table first MS_ITEM which is in MySQL database and other is OR_PURCHASE which is in ORACLE. There are two DAO classes each for their one table with having their corresponding hibernate session factory which is being injected by Spring. 
There is one service class PurchaseServiceImpl which is having instances of both the dao and they will be injected in this object by Spring. PurchaseServiceImpl is having one method which is inserting rows in both the databases and not doing any transaction handling.

All the transaction handing is managed by Spring declarative transaction management using two transaction mangers.

This example uses both xml as well as annotations to plug the components. There is one service class which is having one method who will interact with both the databases and if any think wrong happens in this method all transaction will be rolled back.

Below picture shows the directory structure classes and xml files I have used.


Item.java:

package org.paandav;


import java.io.Serializable;


public class Item implements Serializable
{
private static final long serialVersionUID = 1L;
private Long id;
private String itemName;
private Long version;
public Long getVersion(){
return version;
}
public void setVersion(Long version){
this.version = version;
}
public Long getId(){
return id;
}
public void setId(Long id){
this.id = id;
}
public String getItemName(){
return itemName;
}
public void setItemName(String itemName){
this.itemName = itemName;
}
}


Purchase.java:

package org.paandav;


public class Purchase
{
private static final long serialVersionUID = 1L;
private Long id;
private String itemName;
private Long iteNoReference;
private Long version;


public Long getId() {
return id;
}
public void setId(Long id){
this.id = id;
}
public String getItemName(){
return itemName;
}
public void setItemName(String itemName){
this.itemName = itemName;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version){
this.version = version;
}
public Long getIteNoReference(){
return iteNoReference;
}
public void setIteNoReference(Long iteNoReference){
this.iteNoReference = iteNoReference;
}
}


Item.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.paandav">
<class name="Item" table="MS_ITEM" optimistic-lock="version">
<id name="id" column="id">
<generator class="native" />
</id>
<version name="version" type="long" />
<property name="itemName" length="10" />
</class>
</hibernate-mapping>

Purchase.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="org.paandav">
<class name="Purchase" table="OR_PURCHASE" optimistic-lock="version">
<id name="id" column="id">
<generator class="native" />
</id>
<version name="version" type="long" />
<property name="itemName" length="10" />
<property name="iteNoReference" />
</class>
</hibernate-mapping>

ItemDAOIfc.java:
package org.paandav;

public interface ItemDAOIfc
{
public Item save(Item it) throws Exception;
}

ItemDAOImpl.java:
package org.paandav;

import javax.annotation.Resource;

import org.hibernate.SessionFactory;

public class ItemDAOImpl implements ItemDAOIfc
{

SessionFactory mysqlSessfac;

@Resource(name = "mysqlSessionFactory")
public void setMysqlSessfac(SessionFactory mysqlSessfac)
{
this.mysqlSessfac = mysqlSessfac;
}

@Override
public Item save(Item it) throws Exception
{
mysqlSessfac.getCurrentSession().save(it);
return it;
}

}

PurchaseDAOIfc.java:
package org.paandav;

public interface PurchaseDAOIfc
{
public Purchase save(Purchase pr) throws Exception;
}

PurchaseDAOImpl.java:

package org.paandav;

import javax.annotation.Resource;

import org.hibernate.SessionFactory;

public class PurchaseDAOImpl implements PurchaseDAOIfc
{

SessionFactory oracleSessfac;

@Resource(name = "oracleSessionFactory")
public void setOracleSessfac(SessionFactory oracleSessfac)
{
this.oracleSessfac = oracleSessfac;
}
@Override
public Purchase save(Purchase pr) throws Exception
{
oracleSessfac.getCurrentSession().save(pr);
return pr;
}

}

PurchaseServiceIfc.java:
package org.paandav;

public interface PurchaseServiceIfc
{
String purchaseItem(Item[] items, Purchase[] pr) throws Exception;
}

PurchaseServiceImpl.java:

package org.paandav;

import javax.annotation.Resource;

public class PurchaseServiceImpl implements PurchaseServiceIfc
{

ItemDAOIfc itemDao;
PurchaseDAOIfc purDao;

@Resource(name = "purDAO")
public void setPurDao(PurchaseDAOIfc purDao){
this.purDao = purDao;
}

@Resource(name = "itemDAO")
public void setItemDao(ItemDAOIfc itemDao){
this.itemDao = itemDao;
}

@Override
public String purchaseItem(Item[] items, Purchase[] pr) throws Exception{
//Insert in MySQL
for (Item i : items){
itemDao.save(i);
}
//Insert in ORACLE
for (Purchase p : pr){
purDao.save(p);
}
return "GOT IT";
}

}

applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!--
Application context definition for Online Exam
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://www.springframework.org/schema/jdbc
            http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config />
<aop:aspectj-autoproxy />

<!-- Hibernate plugin code -->
<bean id="mysqlSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
<property name="configLocation" value="classpath:mysql_hibernate.cfg.xml" />
</bean>
<bean id="oracleSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
<property name="configLocation" value="classpath:oracle_hibernate.cfg.xml" />
</bean>
<!-- Transaction managers -->
<bean id="mySqlTxManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mysqlSessionFactory" />
</bean>
<bean id="oracleTxManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="oracleSessionFactory" />
</bean>
<aop:config>
<aop:pointcut id="purchaseBeanMethods"
expression="execution(* org.paandav.PurchaseServiceIfc.*(..))" />
<aop:advisor advice-ref="mysqlTxAdvice" pointcut-ref="purchaseBeanMethods" />
<aop:advisor advice-ref="oracleTxAdvice" pointcut-ref="purchaseBeanMethods" />
</aop:config>
<tx:advice id="mysqlTxAdvice" transaction-manager="mySqlTxManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<tx:advice id="oracleTxAdvice" transaction-manager="oracleTxManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- Service -->
<bean id="purchaseBean" class="org.paandav.PurchaseServiceImpl" />
<!-- DAO -->
<bean id="itemDAO" class="org.paandav.ItemDAOImpl"></bean>
<bean id="purDAO" class="org.paandav.PurchaseDAOImpl"></bean>
</beans>

Test Java File:
package org.paandav;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MultipleDataBaseConfiguration
{

/**
* @param args
*/
public static void main(String[] args) throws Exception
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"applicationContext.xml");
PurchaseServiceIfc ps = (PurchaseServiceIfc) ctx
.getBean("purchaseBean");
Item[] items = new Item[2];
items[0] = new Item();
items[1] = new Item();
items[0].setItemName("MYSLITEMMM");
items[1].setItemName("ORACLEITEM");

Purchase[] prs = new Purchase[2];
prs[0] = new Purchase();
prs[1] = new Purchase();
prs[0].setItemName("PURCHASE1");
prs[1].setItemName("PURCHASE2");

System.out.println(ps.purchaseItem(items, prs));
}

}


Validation: Run the above MultipleDataBaseConfiguration file and you will see that all rows has been inserted inn MySQL and ORACLE database successfully.

Now change the test java file as

items[1].setItemName("ORACLEITEMMMMMMMMMMMMMMMMMMMMM");

because of above changes hibernate will throw an exception because length of column is 10 and again run. Now an exception will be thrown and no rows will be inserted in MySQL and ORACLE and this MySQL has thrown exception and ORACLE insertion has also been rolled back.

Now revert back your changes and change file as

prs[0].setItemName("PURCHASEEEEEEEEEEEEEEEEEEEEEEEEEEEEE1");

and tun the java program. This time MySQL insertions are successful but ORACLE will throw an exception and due to this exception MySQL insertions will be rolled back.

Conclusion: We can observe that we are able to manage the transactions without using XA database drivers for different databases.