2016-10-09 5 views
1

В настоящее время я реализую веб-службу REST API с использованием инфраструктуры Dropwizard вместе с dropwizard-hibernate, соответственно JPA/Hibernate (с использованием базы данных PostgreSQL). У меня есть метод внутри ресурса, который я аннотировал с помощью @UnitOfWork, чтобы получить одну транзакцию для всего запроса. Метод ресурса вызывает метод одного из моих DAO s, который расширяет AbstractDAO<MyEntity> и используется для связи с поиском или модификацией моих объектов (типа MyEntity) с базой данных.Автоматическое повторение транзакций/запросов в Dropwizard/JPA/Hibernate

Этот метод DAO выполняет следующие действия: сначала он выбирает экземпляр объекта и, следовательно, строку из базы данных. После этого экземпляр объекта проверяется и основывается на его свойствах, некоторые его свойства могут быть изменены. В этом случае строка в базе данных должна быть обновлена. Я ничего не указывал в отношении кэширования, блокировки или транзакций в любом месте, поэтому я предполагаю, что по умолчанию используется какой-то оптимистический механизм блокировки, применяемый Hibernate. Поэтому (я думаю) при удалении экземпляра объекта в другом потоке после его выбора из базы данных в текущем, при попытке совершить транзакцию возникает , потому что экземпляр объекта, который должен быть обновлен, был удален ранее другой поток.

При использовании аннотации @UnitOfWork я понимаю, что я не могу уловить это исключение ни в методе DAO, ни в методе ресурсов. Теперь я могу реализовать ExceptionMapper<StaleStateException> для Джерси, чтобы доставить ответ HTTP 503 с Retry-After header или что-то в этом роде для клиента, чтобы он попросил его повторить запрос. Но я бы предпочел сначала повторить запрос/транзакцию (которая в основном такая же из-за аннотации @UnitOfWork), хотя она все еще находится на сервере.

Есть ли пример реализации для механизма повторной попытки транзакции при использовании Dropwizard? Подобно повторению заданного количества раз (например, 3), а затем сбоя с ответом об исключении/HTTP 503. Как бы вы это реализовали? Первое, что мне пришло в голову, - это еще одна аннотация, например, @Retry(exception = StaleStateException.class, count = 3), которую я мог бы добавить в свой ресурс. Любые предложения по этому поводу? Или есть альтернативное решение моей проблемы, учитывая разные вещи, связанные с блокировкой/транзакциями?

+0

Я делаю то же самое с Guice и перехватчики , Ничто не мешает вам применять то же самое через майку, но у ИМО Джерси есть проблема, что вам нужно будет убедиться, что ваш запрос не скомпрометирован (например, если вы сделали это через фильтр).Имея guice methodinterceptors, вы можете просто поймать любое исключение и повторно вызвать тот же метод. – pandaadb

+0

Я только что нашел этот PR: https://github.com/dropwizard/dropwizard/pull/1361 С этим я смог создать неличный метод (аннотированный с помощью '@ UnitOfWork'), который выполняет работу DAO метод, который приводит к 'StaleStateException'. Затем я написал другой метод, который использует первый, а также реализует какой-то механизм повтора. Так что я в основном сделал это, чтобы перевести жизненный цикл сеанса из метода ресурса на другой метод, чтобы я мог создать механизм повтора вокруг него. Я добавлю это как ответ в ближайшее время, если никто другой не сможет предложить лучший подход. – mxscho

ответ

0

Я нашел a pull request в хранилище Dropwizard, который мне помог. Это в основном позволяет использовать аннотацию @UnitOfWork, отличную от методов ресурсов.

Используя это, я смог отделить сеанс открытия/закрытия и создания/создания транзакции жизненного цикла из метода ресурсов, перемещая аннотацию @UnitOfWork от метода ресурса до метода DAO, который отвечает за манипуляцию данными, которая вызывает StaleStateException. Тогда мне удалось создать механизм повтора вокруг этого метода DAO.

образцовое объяснение:

// class MyEntityDAO extends AbstractDAO<MyEntity> 
@UnitOfWork 
void tryManipulateData() { 
    // Due to optimistic locking, this operations cause a StaleStateException when 
    // committed "by the @UnitOfWork annotation" after returning from this method. 
} 

// Retry mechanism, implemented wheresoever. 
void manipulateData() { 
    while (true) { 
     try { 
      retryManipulateData(); 
     } catch (StaleStateException e) { 
      continue; // Retry. 
     } 
     return; 
    } 
} 

// class MyEntityResource 
@POST 
// ... 
// @UnitOfWork can also be used here if nested transactions are desired. 
public Response someResourceMethod() { 
    // Call manipulateData() somehow. 
} 

Конечно можно также прикрепить @UnitOfWork аннотации, а на методе внутри класса обслуживания, который использует DAO, вместо того, чтобы непосредственно применить его к способу DAO. В любом классе используется аннотация, не забудьте создать прокси экземпляров с UnitOfWorkAwareProxyFactory, как описано в запросе на растяжение.

+0

