Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Orphans remain in database even with orphanRemoval=true on one-to-many relationship (JPA/Hibernate)

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "company_policies")
@DiscriminatorColumn(name = "rule_name")
public abstract class AbstractPolicyRule implements Serializable {

  @Transient
  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue
  private Long id;
  private String value;

  ...
}

_

@Entity
public class Category implements Serializable {

  @Transient
  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue
  private Long id;
  @Column(name = "category_name")
  private String name;

  @OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL }, orphanRemoval = true)
  @JoinColumn(name = "category_policy_id", referencedColumnName = "id")
  private Set<AbstractPolicyRule> activePolicyRules;

  ...
}

When this Set is updated the existing activePolicyRules have their category_policy_id set to null in the database and new ones are inserted. I'd like for the original ones to be deleted.

I thought adding the orphanRemoval = true would do that but it's not. Other questions I've seen on this appear to have bi-directional relationships and setting the parent to null solves it, but this is not a bi-directional relationship.

Any suggestions?

Using Hibernate 3.5.3

Edit: This only happens when an existing AbstractPolicyRule exists in the database, I remove it from the list and then save the Category again. It's foreign key, category_policy_id, is set to null instead of being deleted.

[DEBUG] Collection found: [domain.category.Category.activePolicyRules#1], was: 
[<unreferenced>] (initialized)
[DEBUG] Flushed: 0 insertions, 2 updates, 0 deletions to 2 objects
[DEBUG] Flushed: 1 (re)creations, 0 updates, 1 removals to 1 collections
...
[DEBUG] Deleting collection: [domain.category.Category2.activePolicyRules#1]
[DEBUG] about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
[DEBUG] update company_policies set category_policy_id=null where category_policy_id=?
[DEBUG] done deleting collection

Also tried a join table since the Hibernate documentation discourages the previous way:

@Entity
public class Category implements Serializable {

  @Transient
  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue
  private Long id;
  @Column(name = "category_name")
  private String name;

  @OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL }, orphanRemoval = true)
  @JoinTable(name = "policy_rule_mapping", 
    joinColumns = @JoinColumn(name = "category_id"), 
    inverseJoinColumns = @JoinColumn(name = "rule_id"))
  private Set<AbstractPolicyRule> activePolicyRules;

  ...
}

This has the same issue. The row in the mapping table is deleted but the AbstractPolicyRule still contains the removed item.

like image 440
Josh Avatar asked Jul 21 '10 21:07

Josh


People also ask

Does orphanRemoval need to be set to true in order to support removing elements from a collection?

If orphanRemoval is set to true, the line item entity will be deleted when the line item is removed from the order. The orphanRemoval attribute in @OneToMany and @oneToOne takes a Boolean value and is by default false.

What is orphanRemoval true in JPA?

orphanRemoval is an entirely ORM-specific thing. It marks "child" entity to be removed when it's no longer referenced from the "parent" entity, e.g. when you remove the child entity from the corresponding collection of the parent entity.

What is orphanRemoval?

The orphanRemoval is an ORM concept, it tells if the child is orphaned. it should also be removed from the database. A child is orphaned when it can`t be accessed from its parent.


1 Answers

I'm using orphanRemoval=true with an unidirectional One-to-Many association without any problem.

And actually, I tested your code and the following scenario (AbstractPolicyRule implementing equals/hashCode correctly):

Category category = new Category();
AbstractPolicyRule policyRule1 = new AbstractPolicyRule("foo");

category.addToActivePolicyRules(policyRule1);
em.persist(category);
em.flush();

assertNotNull(category.getId());
assertNotNull(category.getActivePolicyRules());
assertEquals(1, category.getActivePolicyRules().size());

category.removeFromActivePolicyRules(policyRule1);
category.addToActivePolicyRules(new AbstractPolicyRule("bar"));
// category = em.merge(category); // works with or without
em.flush();
assertEquals(1, category.getActivePolicyRules().size());

just works as expected. Below the generated traces:

22:54:30.817 [main] DEBUG org.hibernate.SQL - insert into Category (id, category_name) values (null, ?)
Hibernate: insert into Category (id, category_name) values (null, ?)
22:54:30.824 [main] TRACE org.hibernate.type.StringType - binding null to parameter: 1
22:54:30.844 [main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 1
...
22:54:30.872 [main] DEBUG org.hibernate.SQL - insert into AbstractPolicyRule (id, name) values (null, ?)
Hibernate: insert into AbstractPolicyRule (id, name) values (null, ?)
22:54:30.873 [main] TRACE org.hibernate.type.StringType - binding 'foo' to parameter: 1
22:54:30.874 [main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 1
...
22:54:30.924 [main] DEBUG org.hibernate.SQL - update AbstractPolicyRule set category_policy_id=? where id=?
Hibernate: update AbstractPolicyRule set category_policy_id=? where id=?
22:54:30.927 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 1
22:54:30.928 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 2
22:54:30.929 [main] DEBUG o.h.p.c.AbstractCollectionPersister - done inserting collection: 1 rows inserted
22:54:30.929 [main] DEBUG org.hibernate.jdbc.AbstractBatcher - Executing batch size: 1
...
22:54:30.945 [main] DEBUG org.hibernate.SQL - insert into AbstractPolicyRule (id, name) values (null, ?)
Hibernate: insert into AbstractPolicyRule (id, name) values (null, ?)
22:54:30.948 [main] TRACE org.hibernate.type.StringType - binding 'bar' to parameter: 1
22:54:30.948 [main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 2
...
22:54:30.990 [main] DEBUG org.hibernate.SQL - update AbstractPolicyRule set category_policy_id=null where category_policy_id=? and id=?
Hibernate: update AbstractPolicyRule set category_policy_id=null where category_policy_id=? and id=?
22:54:30.991 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 1
22:54:30.992 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 2
22:54:30.993 [main] DEBUG o.h.p.c.AbstractCollectionPersister - done deleting collection rows: 1 deleted
22:54:30.993 [main] DEBUG o.h.p.c.AbstractCollectionPersister - Inserting rows of collection: [com.stackoverflow.q3304092.Category.activePolicyRules#1]
22:54:30.994 [main] DEBUG org.hibernate.jdbc.AbstractBatcher - Executing batch size: 1
...
22:54:30.996 [main] DEBUG org.hibernate.SQL - update AbstractPolicyRule set category_policy_id=? where id=?
Hibernate: update AbstractPolicyRule set category_policy_id=? where id=?
22:54:30.997 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 1
22:54:30.998 [main] TRACE org.hibernate.type.LongType - binding '2' to parameter: 2
22:54:31.002 [main] DEBUG o.h.p.c.AbstractCollectionPersister - done inserting rows: 1 inserted
...
22:54:31.015 [main] DEBUG org.hibernate.SQL - delete from AbstractPolicyRule where id=?
Hibernate: delete from AbstractPolicyRule where id=?
22:54:31.017 [main] TRACE org.hibernate.type.LongType - binding '1' to parameter: 1

The first policy rule gets deleted.

If this is not representative of what you're doing, you should maybe provide more code.

Update: Answering a comment from the OP...

Wow I just changed the saveOrUpdate call to merge and now it's removing appropriately. You have any insight why that is?

Just a guess: since orphanRemoval is a JPA thing, I wonder if saveOrUpdate will deal appropriately with it (actually, I thought you were using the EntityManager API since you mentioned JPA).

like image 51
Pascal Thivent Avatar answered Sep 30 '22 13:09

Pascal Thivent