Search code examples
hibernatejpajpa-2.1hibernate-5.x

JPA/Hibernate cascade remove not working


I have these entities:

@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;

    ...
}

Now I just want to perform a simple em.remove(item), but Hibernate does not issues the related DELETE statements for lowerItemRelations/higherItemRelations.

Conversely, for all other fields annotated with @OneToMany(mappedBy = "...", cascade = CascadeType.ALL/REMOVE, orphanRemoval=true) it issues the statements.

Here is a little MySQL log snippet:

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

As you can see, there's no line for deleting from ITEM_RELATION, and I'm expecting something like:

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

Obviously the transaction is rolled back, because of:

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`)) 

Another strange thing is that Hibernate performs an (unnecessary?) UPDATE as first statement.

However,

  • is this different behavior related to the fact that lowerItemRelations/higherItemRelations references the same entity type (although on different fields/columns AND different rows)?

  • Is this a bug or there's a reason for such behavior?

What I tried:

  • initialize the collections
  • initialize and clear the collections (to trigger orphanRemoval)
  • em.remove() each collection element prior to em.remove(item)

without success.

The only working way I found till now is to issue:

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);

I'm using Hibernate 5.2.2.Final on Wildfly 10.1.0.Final

Thanks


As requested here is where em.remove() is called:

@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
    }
}

Solution

  • Ok, it's a bug.

    I found this behavior is coupled with lazy initialization of both collections, so I submitted the issue HHH-11144 and produced a simple test case (also available on GitHub).

    In short, this happens when

    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();