【发布时间】:2015-02-04 17:14:11
【问题描述】:
我正在使用 Spring、Spring JPA、Hibernate 和 Postgresql 开发 REST API。 我有一个要求,我需要在实体中有一系列代码。
考虑以下实体:
public class Document{
private Long id;
private String code;
//getters and setters
}
在我的 spring 方法中,我正在做这样的事情来保存一个新的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 对这个隔离级别采取了“积极”的方法,期望两个事务都成功提交。但是,情况可能并非总是如此,客户端应用程序需要重试失败的事务。
观察:
- 如果现有文档
D4被删除,则接受下一个创建的文档变为D5。因此,这并不像名称本身所说的那样“无缝”。 - 我的实际用例要复杂一些。我不只是在做
findAll,我只是为了这个问题而简化了。在我的例子中,假设Documents 包含在Folder中。无间隙序列只是在该文件夹中,而不是全局。每个Folder有很多Documents。因此,对于每个文件夹,我们都有D1,D2,D3,...
可接受的解决方案:
- 如果失败,让 spring 重试事务
- 这种无间隙序列问题的替代方法
- 表锁定会起作用吗?
【问题讨论】:
-
为什么不直接使用 JPA 的
@GeneratedValue? stackoverflow.com/questions/11788483/…(所以,这并不像名称本身所说的那样“无缝”。——这就是序列的作用) -
为什么需要无缝?要真正确保在并发或分布式系统中将是一个相当大的挑战。通常,随着间隔顺序增加就足够了,因为您仍然保持秩序(但同样,在并发系统中,秩序也有待商榷)
-
@pozs,这种方法能确保事务隔离吗?我认为这种方法行不通,因为这会强制执行“全局”序列?我在
Document中寻找多个序列。每个Folder有很多Documents。因此,对于每个文件夹,我们都有D1,D2,D3,... -
@Alex 它必须是无缝的,因为它只是系统的要求。我们可以放宽该要求的唯一情况是删除的实体。如果我们删除“中间”的实体,我们可以忍受这个差距。
-
@miguelcobain,那么从设计的角度来看,这不是一个无缝系统......希望您的团队意识到您提出的 100% 工作是多么困难。这听起来像是一个任意选择的设计决策(不是技术限制),会困扰你很长时间
标签: java spring hibernate postgresql jpa