2016-09-28 5 views
1

У меня есть эти объекты:JPA/Hibernate каскадного удаления не работает

@Entity 
public class Item extends Unit 
{ 
    // @Id is in superclass 

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) 
    private Set<ItemRelation> lowerItemRelations = new LinkedHashSet<>(); 

    @OneToMany(mappedBy = "child", cascade = CascadeType.ALL, orphanRemoval = true) 
    private Set<ItemRelation> higherItemRelations = new LinkedHashSet<>(); 

    // this too is in superclass 
    @OneToMany(mappedBy = "unit", cascade = CascadeType.REMOVE, orphanRemoval = true) 
    @OrderBy("date") 
    protected Set<UnitRegistration> registrations = new LinkedHashSet<>(); 

    ... 
} 

@Entity 
@Table(name = "ITEM_RELATION", 
    indexes = @Index(columnList = "PARENT_ID, CHILD_ID", unique = true)) 
public class ItemRelation extends AbstractEntity 
{ 
    // @Id is in superclass 

    @ManyToOne(optional = false) 
    @JoinColumn(name = "PARENT_ID") 
    private Item parent; 

    @ManyToOne(optional = false) 
    @JoinColumn(name = "CHILD_ID") 
    private Item child; 

    @NotNull 
    @Min(0) 
    @Column(nullable = false, columnDefinition = "INT DEFAULT 1 NOT NULL") 
    private int quantity = 1; 

    ... 
} 

Теперь я просто хочу, чтобы выполнить простой em.remove(item), но Hibernate не соответствующие выпуски DELETE заявления на lowerItemRelations/higherItemRelations.

И наоборот, для всех остальных полей, аннотированных @OneToMany(mappedBy = "...", cascade = CascadeType.ALL/REMOVE, orphanRemoval=true), он выдает утверждения.

Вот небольшой лог MySQL фрагмент:

2016-09-28T08:47:52.090453Z 13 Query update UNIT set CODE='CE13000003167', ... where ID=132241 and version=1 
2016-09-28T08:47:52.094971Z 13 Query delete from UNIT_ACTION where PARENT_ID=132241 
2016-09-28T08:47:52.134999Z 13 Query update AUTHORIZATION set UNIT_ID=null where UNIT_ID=132241 
2016-09-28T08:47:52.158014Z 13 Query delete from UNIT_DOCUMENT where PARENT_ID=132241 
2016-09-28T08:47:52.248074Z 13 Query delete from UNIT_PRODUCT where UNIT_ID=132241 
2016-09-28T08:47:52.315641Z 13 Query delete from UNIT_PROJECT where UNIT_ID=132241 
2016-09-28T08:47:52.586008Z 13 Query delete from ITEM_ALTERNATIVE where ITEM_ID=132241 
2016-09-28T08:47:52.853350Z 13 Query delete from AUTHORIZATION where ID=714491 
2016-09-28T08:47:52.910835Z 13 Query delete from UNIT_REGISTRATION where ID=173505 
2016-09-28T08:47:52.980887Z 13 Query delete from UNIT where ID=132241 and version=1 
2016-09-28T08:47:53.133290Z 13 Query rollback 

Как вы можете видеть, что нет строки для удаления из ITEM_RELATION, и я ожидал что-то вроде:

0000-00-00T00:00:00.000000Z 13 Query delete from ITEM_RELATION where PARENT_ID=132241 
0000-00-00T00:00:00.000000Z 13 Query delete from ITEM_RELATION where CHILD_ID=132241 

Очевидно, что сделка откатывается из-за:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`edea2`.`item_relation`, CONSTRAINT `FK_ITEM_RELATION_CHILD_ID` FOREIGN KEY (`CHILD_ID`) REFERENCES `unit` (`ID`)) 

Еще одна странная вещь: Hibe rnate выполняет (необязательно?) UPDATE как первый оператор.

Однако

  • является это другое поведение, связанное с тем, что lowerItemRelations/higherItemRelations ссылки тот же тип объекта (хотя на разных полей/столбцов и строк разных)?

  • Это ошибка или есть основания для такого поведения?

