我正在尝试使用parallelStream()
模拟分布式应用程序,并在db上编写,其中条目组合应该是唯一的。但是,我尝试了@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))]
但是实际上它应该只是一个结果。当然,更多线程会导致异常。
答案 0 :(得分:1)
@Transactional
注释不会提供任何应用程序级别的线程安全性。您看到的是线程安全问题。使用在orElseGet
之后创建的save
创建的UPSERT模式,您将需要在应用程序中进行线程级保护。由于您将在不同的事务中创建不同的行,因此数据库对这种模式一无所知。大概是这样的:
@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()
)
);
}
}