Статьи

Аспект автоматической повторной блокировки тупика с помощью Spring и JPA / Hibernate

В настоящее время я работаю над проектом, который преобразуется из приложения для мэйнфрейма в веб / пакетное приложение Java. Мы не «большой взрыв» в производстве, поэтому мэйнфреймы и код Java будут работать рядом друг с другом в течение довольно продолжительного времени. Поскольку у нас есть несколько пакетных процессов и много одновременных пользователей, мы начинаем видеть ошибки взаимоблокировки в определенных частях приложения. Некоторые специфические детали должны занимать пессимистичную блокировку , и здесь все идет не так.
Поскольку взаимоблокировка — это ошибка, которую можно устранить, повторив действие, мы решили встроить механизм повторных попыток, чтобы перезапустить транзакцию, если она откатилась.
Я начал с создания аннотации. Эта аннотация помечает точку входа, которую мы хотим повторить в случае тупика.

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

Число повторов — это значение, которое вы можете указать вместе с аннотацией, чтобы вы могли указать, сколько раз мы хотим повторить нашу операцию.
Используя AOP, мы можем подобрать эту аннотацию, и давайте окружим вызов метода механизмом повтора.

@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;
    }
}

From http://styledideas.be/blog/2011/07/05/automatic-deadlock-retry-aspect-with-spring-and-jpahibernate/