如何处理JPA和Postgres的无间隙序列?

时间:2015-02-04 17:14:12

标签: java spring hibernate postgresql jpa

我正在使用Spring,Spring JPA,Hibernate和Postgresql开发REST API。 我需要在实体中拥有一系列代码。

考虑以下实体:

public class Document{
    private Long id;
    private String code;

    //getters and setters
}

在我的春天方法中,我做了类似的事情来保存新的entity

    String prefix = "D";

    //get records
    List<Document> documents = this.documentRepository.findAll();

    //find max value of code
    int max = 0;
    for(Document d:documents){
        String code = d.getCode();
        int number = Integer.parseInt(code.substring(prefix.length()));
        if(number>max) max = number;
    }

    //increment
    long currentNumber = max+1;

    entity.setCode(prefix+currentNumber);

    this.documentRepository.save(entity);

这导致我遇到这样的情况:如果我尝试两次调用此方法,我会得到两个具有相同code的文档。

要解决此问题,我尝试在我的方法中添加@Transactional@Transactional(isolation = Isolation.SERIALIZABLE)注释。正在正确创建事务,但随后其中一个api调用失败并带有

ERROR:  could not serialize access due to read/write dependencies among transactions

从我读到的关于隔离级别的内容来看,我需要SERIALIZABLE。此外,Postgres看起来像是积极的&#34;处理此隔离级别,期望两个事务成功提交。但是,情况可能并非总是如此,并且客户端应用程序应该重试失败的事务。

观察:

  • 如果现有文档D4 已删除,则接受下一个创建的文档变为D5。所以,这并非如此&#34;无间隙&#34;正如名称本身所述。
  • 我的真实用例有点复杂。我不只是做findAll,我只是为了这个问题而简化了。在我的例子中,假设Document包含在Folder中。无间隙序列只是该文件夹中,而不是全局。每个Folder都有许多Documents。因此,对于每个文件夹,我们都有D1D2D3,...

可接受的解决方案:

  • make spring如果失败则重试该事务
  • 这种无间隙序列问题的替代方法
  • 会锁定表吗?

1 个答案:

答案 0 :(得分:0)

  

从我读到的关于隔离级别的内容来看,我需要的是SERIALIZABLE

如果以这种方式存在很多冲突,您将获得非常高的回滚率。

我建议使用锁定方法。

有一个序列生成器表。在此表中,使用本机查询获取新值:

UPDATE mysequencetable SET nextvalue = nextvalue + 1 RETURNING nextvalue

但是,您仍然需要处理重试,因为如果您有多个这样的表,您的事务可以以不同的顺序获取行,然后死锁。

因此,您的应用必须才能重试交易。编写良好的应用程序无论如何都必须这样做,否则如果数据库重新启动,暂时无法访问等,它将无法正常运行。

现在,如果你必须在DELETE上重新编号,这是一个不同的问题,你需要一个触发器。