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

问题描述 投票:0回答:3

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

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=?

我想将 then 合并到与此尝试等效的单个查询中:

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 (?, ?, ?, ?);

这可能吗?

java spring hibernate spring-data-jpa jpql
3个回答
3
投票

您可能想要这样的东西

@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 和名称列表的顺序不同,则该方法不起作用。 如果您需要更新大量行,或者 ids 和名称列表的顺序不固定,您可能需要考虑使用不同的方法,例如为每行执行单独的更新语句或使用临时表.


3
投票

(Un)幸运的是,

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

顺便说一下,您需要记住,一般来说,通过直接更新来更新休眠实体并不是一个好主意,原因如下:

  • 它不是缓存友好的 - 如果您使用二级缓存,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());
}

0
投票

我知道它很旧,但无论如何我都会分享它。

请检查我的帖子以及我在与这个问题斗争了很长时间后的回答。

它正在使用休眠6。

其背后的想法是在数据库中创建一个自定义类型(在您的情况下)具有长和字符串 - Id,名称。

创建一个 UserType 以允许 hibernate 将您的类型转换为 db 类型。

创建存储过程以在单个语句中更新数据。

-- db script

CREATE TYPE YourCustomType AS (
    id long,
    name varchar(100)
 );

CREATE OR REPLACE PROCEDURE updateBulk(yourData YourCustomType[])  AS $$
BEGIN
   UPDATE Parameter as db
   SET db.name = val.name 
   FROM (SELECT * from UNNEST(yourArrayOfData)) as val
   WHERE db.id = val.id
END;
$$ LANGUAGE 'plpgsql';

在我的帖子中添加java中的对象+创建一个

UserType
+贡献类型+然后将其添加到存储库中

@Procedure(value= "update_bulk")
void updateBulk(Yourtype[] data);

玩得开心..

© www.soinside.com 2019 - 2024. All rights reserved.