我开发了一个典型的企业应用程序,负责将客户配置到第三方系统。该系统有一个限制,即只有一个线程可以对某个客户起作用。因此,我们添加了一个由@Singleton
组成的简单锁定机制,其中包含当前正在进行的Set
个customerId。每当有新的请求进行配置时,它首先检查此Set
。如果cusotomerId存在,则等待,否则将其添加到Set
并进入处理。
最近决定将此应用程序部署在集群中,这意味着此锁定方法不再有效。我们想出了一个使用DB进行锁定的解决方案。我们创建了一个包含customerIds的单列表(它还有一个唯一约束)。当新的供应请求到来时,我们启动一个事务并尝试用SELECT FOR UPDATE
的customerId锁定行(如果customerId尚不存在,我们将其插入)。之后我们开始配置客户,完成后我们提交交易。
概念有效,但我的交易有问题。目前,我们有一个CustomerLock
类,其add()
和remove()
方法负责从Set
添加和删除customerIds。我想将此类转换为具有bean管理事务的无状态EJB。 add()
方法将启动事务并锁定行,而remove()
方法将提交事务,从而解锁该行。但似乎交易的开始和结束必须以相同的方法发生。有没有办法使用我描述的方法或我必须修改逻辑,以便事务以相同的方法开始和结束?
CustomerLock类:
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class CustomerLock {
@Resource
private UserTransaction tx;
public void add(String customerId) throws Exception {
try {
tx.begin();
dsApi.lock()
} catch (Exception e) {
throw e;
}
}
public void remove(String customerId) throws Exception {
try {
tx.commit();
} catch (Exception e) {
throw e
}
}
}
CustomerProvisioner类摘录:
public abstract class CustomerProvisioner {
...
public void execute(String customerId) {
try {
customerLock.add(customerId);
processing....
customerLock.remove(customerId);
} catch (Exception e) {
logger.error("Error", e);
}
}
...
}
StandardCustomerProvisioner类:
@Stateless
public class StandardCustomerProvisioner extends CustomerProvisioner {
...
public void provision(String customerId) {
// do some business logic
super.execute(customerId);
}
}
答案 0 :(得分:0)
正如@Gimby所说,你不应该混合容器管理和bean管理的事务。由于您的StandardCustomerProvisioner没有像" @TransactionManagement(TransactionManagementType.BEAN)" - 它使用容器管理的事务,默认情况下为REQUIRED。
您有两个选项可以让它发挥作用:
1)删除" @TransactionManagement(TransactionManagementType.BEAN)"使用UserTransaction调用并运行CMT
2)将此注释(" @TransactionManagement(TransactionManagementType.BEAN)")添加到StandardCustomerProvisioner并使用此方法的事务标记调用,因此所有调用的方法都使用相同的事务上下文。无论如何,应该删除来自CustomerLock的标记调用。