目前我有以下代码迭代参数实体列表并更新数据库中的每个名称:
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());
}