如何解决在同一资源上具有悲观锁的并行事务
@Entity
public class Foo {
@Id
private long id;
private int status;
// getters and setters
}
@ApplicationScoped
public class FooRepository {
@PersistenceContext(unitName="fooPU")
private EntityManager em;
public Foo get(long id) {
return em.find(Foo.class,id);
}
// added by me
public Foo getAndLock(long id) {
Map<String,Object> props = new HashMap<>();
props.put("javax.persistence.query.timeout",60000);
return em.find(Foo.class,id,LockModeType.pessimistic_WRITE,props);
}
public Foo update(Foo f) {
return em.merge(f);
}
}
@Stateless
public class FooService {
@Inject
private FooRepository fooRepository;
public void doStuff(long id) {
System.out.console("attempting to lock");
Foo f = fooRepository.getAndLock(id);
System.out.println("got lock,status is = " + f.getStatus());
if (f.getStatus() == 200) {
doStuffwithFoo(f);
}
}
private void doStuffWithFoo(Foo f) {
// slow code,500-5000 ms
System.out.println("doing heavy work");
f.setStatus(400);
fooRepository.update(f);
System.out.println("done,new status is = " + f.getStatus());
}
}
@WebServlet(name="...",urlPatterns={"..."})
public class FooServlet extends AbstractServlet {
@Inject
private FooService fooService;
@Override
public void doGet(HttpServletRequest req,HttpServletResponse resp){
System.out.println("in servlet");
fooService.doStuff(Long.parseLong(req.getParameter("fooId")));
}
}
其他信息:
- persistence.xml将事务类型定义为
JTA
(因此,由容器管理) - 将数据源(在JBoss中设置)上的事务隔离级别设置为默认值(在Web界面中显示为空,但我相信是
REPEATABLE_READ
。
这个想法是,使用特定的fooId
参数调用servlet将从数据库中获取Foo
的匹配实例,并且只有当它具有status == 200
时,它才应执行{{ 1}}。此方法通常会花费相对较长的时间,但最终应将该Foo实例的状态更新为doStuffWithFoo
(在快乐的路径中)。
当两个请求几乎同时调用servlet时存在一个问题:两个请求都获得正确的400
实例,但是因为它们都看到状态Foo
,所以它们都执行200
。这不应该发生。
我试图使用doStuffWithFoo
锁来解决此问题(在名为pessimistic_WRITE
的服务之前,我在存储库中添加了getAndLock
方法)。据我了解,该锁定应防止第二个请求只要被锁定就读取数据,并且(由于超时)要等到锁定释放后才能继续(或者如果需要等待太久则抛出超时) )。
不过,在测试时,我注意到日志中打印了以下内容:
get
因此,锁似乎在顺序访问in servlet
in servlet
attempting to lock
attempting to lock
got lock,status is = 200
doing heavy work
done,new status is = 400
got lock,status is = **200**
doing heavy work
done,new status is = 400
方面起作用,但是我不明白的是为什么当我期望第二次调用从数据库读取状态为doStuffWithFoo
时它应该是400。我认为锁定的全部目的是确保不会发生这种情况。
我觉得JEE应该可以实现,但是我的配置中缺少一些东西。我的问题是:有什么我可以做的,以确保第二个请求读取更新的状态(400),最好不要:
到目前为止,我已经尝试过:
- 在
200
方法中添加em.flush
,但这没有效果 - 将事务隔离级别设置为
update
和READ_UNCOMMITTED
,但是在两种情况下,这都会破坏数据源(无法再连接到数据库,我怀疑jdbc驱动程序不支持它)
注意:这不是我的代码,这是一个遗留项目,我被要求对以下事实做一些事情:两个快速连续的请求导致冲突。我发现有关代码的一些问题值得怀疑,但是进行重构或重写是不现实的选择。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。