将复合标识符 @EmbeddedId 与 @OneToOne 一起使用
Posted
技术标签:
【中文标题】将复合标识符 @EmbeddedId 与 @OneToOne 一起使用【英文标题】:Using composite identifiers @EmbeddedId with @OneToOne 【发布时间】:2019-08-12 18:59:13 【问题描述】:我下面的示例是出于学习目的。目标是使用Composite identifiers 创建一些示例。不幸的是,我得到了一些奇怪的结果。
实体:Course
、Teacher
、CoursePk
。
每门课程只能由一位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(…)
方法保存实体。它通过使用底层 JPAEntityManager
来持久化或合并给定的实体。如果实体还没有被持久化,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 一起使用的主要内容,如果未能解决你的问题,请参考以下文章
使用 @EmbeddedId 映射时出现 Eclipse 错误