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;
上下文
我们的 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;