将复合标识符 @EmbeddedId 与 @OneToOne 一起使用

Posted

技术标签:

【中文标题】将复合标识符 @EmbeddedId 与 @OneToOne 一起使用【英文标题】:Using composite identifiers @EmbeddedId with @OneToOne 【发布时间】:2019-08-12 18:59:13 【问题描述】:

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

实体CourseTeacherCoursePk

每门课程只能由一位Teacher 授课。

我在 Teacher 类和 Course 类之间使用双向关系 @OneToOne()

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

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

Teacher 类:

@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 类:

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

这样我的表格看起来像:

课程表

course_name | official_teacher_id
     1      |      1

教师桌

id  | first_name |last_name
 1  |   null     |  null

如您所见,老师的信息并没有被持久化,而只有id字段。

神奇的是,当我将级联类型更改为CascadeType.ALL 时,所有内容都被持久化了。

id  | first_name |last_name
 1  |   Jeff     |  Boss

有人可以解释为什么它不能仅与 CascadeType.PERSIST 一起使用。

【问题讨论】:

【参考方案1】:

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

鉴于使用了 Spring Data JPA,有一些关于 CrudRepository.save(…) 方法如何检测要执行的方法调用的细节:EntityManager.persist(…)EntityManager.merge(…)

5.2.1。保存实体

可以使用CrudRepository.save(…) 方法保存实体。它通过使用底层 JPA EntityManager 来持久化或合并给定的实体。如果实体还没有被持久化,Spring Data JPA 通过调用entityManager.persist(…) 方法来保存实体。否则,它会调用entityManager.merge(…) 方法。

实体状态检测策略

Spring Data JPA 提供以下策略来检测实体是否为新实体:

Id-Property 检查(默认):默认情况下,Spring Data JPA 检查给定实体的标识符属性。如果标识符属性为空,则假定实体是新的。否则,假定它不是新的。

——Spring Data JPA - Reference Documentation.

回到问题

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

其他参考

另外,请参考相关问题:java - JPA CascadeType Persist doesn't work with spring data。

【讨论】:

感谢您的明确回复。我在使用 Spring Data Jpa 和休眠时遇到了一些困惑。是否必须明确使用 Entitmanager?还是 spring 数据存储库就足够了?如果您有一些关于使用它们的良好实践的参考,那将非常有帮助。 > 是否必须明确使用 Entitmanager?还是弹簧数据存储库就足够了?粗略地说,Spring Data JPA Repository 是比 JPA 实体管理器更高级的抽象。虽然两者都可以,但如果可能,请考虑使用更高级的抽象——Spring Data JPA Repository。请参考问题:Spring + hibernate versus Spring Data JPA: Are they different?. @zakzak,顺便问一下,你确定Teacher 可能只有一个Course@OneToOne(mappedBy = "officialTeacher") private Course course;?在我看来,一个Teacher 可能有很多Courses (0..*)。 我只是在尝试两个实体之间的不同可能关联。在另一个例子中,我有一个老师可以教很多课程......【参考方案2】:

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

更改为 MERGE 更新参考以及教师对象。 CascadeType.ALL 因为 MERGE 而起作用。

【讨论】:

以上是关于将复合标识符 @EmbeddedId 与 @OneToOne 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

JPA @EmbeddedId 未生成序列

使用 @EmbeddedId 映射时出现 Eclipse 错误

使用@EmbeddedId进行映射时出现Eclipse错误

将 @EmbeddedId 与 JpaRepository 一起使用

hibernate的@EmbeddedId嵌入式主键详解

复合标识符,但使用 ID 生成器而不是手动分配 + Symfony2