martedì 10 maggio 2011

Hibernate with Guice and a little of AOP to manage transaction.

What do you think abount @Transactional annotation? I used it with spring framework on service class to manage transaction into and intra service call.
It's a very elegant and easy way to manage transactions.

I've started working on a new project and I decided to use Guice as Dependency Injection framework, IMHO more lite and easy than Spring, and Hibernate
but I would like to continue to use the @Transactional annotation on my service class.
I didn't found anything good to use and I decided to use a little of AOP to recreate this functionality.

Let's started with order, I would like that:
1) When I call a method on a service class, the operations in this method belong to a single transaction.
2) If the caller of the method 1) has a transaction, the thansanction must is the parent (caller) transaction.

In practice the "Required" attribute of EBJ's Transactional parameter, see Required Attribute

This is the first case:

And this the second one:

I'm going to use AOP and Guice to set a Method Interceptor that check is the called method has a transaction, and in case of not, create a new one and link it to thread with ThreadLocal class. I create also an annotation that identify che class and method subject and not subject to this check:

A) The Transactional Annotation:
01 package net.zonablog.persistence.aop;
02 
03 import java.lang.annotation.ElementType;
04 import java.lang.annotation.Retention;
05 import java.lang.annotation.RetentionPolicy;
06 import java.lang.annotation.Target;
07 
08 @Retention(RetentionPolicy.RUNTIME)
09 @Target({ ElementType.TYPE, ElementType.METHOD })
10 public @interface Transactional {
11 
12 }


The NoTransactional annotation, that mark the non-transactional method on a class marked as transactional:
01 package net.zonablog.persistence.aop;
02 
03 import java.lang.annotation.ElementType;
04 import java.lang.annotation.Retention;
05 import java.lang.annotation.RetentionPolicy;
06 import java.lang.annotation.Target;
07 
08 @Retention(RetentionPolicy.RUNTIME)
09 @Target({ ElementType.METHOD })
10 public @interface NoTransactional {
11 
12 }


The implementation of this interceptor:

01 package net.zonablog.persistence.aop;
02 
03 import net.zonablog.service.persistence.BasePersistentService;
04 import net.zonablog.service.persistence.HibernateSessionService;
05 
06 import org.aopalliance.intercept.MethodInterceptor;
07 import org.aopalliance.intercept.MethodInvocation;
08 import org.hibernate.Session;
09 import org.hibernate.Transaction;
10 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory;
12 
13 import com.google.inject.Inject;
14 
15 public class TransactionInterceptor implements MethodInterceptor {
16 
17   private Logger logger = LoggerFactory.getLogger(this.getClass());
18   private ThreadLocal<Session> threadLocal;
19 
20   @Inject
21   private HibernateSessionService hibernateSessionService;
22 
23   public TransactionInterceptor() {
24     logger.info("Inizializzation of " this.getClass());
25     threadLocal = new ThreadLocal<Session>();
26   }
27 
28   @SuppressWarnings("rawtypes")
29   public Object invoke(MethodInvocation invocationthrows Throwable {
30 
31     logger.trace("Start of transactional method");
32 
33     // Verifico se vengo chiamato da un'altra applicazione che ha già la
34     // transazione attiva
35     Transaction transaction = null;
36     Session session = threadLocal.get();
37     boolean startNewTransaction = true;
38 
39     if (session == null) {
40       logger.trace("Start new transaction");
41       session = hibernateSessionService.openSession();
42       threadLocal.set(session);
43       transaction = session.getTransaction();
44       transaction.begin();
45     else {
46       logger.trace("Already in transaction");
47       startNewTransaction = false;
48     }
49 
50     if (invocation.getThis() instanceof BasePersistentService)
51       ((BasePersistentServiceinvocation.getThis()).setSession(session);
52 
53     try {
54 
55       Object result = invocation.proceed();
56 
57       if (transaction != null) {
58         logger.trace("Commit transaction");
59         transaction.commit();
60         threadLocal.remove();
61       }
62 
63       return result;
64 
65     catch (Exception e) {
66 
67       if (transaction != null) {
68         logger.debug("Rollback transaction due [" + e.getMessage() "]");
69         transaction.rollback();
70         threadLocal.remove();
71       }
72 
73       throw e;
74     finally {
75       logger.trace("Exit transactional method");
76 
77       if (startNewTransaction && session != null)
78         session.close();
79     }
80 
81   }
82 
83   @Override
84   protected void finalize() throws Throwable {
85     threadLocal.remove();
86     super.finalize();
87   }
88 
89 }

I'm using Jersey with Guice, and I use the @Transactional annotation also on webresources. Es:


01 package net.zonablog.rest.controller;
02 
03 import java.util.UUID;
04 
05 import javax.mail.internet.AddressException;
06 import javax.mail.internet.InternetAddress;
07 import javax.ws.rs.FormParam;
08 import javax.ws.rs.GET;
09 import javax.ws.rs.POST;
10 import javax.ws.rs.Path;
11 import javax.ws.rs.PathParam;
12 import javax.ws.rs.Produces;
13 import javax.ws.rs.core.MediaType;
14 import javax.ws.rs.core.Response;
15 import javax.ws.rs.core.Response.ResponseBuilder;
16 import javax.ws.rs.core.Response.Status;
17 
18 import com.google.inject.Inject;
19 import com.sun.jersey.spi.resource.PerRequest;
20 
21 @Transactional
22 @Path("services/register")
23 @PerRequest
24 public class RegisterController extends BaseController {
25 
26     @Inject
27     private MailService mailService;
28 
29     @Inject
30     private UserService userService;
31 
32     @Inject
33     public RegisterController() {
34     }
35 
36     @POST
37     @Produces(MediaType.APPLICATION_XML)
38     public Object register(@FormParam("registerDialogFormEmail"String email,
39             @FormParam("registerDialogFormPass"String password, @FormParam("registerDialogFormRPass"String rpassword) {
40 
41         ...
42     }
43 }


Note that to let Guice istantiate the webresource, in addition to configuration needed by jersey-guice module, the @Inject must present on constructor's class.