Spring-data-jpa: уникальная многопоточная вставка - PullRequest
0 голосов
/ 03 июля 2018

Я пытаюсь эмулировать распределенное приложение с 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))]

Но на самом деле это должен быть только один результат. Другие темы, конечно, приводят к исключениям.

1 Ответ

0 голосов
/ 03 июля 2018

Аннотация @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()
                )
        );
    }
}
...