如何在读写操作之间锁定表

4

我有一个情况,每个新记录都应该包含一个独特且可读的值。这个值对于用户具有业务意义,并将作为自然ID(除主键外)在我们的数据库中处理。

为了让您了解该值的结构:

 - record 1 has business value 'INVOICE_AAA_001'
 - record 2 has business value 'INVOICE_AAA_002'
   ...
 - record 999  has business value 'INVOICE_AAA_999'
 - record 1000 has business value 'INVOICE_BAA_001'
 - record 1001 has business value 'INVOICE_BAA_002'
   ...

这个企业价值通过工厂来创造:

class BusinessFactory {

    ...    

    public String createUniqueValue() {
        String lastValue = (String) getSession().createQuery("select businessValue " + 
                                                             "from Invoice as invoice " + 
                                                             "order by businessValue")
                                                .setMaxResults(1)
                                                .setReadOnly(true)
                                                .uniqueResult();    

       // generate new value 

       return newValue;
    }

}

服务层将调用工厂并保存新的发票:

  @Transactional
  public void saveNewInvoice() {
      final String newValue = businessFactory.createUniqueValue();        
      Invoice invoice = new Invoice(newValue);
      invoiceRepository.save(invoice);
  }

这里的问题是,有可能出现trx1和trx2同时读取'INVOICE_BAA_002'业务值的情况。接下来会有两个事务同时使用相同的值。第一个提交的事务将成功,第二个事务将由于唯一约束异常而失败。
因此,当读取最新的业务值时,我需要在发票表上放置一个锁。我认为该锁应该在将新发票实体保存到数据库之前一直保持活动状态。
在Hibernate中,我该如何做呢?

一个序列可以帮助你。你能创建一个吗? - adelmo00
我不知道如何创建一个序列。你能给我更多关于这个的信息吗?创建序列可以防止两个并发事务使用相同的值吗? - user2054927
2个回答

6

您可以使用悲观锁来代替冲突检测并发控制机制,例如依赖于唯一约束或乐观锁定

您需要:

  1. An InvoiceSequence table with an Entity mapped to it

  2. This table has only one row, storing the latest invoice sequence value

  3. You acquire an exclusive lock on the record:

    InvoiceSequence invoiceSequence = em.find(InvoiceSequence.class, 1L, LockModeType.PESSIMISTIC_WRITE)
    
  4. You increment the sequence using your business logic and modify the entity to store the latest value:

    String currentSequence = invoiceSequence.getValue();
    String nextSequence = sequenceGenerator.nextValue(currentSequence);
    invoiceSequence.setValue(nextSequence);
    

独占锁将防止并发读取和写入。


谢谢你的回答!我想我必须定义某种锁定超时来防止另一个事务无限期地等待并确保锁定将被释放?也许我还应该添加一个方面,自动重试由于锁定超时而失败的trx? - user2054927

1

序列是一组按需按顺序生成的整数1、2、3等。由于许多应用程序要求表中的每行包含唯一值,序列在数据库中经常被使用,并为其提供了一种简便的生成方式。

您可以通过以下方式之一进行操作:

class BusinessFactory {
public String createUniqueValue() {
    String valueNotUsed = (String) getSession().createSQLQuery("select nextval('hibernate_sequence')");    

   // generate new value 

   return newValue;
}}

这里的lastUniqueValue名称有误导性,因为您得到的值已经是未使用的值。nextval返回下一个未使用的值,因此您可以直接使用它。 - Jan Zyka
谢谢您的回复。nextval('hibernate_sequence')是Hibernate还是数据库特定的内容?这个函数是否在所有数据库中都可用,以防止方言问题? - user2054927
请参见:https://dev59.com/rHI-5IYBdhLWcg3w1sTi - Jan Zyka
再次感谢您的回复。但我怀疑在这种情况下是否有用的是“nextval”,因为我还必须管理字母数字位置(AAA,BAA,BBA...)。所以我认为我真的需要读取最新插入记录的业务值并应用锁定。 - user2054927
您可以捕获最后插入的生成字母,获取序列的新值并与字母合并。 - adelmo00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接