首页 > 解决方案 > 外键/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 注释的使用是否正确?
请指出并指导哪些更改可以帮助将实体保存到数据库中。

标签: javaspring-bootjpa

解决方案


您的假设是正确的: aStudent可以注册 a Course,但 aCourse将选择许多Students,您正在建模多对一关系。@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;
}

//...

另外,请确保表中的CourseCodeStudent可以null:您的日志似乎是数据库引擎本身报告的错误。


推荐阅读