Принуждение транзакции откатывать на ошибках проверки во Шве

Быстрая версия: мы ищем способ вынудить транзакцию откатывать, когда определенные ситуации происходят во время осуществления метода на отступающем бобе, но мы хотели бы, чтобы откат произошел, не имея необходимость показывать пользователю универсальные 500 ошибочных страниц. Вместо этого мы хотели бы, чтобы пользователь видел форму, которую она просто отправила и FacesMessage, который указывает, какова проблема была.

Долгая версия: у нас есть несколько отступающих бобов, которые используют компоненты для выполнения нескольких связанных операций в базе данных (использующий JPA/Hibernate). Во время процесса может произойти ошибка после того, как некоторые операции базы данных произошли. Это могло быть по нескольким различным причинам, но по этому вопросу, давайте предположим, что была ошибка проверки, которая обнаруживается после того, как некоторые записи DB произошли, которые не были обнаружимы, прежде чем записи произошли. Когда это происходит, мы хотели бы удостовериться, что все изменения дб до этой точки будут откатываться. Шов может иметь дело с этим, потому что при броске RuntimeException из текущего FacesRequest Шов будет откатывать текущую транзакцию.

Проблема с этим состоит в том, что пользователю показывают универсальную ошибочную страницу. В нашем случае мы на самом деле хотели бы, чтобы пользователь был показан страницу, она шла с описательным сообщением о том, что пошло не так, как надо, и имейте возможность исправить плохой вход, который вызвал проблему. Решение, которое мы предложили, состоит в том, чтобы выдать Исключение от компонента, который обнаруживает проблему проверки с аннотацией:

@ApplicationException( rollback = true )

Затем наш боб поддержки может поймать это исключение, предположить компонент, который бросил его, опубликовал соответствующий FacesMessage и просто возвращают пустой указатель для забирания пользователя к входной странице с отображенной ошибкой. Аннотация ApplicationException говорит Шву откатывать транзакцию, и мы не показываем пользователю универсальную ошибочную страницу.

Это работало хорошо на первое место, мы использовали его, который, оказалось, только делал, вставляет. Второе место мы пытались использовать его, мы должны удалить что-то во время процесса. В этом втором случае все работает, если нет никакой ошибки проверки. Если ошибка проверки действительно происходит, Исключение отката выдается, и транзакция отмечена для отката. Даже если никакие модификации базы данных, оказалось, не откатывались, когда пользователь фиксирует неправильные данные и повторно отправляет страницу, мы добираемся:

java.lang.IllegalArgumentException: Removing a detached instance 

Отдельный экземпляр лениво загружается из другого объекта (существуют многие к отношениям). Тот родительский объект загружается, когда отступающий боб инстанцируют. Поскольку транзакция откатывалась после ошибки проверки теперь отсоединяется объект.

Наш следующий шаг должен был изменить эту страницу от объема разговора до объема страницы. Когда мы сделали это, Шов не может даже представить страницу после ошибки проверки, потому что наша страница должна поразить DB для рендеринга, и транзакция была отмечена для отката.

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

Я прочитал статью Seam Framework на Объединенной ошибочной странице и обработке исключений, но это приспособлено больше к большему количеству универсальных ошибок, с которыми могло бы встретиться Ваше приложение.

Обновление: вот некоторый psudo-код и детали потока страницы.

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

Функциональность редактирования файл edit.page.xml содержит простое, переписывает шаблон для УСПОКОИТЕЛЬНОГО URL и двух правил перехода:

  1. Если результатом было успешное редактирование, перенаправьте пользователя к соответствующей странице представления для наблюдения обновленной информации.
  2. Если пользователь поразил кнопку отмены, перенаправляет пользователя к соответствующей странице представления.

edit.xhtml является довольно основным с полями для всех частей пользователя, который может быть отредактирован.

Отступающий боб имеет следующие аннотации:

@Name( "editUser" )
@Scope( ScopeType.PAGE )

Существуют некоторые введенные компоненты как Пользователь:

