问题
I have an existing database table For e.g. T_STUDENTS
on top of which I have to create a JPA entity. All three columns in the table are NON NULL
and the table has a self-reference as mentor_id
id | name | mentor_id
-----|---------|----------
1 | John | 1
-----|---------|----------
2 | Marc | 1
-----|---------|----------
3 | Abby | 2
-----|---------|----------
4 | Jimy | 3
-----|---------|----------
5 | Boni | 4
-----|---------|----------
Each student has a mentor who is also a student. There is a strict OneToOne relationship between the student and the mentor. For id 1, there can't be any mentor, therefore it has the mentor id as it's own id
. The ids are generated using a database sequence.
The problem is that while generating the first record with id 1, hibernate is not assigning the same id as mentor id even though I have created necessary relationships. Since columns can't be null and hibernate is not assigning mentor_id, SQLConstraint nonnull exception is thrown.
Following is how I have created the relationship.
@Entity
@Table(name = 'T_STUDENTS')
public class Student implements Serializable {
@Id
@SequenceGenerator(name = 'S_STUDENTS_SEQUENCE', allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = 'S_STUDENTS_SEQUENCE')
@Column(name = "id")
private Long studentId;
@Column(name = "name", length = 20)
private String studentName;
@OneToOne(optional = false, cascade = CascadeType.NONE)
@JoinColumn(name = "mentor_id")
private Student mentor;
// getters and setters
}
I have set CascadeType.NONE
because else hibernate tries to retrieve 2 id's from sequence and tries to create 2 records which are not desirable.
The problem is how can I insert the very first record. Following is how the insert is being done.
Student student = Student.builder()
.setName('John')
.build();
student = student.toBuilder().setMentor(student).build();
return studentRepository.save(student);
If I change the relationship annotation to @ManyToOne
since technically mentor_id is 1 is mapped to 2 students, I get the following exception
.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation
Edit 1: If relationship type changed to @ManyToOne and cascade is removed following error is observed.
org.hibernate.action.internal.UnresolvedEntityInsertActions.logCannotResolveNonNullableTransientDependencies - HHH000437: Attempting to save one or more entities that have a non-nullable association with an unsaved transient entity. The unsaved transient entity must be saved in an operation prior to saving these dependent entities.
Edit 2: Changed the cascade type to cascade = CascadeType.PERSIST
and hibernate tries to persist the mentor as a separate record. I verified from logs that it tries to retrieve 2 different sequence ids and creates 2 insert queries, with both mentor_id as null.
回答1:
NOTE: Finally I found the root cause. I was using Lombok builder in the JPA entity and it does not support the self-reference relationship yet.
I switched to public setters and it worked fine. See the link below for more details https://github.com/rzwitserloot/lombok/issues/2440#event-3270871969
You can ignore the below solution.
I'm not very proud of the solution, but here is how I achieved it.
1.Removed auto sequence generation from the id.
@Id
@SequenceGenerator(name = 'S_STUDENTS_SEQUENCE', allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = 'S_STUDENTS_SEQUENCE')
@Column(name = "id")
private Long studentId
to
@Id
@Column(name = "id")
private Long studentId;
2.Changed the mapping to the simple foreign key field.
@OneToOne(optional = false, cascade = CascadeType.NONE)
@JoinColumn(name = "mentor_id")
private Student mentorId;
to
@Column(name = "mentor_id")
private Long mentorId;
3.Created a method to retrieve the sequence manually and then assigned the value to both 'id' and 'mentorId'
@Override
public Student saveExtended(Student student) {
Object sequence =
em.createNativeQuery(
"SELECT NEXT VALUE FOR S_STUDENTS_SEQUENCE AS VALUE FROM SYSIBM.SYSDUMMY1")
.getSingleResult();
BigInteger sequenceLong = (BigInteger) sequence;
student = student.toBuilder().id(sequenceLong.longValue()).mentorId(sequenceLong.longValue()).build();
em.persist(student);
em.flush();
return student;
}
来源:https://stackoverflow.com/questions/61289891/jpa-onetoone-self-reference-relationship-with-both-columns-non-null