Spring Data JPA: Cannot save entity with composite primary key which contains foreign key

3.3k views Asked by At

Stumped, utterly stumped...

Assume two entities, Parent and Child, with many Child entities to one Parent. Parent's primary key is of type java.util.UUID, and Child's primary key is a composite of the Parent's UUID and a sequence number.

The short of the issue is when I try to save a new Child using childRepository.save(child), I get the following exception:

Caused by: java.lang.IllegalArgumentException: Cannot convert value of type [com.package.entities.ParentEntity$$_jvst149_0] to required type [java.util.UUID] for property 'parent': PropertyEditor [org.springframework.beans.propertyeditors.UUIDEditor] returned inappropriate value of type [com.package.entities.ParentEntity_$$_jvst149_0]

Please look at my classes below. The best I can tell I am following the JPA spec correctly, so I'm wondering if this is a bug in Spring Data JPA, perhaps specific to UUID type IDs (similar thing has happened before, see DATAJPA-269)

Note I am using spring-boot-starter-data-jpa 1.4.1.RELEASE

Parent.java:

@Entity
@Table(name = "parent")
public class Parent implements Serializable {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private UUID id;

    //...other fields, getters + setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Parent that = (Parent) o;
        return Objects.equals(id, that.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

Child.java

@Entity
@Table(name = "child")
@IdClass(ChildKey.class)
public class Child implements Serializable {

    @Id
    @ManyToOne
    @JoinColumn(name = "parent_id", referencedColumnName = "id", insertable = false, updatable = false)
    private Parent parent;
    @Id
    private Integer seqNum;

    //...other fields, getters + setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Child that = (Child) o;
        return Objects.equals(parent, that.parent) &&
                Objects.equals(seqNum, that.seqNum);
    }

    @Override
    public int hashCode() {
        return Objects.hash(parent, seqNum);
    }
}

ChildKey.class

public class ChildKey implements Serializable {

    private UUID parent;
    private Integer seqNum;

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ChildKey that = (ChildKey) o;
        return Objects.equals(parent, that.parent) &&
                Objects.equals(seqNum, that.seqNum);
    }

    @Override
    public int hashCode() {
        return Objects.hash(parent, seqNum);
    }
}

ParentRepository.java

@Repository
public interface ParentRepository extends JpaRepository<Parent, UUID> {
}

ChildRepository.java

@Repository
public interface ChildRepository extends CrudRepository<Child, ChildKey> {
}

And finally, the code I execute:

@Transactional
public void createChild(Parent parent) {
    // needed to do this to get over "detached entity passed to persist"
    parent = parentRepository.getOne(parent.getId());
    child = new Child();
    child.setParent(parent);
    child.setSeqNum(1);
    childRepository.save(child);
}
3

There are 3 answers

1
nick79 On

In Many-To-One relationship your child entity has it's own ID, and ID from parent entity is FK not a part of PK. Example

0
Jpnh On

In the months since I posted this question, I have not found a suitable answer. I unfortunately had to work around the issue by not using @ManyToOne and instead just reference the parent by UUID:

public class Child implements Serializable {

    @Id
    private UUID parentId;
    @Id
    private Integer seqNum;

I leave JPA ignorant of the foreign key and just let the database throw an error should i violate reference integrity.

1
Alexander Demchenko On

You need to change your ChildKey class:

public class ChildKey implements Serializable {

    private Parent parent; // <-- Parent type instead of UUID
    private Integer seqNum;
    ...

UPD: I read JPA spec. and understood that it is incorrect. But it works in my case.