Подтвердить что ты не робот

Как улавливать и обматывать исключения, выданные JTA, когда транзакция EJB, управляемая контейнером?

Я боюсь проблемы с классом EJB3, который управляет нетривиальной моделью данных. У меня есть исключения проверки проверки ограничений, которые вызывают, когда мои транзакционные методы, управляемые контейнером, совершают. Я хочу, чтобы они не были обернуты в EJBException, вместо этого бросая разумное исключение приложения, которое могут обрабатывать вызывающие.

Чтобы обернуть его в подходящее исключение приложения, я должен уметь его поймать. В большинстве случаев простой try/catch выполняет задание, потому что исключение проверки исключается из вызова EntityManager, который я сделал.

К сожалению, некоторые ограничения проверяются только в момент фиксации. Например, нарушение @Size(min=1) в сопоставленной коллекции происходит только тогда, когда транзакция, управляемая контейнером, совершает транзакцию, когда она покидает мой контроль в конце моего транзакционного метода. Я не могу уловить исключение, возникшее при завершении проверки и его завершении, поэтому контейнер обертывает его в javax.transaction.RollbackException и обертывает в проклятом EJBException. Вызывающий должен поймать все EJBException и пойти в дайвинг в цепочке причин, чтобы попытаться выяснить, есть ли проблема проверки, что действительно не приятно.

Я работаю с транзакциями, управляемыми контейнерами, поэтому мой EJB выглядит так:

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER
class TheEJB {

    @Inject private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterest() throws AppValidationException {
       try {
           // For demonstration sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
       } catch (ValidationException ex) {
           // Won't catch violations of @Size on collections or other
           // commit-time only validation exceptions
           throw new AppValidationException(ex);
       }
    }

}

... где AppValidationException - проверенное исключение или неконтролируемое исключение, аннотированное @ApplicationException, поэтому оно не обернуто EJB3.

Иногда я могу вызвать раннее нарушение ограничений с помощью EntityManager.flush() и поймать это, но не всегда. Даже тогда я действительно хотел бы уловить нарушения ограничений на уровне базы данных, вызванные проверками с задержкой на ограничение времени фиксации, и они будут возникать только тогда, когда JTA совершит.

Справка


Уже пробовали:

Bean управляемые транзакции решают мою проблему, позволяя мне инициировать фиксацию в коде, которым я управляю. К сожалению, они не являются опцией, поскольку управляемые транзакции bean не предлагают никакого эквивалента TransactionAttributeType.REQUIRES_NEW - нет способа приостановить транзакцию с использованием BMT. Один из раздражающих оплошностей JTA.

Смотрите:

... но см. ответы на предостережения и подробности.

javax.validation.ValidationException - исключение JDK; Я не могу изменить его, чтобы добавить an @ApplicationException аннотацию, чтобы предотвратить упаковку. Я не могу подклассифицировать его, чтобы добавить аннотацию; он был выброшен EclpiseLink, а не мой код. Я не уверен, что его отметка @ApplicationException остановит Arjuna (AS7 JTA impl), завернув ее в RollbackException.

Я попытался использовать EJB3-перехватчик следующим образом:

@AroundInvoke
protected Object exceptionFilter(InvocationContext ctx) throws Exception {
    try {
        return ctx.proceed();
    } catch (ValidationException ex) {
        throw new SomeAppException(ex);
    }
}

... но кажется, что перехватчики срабатывают внутри JTA (что разумно и обычно желательно), поэтому исключение, которое я хочу поймать, еще не было выброшено.

Я предполагаю, что я хочу, чтобы иметь возможность определять фильтр исключений, который применял после, JTA делает свою вещь. Любые идеи?


Я работаю с JBoss AS 7.1.1.Final и EclipseLink 2.4.0. EclipseLink устанавливается как модуль JBoss в соответствии с этими инструкциями, но это не имеет большого значения для данной проблемы.


