1

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.

xmen-5
  • 1,806
  • 1
  • 23
  • 44

2 Answers2

2

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.

  • Thanks for the clear response. I'm facing some confusion about using Spring Data Jpa with hibernate. is it mandatory to use explicitly the Entitmanager? or the spring data repositories are enough?if you have some references about the good practise in using them both, it will be very helpful. – xmen-5 Aug 16 '19 at 11:44
  • > is it mandatory to use explicitly the Entitmanager? or the spring data repositories are enough? Roughly, Spring Data JPA Repository is more high-level abstraction than JPA entity manager. While both would work, if possible, please, consider using the more high-level abstraction — Spring Data JPA Repository. Please, refer to the question: [Spring + hibernate versus Spring Data JPA: Are they different?](https://stackoverflow.com/questions/37012437/spring-hibernate-versus-spring-data-jpa-are-they-different). – Sergey Vyacheslavovich Brunov Aug 17 '19 at 01:16
  • @zakzak, by the way, are you sure that `Teacher` may have only one `Course`: `@OneToOne(mappedBy = "officialTeacher") private Course course;`? In my humble opinion, one `Teacher` may have many `Courses` (`0..*`). – Sergey Vyacheslavovich Brunov Aug 17 '19 at 01:21
  • I was only trying the differents possible associations between the two entities.In another example I have a Teacher can give many courses...... – xmen-5 Aug 17 '19 at 17:33
0

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.