Database Transactions With Google Guice AOP
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.