ОБНОВЛЕНИЕ:. После того, как мы подумали над этой проблемой, я понял, что в дополнение к исключениям проверки JSR330 мне действительно нужно уметь ловить SQLIntegrityConstraintViolationException из DB и откаты блокировки или сериализации с помощью SQLSTATE 40P01 и 40001 соответственно. Поэтому подход, который просто пытается убедиться, что фиксация никогда не будет брошена, не будет работать хорошо. Проверенные исключения приложений не могут быть переданы с помощью JTA-фиксации, потому что интерфейсы JTA, естественно, не объявляют их, но неконтролируемые исключения @ApplicationException должны быть в состоянии.

Кажется, что в любом месте, где я могу с пользой поймать исключение приложения, я могу - хотя и менее красиво - поймать исключение EJBException и углубиться в него для исключения JTA и базовой проверки или исключения JDBC, а затем принять решение на основе этого. Без функции фильтра исключений в JTA мне, вероятно, придется.

4b9b3361

Ответ 1

Я не пробовал это. Но я предполагаю, что это должно сработать.

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
class TheEJB {

    @Inject
    private TheEJB self;

    @Inject private EntityManager em;

    public methodOfInterest() throws AppValidationException {
       try {
           self.methodOfInterestImpl();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       }
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public methodOfInterestImpl() throws AppValidationException {
        someEntity.getCollectionWithMinSize1().removeAll();
        em.merge(someEntity);
    }    
}

Ожидается, что контейнер начнет новую транзакцию и зафиксирует внутри methodOfInterest, поэтому вы должны уловить исключение в методе обертки.

Ps: Ответ обновляется на основе элегантной идеи, предоставленной @LairdNelson...

Ответ 2

Там предостережение о том, что я сказал о REQUIRES_NEW и BMT в исходном вопросе.

См. спецификацию EJB 3.1, раздел 13.6.1 Демаркация транзакций с управляемым управлением, в обязанности Контейнера. Он гласит:

Контейнер должен управлять клиентскими вызовами для предприятия beanэкземпляр с демаркацией bean -managed transaction следующим образом. Когда клиент вызывает бизнес-метод через один из предприятий bean s клиента, контейнер приостанавливает любую транзакцию, которая может быть связанные с запросом клиента. Если есть транзакция связанные с экземпляром (это произойдет, если сеанс с состоянием bean экземпляр начал транзакцию в предыдущем бизнесе метод), контейнер связывает выполнение метода с этим сделка. Если существуют методы перехватчика, связанные с beanслучаев, эти действия предпринимаются до того, как методы перехватчика вызывается.

(курсив мой). Это важно, потому что это означает, что BMT EJB не наследует JTA tx вызывающего, у которого есть связанный с контейнером tx. Любой текущий tx приостанавливается, поэтому, если BMT EJB создает tx, это новая транзакция, и когда она совершает это, она совершает только свою транзакцию.

Это означает, что вы можете использовать метод BMT EJB, который начинается и совершает транзакцию, как если бы она была эффективно REQUIRES_NEW и делать что-то вроде этого:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
class TheEJB {

    @Inject private EntityManager em;

    @Resource private UserTransaction tx; 

    // Note: Any current container managed tx gets suspended at the entry
    // point to this method; it effectively `REQUIRES_NEW`.
    // 
    public methodOfInterest() throws AppValidationException, SomeOtherAppException {
       try {
           tx.begin();
           // For demonstration sake create a situation that'll cause validation to
           // fail at commit-time here, like
           someEntity.getCollectionWithMinSize1().removeAll();
           em.merge(someEntity);
           tx.commit();
       } catch (ValidationException ex) {
           throw new AppValidationException(ex);
       } catch (PersistenceException ex) {
           // Go grubbing in the exception for useful nested exceptions
           if (isConstraintViolation(ex)) {
               throw new AppValidationException(ex);
           } else {
               throw new SomeOtherAppException(ex);
           }
       }
    }

}

Это ставит коммит под мой контроль. Там, где мне не нужна транзакция, охватывающая несколько вызовов через несколько разных EJB, это позволяет мне обрабатывать все ошибки, включая ошибки в момент фиксации, в моем коде.

Страница Java EE 6 на управляемых транзакциях bean не упоминает об этом или что-то еще о том, как вызываются BMT.

Разговор о том, что BMT не умеет имитировать REQUIRES_NEW в блогах, которые я связал, действительно, просто неясно. Если у вас есть bean управляемый транзакционный EJB, вы не можете приостановить транзакцию, которую вы начали, чтобы начать другую. Вызов отдельного вспомогательного EJB может приостановить ваш tx и дать вам эквивалент REQUIRES_NEW, но я еще не тестировал.


Другая половина проблемы - случаи, когда мне нужны транзакции, управляемые контейнерами, потому что у меня есть работа, которая должна выполняться через несколько разных EJB и EJB-методов - решается защитным кодированием.

Раннее стремление к очистке диспетчера объектов позволяет мне уловить любые ошибки проверки, которые могут найти мои правила проверки JSR330, поэтому мне просто нужно убедиться, что они полные и всеобъемлющие, поэтому я никогда не получаю ошибок проверки или ошибок нарушения целостности БД в момент фиксации. Я не могу обрабатывать их чисто, поэтому мне нужно действительно защищать их.

Часть защитного кодирования:

