Используя 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;
}
}
@ C.Springer Намерение не установлено мной, но аннотировано с помощью 'Id' и 'GeneratedValue' и, таким образом, установлено db. Пожалуйста, просмотрите обновления для Factory и Entity code. Спасибо
Проблема в том, что TransactionalEntity (User) прикреплен к другому потоку, чем TransactionIntent. Когда я удаляю cascade = CascadeType.ALL, он не вызывает ошибку. Но это означает, что для каждого потока существует отдельная «постоянство». Как я могу работать с одним экземпляром сохраняемости jpa?
Когда-то у меня была аналогичная проблема с проектом, использующим Spring Integration. Мое решение, насколько я помню, заключалось в том, чтобы сохранить только идентификатор объекта, на который ссылается база данных, и отложить фактическое извлечение для кода / потока, фактически выполняющего постоянство. В вашем случае должна быть какая-то операция pre-persist (предположительно в попыткеPublish), которая сообщит объекту TransactionIntent загрузить соответствующие объекты в текущий HibernateSession, а затем сохранить объект.
Я хотел бы добавить сюда пищу для размышлений и предложить вам полностью пересмотреть частое использование CompletableFuture. Например, ваша служба возвращает CompletableFuture при получении объектов User, но затем вызывает join (), таким образом выполняясь последовательно. В этом случае будущее добавляет сложности. Возможно, другое, более дружественное к Hibernate, решение будет включать в себя создание некоторого сериализуемого объекта значения выброса, который представляет ожидающую транзакцию, которая может быть передана по потокам. Код в будущем будет выполнять все операции сохранения / поиска.




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