Home EclipseLink/JPA: Merge on detached object tree causes primary key violation in child object
Reply: 1

EclipseLink/JPA: Merge on detached object tree causes primary key violation in child object

ronin667
1#
ronin667 Published in 2017-12-05 13:40:57Z

I have a problem with JPA and detached entity objects passed from outside the application via JSON deserialization.

I deserialize an object graph from JSON (passed through a HTTP POST request) and then try to update an existing entity from it.

As far as I understand JPA, using EntityManager.merge() on an object should automatically and recursively (depending on cascade) attach it and issue an UPDATE to the DB for all the objects in the tree. However, this apparently only works well for 1) a single entity with no children or 2) the topmost entity in an object graph.

For the child entities, JPA will always issue an INSERT INTO, regardless if the object is already there, and thus run into a primary key violation.

What am I doing wrong?

The relevant part of the implementation is this:

In AdminResource.java:

@Autowire
protected CustomerConfigDAO customerConfigDao; // wrapper for the JPA persistence

@POST
@Consumes(MediaType.APPLICATION_JSON)
public void storeConfigurationData(CustomerConfigEntity config) {
    customerConfigDao.update(config); // calls EntityManager.merge(...)
}

In CustomerConfigEntity.java:

@Entity
public class CustomerConfigEntity implements Serializable {

    @Id
    private String customerID;

    @OneToOne(mappedBy = "customerConfigEntity", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private DefaultValues defaultValues;

    /* ... snip ... */

    @PrePersist
    @PreUpdate
    public void fixBackReference() {
        if(this.defaultValues != null)
            this.defaultValues.setCustomerConfigEntity(this);
    }
}

In DefaultValues.java:

@Entity
public class DefaultValues implements Serializable {

    @Id
    @OneToOne
    @JoinColumn(name = "customerID", referencedColumnName = "cloudID")
    @JsonIgnore
    private CustomerConfigEntity customerConfigEntity;

    /* ... snip ... */

}

Someone suggested to drop the bi-directional one-to-one relationship in favour of a uni-directional one, but since customerConfigEntity is both the foreign AND the primary key in DefaultValues, removing it would require me to introduce a surrogate key. I did this by introducing an auto-incremented key, but the result was that JPA would create a new record in DEFAULTVALUES whenever a CustomerConfigEntity was saved.

ronin667
2#
ronin667 Reply to 2017-12-09 14:55:02Z

I solved the problem by eliminating all the bi-directional relationships and replacing them with unidirectional ones.

In CustomerConfigEntity.java:

public class CustomerConfigEntity implements Serializable {

   // ...

   @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
   @PrimaryKeyJoinColumn(name = "customerId", referencedColumnName = "customerId")
   private DefaultValues defaultValues;

   // ...

}

Note that one must not use mappedBy here.

In DefaultValues.java:

   @Id
   private String customerId;

CustomerConfigEntity has some more relationships which are all of the @OneToMany cardinality.

For those, I did the same thing but had to use @JoinColumn instead of @PrimaryKeyJoinColumn for customerId - despite the fact that the customerId is part of the primary key of the child entity. But using @PrimaryKeyJoinColumn caused JPA to create join tables, which is not what I wanted.

However, I still don't know how I would have to solve this if I really needed bi-directional relationships.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.408828 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO