Запретить откат транзакции в JBoss + Hibernate - PullRequest
7 голосов
/ 04 января 2012

У нас есть приложение 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 ]

5 голосов
/ 06 января 2012

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

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

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

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


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

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

С другой стороны, вы можете реорганизовать ваши методы так,Контейнер будет использовать другую транзакцию для вашего запроса.Примерно так:

@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 (который все еще активен).

Надеюсь, это поможет!

2 голосов
/ 04 января 2012

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

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

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

...