java - 外键/JoinColumn 值错误地始终为空
问题描述
我的 Spring Boot JPA 演示应用程序有两个实体,学生和课程,定义如下。
import com.sp.utils.RandomStringIdGenerator;
@Entity(name="Student")
@Table(name="Student_master")
public class Student {
@Id
@GeneratedValue(generator = RandomStringIdGenerator.generatorName)
@GenericGenerator(name = RandomStringIdGenerator.generatorName, strategy = "com.sp.utils.RandomStringIdGenerator")
@Column(name="`Student ID`")
private String StudentId;
@OneToOne
@JoinColumn(name="CourseCode", referencedColumnName ="CourseCode")
private Course course;
@Column(name="`First Name`")
private String firstName;
@Column(name="`Last Name`")
private String lastName;
@Column(name="`Mobile Number`")
private Long mobileNumber;
@Column(name="City")
private String city;
@Column(name="Country")
private String country;
@Column(name="`Email ID`")
private String emailId;
@Column(name="`Date of Birth`")
private Date dateOfBirth;
public String getStudentId() {
return StudentId;
}
public void setStudentId(String StudentId) {
this.StudentId = StudentId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Long getMobileNumber() {
return mobileNumber;
}
public void setMobileNumber(Long mobileNumber) {
this.mobileNumber = mobileNumber;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
}
@Entity(name="course")
@Table(name="course_master")
public class Course {
@Column(name="CourseCode")
@Id
private String courseCode;
@Column(name="CourseDescription")
private String courseDescription;
public String getCourseCode() {
return courseCode;
}
public void setCourseCode(String courseCode) {
this.courseCode = courseCode;
}
public String getCourseDescription() {
return courseDescription;
}
public void setCourseDescription(String courseDescription) {
this.courseDescription = courseDescription;
}
}
course_master 表的 Course 实体是只读的。意思是,已经有固定的条目,不能添加/更新或删除任何内容。
创建/保存学生时,CourseCode 必须是 Course 中的 CourseCode 之一。
尝试使用带有以下输入的邮递员创建学生:
{
"firstName": "Steven",
"lastName": "Wilson",
"mobileNumber": 2256443355,
"city": "Kualalumpur",
"country": "Malaysia"
"emailId": "steven.wilson@att.net",
"dateOfBirth": "1976-09-04",
"courseCode": "MA"
}
尽管上面的输入中提供了 CourseCode,但它总是会产生如下 SQL 错误:
2021-05-23 17:24:44.218 WARN 154196 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1048, SQLState: 23000
2021-05-23 17:24:44.219 ERROR 154196 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : Column 'CourseCode' cannot be null
学生只能注册单门课程。许多学生可以选择一门课程。在这种情况下,@OneToOne 注释的使用是否正确?
请指出并指导哪些更改可以帮助将实体保存到数据库中。
解决方案
您的假设是正确的: aStudent
可以注册 a Course
,但 aCourse
将选择许多Student
s,您正在建模多对一关系。@OneToOne
请在您的实体course
中按如下方式定义字段,而不是使用:Student
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="CourseCode", referencedColumnName ="CourseCode")
private Course course;
我们使用惰性获取而不是适用于多对一关系的默认急切获取策略,但如有必要,请随意使用默认行为。
现在,为了保持你的Student
需要,你需要执行类似于以下的操作:
String courseCode = ...; // from your request
Course course = entityManager.find(Course.class, courseCode);
// Build you `Student` information
Student student = new Student();
// Populate fields appropriately
student.setFirstName(...);
student.setLastName(...);
// Set course information
student.setCourse(course);
entityManager.persist(student);
如果您使用的是 Spring Data JPA,请改用以下内容:
String courseCode = ...; // from your request
Course course = courseRepository.findByCourseCode(courseCode);
// Or
// Optional<Course> optCourse = courseRepository.findById(courseCode);
// if (optCourse.isPresent) { ... }
// Build you `Student` information
Student student = new Student();
// Populate fields appropriately
student.setFirstName(...);
student.setLastName(...);
// Set course information
student.setCourse(course);
studentRepository.save(student);
事实上,正如Vlad Mihalcea 这篇伟大的文章中所解释的,您不需要实际Course
从数据库中获取实体,只需对其进行引用即可建立外键,因此您的代码可以简化如下:
String courseCode = ...; // from your request
// Not the use of getReference
Course course = entityManager.getReference(Course.class, courseCode);
// Build you `Student` information
Student student = new Student();
// Populate fields appropriately
student.setFirstName(...);
student.setLastName(...);
// Set course information
student.setCourse(course);
entityManager.persist(student);
使用 Spring Data JPA:
String courseCode = ...; // from your request
Course course = courseRepository.getById(courseCode);
// Build you `Student` information
Student student = new Student();
// Populate fields appropriately
student.setFirstName(...);
student.setLastName(...);
// Set course information
student.setCourse(course);
studentRepository.save(student);
此外,对于您的评论,要正确接收您的课程代码,您需要在您的Student
班级中提供相应的字段。请考虑例如:
// Define the course code property in addition to course, and define it as neither insertable or updatable
@Column(name="CourseCode", insertable = false, updatable = false)
private String courseCode;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="CourseCode", referencedColumnName ="CourseCode")
private Course course;
//...
// Define setters and getters
public void setCourseCode(String courseCode){
this.courseCode = courseCode;
}
public String getCourseCode() {
return this.courseCode;
}
//...
另外,请确保表中的CourseCode
列Student
可以null
:您的日志似乎是数据库引擎本身报告的错误。
推荐阅读
- c# - 队列函数未使用出队执行
- wpf - 您是使用 Blend 用控件填充 WPF 视图还是手动填充?
- javascript - 在节点js中使用@babel/register时如何编译单个节点包?
- python - 是否可以编写一个 Python 网络爬虫,当元素的文本发生变化时播放 mp3?
- xml - 开发者工作室 6.5。移除端点的设计视图
- php - 无法通过 if 语句检查查询
- java - 尝试从加密列-sql 服务器获取纯文本时,如何解决“在证书存储中找不到带有指纹 null 的证书”?
- python - 如何识别浮点变量?获取 ValueError:无法将字符串转换为浮点数:
- java - 如何在 Firebase 图像标记中使用具有 uint8 输入和输出数据类型的 TFLite 模型
- flutter - Flutter Provider 不会重建小部件