Как реагировать на принятие или откат транзакции EJB3?

Я реализовал Сервис как EJB сеанса без сохранения состояния (3.2) для хранения данных через JPA. Кроме того, EJB также обновляет индекс Lucene при каждом обновлении данных. Сессионный компонент управляется контейнером. Код ejb выглядит так:

@Stateless
@LocalBean
public class DataService {
    ....

    public Data save(Data myData)  {
        // JPA work....
        ...
        // update Lucene index
        ....
    }
    ....
} 

У меня есть другие другие BusinessService-EJB, вызывающие этот DataService EJB для вставки или обновления данных. Я не имею никакого контроля над реализациями BusinessService-EJB. Транзакция, запущенная BusinessService-ejb, может содержать несколько вызовов метода save() моего EJB DataService.

@Stateless
@LocalBean
public class SomeBusinessService {
    @EJB
    DataService dataService;
    ....

    public void process(....)  {
        dataService.save(data1);
        ...
        dataService.save(data2);
        ....
    }
    ....
} 

Моя проблема возникает в случае сбоя метода ProcessService-EJBs. Каждый вызов метода DataService.save() обновляет индекс Lucene данного объекта данных. Но в случае сбоя одного из последующих вызовов полная транзакция откатывается. Моя работа с JPA будет откачена, как и ожидалось (данные в базу данных не записываются). Но теперь индекс Lucene уже обновлен для всех полных успешных вызовов метода save() до отмены транзакции.

Итак, мой вопрос: как я могу реагировать на такую ​​ситуацию в моем EJB DataService? Это вообще возможно?

Я видел, что с помощью Statefull Session EJB 3.2 я могу аннотировать метод с помощью аннотации " @AfterCompletion ". Поэтому я предполагаю, что это может быть решением для написания индекса lucene, только если @AfterCompletion вызывается с 'success'. Но эта аннотация недопустима для EJB сеансов без сохранения состояния. Должен ли я просто изменить тип EJB с состояния без состояния на statefull "Но как это повлияет на мой сценарий с BusinessService-EJB, который все еще является EJB сеанса без состояния?" Будет ли это работать? Я также боюсь, что изменение формы ejb без сохранения состояния на statefull повлияет на производительность.

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


2018-08-29 - Решение:

Я решил эту проблему следующим образом:

Вместо непосредственного обновления индекса Lucene во время моего метода DataService.save(data) я просто создаю новый eventLogEntry с JPA, используя ту же самую транзакцию.

@Stateless
@LocalBean
public class DataService {
    ....

    public Data save(Data myData)  {
        // JPA work....
        ...
        // Write a JPA eventLog entry indicating to update Lucene index
        ....
    }
    ....
} 

Теперь, когда клиент вызывает метод поиска lucene, я запускаю метод flush для обновления моего индекса lucene на основе записей журнала событий:

@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
public void flush() {
    Query q = manager.createQuery("SELECT eventLog FROM EventLog AS eventLog" );
    Collection<EventLog> result = q.getResultList();
    if (result != null && result.size() > 0) {

        for (EventLog eventLogEntry : result) {
            .... update lucen index for each entry
            .......
            // remove the eventLogEntry.
            manager.remove(eventLogEntry);
        }
    }
}

С аннотацией TransactionAttributeType.REQUIRES_NEW метод flush() будет только читать уже зафиксированные записи в EventLog.

Таким образом, клиент будет видеть только подтвержденные обновления в индексе Lucene. Даже во время транзакции от одного из моих BusinessService-EJB-компонентов lucene не будет содержать "незаполненные" документы. Это поведение соответствует модели транзакции "Read Committed".

Смотрите также подобное обсуждение на: Как сделать так, чтобы сессионные компоненты без сохранения состояния работали с учетом транзакций?

2 ответа

Решение

Я решил эту проблему следующим образом:

Вместо непосредственного обновления индекса Lucene во время моего метода DataService.save(data) я просто создаю новый eventLogEntry с JPA, используя ту же самую транзакцию.

@Stateless
@LocalBean
public class DataService {
    ....

    public Data save(Data myData)  {
        // JPA work....
        ...
        // Write a JPA eventLog entry indicating to update Lucene index
        ....
    }
    ....
} 

Теперь, когда клиент вызывает метод поиска lucene, я запускаю метод flush для обновления моего индекса lucene на основе записей журнала событий:

@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
public void flush() {
    Query q = manager.createQuery("SELECT eventLog FROM EventLog AS eventLog" );
    Collection<EventLog> result = q.getResultList();
    if (result != null && result.size() > 0) {

        for (EventLog eventLogEntry : result) {
            .... update lucen index for each entry
            .......
            // remove the eventLogEntry.
            manager.remove(eventLogEntry);
        }
    }
}

С аннотацией TransactionAttributeType.REQUIRES_NEW метод flush() будет только читать уже зафиксированные записи в EventLog.

Таким образом, клиент будет видеть только подтвержденные обновления в индексе Lucene. Даже во время транзакции от одного из моих BusinessService-EJB-компонентов lucene не будет содержать "незаполненные" документы. Это поведение соответствует модели транзакции "Read Committed".

Смотрите также подобное обсуждение на: Как сделать так, чтобы сессионные компоненты без сохранения состояния работали с учетом транзакций?

Если индекс lucene не является транзакционным, вы не сможете выполнить откат в случае сбоя.

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

Что если обновление индекса завершится неудачно?

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

Напишите несколько тестов, чтобы проверить, как транзакция реагирует, когда вы аннотируете метод вызывающей стороны с помощью @Transactional или с помощью UserTransaction.

Другие вопросы по тегам