Spring Async нарушает JPA - отдельный объект передан для сохранения

Используя H2 и JPA, мое приложение REST работало хорошо до Ansyc, но после реализации ломает модель сохраняемости JPA.

Вот такой случай:

В моем репозитории есть метод JpaRepository.save(), но при вызове из отдельного потока он выдает ошибку InvalidDataAccessApiUsageException.

Мой Контроллер вызывает Услуга, который вызывает Репозиторий для вставки нового объекта, и я получаю следующую ошибку:

InvalidDataAccessApiUsageException: detached entity passed to persist: TransactionalEntity; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: TransactionalEntity]

КОНТРОЛЛЕР:

@Autowired
@Qualifier("depositIntentService")
private TransactionIntentService depositIntentService;

@PostMapping("/services/transactions/deposit")
public CompletableFuture<ResponseEntity<TransactionIntent>> deposit(@Valid @RequestBody TransactionClientRequest request) {

    CompletableFuture<TransactionIntent> depositIntentFuture = 
        transactionIntentFactory.createDepositIntent(
                request.entity.id,
                Money.of(CurrencyUnit.of(request.money.currency), request.money.amount));   

    return depositIntentFuture.thenApply(intent -> {
        TransactionIntent publishedIntent = depositIntentService.attemptPublish(intent);    //<-- causes error
        ResponseEntity.ok(publishedIntent)
    });
}

УСЛУГА:

@Component
@Repository
public abstract class TransactionIntentServiceImpl implements TransactionIntentService{

    @Autowired
    private TransactionIntentRepository transactionIntentRepo;

    @Transactional
    public TransactionIntent attemptPublish(TransactionIntent intent){
        transactionIntentRepo.save(intent);     //<-- Throws error: ...detached entity passed to persist

    }
}

РЕПОЗИТОРИЙ

@Repository
public interface TransactionIntentRepository extends JpaRepository<TransactionIntent, Long>{
}

Есть идеи, как поддерживать устойчивость JPA в среде Async? Спасибо!

Обновление1

ФАБРИКА

@Component
public class TransactionIntentFactory {
    @Autowired
    private UserService userService;

    @Async("asyncExecutor")
    public CompletableFuture<TransactionIntent> createDepositIntent(long beneficiaryId, Money money) {

        CompletableFuture<User> bank = userService.findByUsername("[email protected]");
        CompletableFuture<User> user = userService.find(beneficiaryId);

        CompletableFuture<Void> allUserFutures = CompletableFuture.allOf(bank, user);

        return allUserFutures.thenApply(it -> {
            User userSource = bank.join();
            User userBeneficiary = user.join();

            TransactionIntent intent = new TransactionIntentBuilder()
                .status(new TransactionIntentStatus(TRANSFER_STATUS.CREATED, "Deposit"))
                .beneficiary(userBeneficiary)
                .source(userSource)
                .amount(money)
                .build();

            return intent;

        });
    }

}

ОРГАНИЗАЦИЯ

@Entity
public class TransactionIntent {
    @Id
    @GeneratedValue
    public long id;

    public final Money amount;
    public final Date createdAt;

    @OneToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    public final TransactionIntentStatus status;

    @OneToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    public final TransactionalEntity beneficiary;   //to

    @OneToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    public final TransactionalEntity source;            //from

    TransactionIntent(){
        this.amount= null;
        this.createdAt = null;
        this.status = null;
        this.beneficiary = null;
        this.source = null;
    }

    public TransactionIntent(TransactionIntentBuilder builder) {
        this.amount = builder.amount;
        this.createdAt = new Date();
        this.status = builder.status;
        this.beneficiary = builder.beneficiary;
        this.source = builder.source;
    }
}

Вы устанавливаете идентификатор TransactionIntent в TransactionIntentFactory? Если да, то это могло быть причиной данной ошибки. Было бы полезно иметь код для фабричного метода createDepositIntent. Также было бы полезно предоставить аннотации / источник для сущности TransactionIntent.

C. Springer 12.11.2018 05:52

@ C.Springer Намерение не установлено мной, но аннотировано с помощью 'Id' и 'GeneratedValue' и, таким образом, установлено db. Пожалуйста, просмотрите обновления для Factory и Entity code. Спасибо

KasparTr 12.11.2018 06:11

Проблема в том, что TransactionalEntity (User) прикреплен к другому потоку, чем TransactionIntent. Когда я удаляю cascade = CascadeType.ALL, он не вызывает ошибку. Но это означает, что для каждого потока существует отдельная «постоянство». Как я могу работать с одним экземпляром сохраняемости jpa?

KasparTr 12.11.2018 12:55

Когда-то у меня была аналогичная проблема с проектом, использующим Spring Integration. Мое решение, насколько я помню, заключалось в том, чтобы сохранить только идентификатор объекта, на который ссылается база данных, и отложить фактическое извлечение для кода / потока, фактически выполняющего постоянство. В вашем случае должна быть какая-то операция pre-persist (предположительно в попыткеPublish), которая сообщит объекту TransactionIntent загрузить соответствующие объекты в текущий HibernateSession, а затем сохранить объект.

C. Springer 12.11.2018 13:35

Я хотел бы добавить сюда пищу для размышлений и предложить вам полностью пересмотреть частое использование CompletableFuture. Например, ваша служба возвращает CompletableFuture при получении объектов User, но затем вызывает join (), таким образом выполняясь последовательно. В этом случае будущее добавляет сложности. Возможно, другое, более дружественное к Hibernate, решение будет включать в себя создание некоторого сериализуемого объекта значения выброса, который представляет ожидающую транзакцию, которая может быть передана по потокам. Код в будущем будет выполнять все операции сохранения / поиска.

C. Springer 12.11.2018 14:17
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
915
0

Другие вопросы по теме