Почему поле внешнего ключа отношения «многие к одному» не устанавливается при вставке? - PullRequest
0 голосов
/ 17 мая 2019

Веб-приложение My Spring позволяет пользователям обновлять записи «Сотрудник», изменять поля или добавлять новые записи «Телефон», связанные с этой записью «Сотрудник».Однако, когда запись «Сотрудник» отправляется на обновление после добавления новой записи «Телефон», возникает исключение ошибки SQL.

Проблема заключается в том, что внешний ключ «employee_id» в таблице «Телефон»таблица «Сотрудник» не задана в возможном операторе вставки SQL, переданном в базу данных.Однако в объекте сущности JPA «PhoneEntity», на который ссылается обновленный / объединенный объект «EmployeeEntity», свойство, связанное с полем базы данных employee_id, не равно нулю, для него установлено значение «EmployeeEnity»: объект обновляется / объединяется.

Исходя из моего понимания JPA, наличие свойства сущности, связанного с полем базы данных, должно устанавливать его, когда оператор вставки для записи сущности передается в базу данных, но в этом случае это не вызываетэта ошибка.

Я попытался пройти через отладчик, и я убедился, что созданный объект PhoneEntity является членом свойства EmployeeEntity phones и того же * 1011Свойство * 'employee установлено для того же объекта EmployeeEntity (с теми же идентификаторами объекта) в двунаправленном отношении.

Я также установил hibernate.show_sql=true, чтобы увидеть отправляемую инструкцию SQLв базу данных и включает в себя оператор (с эллипсами, являющимися большим количеством полей):

Hibernate: 
    insert 
    into
        phone
        (id, employee_id, ...) 
    values
        (?, ?, ...)

Это означает, что он вставляет новый phone для нового PhoneEntity объекта.

После попытки выполнить этот оператор вставки выдает ошибку SQL "Столбец 'employee_id' не может быть пустым".Однако, как я уже говорил ранее, я проверил с помощью отладчика, и свойство employee действительно установлено на объект EmployeeEntity.

Это упрощенный пример того, как выглядит мой код:

@Entity
@Table(name = "employee")
public class EmployeeEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;

    @OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST})
    private Set<PhoneEntity> phones = new HashSet<>();

...
}

@Entity
@Table(name = "phone")
public class PhoneEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "employee_id", nullable = false)
    private EmployeeEntity employee;

...
}

С таблицами, структура которых создается с помощью следующих операторов SQL.

CREATE TABLE employee (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
...
);

CREATE TABLE phone (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    employee_id INT NOT NULL,
...
    FOREIGN KEY(employee_id) REFERENCES employee(id)
);

И далее, когда он фактически отправляет обновления менеджеру сущностей для обновления базы данных.

    public void update(EmployeeDomain employee) {
        EmployeeEntity entity = employeeDomainToEntity.transform(employee)
        getEntityManager().merge(entity);
    }

Объекты EmployeeEntity и PhoneEntity создаются путем преобразования похожих объектов домена, которые в свою очередь были десериализованы из запроса http.Я бы включил больше этого раздела кода, но, как я уже упоминал, я уже подтвердил с помощью моего отладчика, что реальные объекты сущностей, отправляемые на слияние, уже находятся в форме, которую мы ожидали с phonesполя и employee поля установлены правильно, поэтому конечные объекты должны быть правильными.

Ответы [ 2 ]

3 голосов
/ 18 мая 2019

В официальном документе спецификации JPA (версия 2.1) в разделе " 3.2.7.1 Слияние состояния отдельного объекта " (стр. 85) мы находим:

Для всех объектов Y, на которые ссылаются отношения из X, имеющие значение элемента каскада cascade=MERGE или cascade=ALL, Y рекурсивно объединяется в Y'.Для всех таких Y, на которые ссылается X, X' устанавливается на ссылку Y'.(Обратите внимание, что если X управляется, то X является тем же объектом, что и X '.)

Это объясняет, что вам не хватает cascade=MERGE для аннотации поля phones.

Как предложено в ответе thanh ngo , вышеупомянутое определение (или: объяснение), таким образом, переводится в:

@OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();

В качестве альтернативы, вы также можете использовать cascade=CascadeType.ALL.Однако это также может включать такие операции, как CascadeType.REMOVE, которые не всегда могут быть предназначены.

Надеюсь, это поможет.

1 голос
/ 18 мая 2019

Я думаю, проблема в том, что вы используете слияние. Настройка каскадного типа для объекта должна быть:

@OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Set<PhoneEntity> phones = new HashSet<>();
...