JPA saveAll 在 OneToMany 上的 INSERT INTO 之后强制更新调用

JPA saveAll forces UPDATE call after INSERT INTO on OneToMany

上下文

我们的 PUT 端点本质上是通过首先对所有实体调用 DELETE,然后调用 INSERT INTO.

来执行 UPSERT

但是,在调试时,我发现 JPA 也发出了一些 UPDATE 调用,出于性能原因,我试图避免这种情况。我希望只有 2 个 INSERT 调用:一个用于 Parent,一个用于所有 Child

这似乎只发生在 @OneToMany 个实体上。

如果有帮助,我们使用 EclipseLink 作为我们的 JPA 提供程序,并且 batch-writing 已激活。该应用程序是使用 Spring 数据构建的。

型号

@Entity
@Table
@Data // org.lombok
@IdClass(ParentPrimaryKey.class)
public class Parent {

    @Id
    @Column(name = "product", updatable = false)
    private String product;

    @Id
    @Column(name = "source", updatable = false)
    private String source;

    @Column
    private String whatever;


    @OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true)
    @JoinColumns({
            @JoinColumn(name = "product", referencedColumnName = "product"),
            @JoinColumn(name = "source", referencedColumnName = "source")
    })
    private List<Child> details;
}
@Entity
@Table
@Data // org.lombok
@IdClass(ChildPrimaryKey.class)
public class Child {

    @Id
    @Column(name = "product", updatable = false)
    private String product;

    @Id
    @Column(name = "source", updatable = false)
    private String source;

    @Id
    @Column(name = "extra", updatable = false)
    private String extra;

    @Column
    private String foo;

    @Column
    private String bar;
}

相关的当前日志

INSERT INTO PARENT (product, source, whatever) VALUES (?, ?, ?)
    bind => [A, A, A]
    bind => [B, B, A]

INSERT INTO CHILD (product, source, extra, foo, bar) VALUES (?, ?, ?, ?, ?)
    bind => [A, A, 1, B, B]
    bind => [A, A, 2, C, C]
    bind => [A, A, 3, D, D]
    bind => [B, B, 1, B, B]
    bind => [B, B, 2, C, C]
    bind => [B, B, 3, D, D]

UPDATE CHILD SET product = ?, source = ? WHERE ((product = ?) AND (source = ?) AND (extra = ?))
    bind => [A, A, A, A, 1]
    bind => [A, A, A, A, 2]
    bind => [A, A, A, A, 3]
    bind => [B, B, B, B, 1]
    bind => [B, B, B, B, 2]
    bind => [B, B, B, B, 3]

已尝试修复

基于this answer,我尝试添加nullable=false :

public class Child {

    @Id
    @Column(name = "product", updatable = false, nullable = false)
    private String product;

    @Id
    @Column(name = "source", updatable = false, nullable = false)
    private String source;

    @Id
    @Column(name = "extra", updatable = false, nullable = false)
    private String extra;

    // the rest is identical...
}

生成的日志

可以看出,这确实删除了 UPDATE 调用,但它增加了将向我们的数据库发出的总查询量,因此意味着性能实际上会更差(它会去从总共 3 个请求,到 2N):

INSERT INTO PARENT (product, source, whatever) VALUES (?, ?, ?)
    bind => [A, A, A]
INSERT INTO CHILD (product, source, extra, foo, bar) VALUES (?, ?, ?, ?, ?)
    bind => [A, A, 1, B, B]
    bind => [A, A, 2, C, C]
    bind => [A, A, 3, D, D]


INSERT INTO PARENT (product, source, whatever) VALUES (?, ?, ?)
    bind => [B, B, A]
INSERT INTO CHILD (product, source, extra, foo, bar) VALUES (?, ?, ?, ?, ?)
    bind => [B, B, 1, B, B]
    bind => [B, B, 2, C, C]
    bind => [B, B, 3, D, D]

两个可能更适合您的选项。

第一个选项

保持其他一切不变:

@Entity
@Table
@Data // org.lombok
@IdClass(ParentPrimaryKey.class)
public class Parent {

    @Id
    @Column(name = "product", updatable = false)
    private String product;

    @Id
    @Column(name = "source", updatable = false)
    private String source;

    @Column
    private String whatever;


    @OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true)
    @JoinColumns({
            @JoinColumn(name = "product", referencedColumnName = "product", insertable = false, updatable = false),
            @JoinColumn(name = "source", referencedColumnName = "source", insertable = false, updatable = false)
    })
    private List<Child> details;
}

这将确保子 table 中的外键仅由其各自的 @Id 映射控制。

第二个选项

或者,您可以反过来,让 JPA 使用所谓的派生 ID 完全为您设置子实体实例中的值:

@Entity
@IdClass(ChildPrimaryKey.class)
public class Child {
    @Id
    @JoinColumns({
            @JoinColumn(name = "product", referencedColumnName = "product"),
            @JoinColumn(name = "source", referencedColumnName = "source")
    })
    Parent parent;

    @Id
    @Column(name = "extra")
    private String extra;

    // the rest is identical...
}

public class ChildPrimaryKey {
    ParentPrimaryKey parent;
    String extra;
}

这将确保根据父实例的值在子 table 中设置来源和产品列。 Parent 的详细信息映射然后可以删除连接列定义并仅声明它由 Child 的父关系映射:

@OneToMany(mappedBy = "parent",cascade = {CascadeType.ALL}, orphanRemoval = true)
private List<Child> details;