Ответ 1
Оказывается, что фактическое решение использовало аннотацию @JoinColumn вместо параметра mappedBy = "".
У меня есть коллекция (список) @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);
Оказывается, что фактическое решение использовало аннотацию @JoinColumn вместо параметра mappedBy = "".
Вы пытаетесь очистить только одну сторону двунаправленной связи.
Итак, вместо:
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, как описано в этой статьи.
Правильный способ сделать это - проверить, какие записи нужно удалить, и просто удалить их. Затем добавьте новые и обновите те, которые были изменены. Вот как вы делаете сбор слитно.
Это действительно кажется ошибкой во многих версиях 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)
В разделе 2.9 "Связи сущностей" спецификация JPA 2.1 говорит:
Если объект, осиротевший, является отдельным, новым или удаленным объектом, семантика orphanRemoval не применяется.
Вы уверены, что ваш объект управляется в контексте персистентности при его удалении из коллекции?
Вы можете исправить это, используя fetch=fetchType.EAGER
или fetch joins
. В качестве альтернативы (это зависит от вашего варианта использования), может быть достаточно установить соответствующую опцию cascade
.
В последнее время я столкнулся с аналогичной проблемой. Для меня проблема заключалась в том, что сироты по-прежнему ссылались на другой управляемый объект, и для этого отношения был установлен каскад 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 возвращает удаленный объект обратно в управляемый. В результате функция не удаляется из базы данных.