2

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
    }
}
Michele Mariotti
  • 7,372
  • 5
  • 41
  • 73

2 Answers2

1

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();
Michele Mariotti
  • 7,372
  • 5
  • 41
  • 73
0

My bet is that Unit is refferencing something else and is not exclusive to your root - ItemRelation in this case, thus cannot be deleted.

Your mapping suggest that Unit can have multiple ItemRelations so this is most probably the issue here. You would have to "break" the relation first by removing mutual references on both sides (or at least on entity that helds FK, or any of them if join table is used)

Antoniossss
  • 31,590
  • 6
  • 57
  • 99
  • I don't understand your answer... Of course Unit is referencing other entities (like UnitRegistrations that I put in example), in fact the goal is to delete all of them at once (where cascaded). The mapping between Item <-> ItemRelation is completely expressed in the snippet (@OneToMany/@ManyToOne with @JoinColumn), you see there's no @JoinTable. Please, can you elaborate? – Michele Mariotti Sep 28 '16 at 11:50
  • I know that action can cascade from top to bottom where it ends, but what you want is cascade process bounce back from the bottom to the top and start over again with N number of other related entities. IMHO `cascade` will not handle this, but i can be wrong. – Antoniossss Sep 28 '16 at 11:59
  • Nope, you're wrong. I don't want to delete *related items*, I just want to delete the *managedItem* and all of its *lowerItemRelations* and *higherItemRelations* ItemRelation entities, which hold the two FKs to UNIT table - as you can see ItemRelation.parent and ItemRelation.child are not declaring the cascade attribute, so I'm not asking hibernate to delete the full graph, just the target entity and the immediate ItemRelation neighbours (where cascade is declared) :) – Michele Mariotti Sep 28 '16 at 12:06