Spring-data-jpa:线程安全的唯一插入



我正在尝试模拟一个分布式应用程序,parallelStream(),在数据库上编写,其中条目组合应该是唯一的。但是,我已经尝试了@Transactional@Lock的几个选项,但似乎都没有奏效。

这是代码的一部分,应该使问题变得清晰:

AtomicDbService

@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public TestEntity atomicInsert(TestEntity testEntity) {
TestEntityParent testEntityParent = testEntityParentRepository
.findByStringTwo(testEntity.getTestEntityParent().getStringTwo())
.orElseGet(() -> testEntityParentRepository.save(TestEntityParent.builder()
.stringTwo(testEntity.getTestEntityParent().getStringTwo())
.build()));
return testEnityRepository.findByStringAndTestEntityParentStringTwo(
testEntity.getString(), testEntity.getTestEntityParent().getStringTwo()
).orElseGet(() -> testEnityRepository
.save(
TestEntity.builder()
.string(testEntity.getString())
.testEntityParent(testEntityParent)
.build()
)
);
}

测试:

@Test
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void testOperationsParallelStream() {
List<Integer> list = IntStream.range(0, 3).boxed().collect(Collectors.toList()); 
list.parallelStream().forEach(lala -> atomicDbService.atomicInsert(testEntity));
System.out.println(testEnityRepository.findAll());
}

作为输出,我得到例如:

[TestEntity(id=4, string=test, testEntityParent=TestEntityParent(id=3, stringTwo=testTwo)), TestEntity(id=5, string=test, testEntityParent=TestEntityParent(id=1, stringTwo=testTwo))]

但实际上应该只是一个结果。当然,更多的线程会导致异常。

@Transactional注释不会提供任何应用程序级别的线程安全性。您看到的是线程安全问题。使用您创建的 UPSERT 模式,后跟orElseGet后跟一个save,您将需要在应用程序中进行线程级保护。数据库对此模式一无所知,因为您在不同的事务中创建不同的行。可能是这样的:

@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public TestEntity atomicInsert(TestEntity testEntity) {
synchronized(TestEntity.class) {
TestEntityParent testEntityParent = testEntityParentRepository.findByStringTwo(testEntity.getTestEntityParent().getStringTwo())
.orElseGet(() -> testEntityParentRepository.save(TestEntityParent.builder()
.stringTwo(testEntity.getTestEntityParent().getStringTwo())
.build()));
return testEnityRepository.findByStringAndTestEntityParentStringTwo(
testEntity.getString(), testEntity.getTestEntityParent().getStringTwo()
).orElseGet(() -> testEnityRepository
.save(
TestEntity.builder()
.string(testEntity.getString())
.testEntityParent(testEntityParent)
.build()
)
);
}
}

最新更新