使用复合标识符 @EmbeddedId 和 @OneToOne

Using composite identifiers @EmbeddedId with @OneToOne

我下面的例子是为了学习目的。目标是使用 Composite identifiers 创建一些示例。不幸的是,我得到了一些奇怪的结果。

实体CourseTeacherCoursePk

每门课程只能由一个 Teacher 教授。

我在 class Teacher 和 class Course.

之间使用双向关系 @OneToOne()

每个 Course 包含一个复合主键。在我的示例中,我进行了简化,并使用了仅使用一个属性的复合主键。

我的示例的目标是在持久化课程对象时持久化关联的教师。

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
}

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
}

应该表示一个复合主键的CoursePk

@Embeddable
public class CoursePk implements Serializable {

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

我的运行例子:

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);
    }

我的 table 看起来像:

课程table

course_name | official_teacher_id
     1      |      1

老师table

id  | first_name |last_name
 1  |   null     |  null

可以看到,老师的信息没有持久化,只有id字段。

神奇的是,当我将级联类型更改为 CascadeType.ALL 时,一切都得以保留。

id  | first_name |last_name
 1  |   Jeff     |  Boss

有人可以解释为什么它不适用于 CascadeType.PERSIST

这种情况我遇到过一次。我什至注意到更改为 CascadeType.MERGE 也有效。我对此进行了搜索,发现 PERSIST 假定 Teacher 对象已经创建,并且它只是将 teacherID 分配给 Course 对象。 PERSIST 意味着只将 Teacher 引用放在 Course 对象中。您还可以看到它提供了 Course 对象不应更新 Teacher 对象的功能。教师对象应单独更新。

更改为 MERGE 更新引用以及教师对象。 CascadeType.ALL 因 MERGE 而有效。

Spring 数据 JPA:CrudRepository.save(…) 方法行为

鉴于使用了 Spring Data JPA,CrudRepository.save(…) 方法如何检测要执行的方法调用有一些细节:EntityManager.persist(…)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.

回到问题

回到问题中提到的那段代码。
由于 Course 实例的标识符 属性(主键)不为空,Spring Data JPA 将其视为现有(非新)实体并调用 EntityManager.merge(…)方法而不是 EntityManager.persist(…) 方法。

其他参考文献

另外,请参考相关问题: