15

I'm trying to use orphanRemoval in Hibernate 4.3.5 / JPA2 objects but it does not seem to be working as I expected. I am not sure, however, if I am doing something incorrect, or if this is still a bug in Hibernate.

Given the following relationships (@Version, getters and setters omitted for brevity):

@Entity
public class Provider implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    private String name;

    @OneToMany(orphanRemoval=true,cascade=CascadeType.REMOVE)
    @JoinColumn(name="provider_id", referencedColumnName="id")
    private List<Contract> contracts;
}


@Entity
public class Contract implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    private String volume;

    @OneToMany(orphanRemoval=true,cascade=CascadeType.REMOVE) // delete any attachments that were previously uploaded with this contract
    @JoinTable(name="contract_attachment", joinColumns = @JoinColumn(name = "contract_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "attachment_id", referencedColumnName = "id"))
    private List<Attachment> attachments;
}

@Entity
public class Attachment implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    private String filename;
}

I would expect that if I remove a contract from the Provider.contracts list, that it would delete corresponding rows from the contract table and all associated attachments from the attachment table. However, only the contract table gets deleted. The attachment table is not modified.

Ex:

    // loop over all contracts and delete the one with the matching id
    for(Iterator<Contract> it = provider.getContracts().iterator(); it.hasNext();){
        Contract c = it.next();
        if( c.getId() == contractId ){
            it.remove();
            break;
        }
    }

Given that the attachments are ManyToOne relative to the Contract table, if the Contract is deleted, then the attachments are orphaned. But even with the orphanRemoval=true, this does not delete the rows from the DB.

I found several issues relating to this for Hibernate 3 (both here on SO, and Jira and elsewhere online), but I had understood that it was fixed in Hibernate 4. But using Hibernate 4.3.5 I am still seeing this issue. From this issue, it seems to work, so I am not sure why I cannot get it functional.

Is there something wrong/missing in my code, or is Hibernate still problematic? Am I required to implement equals and hashCode in any of these entity classes for orphanRemoval to work properly? I tried implementing both methods in Contract and Attachment, but has made no difference.

Looking at the Hibernate logs, it shows Hibernate making the changes to the join table (or FK mapping), but does not actually delete the row from the associated table. I can see Hibernate setting the provider_id=null in the Contract table, but shouldn't it be deleting the Contract row instead?

2014-07-04 15:06:41,333 [main] [-] DEBUG org.hibernate.SQL - 
    /* update
        com.ia.domain.Provider */ update
            provider 
        set
            default_contact_id=?,
            name=?,
            type=?,
            version=?,
            website=? 
        where
            id=? 
            and version=?
Hibernate: 
    /* update
        com.ia.domain.Provider */ update
            provider 
        set
            default_contact_id=?,
            name=?,
            type=?,
            version=?,
            website=? 
        where
            id=? 
            and version=?
2014-07-04 15:06:41,334 [main] [-] TRACE hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - [null]
2014-07-04 15:06:41,334 [main] [-] TRACE hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - [name_3]
2014-07-04 15:06:41,335 [main] [-] TRACE org.hibernate.type.EnumType - Binding [CARRIER] to parameter: [3]
2014-07-04 15:06:41,336 [main] [-] TRACE hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [INTEGER] - [2]
2014-07-04 15:06:41,336 [main] [-] TRACE hibernate.type.descriptor.sql.BasicBinder - binding parameter [5] as [VARCHAR] - [website_3]
2014-07-04 15:06:41,337 [main] [-] TRACE hibernate.type.descriptor.sql.BasicBinder - binding parameter [6] as [BIGINT] - [4]
2014-07-04 15:06:41,338 [main] [-] TRACE hibernate.type.descriptor.sql.BasicBinder - binding parameter [7] as [INTEGER] - [1]
2014-07-04 15:06:41,342 [main] [-] DEBUG org.hibernate.SQL - 
    /* delete one-to-many com.ia.domain.Provider.contracts */ update
            contract 
        set
            provider_id=null 
        where
            provider_id=?
Hibernate: 
    /* delete one-to-many com.ia.domain.Provider.contracts */ update
            contract 
        set
            provider_id=null 
        where
            provider_id=?
2014-07-04 15:06:41,344 [main] [-] TRACE hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - [4]
Community
  • 1
  • 1
Eric B.
  • 23,425
  • 50
  • 169
  • 316

2 Answers2

11

Honestly, I don't know why, but if you add CascadeType.PERSIST (or better CascadeType.ALL) to your @OneToMany relationship in Provider entity it will work as expected.

Probably the Hibernate documentation is lacking in this little detail.

update EclipseLink 2.5.1 with JPA2 does not seem to have this issue

2nd update

In Section 2.9, Entity Relationships, the JPA 2.1 spec says: "If the entity being orphaned is a detached, new, or removed entity, the semantics of orphanRemoval do not apply."

I don't know if your related entities are detached, but if yes then it's not a bug :)

Maciej Dobrowolski
  • 11,561
  • 5
  • 45
  • 67
  • 1
    I tried updating the entity with the `CascadeType.ALL` but it still does not work. – Eric B. Jul 07 '14 at 16:55
  • 1
    @EricB. Did you set `@OneToMany(orphanRemoval=true,cascade=CascadeType.ALL)` on `List contracts;`? Here, with Hibernate 4.3.5.Final and jpa-api-2.1 it works good. It removes `contract_attachment`, `Attachment` and `Contract` entities from DB. – Maciej Dobrowolski Jul 07 '14 at 17:15
  • I did a full clean, and indeed, `CascadeType.ALL` or `CascadeType.PERSIST` does seem to work. However, the problem is that I don't want to do a `CascadeType.PERSIST`! Should `orphanRemoval` not be independent of a cascade persist? – Eric B. Jul 07 '14 at 19:27
  • @EricB. IMO - it should be independent of cascade persist and I cannot imagine any reason why it is not. Probably it is a Hibernate bug - I cannot check now how it performs with different vendor, but I will :) – Maciej Dobrowolski Jul 07 '14 at 19:57
  • Please do and let me know what kind of results you get. – Eric B. Jul 08 '14 at 19:22
  • @EricB. see updated answer it's possible that it's hibernate bug – Maciej Dobrowolski Jul 09 '14 at 08:34
  • No - these are not detached entities – Eric B. Jul 15 '14 at 19:04
  • 4
    Hibernate has huge problems with orphan removal. Searching on the topic over at their issue tracker get many hits. For example, [HHH-6709](https://hibernate.atlassian.net/browse/HHH-6709) and [HHH-6037](https://hibernate.atlassian.net/browse/HHH-6037). – Martin Andersson Dec 05 '14 at 14:08
  • Wrote a [test](https://github.com/MartinanderssonDotcom/java-ee-concepts/blob/master/src/test/java/com/martinandersson/javaee/jpa/mapping/orphanremoval/OrphanRemovalTest.java#L299) and it pass only if the relationship is marked `CascadeType.ALL` or `CascadeType.PERSIST`. On EclipseLink, orphan removal work as expected. – Martin Andersson Dec 05 '14 at 14:11
  • I migrated my application from Glassfish (EclipseLink JPA implementation) to WildFly (Hibernate) and this started to happen to me. – Patryk Dobrowolski Apr 27 '15 at 20:39
-3

I am also getting the issue stated. Though it has been deprecated, below use is working fine with orphan removal:

@org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
Math
  • 2,399
  • 2
  • 20
  • 22
dsrajput
  • 1
  • 1