Using composite identifiers @EmbeddedId with @OneToOne

562 views Asked by At

My example below is for learning purpose. The goal is to create some examples using the Composite identifiers. Unfortunately, I'm getting some strange results.

Entities: Course, Teacher, CoursePk.

Each course should be given by only one Teacher.

I'm using a bidirectional relation @OneToOne() between the class Teacher and the class Course.

Each Course contains a composite primary key. In my example I'm simplifying and I'm using a composite primary key that use only one attribute.

The goal of my example is to persist the associated teacher when the course object is persisted.

The Teacher Class:

@Entity(name = "Teacher")
public class Teacher {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "firstName")
    private String firstName;

    @Column(name = "lastName")
    private String lastName;

    @OneToOne(mappedBy = "officialTeacher")
    private Course course;

    //setter getter constructors are omitted for brevity
}

The Course class:

@Entity(name = "Course")
public class Course {

    @EmbeddedId
    private CoursePk pkCourse;

    @OneToOne(cascade = CascadeType.PERSIST)
    private Teacher officialTeacher;

    //setter getter constructors are omitted for brevity
}

The CoursePk that should represents a composite primary key.

@Embeddable
public class CoursePk implements Serializable {

    @Column(name = "courseName")
    private Integer courseId;
}

My running example:

private void composedPrimaryKey() {
        Teacher teacher = new Teacher("Jeff", "Boss");
        CoursePk composedPK = new CoursePk(1);
        Course course = new Course(composedPK, teacher);
        Course savedCourse = courseRepository.save(course);
    }

With that my tables looks like:

course table

course_name | official_teacher_id
     1      |      1

teacher table

id  | first_name |last_name
 1  |   null     |  null

As you can see, the information of the teacher are not persisted, but only the id field.

Magically when I change the cascade type to CascadeType.ALL, everything is persisted.

id  | first_name |last_name
 1  |   Jeff     |  Boss

Could someone explains why it didn't works with only CascadeType.PERSIST.

2

There are 2 answers

4
Sergey Vyacheslavovich Brunov On BEST ANSWER

Spring Data JPA: CrudRepository.save(…) method behaviour

Given that Spring Data JPA is used, there is some specifics on how the CrudRepository.save(…) method detects the method call to be performed: either EntityManager.persist(…) or EntityManager.merge(…):

5.2.1. Saving Entities

Saving an entity can be performed with the CrudRepository.save(…) method. It persists or merges the given entity by using the underlying JPA EntityManager. If the entity has not yet been persisted, Spring Data JPA saves the entity with a call to the entityManager.persist(…) method. Otherwise, it calls the entityManager.merge(…) method.

Entity State-detection Strategies

Spring Data JPA offers the following strategies to detect whether an entity is new or not:

  • Id-Property inspection (default): By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity is assumed to be new. Otherwise, it is assumed to be not new.

<…>

Spring Data JPA - Reference Documentation.

Back to the question

Coming back to the piece of code mentioned in the question.
Since the identifier property (the primary key) of the Course instance is not null, Spring Data JPA considers it as an existing (not new) entity and calls the EntityManager.merge(…) method instead of the EntityManager.persist(…) method.

Additional references

Additionally, please, refer to the related question: java - JPA CascadeType Persist doesn't work with spring data.

0
AudioBubble On

I face this scenario once. I even noticed that changing to CascadeType.MERGE also works. I search on this and come to know that PERSIST assumes Teacher objects is already created and it just assigned teacherID to Course object. PERSIST means just put Teacher reference in Course object. You can also see this like it provides functionality that Course object should not update Teacher object. Teacher object should be updated separately.

Changing to MERGE update reference as well as Teacher object. CascadeType.ALL works because of MERGE.