Cui Pengfei 崔鹏飞
Cui Pengfei 崔鹏飞

Reputation: 8305

JPA / Hibernate, duplicate pkey error when updating entity that is a subclass of a base class that uses IdClass for composite primary key

How to reproduce this issue

https://github.com/cuipengfei/Spikes/tree/master/jpa/ClassIdUpdateIssue

this code can reproduce the issue, just run the main method then the error will happen.

https://github.com/gregturn/spring-data-jpa-id-class-issues/tree/main/src/test/java/com/example/demo

Just make sure you have docker installed and run these 👆 unit tests.

The unit test with EmbeddedId will be ok.

But the one with IdClass will fail, while we expect it to be successful.

Issue Description

There is a base class like this:


@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula("case when vip_number is not null then 'vip' else 'normal' end")
@DiscriminatorValue("normal")
@IdClass(CustomerPK.class)
public class CustomerWithIdClass implements Serializable {

    private String firstName;
    private String lastName;

    @Id
    private Long versionId;
    @Id
    private Long unitId;

    protected CustomerWithIdClass() {
    }

   // getter and setters ......
}

Its IdClass is like this:

@NoArgsConstructor
@EqualsAndHashCode
@Embeddable
public class CustomerPK implements Serializable {

    private Long unitId;

    private Long versionId;

    public void setUnitId(Long unitId) {
        this.unitId = unitId;
    }

    public void setVersionId(Long versionId) {
        this.versionId = versionId;
    }
}

Then it has a subclass:

@Entity
@DiscriminatorValue("vip")
public class VipCustomerWithIdClass extends CustomerWithIdClass {
    private String vipNumber;

    public VipCustomerWithIdClass() {
    }

    public VipCustomerWithIdClass(String firstName, String lastName, String vipNumber) {
        super(firstName, lastName);
        this.vipNumber = vipNumber;
    }

    public String getVipNumber() {
        return vipNumber;
    }

    public void setVipNumber(String vipNumber) {
        this.vipNumber = vipNumber;
    }
}

The subclass only adds one additional field, nothing else fancy.

Then when I try to persist an instance of the subclass like this:

        CustomerWithIdClass customer = new CustomerWithIdClass("a", "b");
        customer.setVersionId(123L);
        customer.setUnitId(456L);

        repository.save(customer);//save object of base class, ok

        customer.setFirstName("a2");
        repository.save(customer);//modify object of base class and save again, ok

        VipCustomerWithIdClass vipCustomer = new VipCustomerWithIdClass("a", "b", "888");
        vipCustomer.setVersionId(987L);
        vipCustomer.setUnitId(654L);

        repository.save(vipCustomer);//save object of subclass, ok

        vipCustomer.setVipNumber("999");
        repository.save(vipCustomer);//modify object of subclass and save again, NOT OK
        // ↑ THIS FAILS BECAUSE OF PRIMARY KEY CONFLICT. INSERT STATEMENT WAS USED INSTEAD OF UPDATE, WHY?
        // this failure only happens when:
        // 1. base class uses IdClass for composite primary key
        // 2. saving an instance of the subclass for the second time after modification

The Error

Then there will be an error of duplicate pkey when I try to save the instance of the subclass for the second time after some modification.

The error seems to be related with the @IdClass annotation, because I have tried using @EmbeddedId and @Embeddable for composite pkey, then the error does not happen.

The question

What is the root reason of this issue? Is @IdClass not supposed to be used for this scenario?

links

https://github.com/spring-projects/spring-data-jpa/issues/2767

https://hibernate.atlassian.net/browse/HHH-16054

Upvotes: 0

Views: 303

Answers (1)

Christian Beikov
Christian Beikov

Reputation: 16400

You have to assign your local customer variables to the object that is returned by repository.save(), since internally it calls EntityManager.merge() which kind of mandates this.

Upvotes: 0

Related Questions