JPA 查询 deleteById 以获取使用 IdClass 声明的复合键
JPA Query to deleteById for a Composite Key declared using IdClass
问题
如果我使用 @IdClass
声明了我的(复合)主键,我该如何编写我的 @Query
才能使用 [=17= 发出 DELETE
查询] ?
副题
尽管使用 @Query
,CASCADE 是否会实际触发删除关联的 AnotherEntity
?
当前型号
@Entity
@Table(name = "myentity")
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@IdClass(MyIdClass.class)
public class MyEntity {
@Id
@Column(updatable = false)
private String foo;
@Id
@Column(updatable = false)
private String bar;
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "my_foreign_key", referencedColumnName = "external_pk")
private AnotherEntity anotherEntity;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MyIdClass implements Serializable {
private String foo;
private String bar;
}
@Entity
@Table(name = "anotherentity")
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class AnotherEntity {
@Id
@Column(name = "external_pk", nullable = false, updatable = false)
private String externalPk;
}
我读过的内容
一些资源:
而且我还发现 this SO question 这似乎与我要找的东西非常接近,但不幸的是没有答案。
目标
类似于:
@Repository
public interface MyCRUDRepository extends CrudRepository<MyEntity, MyIdClass> {
@Modifying
@Query("DELETE FROM myentity m WHERE m IN ?1") // how do I write this?
void deleteAllWithIds(Collection<MyIdClass> ids);
}
最终,我想这样做来批处理我的 DELETE 请求以提高性能。
我试图避免的陷阱
我知道有一个 deleteAll(Iterable<? extends MyEntity>)
但实际上我需要从这些实体开始,这将需要额外调用数据库。
还有 deleteById(MyIdClass)
,但实际上总是在将单个 DELETE 语句作为事务发送之前发出 findById
:不利于性能!
可能不相关的精度
我不确定这是否有帮助,但我的 JPA 提供程序是 EclipseLink
。我的理解是批处理请求有一些属性,而这正是我最终要使用的。
但是,我不完全确定要进行该批处理的内部要求是什么。例如,如果我在 for-loop
中执行 deleteById
,交替的 SELECT
和 DELETE
语句是否会阻止批处理的发生?关于这方面的文档非常少。
我认为您正在寻找可以生成这样的查询的东西
delete from myentity where MyIdClass in (? , ? , ?)
你可以试试这个,它可能对你有帮助。
如果您是肯定的 IdClass is a better choice than EmbeddedId in your situation,您可以向 MyEntity 添加一个额外的映射:
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "foo",
column = @Column(name = "foo", insertable = false, updatable = false)),
@AttributeOverride(name = "bar",
column = @Column(name = "bar", insertable = false, updatable = false))})
private MyIdClass id;
并在您的存储库中使用它:
@Modifying
@Query("DELETE FROM MyEntity me WHERE me.id in (:ids)")
void deleteByIdIn(@Param("ids") Collection<MyIdClass> ids);
这将生成一个查询:delete from myentity where bar=? and foo=? [or bar=? and foo=?]...
,导致此测试通过(具有以下 table 条记录 insert into myentity(foo,bar) values ('foo1', 'bar1'),('foo2', 'bar2'),('foo3', 'bar3'),('foo4', 'bar4');
):
@Test
@Transactional
void deleteByInWithQuery_multipleIds_allDeleted() {
assertEquals(4, ((Collection<MyEntity>) myEntityRepository.findAll()).size());
MyIdClass id1 = new MyIdClass("foo1", "bar1");
MyIdClass id2 = new MyIdClass("foo2", "bar2");
assertDoesNotThrow(() -> myEntityRepository.deleteByIdIn(List.of(id1, id2)));
assertEquals(2, ((Collection<MyEntity>) myEntityRepository.findAll()).size());
}
provided great insight, but it seems like the approach only works for Hibernate。 EclipseLink 是我被迫使用的 JPA 提供程序,对于相同的代码,它会不断向我抛出错误。
我找到的唯一有效解决方案是以下技巧:
Spring@Repository
的 JPA 查询
@Repository
public interface MyCRUDRepository extends CrudRepository<MyEntity, MyIdClass> {
@Modifying
@Query("DELETE FROM myentity m WHERE CONCAT(m.foo, '~', m.bar) IN :ids")
void deleteAllWithConcatenatedIds(@Param("ids") Collection<String> ids);
}
数据库 (Postgres) 的关联索引
DROP INDEX IF EXISTS concatenated_pk_index;
CREATE UNIQUE INDEX concatenated_pk_index ON myentity USING btree (( foo || '~' || bar ));
说明
由于 EclipseLink 拒绝正确处理我的 @IdClass
,我不得不调整服务以将复合键连接成单个字符串。然后,在 Postgres 中,您实际上可以在不同复合键列的串联上创建索引。
将索引标记为 UNIQUE
将大大提高该查询的性能,但只有在您确定连接是唯一的情况下才应该这样做(在我的例子中,因为我使用了所有复合键的列)。
调用服务然后只需要做类似 String.join("~", dto.getFoo(), dto.getBar())
的事情,并将所有这些收集到将传递到存储库的列表中。
问题
如果我使用 @IdClass
声明了我的(复合)主键,我该如何编写我的 @Query
才能使用 [=17= 发出 DELETE
查询] ?
副题
尽管使用 @Query
,CASCADE 是否会实际触发删除关联的 AnotherEntity
?
当前型号
@Entity
@Table(name = "myentity")
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@IdClass(MyIdClass.class)
public class MyEntity {
@Id
@Column(updatable = false)
private String foo;
@Id
@Column(updatable = false)
private String bar;
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "my_foreign_key", referencedColumnName = "external_pk")
private AnotherEntity anotherEntity;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MyIdClass implements Serializable {
private String foo;
private String bar;
}
@Entity
@Table(name = "anotherentity")
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class AnotherEntity {
@Id
@Column(name = "external_pk", nullable = false, updatable = false)
private String externalPk;
}
我读过的内容
一些资源:
而且我还发现 this SO question 这似乎与我要找的东西非常接近,但不幸的是没有答案。
目标
类似于:
@Repository
public interface MyCRUDRepository extends CrudRepository<MyEntity, MyIdClass> {
@Modifying
@Query("DELETE FROM myentity m WHERE m IN ?1") // how do I write this?
void deleteAllWithIds(Collection<MyIdClass> ids);
}
最终,我想这样做来批处理我的 DELETE 请求以提高性能。
我试图避免的陷阱
我知道有一个 deleteAll(Iterable<? extends MyEntity>)
但实际上我需要从这些实体开始,这将需要额外调用数据库。
还有 deleteById(MyIdClass)
,但实际上总是在将单个 DELETE 语句作为事务发送之前发出 findById
:不利于性能!
可能不相关的精度
我不确定这是否有帮助,但我的 JPA 提供程序是 EclipseLink
。我的理解是批处理请求有一些属性,而这正是我最终要使用的。
但是,我不完全确定要进行该批处理的内部要求是什么。例如,如果我在 for-loop
中执行 deleteById
,交替的 SELECT
和 DELETE
语句是否会阻止批处理的发生?关于这方面的文档非常少。
我认为您正在寻找可以生成这样的查询的东西
delete from myentity where MyIdClass in (? , ? , ?)
你可以试试这个
如果您是肯定的 IdClass is a better choice than EmbeddedId in your situation,您可以向 MyEntity 添加一个额外的映射:
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "foo",
column = @Column(name = "foo", insertable = false, updatable = false)),
@AttributeOverride(name = "bar",
column = @Column(name = "bar", insertable = false, updatable = false))})
private MyIdClass id;
并在您的存储库中使用它:
@Modifying
@Query("DELETE FROM MyEntity me WHERE me.id in (:ids)")
void deleteByIdIn(@Param("ids") Collection<MyIdClass> ids);
这将生成一个查询:delete from myentity where bar=? and foo=? [or bar=? and foo=?]...
,导致此测试通过(具有以下 table 条记录 insert into myentity(foo,bar) values ('foo1', 'bar1'),('foo2', 'bar2'),('foo3', 'bar3'),('foo4', 'bar4');
):
@Test
@Transactional
void deleteByInWithQuery_multipleIds_allDeleted() {
assertEquals(4, ((Collection<MyEntity>) myEntityRepository.findAll()).size());
MyIdClass id1 = new MyIdClass("foo1", "bar1");
MyIdClass id2 = new MyIdClass("foo2", "bar2");
assertDoesNotThrow(() -> myEntityRepository.deleteByIdIn(List.of(id1, id2)));
assertEquals(2, ((Collection<MyEntity>) myEntityRepository.findAll()).size());
}
我找到的唯一有效解决方案是以下技巧:
Spring@Repository
的 JPA 查询
@Repository
public interface MyCRUDRepository extends CrudRepository<MyEntity, MyIdClass> {
@Modifying
@Query("DELETE FROM myentity m WHERE CONCAT(m.foo, '~', m.bar) IN :ids")
void deleteAllWithConcatenatedIds(@Param("ids") Collection<String> ids);
}
数据库 (Postgres) 的关联索引
DROP INDEX IF EXISTS concatenated_pk_index;
CREATE UNIQUE INDEX concatenated_pk_index ON myentity USING btree (( foo || '~' || bar ));
说明
由于 EclipseLink 拒绝正确处理我的 @IdClass
,我不得不调整服务以将复合键连接成单个字符串。然后,在 Postgres 中,您实际上可以在不同复合键列的串联上创建索引。
将索引标记为 UNIQUE
将大大提高该查询的性能,但只有在您确定连接是唯一的情况下才应该这样做(在我的例子中,因为我使用了所有复合键的列)。
调用服务然后只需要做类似 String.join("~", dto.getFoo(), dto.getBar())
的事情,并将所有这些收集到将传递到存储库的列表中。