Contact

Technology

Aug 27, 2014

Common Oversights Utilizing Nested Transactions With Spring

Bryan Chapman

Bryan Chapman

Default image background

What Is a Transaction?

Spring transactions are a powerful concept when developing Java applications with complex, connected data access elements. A transaction allows for an “all or nothing” approach to database persistence. So if any attempt to modify the persisted data fails within a given range of operations, all previously successful modifications will be rolled back to the beginning state. As an example, let’s look at transferring money between accounts:

@Transactional @Override public void transferAmount(double transferAmount, Account source, Account destination) { //can throw exception source.withdrawl(transferAmount);

//can throw exception source.deposit(transferAmount); }

In the example above, we first notice the annotation @Transactional. This tells Spring you want it to create a new transaction and handle any commits or rollbacks. We also notice the @Override annotation, which in this example is implementing a method from its interface. We will see why this is important shortly. Looking at this example, we can see why transactions are important. If either the attempt to withdrawal from one account or deposit into the other fails, we have found ourselves in an invalid state, so returning all account balances to their starting point is necessary.

What Is a Nested Transaction?

What about the case where we have an intermediate step which requires data persistence regardless of whether or not all other intermediate steps succeed? This is exactly the scenario where nested transactions can help. Let’s take a look at another example—this time introducing a nested transaction:

@Transactional @Override public boolean authenticationUser(String username, String password) {

//authenticate user, returns true if success boolean success = authenticationProvider.authenticateUser(username, password)

auditLogger.auditAuthentication(username, success)

if(!success) { throw new AuthenticationException(); }

authenticationProvider.updateLastLoginDate(username); return success; } …

@Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void auditAuthentication (String username, boolean success) {

//write record to db … }

In this example, we notice a similar setup with our entry method being annotated with both a Transactional and an Override annotation, as well as some basic data-access methods. Here we attempt to authenticate a user, create an audit entry in the database containing the result, and throw an exception if authentication fails. The key difference is that the auditAuthentication method’s Transactional annotation also includes a propagation type of REQUIRES_NEW. This tells Spring a new transaction should be created regardless of any currently existing transactions. This way even if the authentication attempt fails and an exception is thrown, the audit log is still persisted and not rolled back.

Common Oversights

While the previous example of supporting nested transactions appears to be quite trivial, there are several common mistakes, which developers often make that prevent those nested transactions from performing as we would expect. When these mistakes are made, it is not obvious to the developer because no errors or warnings will occur. This makes debugging the absence of our expected nested transaction that much more aggravating. Once we recognize these common mistakes, nested transactions are truly as trivial as they appear.

Oversight 1: Access Modifiers

So let’s start with the simple interface and implementation of the following classes:

public interface NestedTransactions { public void testTransactional1(); }

public class NestedTransactionImpl implements NestedTransactions { @Transactional @Override public void testTransactional1() { testTransactional2(); }

@Transactional(propagation = Propagation.REQUIRES_NEW) private void testTransactional2 () {

} }

If we were to execute the method testTransactional1(), we would observe that only a single transaction is created, despite the fact that we have given the method testTransactional2 the Transactional annotation and specified a propagation type of REQUIRES_NEW. This is due to the restriction of using the @Transactional on non-public methods as outlined in the Spring 4.0 documentation.

Conclusion: If you attempt to place an @Transactional annotation on any non-public method, you will not see it exhibit the transactional behavior expected, nor will any errors be reported.

*For additional clarification, see: Spring 4.0 Docs – Using @Transactional.

Solution:Based on the clarification in the Spring documentation, it seems that simply changing the method visibility to public should fix the problem:

public interface NestedTransactions { public void testTransactional1(); public void testTransactional2(); } … @Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void testTransactional2 () { }

Unfortunately, this leads us to our next common mistake: self-invocation.

Oversight 2: Self-Invocation

Self-invocation, as defined in the Spring documentation, is a method within the target object calling another method of the target object. In our current example, we have the method testTransactional1() calling the method testTransactional2() inside of the same target object; thus, we are experiencing self-invocation.

If we were to execute the method testTransactional1(), we would still observe that only a single transaction was created, despite the fact that we changed the access modifier to public on our testTransactional2() method. This is because only external method calls coming through the proxy are intercepted when using the default transaction setting mode of “proxy” as outlined in the Spring 4.0 documentation.

Conclusion: Self-Invocation will not lead to the creation of a nested transaction when using the default transaction settings mode of “proxy”.

*For additional clarification, see: Spring 4.0 Docs – Using @Transactional.

Solution One: Solution One is very straightforward and has the least amount of impact to the project as a whole, as it requires no global configuration changes and affects only the current classes we are reviewing. We can simply solve the problem of self-invocation by moving the testTransactional2() method to a new class, thereby removing the self-invocation.

public interface NestedTransactions1 { public void testTransactional1(); }

public interface NestedTransactions2 { public void testTransactional2(); }

public class NestedTransaction1Impl implements NestedTransactions1 { … @Transactional @Override public void testTransactional1() { nestedTransaction2.testTransactional2(); } }

public class NestedTransaction2Impl implements NestedTransactions2 {

@Transactional @Override public void testTransactional2() { } }

Solution Two: The second solution involves modifying your XML configuration, namely the annotation-driven declaration, to use the aspectj mode.

By using the alternative aspectj mode, the affected classes will be weaved with Spring’s aspectj transaction aspect and their byte code modified to apply to any kind of method call. In this case, there are no longer any proxy classes, so our problems with self-invocation are alleviated.

When using the aspectj mode there are a few additional notes worth mentioning:

–  While you can place annotations at the interface level, this only works if you are using interface-based proxies. This means if you enable aspectj mode your annotations must be on the concrete method level or the method calls will not be wrapped in a transactional proxy. (Note: this is also true of proxy mode if you specify proxy-target-class=”true” in your configuration.)

–  Aspectj weaving requires spring-aspects.jar in the classpath, as well as load-time weaving. For more information, please see the Spring documentation: Spring 4.0 Docs – Spring Configuration.

Additional Solutions: While other solutions do exist for this particular problem—namely retrieving the proxy of its own class directly from the application context or starting a new transactional programmatically on your own—the two approaches explained above are more straightforward, and I would not recommend the latter two in most, if not all, cases.

Conclusion

Once we learn to identify and avoid the common oversights regarding nested transactions, they become a trivial task to accomplish and a powerful tool for developers. Hopefully, these tips will save you mindless hours of debugging on what seems to be a simple task. For additional Open Technologies Insights, follow us on Twitter at @CrederaOpen and connect with us on LinkedIn.

Conversation Icon

Contact Us

Ready to achieve your vision? We're here to help.

We'd love to start a conversation. Fill out the form and we'll connect you with the right person.

Searching for a new career?

View job openings