我们将 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列有一个空隙。
非常感谢任何关于我们做错了什么的建议!