我下面的例子是为了学习目的。目标是使用复合标识符创建一些示例。不幸的是,我得到了一些奇怪的结果。
实体: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
.
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 检查给定实体的标识符属性。如果标识符属性为 null,则假定该实体是新的。否则,假定它不是新的。
<...>
— Spring Data JPA - 参考文档。
回到问题
回到问题中提到的代码段。
由于Course
实例的标识符属性(主键)不为空,因此 Spring Data JPA 将其视为现有(不是新)实体,并调用EntityManager.merge(…)
方法而不是EntityManager.persist(…)
方法。
其他参考资料
另外,请参考相关问题:java - JPA CascadeType Persist 不适用于 spring 数据。
我曾经遇到过这种情况。我甚至注意到更改为CascadeType.MERGE也可以工作。我搜索了一下,知道 PERSIST 假设已经创建了教师对象,并且它只是将教师 ID 分配给课程对象。PERSIST 表示只需将教师引用放在课程对象中。您还可以看到它提供了课程对象不应更新教师对象的功能。教师对象应单独更新。
更改为合并更新引用以及教师对象。 CascadeType.ALL 因为 MERGE 而工作。