Быстрая версия:
Мы ищем способ принудительного отката транзакции, когда возникают определенные ситуации во время выполнения метода на компоненте поддержки, но мы хотели бы, чтобы откат происходил без необходимости показывать пользователю общую страницу ошибки 500. Вместо этого мы хотели бы, чтобы пользователь увидел форму, которую она только что отправила, и FacesMessage, указывающее, в чем заключалась проблема.
Длинная версия:
У нас есть несколько компонентов поддержки, которые используют компоненты для выполнения нескольких связанных операций с базой данных (используя JPA / Hibernate). Во время процесса может произойти ошибка после выполнения некоторых операций с базой данных. Это может быть по нескольким причинам, но для этого вопроса, давайте предположим, что произошла ошибка проверки, обнаруженная после того, как произошли некоторые записи в БД, которые не были обнаружены до того, как произошли записи. Когда это произойдет, мы хотим убедиться, что все изменения в БД до этого момента будут отменены. Seam может с этим справиться, потому что если вы выбросите RuntimeException из текущего FacesRequest, Seam откатит текущую транзакцию.
Проблема в том, что пользователю показывается страница с общей ошибкой. В нашем случае нам бы хотелось, чтобы пользователю показывали страницу, на которой он находился, с описательным сообщением о том, что пошло не так, и имели возможность исправить неверный ввод, вызвавший проблему. Решение, которое мы придумали, заключается в создании исключения из компонента, который обнаруживает проблему проверки с помощью аннотации:
@ApplicationException( rollback = true )
Тогда наш компонент поддержки может поймать это исключение, предположить, что компонент, который его выдал, опубликовал соответствующий FacesMessage, и просто вернуть null, чтобы вернуть пользователя на страницу ввода с отображенной ошибкой. Аннотация ApplicationException указывает Seam откатить транзакцию, и мы не показываем пользователю страницу с общей ошибкой.
Это хорошо сработало для первого места, где мы его использовали, и оказалось, что он делал только вставки Второе место, где мы пытались его использовать, мы должны что-то удалить во время процесса. Во втором случае все работает, если нет ошибки проверки. Если возникает ошибка проверки, возникает исключение отката, и транзакция помечается для отката. Даже если откат базы данных не был отменен, когда пользователь исправляет неверные данные и повторно отправляет страницу, мы получаем:
java.lang.IllegalArgumentException: Removing a detached instance
Отсоединенный экземпляр лениво загружается из другого объекта (существует отношение многие к одному). Этот родительский объект загружается при создании экземпляра компонента поддержки. Поскольку транзакция была откатана после ошибки проверки, объект теперь отсоединен.
Нашим следующим шагом было изменение этой страницы с области разговора на область страницы. Когда мы это сделали, Seam даже не смог отобразить страницу после ошибки валидации, потому что наша страница должна попасть в базу данных, а транзакция была помечена для отката.
Итак, мой вопрос: как другие люди справляются с обработкой ошибок одновременно и правильно управляют транзакциями? А еще лучше, если бы я мог использовать все, что у нас есть сейчас, если кто-то обнаружит то, что я делаю неправильно, это было бы относительно легко исправить.
Я прочитал статью Seam Framework на Унифицированная страница ошибок и обработка исключений , но это больше ориентировано на более общие ошибки, с которыми может столкнуться ваше приложение.
Обновление : вот некоторый псевдокод и детали потока страниц.
В этом случае предположим, что мы редактируем некоторую информацию о пользователе (в данном случае мы фактически не имеем дело с пользователем, но я не собираюсь публиковать фактический код).
Файл edit.page.xml функциональности редактирования содержит простой шаблон перезаписи для URL-адреса RESTful и два правила навигации:
- Если результат был успешным, отредактируйте пользователя на соответствующую страницу просмотра, чтобы увидеть обновленную информацию.
- Если пользователь нажал кнопку отмены, перенаправьте пользователя на соответствующую страницу просмотра.
Файл edit.xhtml довольно прост с полями для всех частей пользователя, которые можно редактировать.
Поддерживающий компонент имеет следующие аннотации:
@Name( "editUser" )
@Scope( ScopeType.PAGE )
Есть некоторые внедренные компоненты, такие как пользователь:
@In
@Out( scope = ScopeType.CONVERSATION ) // outjected so the view page knows what to display
protected User user;
У нас есть метод save для компонента поддержки, который делегирует работу для сохранения пользователя:
public String save()
{
try
{
userManager.modifyUser( user, newFName, newLName, newType, newOrgName );
}
catch ( GuaranteedRollbackException grbe )
{
log.debug( "Got GuaranteedRollbackException while modifying a user." );
return null;
}
return USER_EDITED;
}
Наше исключение GuaranteedRollbackException выглядит так:
@ApplicationException( rollback = true )
public class GuaranteedRollbackException extends RuntimeException
{
public GuaranteedRollbackException(String message) {
super(message);
}
}
UserManager.modifyUser выглядит примерно так:
public void modifyUser( User user, String newFName, String newLName, String type, String newOrgName )
{
// change the user - org relationship
modifyUser.modifyOrg( user, newOrgName );
modifyUser.modifyUser( user, newFName, newLName, type );
}
ModifyUser.modifyOrg делает что-то вроде
public void modifyOrg( User user, String newOrgName )
{
if (!userValidator.validateUserOrg( user, newOrgName ))
{
// maybe the org doesn't exist something. we don't care, the validator
// will add the appropriate error message for us
throw new GauaranteedRollbackException( "couldn't validate org" );
}
// do stuff here to change the user stuff
...
}
ModifyUser.modifyUser похож на modifyOrg.
Теперь (вам придется совершить этот прыжок со мной, потому что это не обязательно звучит так, как будто это проблема в этом сценарии пользователя, но это из-за того, что мы делаем) предположим, что изменение org вызывает modifyUser не в состоянии проверить, но это невозможно проверить этот провал заранее. Мы уже записали обновление org в базу данных в нашем текущем txn, но, поскольку пользовательское изменение не может выполнить проверку, исключение GuaranteedRollbackException помечает транзакцию для отката. В этой реализации мы не можем использовать БД в текущей области, когда мы снова визуализируем страницу редактирования, чтобы отобразить сообщение об ошибке, добавленное средством проверки. Во время рендеринга мы нажимаем на БД, чтобы получить что-то для отображения на странице, а это невозможно, потому что сессия недействительна:
Вызвано org.hibernate.LazyInitializationException с сообщением: «не удалось инициализировать прокси - нет сеанса»