JPA очистить коллекцию и добавить новые элементы

У меня есть коллекция (список) @OneToMany, которую я хотел бы очистить, и добавить новые элементы в ту же транзакцию.

Используя

collection.clear();
collection.add(new EntityB());

Просто добавляет новый экземпляр и никогда ничего не удаляет. У меня есть orphanRemoval = true для поля коллекции.

ДОБАВЛЕНО:

// Parent entity
@OneToMany(mappedBy = "product", orphanRemoval = true)
private List<Feature> features = new ArrayList<>();

// Child entity
@ManyToOne(cascade = CascadeType.ALL)
private Product product;

// Clear and add attempt
product.getFeatures().clear();

Feature feature = new Feature(product, ls);
product.getFeatures().add(feature);

Ответ 1

Оказывается, что фактическое решение использовало аннотацию @JoinColumn вместо параметра mappedBy = "".

Ответ 2

Вы пытаетесь очистить только одну сторону двунаправленной связи.

Итак, вместо:

collection.clear();

Как объяснено в в этой статье, попробуйте очистить обе стороны и он должен работать:

for(Iterator<Feature> featureIterator = features.iterator(); 
    featureIterator.hasNext(); ) {
    Feature feature = featureIterator .next();
    feature.setProduct(null);
    featureIterator.remove();
}

Также удалите каскад из @ManyToOne и переместите его в @OneToMany.

Обратите внимание на уникальные ограничения

Однако, если у вас есть уникальное ограничение, этот clear + add Anti-Pattern не будет работать, поскольку действие INSERT выполняется до DELETE, как описано в этой статьи.

Правильный способ сделать это - проверить, какие записи нужно удалить, и просто удалить их. Затем добавьте новые и обновите те, которые были изменены. Вот как вы делаете сбор слитно.

Ответ 3

Это действительно кажется ошибкой во многих версиях Hibernate. Я тестировал его с помощью EclipseLink, и он работает там без проблем.

Как обходной путь в Hibernate (проверен в Hibernate 4.3.6-Final): удалите каскадирование в объекте Feature и добавьте CascadeType.PERSIST (или CascadeType.ALL) в Product объект.

Чтобы убедиться, что он не работает, попробуйте следующее:

EntityManager em = ...//fetch the entitymanager. If a Container-managed transaction, you already got it injected
em.getTransaction().begin();//only if resource-local persistence unit. Otherwise if JTA: open the transaction the JTA-specific way (if that was not already done by the container)
Product product = em.find(Product.class, productId);
for (Feature crtFeature : product.getFeatures()) {
    if (!em.contains(crtFeature)) {
       throw new RuntimeException("Feature is not managed, so removeOrpahns cannot work");
    }
}
product.getFeatures().clear();

Feature feature = new Feature(product, ls);
em.persist(feature);//you need this, as there is no cascading from Product to Feature.
product.getFeatures().add(feature);

em.getTransaction().commit();//if you work with a resource-local persistence unit. Otherwise if JTA: commit the transaction the JTA-specific way (if that was not already done by the container)

Ответ 4

В разделе 2.9 "Связи сущностей" спецификация JPA 2.1 говорит:

Если объект, осиротевший, является отдельным, новым или удаленным объектом, семантика orphanRemoval не применяется.

Вы уверены, что ваш объект управляется в контексте персистентности при его удалении из коллекции?

Вы можете исправить это, используя fetch=fetchType.EAGER или fetch joins. В качестве альтернативы (это зависит от вашего варианта использования), может быть достаточно установить соответствующую опцию cascade.

Ответ 5

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

// Parent entity
@OneToMany(mappedBy = "product", orphanRemoval = true)
private List<Feature> features = new ArrayList<>();

// Child entity
@ManyToOne
private Product product;

@ManyToOne
private Description description;

// Another entity (let say descriptions can be shared between features)
@OneToMany(mappedBy = "description", cascade = CascadeType.PERSIST)
private List<Feature> features = new ArrayList<>();

Предположим, что все задействованные объекты управляются, то есть загружаются в контекст персистентности. Теперь мы делаем то же самое, что и OP:

// Clear and add attempt
product.getFeatures().clear();

Feature feature = new Feature(product, ls);
product.getFeatures().add(feature);

Философски, проблема здесь в том, что объектная модель становится непоследовательной, если вы удаляете только функцию из объекта Product, но не из объекта Description. В конце концов, вы хотите, чтобы функция была удалена, но она по-прежнему ссылается на другие объекты. Технически, происходит то, что в объект Feature есть два конфликтующих каскадирования, и результат может зависеть от порядка, в котором они применяются.

Поскольку функция была удалена из коллекции в продукте, применяется удаление сироты, и объект Feature переходит в состояние "удалено" во время следующего сброса, как указано в спецификации JPA 2.1 (2.9). Я добавил акцент на соответствующие части:

Ассоциации, которые указаны как поддержка OneToOne или OneToMany опции orphanRemoval. Следующие действия применяются, когда orphanRemoval:

  • Если объект, который является объектом отношения удаляются из отношения (путем установки отношение к null или удаление объекта из отношения сбор), операция удаления будет применяться к объекту, являющемуся осиротел. Операция удаления применяется во время флеша операция. Функциональность orphanRemoval предназначена для объектов которые находятся в частной собственности, принадлежащей их материнской компании. портативный приложения в противном случае не должны зависеть от конкретного порядка удаление и не должен переназначить объект, который был потерян другое отношение или в противном случае попытаться сохранить его. Если объект сиротство - это отдельный, новый или удаленный объект, семантика orphanRemoval не применяются.

Тем не менее, эта же функция по-прежнему ссылается на объект описания, который имеет PERSIST, каскадирующий объект. Спецификация JPA 2.1 говорит следующее:

Семантика операции флеша, применяемая к объекту X, как следующим образом:

  • Если X - управляемый объект, он синхронизируется с базы данных.

    • Для всех объектов Y, на которые ссылается связь из X, если отношение к Y было аннотировано каскадным элементом value cascade = PERSIST или cascade = ALL, применяется операция persist к Y.

Таким образом, это каскадирование будет выполнять операцию "persist" в объекте Feature, даже если мы не вызываем em.persist() в описании. Этого достаточно для управления описанием, когда выполняется флеш для запуска этого каскадирования с сохранением.

Это означает, что мы делаем именно то, что спецификатор нам сказал, что мы не должны - выполнять удаление сирот и оставаться на одном объекте. То, что, по-видимому, происходит на практике в Hibernate, заключается в том, что обе операции применяются в свою очередь. Сначала операция удаления делает переход объекта объекта в состояние "удалена", а затем операция persist возвращает удаленный объект обратно в управляемый. В результате функция не удаляется из базы данных.