Home > Web development > Spring @Transactional: Verifying transaction support / Local method calls

Spring @Transactional: Verifying transaction support / Local method calls

January 23rd, 2011 Leave a comment Go to comments

A few days ago, I was debugging a persistence issue in a recently started project that uses Spring ORM + JPA 2.0/Hibernate for persistence. Using Spring’s declarative transaction support, enabling transaction management for your services is just a matter of annotating the service methods with @Transactional, assuming you have properly set up a transaction manager and tell Spring to process your @Transactional bean annotations:

<tx:annotation-driven proxy-target-class="true"/>

Since I couldn’t explain why my code wasn’t working, I began wondering if the transaction support for my service method was configured correctly. And turns out I was right! Calling the following helper method from Spring’s TransactionSynchronizationManager showed that the code was indeed not running within a transaction:

org.springframework.transaction.support.
  TransactionSynchronizationManager.isActualTransactionActive()

So, what was wrong with my code?

Class-based proxying vs. interface-based proxying

First of all, my Spring transaction configuration using the <tx:annotation-driven> tag was missing the proxy-target-class="true" attribute. By default, this attribute is set to false, such that only interfaces with @Transactional annotations are proxied. This is useful if you divide your service architecture into a service interface and an implementation. In my case, this was not the case (by design), so it is required to set this attribute to true for class-based proxies to be created using cglib.

@Transactional local method calls

The second problem I encountered was the way I had implemented my service methods. Have a look at this example:

@Transactional
private void loadCategories() {
	...
	categories = em.query(...);
}

public void getRootCategories() {
	if(null == categories) loadCategories();
	...
}

Now, what’s so bad about this? The bad thing is the local call to the private method loadCategories(). Because the method is private, cglib is not able to proxy the call, so the code within that method is directly executed, without first establishing a transaction. Also, since we call that method locally, we circumvent the proxy: Imagine the proxy as a wrapper around our service class which deals with the transaction management and then delegates to the implementations we have written. If we directly call our implementation instead of the wrapped method, Spring has no chance of noticing that it needs to set up a transaction.

How can we deal with this? My first idea was pretty straightforward, but currently apparently not supported by Spring (at least using @Inject): It involves injecting an instance of the service into itself. Now, instead of invoking our (private) methods locally, we could invoke them on the self-injected service instance, which would be a proxy. As I said, Spring does not seem to support this, because it complains that it cannot auto-wire the field (correct me if I’m wrong).

A better alternative for the moment if you want to have transactional local or private method calls is to switch from cglib-based proxying to AspectJ compile-time or load-time weaving. For the German readers of this blog, Ralph Schär gives a detailled explanation of the backgrounds. The basic idea is that instead of wrapping our service with a proxy, the implementation of our service methods is automatically re-written using reflection and byte-code manipulation, such that the transaction management code can directly be “injected” into our original code. To get started, add the mode="aspectj" attribute to your <tx:annotation-driven/> tag:

<tx:annotation-driven mode="aspectj" proxy-target-class="true"/>

Then, add the following two Maven dependencies:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>3.0.5.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjrt</artifactId>
	<version>1.6.10</version>
</dependency>

Compile-time weaving requires use of a special AspectJ compiler. If you are using Maven, you can include the AspectJ maven plugin to get this working:

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>aspectj-maven-plugin</artifactId>
	<version>1.3</version>
	<executions>
		<execution>
			<goals>
				<goal>compile</goal>
				<goal>test-compile</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<source>1.6</source>
		<target>1.6</target>
		<aspectLibraries>
			<aspectLibrary>
				<groupId>org.springframework</groupId>
				<artifactId>spring-aspects</artifactId>
			</aspectLibrary>
		</aspectLibraries>
	</configuration>
</plugin>

Note that in my case, I had to update my Maven project configuration and then restart Eclipse for AspectJ to begin working properly. After restart, a message popped up, asking me if I wanted to enable the JDT weaving service. Using the AJDT plugin’s Eclipse integration, you will now see aspect markers next to your source code:


For load-time weaving, you instead need to configure a class loader or “agent” that allows load-time weaving. You can either supply an agent within the JVM command line or configure your web container (e.g. Tomcat) to use such a container. See the Spring documentation for more information on this topic (this requires copying a JAR into Tomcat’s lib folder).

While enabling AspectJ weaving does involve a certain configuration effort, it not only solves the problem of (non-)transactional local method calls, but weaving also works if your transactional service methods are private.

Summary

