Предотвращение отката транзакций в JBoss + Hibernate

у нас есть приложение Java, работающее на JBoss 5.1, и в некоторых случаях нам нужно предотвратить закрытие транзакции в случае JDBCException выбрасывается каким-то базовым методом.

у нас есть метод EJB, который выглядит следующим образом

@PersistenceContext(unitName = "bar")
public EntityManager em;

public Object foo() {
  try {
    insert(stuff);
    return stuff;
  } (catch PersistenceException p) {
    Object t = load(id);
    if (t != null) {
      find(t);
      return t;
    }
  }
}

если insert не удается из-за PersistenceException (который обертывает JDBCException вызвано нарушением ограничения), мы хотим продолжить выполнение с load в рамках той же транзакции.

мы не в состоянии сделать это прямо сейчас, потому что транзакция закрыта контейнером. Вот что мы видим в журналах:

org.hibernate.exception.GenericJDBCException: Cannot open connection
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection
    at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)

   ...

Caused by: javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000101:85fe:4f04679d:182 status: ActionStatus.ABORT_ONLY >

класс EJB отмечен следующими примечаниями

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

есть ли какой-либо правильный способ предотвратить откат транзакции только в этом конкретном случае?

2 ответов


вы действительно не должны пытаться сделать это. Как упоминалось в другом ответе и цитировании документов Hibernate, никакое исключение, вызванное Hibernate, не должно рассматриваться как восстанавливаемое. Это может привести вас к некоторым трудным для поиска/отладки проблемам, особенно с автоматической грязной проверкой hibernate.

чистый способ решить эту проблему-проверить эти ограничения перед вставкой объекта. Используйте запрос для проверки, нарушается ли ограничение базы данных.

public Object foo() {
    if (!objectExists()) {
        insertStuff();
        return stuff();
    }
    // Code for loading object...
}

I знаю, это кажется немного болезненным, но это единственный способ узнать, какое ограничение было нарушено (вы не можете получить эту информацию из исключений Hibernate). Я считаю, что это самое чистое решение (самое безопасное, по крайней мере).


Если вы все еще хотите восстановить из исключения, вам придется внести некоторые изменения в свой код.

Как уже упоминалось, вы можете управлять транзакциями вручную, но я не рекомендую этого. API JTA действительно громоздкий. Кроме того, если вы используете Bean Managed Transaction (BMT), вам придется вручную создавать транзакции для каждого метода в вашем EJB, это все или ничего.

С другой стороны, вы могли бы преобразовать свои методы, поэтому контейнер будет использовать различные транзакции для запроса. Что-то вроде этого:--3-->

@Stateless
public class Foo {
    ...
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public Object foo() {
        try {
            entityManager.insert(stuff);
            return stuff;
        } catch (PersistenceException e) {
            if (e.getCause() instanceof ConstraintViolationException) {
                // At this point the transaction has been rolled-backed.
                // Return null or use some other way to indicate a constrain
                // violation
                return null;
            }
            throw e;
        }
    }

    // Method extracted from foo() for loading the object.
    public Object load() {
        ...
    }
}

// On another EJB
@EJB
private Foo fooBean;

public Object doSomething() {
    Object foo = fooBean.insert();
    if (foo == null) {
        return fooBean.load();
    }

    return foo;
}

при вызове foo () текущая транзакция (T1) будет приостановлена, и контейнер создаст новую (T2). При возникновении ошибки T2 будет откатываться, и T1 будет восстановлен. Когда load () вызывается, он будет использовать T1 (который все еще активен).

надеюсь, что это помогает!


Я не думаю, что это возможно.

In может зависеть от вашего поставщика JPA, но, например, Hibernate явно заявляет, что любое исключение оставляет сеанс в несогласованном состоянии и, следовательно, не должно рассматриваться как восстанавливаемое (13.2.3. Обработка исключений).

Я думаю, лучшее, что вы можете сделать, это отключить автоматическое управление транзакциями для этого метода и создать новую транзакцию после исключения вручную (с помощью UserTransaction, насколько я помнить.)