如何对 EJB3 事务提交或回滚作出反应?

How to react on a EJB3 transaction commit or rolleback?

我已经将服务实现为无状态会话 EJB (3.2),以通过 JPA 存储数据。此外,EJB 还会在每次更新数据时更新 Lucene 索引。会话 bean 是容器管理的。 ejb 的代码如下所示:

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

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

我有不同的其他 BusinessService-EJB 调用此 DataService EJB 来插入或更新数据。我无法控制 BusinessService-EJB 实现。由 BusinessService-ejb 启动的事务可以包含对我的 DataService EJB 的 save() 方法的多次调用。

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

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

如果 BusinessService-EJBs 方法 'process' 中断,我的问题就会出现。 DataService.save() 的每个方法调用都会更新给定数据对象的 Lucene 索引。但是,如果后面的调用之一失败,则整个事务将回滚。我的 JPA 工作将按预期回滚(没有数据写入数据库)。但是 Lucene 索引现在已经在事务取消之前针对 save() 方法的所有成功完整调用进行了更新。

所以我的问题是:在我的 DataService EJB 中如何应对这种情况?这可能吗?

我看到使用 Statefull Session EJB 3.2 我可以使用注释“@AfterCompletion”来注释方法。所以我想这可能是一个解决方案,仅当使用 'success' 调用 @AfterCompletion 时才编写 lucene 索引。但是对于无状态会话 EJB,此注释是不允许的。 我应该简单地将我的 EJB 类型从无状态更改为有状态吗? 但是,这如何影响我使用仍然是无状态会话 EJB 的 BusinessService-EJB 的场景?这行得通吗?我还担心将我的 ejb 表单从无状态更改为有状态会对性能产生影响。

或者有其他方法可以解决这个问题吗?比如监听器根据事务ID写日志,事务完成后更新lucene索引....?


2018-08-29 - 解决方案:

我通过以下方式解决了这个问题:

在我的方法 DataService.save(data) 期间,我没有直接更新 Lucene 索引,而是使用相同的事务用 JPA 创建了一个新的 eventLogEntry。

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

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

现在,每当客户端调用 lucene 搜索方法时,我都会 运行 刷新方法根据事件日志条目更新我的 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 也不会包含 'unflushed' 文档。此行为等同于事务模型 'Read Committed'.

另请参阅类似讨论:How making stateless session beans transaction-aware?

如果 lucene 索引不是事务性的,您将无法在发生故障时回滚。

Or is there any other way to solve this problem? For example something like a listener writing a log based on the transaction ID, and updating the lucene index after the transaction was fully completed....?

那索引更新失败怎么办?

整个事情必须在一个事务中,并且只有在其中的每个操作都成功时才能提交该事务。这是保证数据一致性的唯一方法。

编写一些测试来检查当您使用 @Transactional 或使用 UserTransaction 注释调用方方法时事务如何反应。

我通过以下方式解决了这个问题:

在我的方法 DataService.save(data) 期间,我没有直接更新 Lucene 索引,而是使用相同的事务用 JPA 创建了一个新的 eventLogEntry。

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

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

现在,每当客户端调用 lucene 搜索方法时,我都会 运行 刷新方法根据事件日志条目更新我的 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 也不会包含 'unflushed' 文档。此行为等同于事务模型 'Read Committed'.

另请参阅类似讨论:How making stateless session beans transaction-aware?