Spring JPA存储库使用单个查询更新多个实体



目前我有以下代码迭代参数实体列表并更新数据库中的每个名称:

public class test {
@Autowired
private ParameterJpaRepository parameterJpaRepository;
public updateParameters(List<Parameter> parameters) {
for (Parameter parameter : parameters) {
parameterJpaRepository.setNameById(parameter.getId(), parameter.getName());
}
}
}
public interface ParameterJpaRepository extends JpaRepository<Parameter, Long> {
@Modifying
@Query("UPDATE Parameter p SET p.name = :name WHERE p.id = :id")
void setNameById(@Param("id") long id, @Param("name") String name);
}
显然,这会导致N查询:
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?
Hibernate: update parameter set name=? where id=?

我想把它们合并成一个类似于下面这个尝试的查询:

public interface ParameterJpaRepository extends JpaRepository<Parameter, Long> {
@Modifying
@Query("UPDATE Parameter p SET p.name = (:names) WHERE p.id = (:ids)")
void setNameById(@Param("ids") List<Long> ids, @Param("names") List<String> names);
}

应该产生如下内容:

Hibernate: UPDATE parameter
SET name = (case when id = ? then ?
when id = ? then ?
when id = ? then ?
when id = ? then ?
end)
WHERE id in (?, ?, ?, ?);

这可能吗?

您可能想要这样的内容

@Modifying
@Query(value = "UPDATE Parameter p SET p.name = (CASE " +
"WHEN p.id IN (:ids) THEN (CASE " +
"WHEN p.id = :ids[0] THEN :names[0] " +
"WHEN p.id = :ids[1] THEN :names[1] " +
"WHEN p.id = :ids[2] THEN :names[2] " +
// ...
"ELSE p.name " +
"END) " +
"ELSE p.name " +
"END) " +
"WHERE p.id IN (:ids)", nativeQuery = true)
void setNameById(@Param("ids") List<Long> ids, @Param("names") List<String> names);

这是一个糟糕的方法。别想那么做。

对于大列表来说非常糟糕,因为查询将变得非常长并且很难维护。如果id和名称列表的顺序不一致,则无法工作。如果您需要更新大量的行,或者如果id和名称列表的顺序不固定,您可能需要考虑使用不同的方法,例如为每一行执行单独的更新语句或使用临时表。

(Un)幸运的是,spring-data-jpa功能并不像你想看到的那么灵活,但是它确实允许为Spring数据存储库创建自定义实现,因此你可以编写任何你想要的更新查询,一些例子:

  • https://thorben-janssen.com/composite-repositories-spring-data-jpa/
  • https://vladmihalcea.com/custom-spring-data-repository/
  • https://www.baeldung.com/spring-data-composable-repositories
  • 顺便说一下

,您需要记住,通过直接更新hibernate实体通常不是一个好主意,原因如下:

  • 它不是缓存友好的-如果你使用二级缓存,hibernate需要完全清理这些缓存,因为它没有机会知道哪些实体已经更新
  • 如果您正在使用审计解决方案,如envers,直接更新绕过该解决方案

所以,有时启用批处理更新会更好,可以这样写:

@Transactional
default void setNameById(List<Long> ids, List<String> names) {
Map<Long, Parameter> data = StreamSupport.stream(findAllById(ids).spliterator(), false)
.collect(Collectors.toMap(
Customer::getId,
Function.identity()
));

for (int i = 0, n = ids.size(); i < n; i++) {
Parameter parameter = data.get(ids.get(i));
if (parameter != null) {
parameter.setName(names.get(i));
}
}
saveAll(data.values());
}

最新更新