Spring-Data JPA: сохранить новый объект, ссылающийся на существующий

вопрос в основном такой же, как ниже:

каскад JPA сохраняется, а ссылки на обособленные сущности вызывают PersistentObjectException. Почему?

Я создаю новый объект, который ссылается на существующий, отдельно стоящий. Теперь, когда я сохраняю этот объект в своем репозитории данных spring, возникает исключение:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist

если мы посмотрим на метод save () в исходном коде spring data JPA, мы увидим:

public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

и если мы посмотрим at isNew () in AbstractEntityInformation

public boolean isNew(T entity) {

    return getId(entity) == null;
}

так что в основном, если я сохраню () новый объект (id == null), spring data всегда будет вызывать persist и, следовательно, этот сценарий всегда будет терпеть неудачу.

Это, кажется, очень типичный случай использования при добавлении новых элементов в коллекции.

как я могу решить это?

EDIT 1:

Примечание:

эта проблема напрямую не связана с как сохранить новый объект, который ссылается на существующий объект весной В JPA?. Для разработки предположим, вы получаете запрос на создание нового объекта по http. Затем вы извлекаете информацию из запроса и создаете свою сущность и существующую, на которую ссылаются. Поэтому они всегда будут отделены.

3 ответов


лучшее, что я придумал

public final T save(T containable) {
    // if entity containable.getCompound already exists, it
    // must first be reattached to the entity manager or else
    // an exception will occur (issue in Spring Data JPA ->
    // save() method internal calls persists instead of merge)
    if (containable.getId() == null
            && containable.getCompound().getId() != null){
        Compound compound = getCompoundService()
                .getById(containable.getCompound().getId());
        containable.setCompound(compound);   
    }
    containable = getRepository().save(containable);
    return containable; 
}

мы проверяем, находимся ли мы в проблемной ситуации, и если да, просто перезагрузите существующий объект из базы данных по его идентификатору и установите поле нового объекта в этот только что загруженный экземпляр. Затем он будет прикреплен.

для этого требуется, чтобы служба для новой сущности содержала ссылку на службу указанной сущности. Это не должно быть проблемой, так как вы используете spring в любом случае, так что сервис может быть добавлен как новый @Autowired поле.

другая проблема, однако (в моем случае это поведение на самом деле желательно), что вы не можете изменить указанную существующую сущность одновременно с сохранением новой. Все эти изменения будут проигнорированы.

ВАЖНОЕ ПРИМЕЧАНИЕ:

во многих и, вероятно, ваших случаях это может быть намного проще. В службу можно добавить ссылку entity manager:

@PersistenceContext
private EntityManager entityManager;

и выше if(){} блок использовать

containable = entityManager.merge(containable);

вместо моего кода (непроверенных, если он работает).

в моем случае классы абстрактны и targetEntity на @ManyToOne это тоже значит абстрактный. Вызов entityManager.слияние (containable) непосредственно приводит к исключению. Однако, если ваши классы все конкретные, это должно работать.


У меня была аналогичная проблема, когда я пытался сохранить новый объект сущности с уже сохраненным объектом сущности внутри.

то, что я сделал, было реализовано Сохраняемое и реализован isNew () соответственно.

public class MyEntity implements Persistable<Long> {

    public boolean isNew() {
        return null == getId() &&
            subEntity.getId() == null;
    }

или вы можете использовать AbstractPersistable и переопределить isNew там, конечно.

Я не знаю, будет ли это считаться хорошим способом решения этой проблемы, но это сработало довольно хорошо для меня и, кроме того это очень естественно.


у меня такая же проблема с @EmbeddedId и бизнес-данные как часть идентификатора.
Единственный способ узнать, является ли сущность новой, - это выполнение (em.find(entity)==null)?em.persist(entity):em.merge(entity)

но spring-data предоставляет только save() метод и нет способа заполнить Persistable.isNew() метод find() метод.