What do we learn from this?

  • If your services do not implement a service interface, set proxy-target-class="true" in the tx:annotation-driven XML tag. Otherwise, the transactional service methods will not be proxied, and thus, no transactions will be generated.
  • Verify that you have correctly set up transaction management. Use TransactionSynchronizationManager.isActualTransactionActive() from within a service method to check this.
  • Use logging. In his article at dzone.com, Tom Cellucci presents a logging filter for Logback, Log4J and Java Logging that prefixes each logging statement with a [+] or [-] sign if a transaction was active at the time the logging statement was written. Also, setting the logging level to DEBUG for the following namespaces can help in diagnosing issues:
    org.springframework.aop
    org.springframework.transaction
    org.springframework.orm
  • Remember that local method calls (i.e. MyClass#methodA() calling MyClass#methodB()) will not initiate a new transaction, even if the local method you call is annotated with @Transactional. This is unless you use AspectJ compile-time or load-time weaving, instead of the default Java interface-based proxies or cglib proxies. Alternatively, you can inject your bean with its own instance, because that (in comparison to the this object) will yield a proxy.
  • Also, @Transactional methods need to be public, unless you use AspectJ weaving.
  1. hanishi
    July 28th, 2011 at 16:40 | #1
    Reply | Quote
    VA:F [1.9.20_1166]
    0

    Hi, Thank you for this article. It was very helpful completing my solution in the code below.

    public interface Reader extends Iterator{

    void setFetchSize(int fetchSize);

    void setOffset(int offset);
    }

    public abstract class EntityList implements Reader {

    private List currentBatch;
    private E currentResult;
    private boolean hasNext;
    protected int fetchSize;
    protected int offset;

    public EntityList() {}

    public void setFetchSize(int fetchSize) {
    this.fetchSize = fetchSize;
    }

    public void setOffset(int offset) {
    this.offset = offset;
    }

    public synchronized boolean hasNext() {
    if(currentBatch!=null && currentBatch.size()>0) {
    return true;
    }

    currentBatch=getCurrentBatch();
    if (hasNext && currentBatch.size()==0) {
    return hasNext();
    }
    return currentBatch != null && currentBatch.size()>0;
    }

    @Transactional
    private List getCurrentBatch() {
    List entities = findAll();
    hasNext=entities.size() > 0;
    offset+=fetchSize;
    for (Iterator iterator=entities.iterator();iterator.hasNext(); ) {
    final E entity=iterator.next();
    if (!isReferenced(entity)) {
    remove(entity);
    iterator.remove();
    offset–;
    }
    }
    return entities;
    }

    public synchronized T next() {

    if (currentBatch != null && currentBatch.size()>0 || hasNext()) {
    currentResult=currentBatch.remove(0);
    if (currentBatch.size()==0) {
    currentBatch=null;
    }
    return eval(currentResult);
    }
    throw new NoSuchElementException();
    }

    @Transactional
    public synchronized void remove() {
    if (currentResult==null) {
    throw new IllegalStateException();
    }
    remove(currentResult);
    offset–;
    currentResult=null;
    }

    protected abstract List findAll();

    protected abstract void remove(E entity);

    protected abstract boolean isReferenced(E e);

    protected abstract T eval(E entity);

    public static class Servers extends EntityList {
    private ServerDAO serverDAO;
    private PortDAO portDAO;

    @Override
    protected List findAll() {
    return getServerDAO().findAll(fetchSize, offset);
    }

    @Transactional
    public void remove(Server server) {
    getServerDAO().makeTransient(server);
    }

    public void setServerDAO(ServerDAO serverDAO) {
    this.serverDAO = serverDAO;
    }

    public ServerDAO getServerDAO() {
    return serverDAO;
    }

    public void setPortDAO(PortDAO portDAO) {
    this.portDAO = portDAO;
    }

    public PortDAO getPortDAO() {
    return portDAO;
    }

    @Override
    protected String eval(Server server) {
    return server.getName();
    }

    @Override
    protected boolean isReferenced(Server server) {
    for (MeasuringPoint measuringPoint:server.getMeasuringPoints()) {
    if (measuringPoint.getMeasurementResults().size() > 0) {
    return true;
    }
    Port port=measuringPoint.getPort();
    port.getMeasuringPoints().remove(measuringPoint);
    if(port.getMeasuringPoints().size()==0) {
    getPortDAO().makeTransient(port);
    }
    }
    return false;
    }
    }
    }

  2. hanishi
    July 29th, 2011 at 04:44 | #2
    Reply | Quote
    VA:F [1.9.20_1166]
    0

    @Transaciton is only valid to the thread that obtained the bean from the Spring context. It took me some time to figure out because I was implementing this with SWT application and I was passing the bean obtained in the GUI thread to the background thread.

  1. No trackbacks yet.
You must be logged in to post a comment.