问题
如果我已经使用@IdClass
声明了我的(复合)主键,那么我如何编写@Query
以便能够使用Collection<MyIdClass>
发出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;
}
我读到的内容
一些资源:
- https://www.baeldung.com/spring-data-jpa-query
- https://www.baeldung.com/spring-data-jpa-delete
- https://stackoverflow.com/a/36765129/9768291
我还发现了这个SO问题,它似乎非常接近我想要的,但不幸的是没有答案。
目标
类似于:
@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>)
,但实际上我需要从这些实体开始,这将需要对DB进行额外的调用。
还有deleteById(MyIdClass)
,但它实际上总是在将单个DELETE语句作为事务发送之前发出findById
:这对性能不好!
潜在无关精度
我不确定这是否有帮助,但我的JPA提供商是EclipseLink
。我的理解是,有用于批处理请求的属性,而这正是我最终要使用的。
然而,我不完全确定要进行批处理的内部要求是什么。例如,如果我在for-loop
中执行deleteById
,那么交替的SELECT
和DELETE
语句会阻止批处理吗?关于这方面的文献资料很少。
如果您确信在您的情况下,IdClass是比EmbeddedId更好的选择,您可以添加一个额外的映射到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=?]...
,导致此测试通过(具有下表记录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());
}
我认为您正在寻找能够生成类似的查询的东西
delete from myentity where MyIdClass in (? , ? , ?)
你可以试试这个帖子,它可能会对你有所帮助。
这个答案提供了很好的见解,但这种方法似乎只适用于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
,我不得不调整服务,将复合键连接到一个String中。然后,在Postgres中,您实际上可以在不同组合键列的串联上创建索引。
将索引标记为UNIQUE
将极大地提高该查询的性能,但只有当您确信连接是唯一的(在我的情况下,这是因为我使用了组合键的所有列)时,才应该这样做。
然后,调用服务只需要执行类似String.join("~", dto.getFoo(), dto.getBar())
的操作,并将所有这些操作收集到将传递给存储库的列表中。