Automatic Deadlock retry Aspect with Spring and JPA/Hibernate

I’m currently working on a project that is converted from being a Mainframe application, to a Java web/batch application. We don’t ‘big bang’ into production, so the Mainframe and the Java code will work next to each other for a fairly amount of time. Since we have multiple batch processes and many simultaneous users, we start seeing deadlock errors in certain parts of the application. Some specific parts have to take a pessimistic lock, this is where it goes wrong.
Since a deadlock is an error that can be solved by repeating the action, we decide to build in a retry mechanism to restart the transaction if it got rolled back.
I started of with creating an Annotation. This annotation will mark the entry point that we want to retry in case of a deadlock.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DeadLockRetry {
    /**
     * Retry count. default value 3
     */
    int retryCount() default 3;
}

The retry count is a value you can supply together with your annotation, so you can specify the number of times we want to retry our operation.
Using AOP we can pick up this annotation an let us surround the method call with a retry mechanism.

@Around(value = "@annotation(deadLockRetry)", argNames = "deadLockRetry")

So lets view the aspect, we start with adding an @Aspect annotation on top of our class, this way it is configured to be an Aspect.
We also want to implement the Ordered interface. This interface lets us order our aspect. We need this to surround our Transactional aspect. If we don’t surround our Transaction, we will never be able to retry in a new transaction, we would be working in the same (marked as rollback only) transaction.
The rest of the code is pretty straight forward. We create a loop where we loop until we have more retries than we should have. Inside that loop we proceed our ProceedingJoinPoint and catch the PersistenceException that JPA would throw when a deadlock would occur. Inside the catch block we check if the error code is a deadlock error code.
Off course we could not directly configure the database specific error codes inside our aspect, so I’ve created an interface.

/**
 * Interface that marks a dialect aware of certain error codes. When you have to
 * do a low level check of the exception you are trying to handle, you can
 * implement this in this interface, so you can encapsulate the specific error
 * codes for the specific dialects.
 * 
 * @author Jelle Victoor
 * @version 05-jul-2011
 */
public interface ErrorCodeAware {
    Set<Integer> getDeadlockErrorCodes();
}

We already have custom hibernate dialects for our database and database to be, so this let me configure the error codes in the Dialect implementations. It was a bit tricky to get the current dialect. I injected the persistence unit, since we are outside a transaction, and made some casts to get my dialect. The alternative was to use a custom implementation of the ErrorCodeAware interface, not using the dialects. We could inject the needed ErrorCodeAware implementation based on our application context. This added another database specific injection, which added another point of configuration. This is why I chose to store it in our custom dialect.

private Dialect getDialect() {
        final SessionFactory sessionFactory = ((HibernateEntityManagerFactory) emf).getSessionFactory();
        return ((SessionFactoryImplementor) sessionFactory).getDialect();
    }

The only thing left is to configure the aspect, mind the order of the transaction manager and the retry aspect

	<tx:annotation-driven order="100" transaction-manager="transactionManager" />
	<bean id="deadLockRetryAspect" class="DeadLockRetryAspect">
		<property name="order" value="99" />
	</bean>

Now when I have a deadlock exception, and I’ve added this annotation, the transaction will rollback and will be reexecuted.

/**
 * This Aspect will cause methods to retry if there is a notion of a deadlock.
 * 
 * <emf>Note that the aspect implements the Ordered interface so we can set the
 * precedence of the aspect higher than the transaction advice (we want a fresh
 * transaction each time we retry).</emf>
 * 
 * @author Jelle Victoor
 * @version 04-jul-2011 handles deadlocks
 */
@Aspect
public class DeadLockRetryAspect implements Ordered {
    private static final Logger LOGGER = LoggerFactory.getLogger(DeadLockRetryAspect.class);
    private int order = -1;
    @PersistenceUnit
    private EntityManagerFactory emf;

    /**
     * Deadlock retry. The aspect applies to every service method with the
     * annotation {@link DeadLockRetry}
     * 
     * @param pjp
     *            the joinpoint
     * @param deadLockRetry
     *            the concurrency retry
     * @return
     * 
     * @throws Throwable
     *             the throwable
     */
    @Around(value = "@annotation(deadLockRetry)", argNames = "deadLockRetry")
    public Object concurrencyRetry(final ProceedingJoinPoint pjp, final DeadLockRetry deadLockRetry) throws Throwable {
        final Integer retryCount = deadLockRetry.retryCount();
        Integer deadlockCounter = 0;
        Object result = null;
        while (deadlockCounter < retryCount) {
            try {
                result = pjp.proceed();
                break;
            } catch (final PersistenceException exception) {
                deadlockCounter = handleException(exception, deadlockCounter, retryCount);
            }
        }
        return result;
    }

    /**
     * handles the persistence exception. Performs checks to see if the
     * exception is a deadlock and check the retry count.
     * 
     * @param exception
     *            the persistence exception that could be a deadlock
     * @param deadlockCounter
     *            the counter of occured deadlocks
     * @param retryCount
     *            the max retry count
     * @return the deadlockCounter that is incremented
     */
    private Integer handleException(final PersistenceException exception, Integer deadlockCounter, final Integer retryCount) {
        if (isDeadlock(exception)) {
            deadlockCounter++;
            LOGGER.error("Deadlocked ", exception.getMessage());
            if (deadlockCounter == (retryCount - 1)) {
                throw exception;
            }
        } else {
            throw exception;
        }
        return deadlockCounter;
    }

