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
.
CrudRepository.save(…)
method behaviourGiven 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 JPAEntityManager
. If the entity has not yet been persisted, Spring Data JPA saves the entity with a call to theentityManager.persist(…)
method. Otherwise, it calls theentityManager.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.
<…>
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.
Additionally, please, refer to the related question: java - JPA CascadeType Persist doesn't work with spring data.