Подтвердить что ты не робот

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

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

Сохранение каскада JPA и ссылки на отдельные объекты бросают PersistentObjectException. Почему?

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

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

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

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

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

и если мы посмотрим на isNew() в AbstractEntityInformation

public boolean isNew(T entity) {

    return getId(entity) == null;
}

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

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

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

ИЗМЕНИТЬ 1:

Примечание:

Эта проблема НЕ относится непосредственно к Как сохранить новый объект, который ссылается на существующий объект в spring JPA?. Чтобы уточнить, предположим, что вы получаете запрос на создание нового объекта через http. Затем вы извлекаете информацию из запроса и создаете свою сущность и существующую ссылку. Следовательно, они всегда будут отстранены.

4b9b3361

Ответ 1

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

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.

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

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

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

@PersistenceContext
private EntityManager entityManager;

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

containable = entityManager.merge(containable);

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

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

Ответ 2

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

Что я сделал, было реализовано Persistable <T> и реализовано isNew() соответственно.

public class MyEntity implements Persistable<Long> {

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

Или вы можете использовать AbstractPersistable и переопределить здесь isNew.

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

Ответ 3

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

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