Как улавливать и обматывать исключения, выданные 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 мне, вероятно, придется.
Ответы
Ответ 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.