  • Тяжелое использование аннотаций javax.validation для полей сущности и использование методов проверки @AssertTrue, где этого недостаточно.
  • Ограничения валидации на коллекции, которые я очень рад видеть, поддерживаются. Например, у меня есть объект A с коллекцией B. A должен иметь хотя бы один B, поэтому я добавил ограничение @Size(min=1) в коллекцию B, где он определен в A.
  • Пользовательские проверки подлинности JSR330. Я добавил несколько настраиваемых валидаторов для таких вещей, как Australian Business Numbers (ABNs), чтобы убедиться, что я никогда не пытаюсь отправить недопустимый файл в базу данных и вызывать ошибку проверки уровня на уровне DB.
  • Ранняя очистка менеджера объектов с помощью EntityManager.flush(). Это заставляет валидацию проходить, когда она находится под контролем вашего кода, а не позже, когда JTA отправляется для совершения транзакции.
  • Защитный код и логика, специфичные для объекта, на моем фасаде EJB, чтобы убедиться, что ситуации, которые не могут быть обнаружены с помощью проверки JSR330, не возникают и вызывают сбои фиксации.
  • В тех случаях, когда это целесообразно, используя методы REQUIRES_NEW для принудительного раннего фиксации и позволяющие мне обрабатывать сбои в моих EJB, повторите попытку соответствующим образом и т.д. Это иногда требует, чтобы EJB-помощник обошел проблемы с самообслуживаниями бизнес-методам.

Я все еще не могу изящно обрабатывать и повторять неудачи сериализации или взаимоблокировки, как я мог, когда я использовал JDBC напрямую с Swing, поэтому вся эта "помощь" из контейнера сделала несколько шагов назад в некоторых областях. Тем не менее, это экономит огромное количество ошибочного кода и логики в других местах.

В случае возникновения этих ошибок я добавил фильтр исключения уровня пользовательского интерфейса. Он видит, что EJBException обертывает JTA RollbackException, обертывая PersistenceException, обертывая исключение, зависящее от EclipseLink, обертывание PSQLException, рассматривает SQLState и принимает решения на основе этого. Это абсурдно круговое движение, но оно работает

Ответ 3

Настройка eclipselink.exception-handler, чтобы указать на реализацию ExceptionHandler выглядели многообещающими, но не получилось.

JavaDoc для ExceptionHandler - это... плохо... поэтому вам нужно посмотреть тестовую реализацию и тесты (1, 2), которые используют его, Здесь несколько более полезная документация здесь.

Кажется, сложно использовать фильтр исключений для обработки нескольких конкретных случаев, оставив все остальное незатронутым. Я хотел уловить PSQLException, проверить для SQLSTATE 23514 (CHECK нарушение ограничений), выбросить для этого полезное исключение и ничего не менять. Это не выглядит практичным.

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

Ответ 4

javax.validation.ValidationException - исключение JDK; Я не могу измените его, чтобы добавить аннотацию @ApplicationException, чтобы предотвратить обертывание

В дополнение к вашему ответу: вы можете использовать дескриптор XML для аннотации сторонних классов как ApplicationException.