    /**
     * check if the exception is a deadlock error.
     * 
     * @param exception
     *            the persitence error
     * @return is a deadlock error
     */
    private Boolean isDeadlock(final PersistenceException exception) {
        Boolean isDeadlock = Boolean.FALSE;
        final Dialect dialect = getDialect();
        if (dialect instanceof ErrorCodeAware && exception.getCause() instanceof GenericJDBCException) {
            if (((ErrorCodeAware) dialect).getDeadlockErrorCodes().contains(getSQLErrorCode(exception))) {
                isDeadlock = Boolean.TRUE;
            }
        }
        return isDeadlock;
    }

    /**
     * Returns the currently used dialect
     * 
     * @return the dialect
     */
    private Dialect getDialect() {
        final SessionFactory sessionFactory = ((HibernateEntityManagerFactory) emf).getSessionFactory();
        return ((SessionFactoryImplementor) sessionFactory).getDialect();
    }

    /**
     * extracts the low level sql error code from the
     * {@link PersistenceException}
     * 
     * @param exception
     *            the persistence exception
     * @return the low level sql error code
     */
    private int getSQLErrorCode(final PersistenceException exception) {
        return ((GenericJDBCException) exception.getCause()).getSQLException().getErrorCode();
    }

    /** {@inheritDoc} */
    public int getOrder() {
        return order;
    }

    /**
     * Sets the order.
     * 
     * @param order
     *            the order to set
     */
    public void setOrder(final int order) {
        this.order = order;
    }
}

JPA 2, The Access annotation

JPA 1 has a strict rule about what the access when defining your meta annotations (you database to java mapping). In jpa 1 the standard was the way the ID was mapped. So when you placed your @Id or @EmbeddedId annotation (which is mandatory for every bean annotated with the @Entity annotation) on a field, all access will happen through the fields, when you apply it on the getter/setter methods, all fields will use the getter/setter methods.
JPA 2 adds some extra flexibility here. When you want your fields to be access through the fields, but there is one field that you want to modify when setting or getting, you can provide the @Access annotation.
This annotation defines the path of the persistence provider. It basicly has 2 modes

AccessType.FIELD, AccessType.PROPERTY;

You first have to annotate your Entity to set the base access mode, so the @Id rule is overruled.

@Entity
@Access(AccessType.FIELD)
public class Car implements Serializable {
// some fields, getters and setters
}

We have now configured the Pojo to use the fields unless we define it otherways.
by placing the @Access annotation on top of a getter (not setter, this will give an error that the annotation only should be applied on no-param methods), you tell it should use the getter/setter instead of direct access to the field.

@Access(AccessType.PROPERTY)
    public Brand getBrand() {
        return brand;
    }

    public void setBrand(Brand brand) {
        this.brand = brand;
    }

Although the usefulness of this feature is definitely arguable, I do want to share this. :-)

Why you should never use getSingleResult() in JPA

As long as I’m using JPA I notice that the use of getSingleResult and getResultList is often confused. In the javadoc there is no clear specification of what to use.
getResultList()

Execute a SELECT query and return the query results as an untyped List.

getSingleResult()

Execute a SELECT query that returns a single untyped result.

So this doesn’t get us much further. So lets explore the problem. Both methods are used to retrieve Entities from our database. We use the getResultList() if there is no guarantee how many results we want to retrieve, the getSingleResult is used to retrieve exactly one row.
So the only doubt for what method to use, is when we retrieve 1 row from our database. A perfect example for this use-case is a findById query, where we retrieve an Entity based upon its ID.
There are 3 ways to do this. The most appropriate way is to use the EntityManger.find() method. This method returns a typed instance of the entity when it is found, null when it is not found.
The other 2 ways is by a NamedQuery. Why would we want to use a NamedQuery when we have the find method? Sometimes we need to retrieve a lazy collection, by adding a fetch join. An other example is when we have a composite key, we can still use the EntityManager.find() method, but I often see the use of a named query in this case. (don’t do this, bad programming)
So we created our named query

final Query query = getEntityManager().createNamedQuery("Entity.findById");
query.setParameter("id", id);

The most natural reaction is to call the getSingleResult() method on the query. This returns only one row and the result of a ‘findById’ query should only be one row. But what if the row isn’t in the database (anymore)? We get an unchecked exception: NoResultException. Is this what we expect?
If you look in Effective Java by Joshua Bloch, we read: ‘Use checked exceptions for conditions from wich the caller can reasonably be expected to recover. Use runtime exceptions to indicate programming errors’.
So what does this tell us? When a getSingleResult Query returns no rows, we get an unchecked exception, thus a programmer error, there is no way to recover from this. This is not correct. We never know for sure what we can expect from our database, so throwing an unchecked exception seems the wrong choice for this use-case.
The only use for getSingleResult() is when we are executing a scalar (count, sum, avg) query. This is a query wich will alway return a row, otherwise we are in an exceptional case, so the exception is allowed.
So, how do we solve our example with our named query? Simple

List results = query.getResultList();
Entity foundEntity = null;
if(!results.isEmpty()){
    // ignores multiple results
    foundEntity = results.get(0);
}