【问题标题】:Why isn't the foreign key field of a Many-to-One relationship being set on insert?为什么插入时没有设置多对一关系的外键字段?
【发布时间】:2019-05-17 19:29:27
【问题描述】:

我的 Spring Web 应用程序允许用户更新“员工”记录以更改字段或添加与此“员工”记录相关的新“电话”记录。但是,在添加新的“电话”记录后提交“员工”记录进行更新时,会抛出 SQL 错误异常。

问题在于“Phone”表上的“Employee”表的“employee_id”外键未在最终提交给数据库的 SQL 插入语句中设置。但是,在更新/合并的“EmployeeEntity”对象引用的“PhoneEntity”JPA 实体对象中,与employee_id 数据库字段关联的属性不为空,它设置为正在更新/合并的“EmployeeEnity”对象.

根据我对 JPA 的理解,当实体记录的插入语句提交到数据库时,应该设置与数据库字段关联的实体属性,但在这种情况下,它不是导致此错误的原因。

我已经尝试使用调试器单步执行,并且我已经验证创建的 PhoneEntity 对象是 EmployeeEntityphones 属性的成员,并且相同的 PhoneEntityemployee属性设置为双向关系中的同一 EmployeeEntity 对象(具有相同的对象 ID)。

我还设置了hibernate.show_sql=true 以查看正在提交到数据库的 SQL 语句,它包含该语句(省略号是更多字段):

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

这意味着它正在为新的PhoneEntity 对象插入一个新的phone

在尝试运行此插入语句后,它会给出 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);
    }

EmployeeEntityPhoneEntity 对象是通过转换从 http 请求反序列化的类似域对象创建的。我将包含更多这部分代码,但正如我所提到的,我已经用我的调试器确认提交给合并的实际实体对象已经是我们期望的phones 的形式字段和employee 字段设置正确,因此最终实体应该是正确的。

【问题讨论】:

    标签: java hibernate jpa jpa-2.0 jpa-2.1


    【解决方案1】:

    在官方JPA specification document(2.1 版)的“3.2.7.1 合并分离实体状态”部分(第 85 页)中我们发现:

    对于由来自X 的关系引用的具有级联元素值cascade=MERGEcascade=ALL 的所有实体YY 以递归方式合并为Y'。对于X 引用的所有此类YX' 设置为引用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 之类的操作,这可能并不总是有意的。

    希望对你有帮助。

    【讨论】:

      【解决方案2】:

      我认为问题在于您正在使用合并。 实体的级联类型设置应为:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-28
        • 2023-03-20
        • 1970-01-01
        • 1970-01-01
        • 2020-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多