Не совсем уверен, что это лучший подход, поэтому я все еще открыт для лучших, прежде чем принимать этот ответ. – mxscho

+0

Я могу отправить свой ответ с помощью перехватчиков guice, если хотите. – pandaadb

+0

Если для вас не слишком много работы, я бы тоже хотел это увидеть. :) – mxscho

1

Альтернативный подход к использованию заключается в использовании рамок для инъекций - в моем случае - в качестве примера используется перехватчик методов. Это более общее решение.

DW integreates с Guice очень гладко через https://github.com/xvik/dropwizard-guicey

У меня есть родовое реализацию, которая может повторить любое исключение. Он работает, как ваша, аннотацию, следующим образом:

@Target({ElementType.TYPE, ElementType.METHOD}) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface Retry { 

} 

Перехватчик затем делает (с Docs):

/** 
* Abstract interceptor to catch exceptions and retry the method automatically. 
* Things to note: 
* 
* 1. Method must be idempotent (you can invoke it x times without alterint the result) 
* 2. Method MUST re-open a connection to the DB if that is what is retried. Connections are in an undefined state after a rollback/deadlock. 
* You can try and reuse them, however the result will likely not be what you expected 
* 3. Implement the retry logic inteligently. You may need to unpack the exception to get to the original. 
* 
* @author artur 
* 
*/ 
public abstract class RetryInterceptor implements MethodInterceptor { 

    private static final Logger log = Logger.getLogger(RetryInterceptor.class); 

    @Override 
    public Object invoke(MethodInvocation invocation) throws Throwable { 
     if(invocation.getMethod().isAnnotationPresent(Retry.class)) { 
      int retryCount = 0; 
      boolean retry = true; 
      while(retry && retryCount < maxRetries()) { 
       try { 
        return invocation.proceed(); 
       } catch(Exception e) { 
        log.warn("Exception occured while trying to executed method", e); 
        if(!retry(e)) { 
         retry = false; 
        } { 
         retryCount++; 
        } 
       } 
      } 
     } 
     throw new IllegalStateException("All retries if invocation failed"); 
    } 

    protected boolean retry(Exception e) { 
     return false; 
    } 

    protected int maxRetries() { 
     return 0; 
    } 

} 

Несколько вещей, чтобы отметить о таком подходе.

  • повторен метод должен быть разработан, чтобы быть вызваны несколько раз без какого-либо результата изменяющего (например, если метод хранит временные результаты в форме приращений, то исполняющей два раза могут увеличиваться в два раза)

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

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

public class DeadlockRetryInterceptor extends RetryInterceptor { 

    private static final Logger log = Logger.getLogger(MsRetryInterceptor.class); 

    @Override 
    protected int maxRetries() { 
     return 6; 
    } 

    @Override 
    protected boolean retry(Exception e) { 
     SQLException ex = unpack(e); 
     if(ex == null) { 
      return false; 
     } 
     int errorCode = ex.getErrorCode(); 
     log.info("Found exception: " + ex.getClass().getSimpleName() + " With error code: " + errorCode, ex); 
     return errorCode == 1205; 
    } 

    private SQLException unpack(final Throwable t) { 
     if(t == null) { 
      return null; 
     } 

     if(t instanceof SQLException) { 
      return (SQLException) t; 
     } 

     return unpack(t.getCause()); 
    } 
} 

И, наконец, я могу связать это с Guice, выполнив:

bindInterceptor(Matchers.any(), Matchers.annotatedWith(Retry.class), new MsRetryInterceptor()); 

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

Примера метод для повторных попыток будет:

@Override 
    @Retry 
    public List<MyObject> getSomething(int count, String property) { 
     try(Connection con = datasource.getConnection(); 
       Context c = metrics.timer(TIMER_NAME).time()) 
     { 
      // do some work 
      // return some stuff 
     } catch (SQLException e) { 
      // catches exception and throws it out 
      throw new RuntimeException("Some more specific thing",e); 
     } 
    } 

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

Заметим также, как метод (а прибудете) получает новое соединение при вызове дважды из моего источника данных пула, и как никакие изменения не сделаны внутри него (отсюда: безопасно повторить)

Я надеюсь, что помогает.

Вы можете делать похожие вещи, внедряя ApplicationListeners или RequestFilters или аналогичные, однако я думаю, что это более общий подход, который может повторять любой отказ при любом методе, который связан с guice.

Также обратите внимание, что Guice может только перехватывать методы, когда он создает класс (инъекционные аннотированный конструктор и т.д.)

Надежда, что помогает,

Артур

+0

Большое спасибо за подробное объяснение вашего подхода! Я рассмотрю это, когда у меня будет время, это кажется очень многообещающим, так что я мог бы также добавить это к своему слою между ресурсами и DAO даже с перемещенными аннотациями @ @ UnitOfWork, следовательно, upvote. – mxscho

+0

определенно. Вам просто нужно убедиться, что вы правильно распоряжаетесь перехватчиком :) – pandaadb

 Смежные вопросы

  • Нет связанных вопросов^_^