【问题标题】:Why do gaps appear in a List backed by @ManyToMany with @OrderColumn为什么空白出现在由@ManyToMany 和@OrderColumn 支持的列表中
【发布时间】:2014-11-12 02:45:58
【问题描述】:

我们将 Ticket 对象队列持久化到数据库中。我们包含队列的实体如下所示:

@Entity
public class StoreQueueCollection {

    @Id
    private int storeId;

    @ManyToMany(fetch=FetchType.LAZY)
    @OrderColumn
    private List<Ticket> mainQueue = new ArrayList<Ticket>();

    @ManyToMany(fetch=FetchType.LAZY)
    @OrderColumn
    private List<Ticket> cancelledQueue = new ArrayList<Ticket>();

    .. etc

我们有一个操作将工单从一个队列移动到另一个队列(称为 changeStatus),另一个操作将新工单添加到队列末尾(称为 newTicket)。

当两个操作在同一个队列上交错时,这些操作基本上可以工作,但我们最终会在我们的队列中产生一个“间隙”。在数据库中,这看起来像是表的 order 列中缺少索引,像这样:0、1、2、4。当队列重新加载到 Java 中时,缺少的索引成为队列中的空元素。

我们在 StoreQueueCollection 对象上使用悲观锁定来防止不一致的交错更新,但它并没有像我们预期的那样工作。通过额外的日志记录,我们会看到如下奇怪的序列:

- changeTicketStatus() 开始 - 使用 entityManager.refresh() 锁定队列 - 票 X 从队列 A 前面移除 - 队列是 entityManager.flush()ed * newTicket() 开始 * 创建一个新票 Y,使用 entityManager.refresh() 锁定 StoreQueueCollection; * 票 Y 添加到队列 A 的末尾 * 票 Y 的字段已初始化并且是 save()d * 调用 refresh(),方法被阻塞 - changeTicketStatus() 恢复 (打印内存队列显示票 X 不在队列 A 中) - 票 X 添加到另一个队列 B - 票 X 修改了一些字段 - 票据 X 使用 repository.save() 保存 (打印内存队列显示票 X 不在队列 A 中) - changeTicketStatus() 完成 * newTicket() 恢复 * 刷新()返回 (打印内存队列 A 显示票 X 仍在队列中!) * 票 Y 被添加到队列 A 的末尾 (打印内存队列 A 显示票 X 和 Y 在队列中) * 队列是 save()d

所有锁都是 LockModeType.PESSIMISTIC_WRITE,作用域是 PessimisticLockScope.EXTENDED。

在这一系列执行之后,另一个线程触发一个断言,该线程检查队列中的空条目。神秘的是,队列基本正确(X被删除,Y被添加到末尾),但是在Y之前的order列有一个间隙。

非常感谢您对我们做错的任何建议!

【问题讨论】:

  • 您的锁定似乎有问题。您能否提供您的锁定和事务边界代码的简化示例?
  • 感谢您的回复。不幸的是,它有点大,而且分布在不同的方法中。
  • 在 SQL 级别记录(使用 log4jdbc-log4j2),看起来锁定没有按预期工作。看来我需要再次阅读文档。

标签: java hibernate jpa pessimistic-locking


【解决方案1】:

您是锁定单行,还是所有线程都锁定一个共同的“队列”行?如果是后者,请记住,您需要在希望持有锁的整个时间内维护事务。如果是前者,锁定单个(不同)行不会阻止交错操作。

您没有提及您使用的是什么后端,但有时数据库日志工具(如 SQL Server 的分析器和活动监视器)有助于确定正在发生的事情,因为您可以获得所有 SQL 语句的有序列表发布到数据库。

【讨论】:

  • Tyler,我们正在使用 EntityManager.refresh(storeQueueCollection, ...) 和 EntityManager.flush() 来锁定 StoreQueueCollection 实体类的实例。监控低级 SQL 是一个不错的建议(我们使用的是 MySQL)。
  • 您能否通过在获取锁后设置断点,然后尝试在其他线程中获取它来确认锁确实有效?在我使用 Hibernate 的经验中,我发现所有各方都必须获得一个锁才能使其生效。如果一个事务获得锁但另一个事务没有打扰,则另一个事务将被允许继续。这与 Windows 中的文件锁定不同,在 Windows 中锁定文件会导致所有其他写入失败。
  • Tyler,我在 SQL 级别登录(使用 log4jdbc-log4j2),看起来锁定没有按预期工作。看来我需要再次阅读文档。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多