How to: Testing Spring’s @Transactional

By on 8 Feb 2018

Category: Tech matters

Tags: , ,

Blog home

APNIC’s software team often come up with useful solutions to programming problems that they like to share with developers. In this post, APNIC’s Jim Vella suggests that easier programming methods may be a better option than Spring’s @Transactional.

Spring’s @Transactional annotation can be simply applied to methods to make them transactional!

class MyThingService implements ThingService {
   
   @Transactional
   public void doThing(String s1, String s2) {
       ...
  }
}

But when we consider the following, things don’t seem as simple.

ThingService thingService = new MyThingService();
thingService.doThing(a, b);

In this instance thingService is not actually transactional because Spring (in its default configuration) hasn’t been afforded an opportunity to wrap thingService in a Dynamic Proxy.

Yet I’ve made this mistake, and my colleagues have made this mistake. So as diligent developers, given a production project with this bug, we want to cover this regression before implementing a fix.

How can we cover regression before implementing a fix?

We could start a Spring Context with a mock PlatformTransactionManager, excite an endpoint and look for a ‘commit’ interaction, but what about testing without Spring, especially since some folk really encourage unit tests over integration tests, as well as isolating framework dependencies to the outermost layer of an application’s architecture.

Taking a leaf from Test Driven Development, difficulty in writing a test can be a signal that the design of the application needs rethinking. An alternative way to approach transactions in Spring is programmatic transaction management.

static ThingService transactionalThingService(final ThingService thingService, final 
PlatformTransactionManager transactionManager) {
   final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
   
 return new ThingService() {
  public void doThing(String s1, String s2) {
   TransactionCallback<Void> transactionCallback = transactionStatus -> {
    thingService.doThing(s1, s2);
    return null;
   };

   transactionTemplate.execute(transactionCallback);
   }
  };
}

The factory method transactionalThingService now encompasses the concern of decorating a ThingService instance with transactional behaviour in a testable way.

@Test
public void isTransactional() {
  ThingService doNothingThingService = new ThingService() {
   @Override
   public void doThing(String s1, String s2) {
     //nothing
   }
};   


 Queue<String> transactionEvents = new LinkedList<>();
 PlatformTransactionManager monitoringTransactionManager = new PlatformTransactionManager() {
   @Override
   public TransactionStatus getTransaction(TransactionDefinition transactionDefinition) throws 
TransactionException {
      return null;
    }

    @Override
    public void commit(TransactionStatus transactionStatus) throws TransactionException {
      transactionEvents.add("committed");
    }


    @Override
    public void rollback(TransactionStatus transactionStatus) throws TransactionException {
      transactionEvents.add("rolled back");
    }
};

 ThingService transactionalThingService = transactionalThingService(doNothingThingService, 
monitoringTransactionManager);

 transactionalThingService.doThing("a", "b");

 assertThat(transactionEvents, hasItem("committed"));
}

This works well enough for ThingService, but how about in general? The purpose of using a dynamic proxy is that it can abstract over method signatures (see InvocationHandler for more details).

Object invoke(Object proxy, Method method, Object[] args)

But if we can control the design of our interfaces, this can be achieved more simply with the command object pattern:

interface ThingService extends Function<ThingService.Command, Void> {
  interface ThingService extends Function<ThingService.Command, Void> {

  class Command {
   private final String s1;
   private final String s2;

   public Command(String s1, String s2) {
    this.s1 = s1;
    this.s2 = s2;
   }

   public String getS1() {
    return s1;
   }

   public String getS2() {
    return s2;
   }
  }
 }
}

static <T, R> Function<T, R> transactionalFunction(final Function<T, R> function, final 
PlatformTransactionManager transactionManager) {
  final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

return (T t) -> {
  TransactionCallback<R> transactionCallback = transactionStatus -> function.apply(t);
  return transactionTemplate.execute(transactionCallback);
 };
}

Consider avoiding @Transactional

@Transactional appears simple on the surface, but is hiding complex mechanisms that can come unstuck.

The same outcome can be achieved with ordinary programming, which is also arguably more testable.

 

Rate this article

The views expressed by the authors of this blog are their own and do not necessarily reflect the views of APNIC. Please note a Code of Conduct applies to this blog.

Leave a Reply

Your email address will not be published. Required fields are marked *

Please answer the math question * Time limit is exhausted. Please reload CAPTCHA.

Top