Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA 2 / Hibernate orphan removal still not working with @OneToMany?

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]
like image 294
Eric B. Avatar asked Jul 04 '14 18:07

Eric B.


1 Answers

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

like image 116
Maciej Dobrowolski Avatar answered Oct 02 '22 10:10

Maciej Dobrowolski