@In
@Out( scope = ScopeType.CONVERSATION ) // outjected so the view page knows what to display
protected User user;

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

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 отметит транзакцию, которая будет откатываться. С этой реализацией мы не можем использовать DB в текущей области, когда мы представляем страницу редактирования снова для отображения сообщения об ошибке, добавленного блоком проверки допустимости. При рендеринге мы поражаем дб, чтобы заставить что-то отображаться на странице, и это не возможно, потому что Сессия недопустима:

Вызванный org.hibernate. LazyInitializationException с сообщением: "не мог инициализировать прокси - никакая Сессия"

6
задан Chris Williams 17 May 2010 в 18:07
поделиться

3 ответа

Я должен согласиться с @duffymo относительно проверки до начала транзакции. Обработка исключений базы данных и их представление пользователю довольно сложно.

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

Вместо этого вам нужно создать длительный диалог с flushMode , установленным на MANUAL . Затем вы начинаете сохранять данные , а затем вы можете выполнить свою проверку, и если это нормально, вы снова продолжите. После того, как вы закончили и все готово, вы вызываете entityManager.flush () . Что сохранит все в базу данных.

А если что-то не получается, не сливаешься. Вы просто возвращаете null или «ошибку» с некоторым сообщением. Позвольте мне показать вам некоторый псевдокод.

Допустим, у вас есть сущность "Лицо" и "Организация". Теперь вам нужно сохранить Личность, прежде чем вы сможете поместить человека в Организацию.

private Person person;
private Organization org;

@Begin(join=true,FlushMode=MANUAL) //yes syntax is wrong, but you get the point
public String savePerson() {
//Inside some save method, and person contains some data that user has filled through a form

//Now you want to save person if they have name filled in (yes I know this example should be done from the view, but this is only an example
try {
  if("".equals(person.getName()) {
    StatusMessages.instance().add("User needs name");
    return "error"; //or null
  }
  entityManager.save(person);
  return "success";
} catch(Exception ex) {
  //handle error
  return "failure";
}
}

Обратите внимание, что теперь мы сохраняем человека, но не сбрасываем транзакцию. Однако он проверит ограничения, которые вы установили для своего entitybean. (@NotNull, @NotEmpty и т. Д.). Так что это будет только имитация сохранения.

Теперь вы сохраняете организацию для человека.

@End(FlushMode=MANUAL) //yes syntax is wrong, but you get the point
public String saveOrganization() {
//Inside some save method, and organization contains some data that user has filled through a form, or chosen from combobox

org.setPerson(person); //Yes this is only demonstration and should have been collection (OneToMany)
//Do some constraint or validation check
entityManager.save(org);
//Simulate saving org
//if everything went ok
entityManager.flush() //Now person and organization is finally stored in the database
return "success";
}

Здесь вы даже можете поместить что-то в try catch и вернуть успешный результат только в том случае, если не произошло никакого исключения, чтобы не попасть на страницу с ошибкой.

Обновление

Вы можете попробовать следующее:

@PersistenceContext(type=EXTENDED)
EntityManager em;

Это заставит bean-компонент с отслеживанием состояния иметь расширенный контекст сохранения EJB3. Сообщения, полученные в запросе, остаются в управляемом состоянии, пока существует bean-компонент, поэтому любые последующие вызовы метода к bean-компоненту с сохранением состояния могут обновлять их без необходимости делать какой-либо явный вызов EntityManager. Это поможет избежать исключения LazyInitializationException. Теперь вы можете использовать em.refresh (user);

1
ответ дан 17 December 2019 в 22:11
поделиться

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

0
ответ дан 17 December 2019 в 22:11
поделиться

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

Лучшее, что я нашел, - это рассматривать имеющийся у вас объект как объект-значение (которым он, по сути, и является после отката).Чтобы удалить его из базы данных, найдите его «прикрепленный» двойник, используя поиск по его идентификатору (который не попадет в базу данных, поскольку он почти наверняка кэширован), а затем удалите возвращенный объект.

Аналогично для обновлений: получите новую копию и обновите ее.

Это немного хлопотно, но позволяет избежать длинных транзакций и всех связанных с этим проблем с блокировками.

0
ответ дан 17 December 2019 в 22:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: