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;
}

我读过的内容

一些资源:

  1. https://www.baeldung.com/spring-data-jpa-query
  2. https://www.baeldung.com/spring-data-jpa-delete

而且我还发现 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,交替的 SELECTDELETE 语句是否会阻止批处理的发生?关于这方面的文档非常少。

我认为您正在寻找可以生成这样的查询的东西

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()) 的事情,并将所有这些收集到将传递到存储库的列表中。