Работайте с оптимистичной блокировкой Hibernate с помощью Spring - PullRequest
0 голосов
/ 03 июня 2018

Я использую Hibernate и Spring Data, он будет выполнять оптимистическую блокировку при вставке или обновлении сущности, а если версия в базе данных не совпадает с версией, которая будет сохраняться, в Spring будет выдано исключение StaleObjectStateException,вам нужно перехватить его с помощью ObjectOptimisticLockingFailureException.

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

public void cancelRequest()
{
    try
    {
        request.setStatus(StatusEnum.CANCELLED);
        this.request = topUpRequestService.insertOrUpdate(request);
        loadRequests();
        //perform other tasks...
    } catch (ObjectOptimisticLockingFailureException ex)
    {
        FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
    }
}

Я предполагаю, что это также будет работать с кодом ниже, но я еще не проверял его.

public void cancelRequest()
{
    RequestModel latestModel = requestService.findOne(request.getId());
    if(latestModel.getVersion() != request.getVersion())
    {
        FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
    } 
    else
    {
        request.setStatus(StatusEnum.CANCELLED);
        this.request = requestService.insertOrUpdate(request);
        loadRequests();
        //perform other tasks...
    }
}

Мне нужно применять эту проверку везде, где я звоню requestService.insertOrUpdate(request);, и я не хочуприменять их по одному.Поэтому я решаю поместить проверочный код в саму функцию insertOrUpdate(entity).

@Transactional
public abstract class BaseServiceImpl<M extends Serializable, ID extends Serializable, R extends JpaRepository<M, ID>>
        implements BaseService<M, ID, R>
{

    protected R repository;
    protected ID id;

    @Override
    public synchronized M insertOrUpdate(M entity)
    {
        try
        {
            return repository.save(entity);
        } catch (ObjectOptimisticLockingFailureException ex)
        {
            FacesUtils.showErrorMessage(null, FacesUtils.getMessage("message.actionFailed"),
                        FacesUtils.getMessage("message.pleaseReload"));
            return entity;
        }
    }
}

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

public void cancelRequest()
{
    try
    {
        request.setStatus(StatusEnum.CANCELLED);
        this.request = topUpRequestService.insertOrUpdate(request);
        //I want it to stop here if fail to persist, don't load the requests and perform other tasks.
        loadRequests();
        //perform other tasks...
    } catch (ObjectOptimisticLockingFailureException ex)
    {
        FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
    }
}

Я знаю, что при вызове insertOrUpdate я могу перехватить возвращаемое значение entiry, объявив новую переменную модели, и сравнить ее версию с исходной., если версия такая же, означает, что сохранение не удалось.Но если я делаю это таким образом, я должен писать код проверки версии везде, где я звоню insertOrUpdate.Есть ли лучший подход, чем этот?

1 Ответ

0 голосов
/ 03 июня 2018

Самый близкий способ сделать это и не обязательно вносить существенные изменения кода во всех точках вызова - это взглянуть на некоторые рекомендации Spring AOP, которые работают аналогично аннотации Spring * @Transactional.

@FacesReloadOnException( ObjectOptimisticLockingFailureException.class )
public void theRequestHandlerMethod() {
  // call your service here
}

Идея заключается в том, что аннотация @FacesReloadOnException запускает общий совет, который перехватывает любое исключение, указанное в значении аннотации, и в основном обрабатывает вызов FacesUtils, если какой-либо из этих классов исключений будет выдан.

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

Но я, конечно, не стал бы ставить вопросблок try/catch на уровне сервиса, если вы не хотите изменять типы возвращаемых методов уровня сервиса, потому что контролерам понадобится больше контекста, как вы указали.Единственный способ сдвинуть этот блок try/catch вниз по течению, это если бы вы вернули объект типа Result , который ваш контроллер мог бы затем проверить, например,

public void someControllerRequestMethod() {
  InsertOrUpdateResult result = yourService.insertOrUpdate( theObject );
  if ( result.isSuccess() ) {
    loadRequests();       
  }
  else {
    FacesUtils.showErrorMessage( ... );
  }
}

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

public <T extends BaseService, U> U insertOrUpdate(T service, U object, Consumer<U> f) {
  try {
    U result = service.insertOrUpdate( object );
    f.accept( result );
    return result;
  }
  catch ( ObjectOptimisticLockingFailureException e ) {
    FacesUtils.showErrorMessage( ... );
  }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...