Database Transactions With Google Guice AOP

24 Oct 2011 | By Mihai Roman | Tags google guice aop dependency injection

Today I’m going to show you a simple way of implementing transactional class methods with Google Guice Aspect Oriented Programming.

The endgame would be to annotate methods as transactional and automatically handle the JDBC transactions boilerplate code:

 @Transaction
  public void executeStatements(Connection conn, String[] statements) throws SQLException {
    /* run db queries here */
  }

Transaction Annotation

We need to declare a Transaction annotation class. Only methods can be annotated with Transaction and annotations can be intercepted at runtime.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
public @interface Transaction {
}

Method Intercepter

We’re going to intercept methods annotated with Transaction. Executing SQL queries using JDBC requires either a DataSource or a Connection. In this example, I’m assuming that all transactional methods receive a Connection object as the first parameter.

import java.lang.reflect.Method;
import java.sql.Connection;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TransactionIntercepter implements MethodInterceptor {
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    Method method = invocation.getMethod();
    Transaction annotation = method.getAnnotation(Transaction.class);
    // Make sure the intercepter was called for a transaction method
    if (annotation == null) {
      return invocation.proceed();
    }
    if (invocation.getArguments().length == 0 ||
        !(invocation.getArguments()[0] instanceof Connection)) {
      throw new Exception("First parameter must be a Connection instance: " + method.getName());
      
    }
    Connection conn = (Connection)invocation.getArguments()[0];
   try {
     conn.setAutoCommit(false);
     // Proceed with the original method's invocation.
     Object returnObj = invocation.proceed();
     // Commit if successful.
     conn.commit();
     conn.setAutoCommit(true);
     return returnObj;
   } catch (Throwable ex) {
     // Rollback on error.
     conn.rollback();
     conn.setAutoCommit(true);
     throw ex;
   }
  }
}

Binding the Method Intercepter With Guice

We need to bind the MethodIntercepter in the Guice module. Here’s the code for a dedicated module that can be installed in other modules:

import com.google.inject.AbstractModule;
import com.google.inject.matcher.Matchers;
public class TransactionModule extends AbstractModule {
  @Override
  protected void configure() {
    TransactionIntercepter intercepter = new TransactionIntercepter();
    requestInjection(intercepter);
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transaction.class), intercepter);
  }
}

Transactional Method Example

Now you can annotated methods that receive the Connection object as parameter:

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Manager {
  @Transaction
  public void executeStatements(Connection conn, String[] statements) throws SQLException {
    if (statements == null || conn == null) {
      throw new IllegalArgumentException();
    }
    Statement stmt = conn.createStatement();
    for (int i = 0; i < statements.length; i++) {
      stmt.execute(statements[i]);
    }
  }
}

Some Demo Code

Let’s wrap the above code in a simple example using PostgreSQL:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import com.google.inject.Guice;

public class Main {

  public static void main(String[] args) throws SQLException {
    System.out.println("Checking if Driver is registered with DriverManager.");
    
    try {
      Class.forName("org.postgresql.Driver");
    } catch (ClassNotFoundException cnfe) {
      System.out.println("Couldn't find the driver!");
      cnfe.printStackTrace();
      System.exit(1);
    }
    
    TransactionModule module = new TransactionModule();
    Connection conn = null;
    try {
      conn = DriverManager.getConnection("jdbc:postgresql://localhost/demo",
                                      "demo", "demo");
      // Get an instance from Guice, to intercept the methods annotated with @Transaction
      Manager manager = Guice.createInjector(module).getInstance(Manager.class);
      String[] stmts = {"CREATE TABLE a(a int);", 
          "CREATE TABLE b(b int);", 
          "INSERT INTO a(a) values(1);", 
          "INSERT INTO b(b) values(1)"
          };
      manager.executeStatements(conn, stmts);

      String[] stmts_bad = {"CREATE TABLE c(c int);", 
          "CREATE TABLE d(b int;", // a mistake in the SQL statement
          "INSERT INTO c(a) values(1);", 
          "INSERT INTO d(b) values(1)"
          };
      manager.executeStatements(conn, stmts_bad);
     } finally {
       if (conn != null && !conn.isClosed()) {
         conn.close();
       }
     }
  }
}

Conclusion

Guice is a lightweight library that makes it easy to deal with dependency injection but also implement aspect oriented programming paradigms. I’ve used and implemented database transactions programatically with Spring and I have to say that the pattern above is much more flexible and lightweight.


blog comments powered by Disqus