Transaction Design with Session Bean (EJB 3.0) and JPA: This tutorial discuss about following points
1. Integration of Session Bean (at Business Layer) and JPA(at Integration layer) using annotation.
2. Use of container management transaction in Session Beans(EJB 3.0) using annotation.
3. How to design a robust transaction strategy in J2EE business layer and Integration layer using CMT(Container Mangement Transaction).
To discuss/design a transaction management we will take the following scenarios.
During application design generally transaction starts at business layer and we use Session Beans at business layer to write business logic and access DB at Integration layer. At Integration layer we can use JBDC, Entity Bean, and JPA etc to access the database.
Generally transaction boundary starts when we start processing logic at business layer and should end (commit or rollback) when we returns from business layer. So if anything happens wrong at business layer or integration layer transaction should be rolled back otherwise commit.
To complete a single use case request there may be more than one session beans are required. So to reduce the network traffic we use Session Facade. To process a single use case request if we have to call two Session Beans from client then in this case we use a Session Facade which will call these two session beans and also handle all transaction related issue. So the client which is either JSP/Servlet or other java class need to process a request it calls the Session facade bean which in turns start transaction do all processing with the help of other helper session beans and if everything is fine it commit the transaction or rollback the transaction and throws the exception to caller to indicate the failure of transaction.
In below figure there is one Session Facade bean which in turn calls HelperSessionBean1 and then calls HelperSessionBean2 and these helper session beans use JPA to access DB.
Start Design Transaction Strategy: We will use Session Beans (EJB 3.0) at business layer and JPA to access DB. Each method of Session Facade bean will be used to complete process of one use case request which in turn will call Session Bean Helper One and Two to complete the request. Here Session Facade Bean will also be responsible to handle transaction related processing. So if there is something happens wrong anywhere during processing the request it will rollback the request or commit the request processing.
In this transaction design we will use Container Management Transaction Strategy that means all transaction related processing like start transaction, commit transaction or rollback transaction will be handled by container(here Glassfish) and we will define only the transaction demarcation and transaction attributes in Session Beans.
In the below example annotations have been used to define all properties in Session Beans.
In the below example we are going to choose TransactionAttributeType.REQUIRES_NEW for each method call in Session facade bean that means every call to any method in Session Facade bean from client will need a new transaction. As per EJB specification when this transaction attribute is used then every method will run in the new transaction context. For Helper Session Beans we are going to use TransactionAttributeType.MANDATORY because the transaction will always be started by Session facade beans so helper session beans should support transaction started by Session facade bean and helper beans cannot start the transaction so MANDATORY attribute is used which means helper beans will support already started transaction and if they are being called without any transaction context will throw javax.ejb.EJBTransactionRequiredException.
So if one use case is being handled by on method of Session Façade and façade is calling other unlimited helper beans and if anything happens wrong during this request processing in nay of helper beans or façade bean transaction must be rolled back and if everything is going fine transaction must be committed.
In the below code example we will use the following.
1. Glassfish Server version 2.1 which supports EJB3.0.
2. Oracle 11g Database.
3. Eclipse
4. Top Link JPA
Following configuration is required to run the below code.
1. Create a JBDC resource in GlassFish Server
First create a Connection Pool using Glasfish Admin Console, Resources--> JBDC--> Connection Pools
Now create a JNDI name using Resources--> JDBC--> JDBC Resources. Oracle JBDC driver should be available in Glassfish library to complete this process.
2. Create two Database tables as below
create table OP_PERSON (personid INTEGER primary key,firstname varchar2(30), lastname varchar2(30), age integer);
create table OP_ADDRESS(ADDRESSID integer primary key, ADDRESSLINE1 varchr2(30),ADDRESSLINE2 varchar2(30), CITY varchar2(10), ADDRESSTYPE varchar2(5), COUNTY varchar2(20), COUNTRY varchar2(20), STREET varchar2(20), ZIPCODE integer, PERSONID integer foreign key references op_person persionid);
3. Persistent.xml file configuration
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
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_1_0.xsd">
<persistence-unit name="myprojectmanager"
transaction-type="JTA" >
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>jdbc/oraclepool</jta-data-source>
<class>org.paandav.relation.OpAddress</class>
<class>org.paandav.relation.OpPerson</class>
<properties>
<property name="toplink.jdbc.native-sql" value="false" />
<property name="eclipselink.ddl-generation" value="create-tables"/>
<property name="eclipselink.ddl-generation.output-mode" value="both"/>
</properties>
</persistence-unit>
</persistence>
In this xml we are using transaction type as JTA when we want to inject an EntityManager in Session Beans and container management transaction we must use transaction type as JTA not RESOURCE_LOCAL. jta-data-source tag value must be match to JDBC Resources JNDI name. Here we cannot use local connection properties when we are using container management transaction.
4. I have used the following structure in Eclipse for this example
4. Java code for all classes
package org.paandav.relation;
import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "OP_ADDRESS")
public class OpAddress implements Serializable {
@Id
private long addressid;
private String addresstype;
private String street;
private String addressline1;
private String addressline2;
private String city;
private String county;
private String country;
private BigDecimal zipcode;
private long personid;
private static final long serialVersionUID = 1L;
public OpAddress() {
super();
}
public long getAddressid() {
return this.addressid;
}
public void setAddressid(long addressid) {
this.addressid = addressid;
}
public String getAddresstype() {
return this.addresstype;
}
public void setAddresstype(String addresstype) {
this.addresstype = addresstype;
}
public String getStreet() {
return this.street;
}
public void setStreet(String street) {
this.street = street;
}
public String getAddressline1() {
return this.addressline1;
}
public void setAddressline1(String addressline1) {
this.addressline1 = addressline1;
}
public String getAddressline2() {
return this.addressline2;
}
public void setAddressline2(String addressline2) {
this.addressline2 = addressline2;
}
public String getCity() {
return this.city;
}
public void setCity(String city) {
this.city = city;
}
public String getCounty() {
return this.county;
}
public void setCounty(String county) {
this.county = county;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
public BigDecimal getZipcode() {
return this.zipcode;
}
public void setZipcode(BigDecimal zipcode) {
this.zipcode = zipcode;
}
public long getPersonid() {
return this.personid;
}
public void setPersonid(long personid) {
this.personid = personid;
}
}
package org.paandav.relation;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "OP_PERSON")
public class OpPerson implements Serializable {
@Id
private long personid;
private String firstname;
private String lastname;
private BigDecimal age;
private static final long serialVersionUID = 1L;
public OpPerson() {
super();
}
public long getPersonid() {
return this.personid;
}
public void setPersonid(long personid) {
this.personid = personid;
}
public String getFirstname() {
return this.firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return this.lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public BigDecimal getAge() {
return this.age;
}
public void setAge(BigDecimal age) {
this.age = age;
}
package org.paandav.session;
public class BusinessLayerException extends Exception {
private static final long serialVersionUID = 1L;
public BusinessLayerException() {
super();
}
public BusinessLayerException(String msg) {
super(msg);
}
}
package org.paandav.session;
import org.paandav.relation.OpAddress;
import org.paandav.relation.OpPerson;
import java.util.List;
public interface SessionFacade {
public String sessionFacadeUseCaseOne(OpPerson person, OpAddress[] add)
throws BusinessLayerException;
public List<String> getDropList() throws BusinessLayerException;
public List<String> getDropList() throws BusinessLayerException;
}
package org.paandav.session;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.paandav.relation.OpAddress;
import org.paandav.relation.OpPerson;
/**
* Session Bean implementation class SessionFacade
*/
@Stateless
@Remote(SessionFacade.class)
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class SessionFacadeBean implements SessionFacade {
@EJB
private SessionHelperOne hone;
@EJB
private SessionHelperTwo htwo;
@PersistenceContext(unitName = "myprojectmanager")
private EntityManager em;
@Override
public String sessionFacadeUseCaseOne(OpPerson person, OpAddress[] add)
throws BusinessLayerException {
System.out.println("Entity Manager In Session Facade=" + em);
String resFronHelperOne = hone.sessionHelperOne(person);
String resFronHelperTwo = htwo.sessionHelperTwo(add);
return "Hello " + resFronHelperOne + resFronHelperTwo;
}
@Override
public List<String> getDropList() throws BusinessLayerException {
List<String> dropList = new ArrayList<String>(2);
dropList.add("One");
dropList.add("Two");
return dropList;
}
}
package org.paandav.session;
import org.paandav.relation.OpPerson;
public interface SessionHelperOne {
public String sessionHelperOne(OpPerson person) throws BusinessLayerException;
}
package org.paandav.session;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.paandav.relation.OpPerson;
/**
* Session Bean implementation class SessionHelperOneBean
*/
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class SessionHelperOneBean implements SessionHelperOne {
@PersistenceContext(unitName = "myprojectmanager")
private EntityManager em;
@Override
public String sessionHelperOne(OpPerson person)
throws BusinessLayerException {
em.persist(person);
System.out.println("Person Inserted Successfulyy");
return "Person Inserted Successfulyy";
}
}
package org.paandav.session;
import org.paandav.relation.OpAddress;
public interface SessionHelperTwo {
public String sessionHelperTwo(OpAddress[] add)
throws BusinessLayerException;
}
package org.paandav.session;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.paandav.relation.OpAddress;
/**
* Session Bean implementation class SessionHelperOneBean
*/
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class SessionHelperTwoBean implements SessionHelperTwo {
@PersistenceContext(unitName = "myprojectmanager")
private EntityManager em;
@Override
public String sessionHelperTwo(OpAddress[] add)
throws BusinessLayerException {
if (add != null && add.length > 0) {
for (OpAddress ad : add) {
em.persist(ad);
}
}
return "Addresses inserted successfully";
}
}
import javax.naming.Context;
import javax.naming.InitialContext;
import org.paandav.relation.OpAddress;
import org.paandav.relation.OpPerson;
import org.paandav.session.SessionFacade;
public class SessionFacadeTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
SessionFacade fac = (SessionFacade) ctx
.lookup("org.paandav.session.SessionFacade");
OpPerson p = new OpPerson();
p.setPersonid(System.nanoTime());
p.setFirstname("Pradeep");
p.setLastname("Tiwari");
OpAddress ad = new OpAddress();
ad.setAddressid(System.nanoTime());
ad.setAddressline1("Line1");
OpAddress ad2 = new OpAddress();
ad2.setAddressid(System.nanoTime());
ad2.setAddressline1("Line1");
ad2.setAddresstype("PPP");
OpAddress[] as = new OpAddress[2];
as[0] = ad;
as[1] = ad2;
fac.sessionFacadeUseCaseOne(p, as);
System.out.println();
}
}
Running the Example:
Run the SessionFacadeTest and will get two entries in OP_ADDRESS table and one entry in OP_ADDRESS table. That means everything was fine during the flow and transaction has been committed.
Now Change ad2.setAddresstype("PPP"); to ad2.setAddresstype("PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"); and run the test program you will get an exception and no entries either in OP_ADDRESS or OP_PERSON table.
IF we look at the flow, exception is occurred when JPA tries to insert values in OP_ADDRESS table that means helperSessionBean1 has completed its work to insert data into OP_PERSON table but helperSessionBean2 could not insert data because of more than specified value in personType field and when heleprbean2 throws an exception container automatically rollback all transactions including insertion of data in helperSessionBean1.
Here all the transaction management are being done by container (Glassfish) like injecting EntityManager and starting transaction at Session Façade’s method call and commit or rollback transaction when any exception is being thrown.
Mastering Enterprise Java Beans 3.0 By Rima Patel Sriganesh, Gerald Brose, Micah Silverman
No comments:
Post a Comment