Spring-Data-JPA:线程安全的唯一插入

时间:2018-07-03 09:22:35

标签: java spring concurrency spring-data-jpa thread-safety

我正在尝试使用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))]

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

1 个答案:

答案 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()
                )
        );
    }
}