Что я пробовал:

  • инициализировать коллекциям
  • Initialize и очистить коллекции (для запуска orphanRemoval)
  • em.remove() каждый элемент коллекции до em.remove(item)

без успех.

Единственный рабочий способ я нашел до сих пор является выпуск:

CriteriaBuilder builder = em.getCriteriaBuilder(); 
CriteriaDelete<ItemRelation> delete = builder.createCriteriaDelete(ItemRelation.class); 
Root<ItemRelation> rel = delete.from(ItemRelation.class); 
delete.where(builder.or(
    builder.equal(rel.get(ItemRelation_.parent), managedItem), 
    builder.equal(rel.get(ItemRelation_.child), managedItem))); 

em.flush(); 

em.remove(managedItem); 

Я использую Hibernate 5.2.2.Final на Wildfly 10.1.0.Final

Благодарности


В соответствии с просьбой здесь, где em.remove() называется:

@Stateless 
@Local 
public class PersistenceService implements Serializable 
{  
    @PersistenceContext 
    private EntityManager em; 

    ... 

    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    public <T> void delete(T entity) 
    { 
     T managed; 

     if(!em.contains(entity)) 
     { 
      Class<T> entityClass = EntityUtils.getClass(entity); 
      Object entityId = EntityUtils.getIdentifier(entity); 

      managed = em.find(entityClass, entityId); 
     } 
     else 
     { 
      managed = entity; 
     } 

     em.remove(managed); 

     // em.flush(); // just for debugging 
    } 
} 
+0

Не могли бы вы apend отрезала кода, где 'em.remove (пункт)' терпит неудачу? – Antoniossss

+0

См. Обновление. Он не работает на tx commit (или em.flush() при раскомментировании) –

ответ

0

Хорошо, это ошибка.

Я нашел, что это поведение связано с ленивой инициализацией обеих коллекций, поэтому я отправил вопрос HHH-11144 и выпустил простой тестовый пример (также доступен по адресу GitHub).

Короче говоря, это происходит, когда

EntityManager em = emf.createEntityManager(); 
EntityTransaction tx = em.getTransaction(); 

tx.begin(); 

Item item = em.createQuery("select x from Item x where x.code = 'first'", Item.class).getSingleResult(); 

Set<ItemRelation> lowerItemRelations = item.getLowerItemRelations(); 
Hibernate.initialize(lowerItemRelations); 

// initializing 'higherItemRelations' prevents orphanRemoval to work on 'lowerItemRelations' 
Set<ItemRelation> higherItemRelations = item.getHigherItemRelations(); 
Hibernate.initialize(higherItemRelations); 

lowerItemRelations.clear(); 

tx.commit(); 
em.close(); 
0

Моя ставка заключается в том, что Unit реферирует что-то еще и не является исключительным для вашего корня - ItemRelation в этом случае, поэтому его нельзя удалить.

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

+0

Я не понимаю ваш ответ ... Конечно, Unit ссылается на другие объекты (например, UnitRegistrations, которые я приводил в пример), на самом деле цель состоит в том, чтобы удалить все они сразу (где каскадно). Отображение между Item <-> ItemRelation полностью выражено в фрагменте (@ OneToMany/@ ManyToOne с @JoinColumn), вы видите, что нет @JoinTable. Пожалуйста, вы можете уточнить? –

+0

Я знаю, что действие может каскадироваться сверху вниз, где оно заканчивается, но то, что вы хотите, - это каскадный процесс, возвращающийся снизу вверх и начинающий снова с N числа других связанных объектов. IMHO 'cascade' не справится с этим, но я могу ошибаться. – Antoniossss

+0

Нет, вы ошибаетесь. Я не хочу удалять * связанные элементы *, я просто хочу удалить * managedItem * и все его * lowerItemRelations * и * upperItemRelations * ItemRelation сущности, которые удерживают две таблицы FK в таблице UNIT, - как вы можете видеть ItemRelation. parent и ItemRelation.child не объявляют атрибут cascade, поэтому я не прошу hibernate удалить полный график, только целевой объект и непосредственные сокеты ItemRelation (где объявлен каскад) :) –

Смежные вопросы