JPA @OnetoOne Самостоятельная связь с обоими столбцами, не равными NULL - PullRequest
1 голос
/ 18 апреля 2020

У меня есть таблица базы данных. Например, T_STUDENTS, поверх которой я должен создать объект JPA. Все три столбца в таблице: NON NULL, а в таблице есть собственная ссылка: mentor_id

id   | name    | mentor_id  
-----|---------|----------
1    | John    | 1
-----|---------|----------
2    | Marc    | 1
-----|---------|----------
3    | Abby    | 2
-----|---------|----------
4    | Jimy    | 3
-----|---------|----------
5    | Boni    | 4
-----|---------|----------

. У каждого ученика есть наставник, который также является учеником. Между учеником и наставником существуют строгие отношения OneToOne. Для идентификатора 1 не может быть никакого наставника, поэтому у него есть идентификатор наставника, так как он id. Идентификаторы генерируются с использованием последовательности базы данных.

Проблема заключается в том, что при создании первой записи с идентификатором 1 hibernate не назначает тот же идентификатор, что и идентификатор наставника, даже если я создал необходимые отношения. Поскольку столбцы не могут быть нулевыми, а hibernate не назначает mentor_id, генерируется ненулевое исключение SQLConstraint.

Ниже описано, как я создал отношение.

@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

}

Я установил CascadeType.NONE потому что иначе hibernate пытается извлечь 2 идентификатора из последовательности и пытается создать 2 записи, которые не желательны.

Проблема в том, как я могу вставить самую первую запись. Ниже описывается, как выполняется вставка.

Student student = Student.builder()
                        .setName('John')
                        .build();
student = student.toBuilder().setMentor(student).build();
return studentRepository.save(student);

Если я поменяю аннотацию отношений на @ManyToOne, поскольку технически значение mentor_id равно 1, сопоставлено 2 студентам, я получаю следующее исключение

.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation 

Редактировать 1 : Если тип отношения изменен на @ManyToOne и удален каскад, появляется следующая ошибка:

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.

Редактировать 2: Изменен тип каскада to cascade = CascadeType.PERSIST и hibernate пытается сохранить наставника в виде отдельной записи. Я проверил из журналов, что он пытается получить 2 разных идентификатора последовательности и создает 2 запроса на вставку, оба значения mentor_id равны нулю.

1 Ответ

0 голосов
/ 18 апреля 2020

ПРИМЕЧАНИЕ : Наконец-то я нашел причину root. Я использовал Lombok Builder в сущности JPA, и он пока не поддерживает отношения самоссылки.

Я переключился на сеттеры publi c, и он работал нормально. Для получения более подробной информации см. Ссылку ниже https://github.com/rzwitserloot/lombok/issues/2440#event -3270871969

Вы можете игнорировать приведенное ниже решение.

Я не очень горжусь решение, но вот как я этого добился.

1. Убрано автоматическое создание последовательности из идентификатора.

@Id
@SequenceGenerator(name = 'S_STUDENTS_SEQUENCE', allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = 'S_STUDENTS_SEQUENCE')
@Column(name = "id")
private Long studentId

до

@Id
@Column(name = "id")
private Long studentId; 

2. Изменил отображение в поле простого внешнего ключа.

@OneToOne(optional = false, cascade = CascadeType.NONE)
@JoinColumn(name = "mentor_id")
private Student mentorId;

до

@Column(name = "mentor_id")
private Long mentorId;

3.Создал метод для извлечения последовательности вручную, а затем присвоил значение обоим 'id' и '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;
}
...