Spring: Annotation-based Transactions and DAOs

I’ve used Spring’s Transaction Manager and Transaction Proxy Factory classes before, but I hadn’t noticed before today that there’s an option for Annotation-based Transaction demarcation.

Here’s how it works.

First create a DAO interface:

import static org.springframework.transaction.annotation.Isolation.SERIALIZABLE;
import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;

import org.springframework.transaction.annotation.Transactional;

public interface AccountDao {
  public Account load(Integer accountNumber);
  @Transactional(isolation=Isolation.SERIALIZABLE,
     propagation=Propagation.REQUIRES_NEW)
  public void save(Account account);
}

Use the special “Transactional” annotation.

Then implement this DAO as normal.

In your Spring configuration file, include the following:

<bean id="ca.intelliware.example.AccoutDao"
  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="proxyInterfaces">
    <value>ca.intelliware.example.AccountDao</value>
  </property>
  <property name="target">
  <bean class="ca.intelliware.example.AccountDaoImpl">
    <property name="sessionFactory" ref="org.hibernate.SessionFactory" />
    </bean>
  </property>
  <property name="transactionManager" ref="transactionManager" />
  <property name="transactionAttributeSource">
    <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" />
  </property>
</bean>

And, voila! You can now define your transactionality based on the annotations on your interface.

Unit Testing Transactionality

I wanted to prove to myself that this really worked, so I wrote a quick unit test to prove it. I often use an in-memory database (HSQLDB) for unit tests. So the first trick was to tweak the JDBC Driver to make it possible to interrogate the transaction settings.

First, I overrode the getConnection methods, so that I could wrap the connections in a Proxy interface.

public class HSQLDBDataSourceWrapper extends jdbcDataSource {

  @Override
  public Connection getConnection() throws SQLException {
    return (Connection) Proxy.newProxyInstance(
      null, new Class[] { Connection.class },
      new ConnectionHandlerImpl( super.getConnection()));
  }

  @Override
  public Connection getConnection(String user, String password)
      throws SQLException {
    return (Connection) Proxy.newProxyInstance(
      null, new Class[] { Connection.class },
      new ConnectionHandlerImpl( super.getConnection(
         user, password)));
  }

Next, I created an InvocationHandler to track specific events. This handler was an inner class of my wrapper:

public class ConnectionHandlerImpl implements InvocationHandler {
  private final Connection connection;
  public ConnectionHandlerImpl(Connection connection) {
    this.connection = connection;
    transactionStarted.set(false);
    transactionCommitted.set(false);
  }
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    if ("setAutoCommit".equals(method.getName())) {
      if (Boolean.FALSE.equals(args[0])) {
        transactionStarted.set(true);
      }
    } else if ("commit".equals(method.getName())) {
      transactionCommitted.set(true);
    }

    return method.invoke(this.connection, args);
  }
}

And I added some static final members to my wrapper to keep track of the most recent state of the connection.

Now I wrote my JUnit test:

public void testTransactionality() throws Exception {
  Account account = new Account();
  accountsetName("Kermit the Frog's account");
  lookupDaoFromSpring().save(account);

  assertTrue("write transaction started",
      HSQLDBDataSourceWrapper.wasTransactionStarted());
  assertTrue("write transaction committed",
      HSQLDBDataSourceWrapper.wasTransactionCommitted());

}

This seemed a lot easier (to me, anyway) than messing with jMock or anything like that.

I even evolved my wrapper so that I could set some switches that allowed me to throw an SQLException when Hibernate issued an executeUpdate on a prepared statement, then assert that the transaction was rolled back.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply