JPA可以将结果作为地图返回吗?

Edd*_*die 32 jpa resultset map openjpa

我们目前基于命名查询返回的两个字段手动构建映射,因为JPA仅提供getResultList().

@NamedQuery{name="myQuery",query="select c.name, c.number from Client c"}

HashMap<Long,String> myMap = new HashMap<Long,String>();

for(Client c: em.createNamedQuery("myQuery").getResultList() ){
     myMap.put(c.getNumber, c.getName);
}
Run Code Online (Sandbox Code Playgroud)

但是我觉得自定义映射器或者类似物会更高效,因为这个列表很容易就会产生30,000多个结果.

任何想法,无需手动迭代即可构建Map.

(我使用的是OpenJPA,而不是休眠)

Vla*_*cea 32

使用 JPA 查询 getResultStream 返回 Map 结果

从 JPA 2.2 版本开始,您可以使用该getResultStream Query方法将List<Tuple>结果转换为Map<Integer, Integer>

Map<Integer, Integer> postCountByYearMap = entityManager.createQuery("""
    select
       YEAR(p.createdOn) as year,
       count(p) as postCount
    from
       Post p
    group by
       YEAR(p.createdOn)
    """, Tuple.class)
.getResultStream()
.collect(
    Collectors.toMap(
        tuple -> ((Number) tuple.get("year")).intValue(),
        tuple -> ((Number) tuple.get("postCount")).intValue()
    )
);
Run Code Online (Sandbox Code Playgroud)

使用 JPA 查询 getResultList 和 Java 流返回 Map 结果

如果您使用的是 JPA 2.1 或更旧版本,但您的应用程序在 Java 8 或更高版本上运行,那么您可以使用getResultList并将其转换List<Tuple>为 Java 8 流:

Map<Integer, Integer> postCountByYearMap = entityManager.createQuery("""
    select
       YEAR(p.createdOn) as year,
       count(p) as postCount
    from
       Post p
    group by
       YEAR(p.createdOn)
    """, Tuple.class)
.getResultList()
.stream()
.collect(
    Collectors.toMap(
        tuple -> ((Number) tuple.get("year")).intValue(),
        tuple -> ((Number) tuple.get("postCount")).intValue()
    )
);
Run Code Online (Sandbox Code Playgroud)

使用特定于 Hibernate 的 ResultTransformer 返回 Map 结果

另一种选择是使用MapResultTransformerHibernate Types 开源项目提供的类:

Map<Number, Number> postCountByYearMap = (Map<Number, Number>) entityManager.createQuery("""
    select
       YEAR(p.createdOn) as year,
       count(p) as postCount
    from
       Post p
    group by
       YEAR(p.createdOn)
    """)
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(
    new MapResultTransformer<Number, Number>()
)
.getSingleResult();
Run Code Online (Sandbox Code Playgroud)

MapResultTransformer适用于项目的Java 6的仍在运行或使用旧版本的Hibernate。

避免返回大型结果集

OP 说:

但是,我觉得自定义映射器或类似的会更高效,因为此列表很容易包含 30,000 多个结果。

这是一个可怕的想法。您永远不需要选择 30k 条记录。这将如何适应用户界面?或者,为什么要对如此大量的记录进行操作?

您应该使用查询分页,因为这将帮助您减少事务响应时间并提供更好的并发性。

  • 这是一个 JPA 功能,这就是该接口被称为“javax.persistence.Tuple”的原因。任何 JPA 提供商都应该支持它。如果 EclipseLink 不支持它,您应该为此提出一个问题。 (2认同)
  • JPA似乎只在规范中将Tuple添加到条件查询中。JSR 添加了脚注 26+27,明确指出 JPQL 查询不支持元组等其他类型:“指定其他结果类型(例如 Tuple.class)的应用程序将不可移植。” EclipseLink 没有理由不支持它,但它不是规范的一部分。 (2认同)
  • 事实上,JPA 可移植性只是一个神话。就像 SQL 标准一样。我个人从未见过任何更换 JPA 提供商的人。即使在 Hibernate 论坛上,我也不记得有超过 2 或 3 个与提供程序迁移相关的问题。因此,实际上,可移植性并不是真正的问题。在这种情况下,我认为 `createQuery` 应该允许 `Tuple`,就像它允许通过构造函数表达式进行 DTO 投影一样。 (2认同)

wrs*_*der 15

没有标准的方法让JPA返回地图.

查看相关问题:JPA 2.0本机查询结果为map

手动迭代应该没问题.相对于执行/返回查询结果的时间,在内存中迭代列表/映射的时间将变得很小.除非有确凿的证据表明手动迭代不可行,否则我不会尝试使用JPA内部或定制.

此外,如果您有其他地方将查询结果列表转换为地图,您可能希望将其重构为带有参数的实用程序方法,以指示地图键属性.


mik*_*ika 9

您可以检索java.util的列表.而是Map.Entry.因此,您实体中的集合应建模为地图:

@OneToMany
@MapKeyEnumerated(EnumType.STRING)
public Map<PhoneType, PhoneNumber> phones;
Run Code Online (Sandbox Code Playgroud)

在示例中,PhoneType是一个简单的枚举,PhoneNumber是一个实体.在您的查询中,使用ENTRYJPA 2.0中为地图操作引入的关键字:

public List<Entry> getPersonPhones(){
    return em.createQuery("SELECT ENTRY(pn) FROM Person p JOIN p.phones pn",java.util.Map.Entry.class).getResultList();
}
Run Code Online (Sandbox Code Playgroud)

您现在可以检索条目并开始使用它:

List<java.util.Map.Entry> phoneEntries =   personDao.getPersonPhoneNumbers();
for (java.util.Map.Entry<PhoneType, PhoneNumber> entry: phoneEntries){
    //entry.key(), entry.value()
}
Run Code Online (Sandbox Code Playgroud)

如果您仍然需要条目的地图,但不希望通过您的手动条目列表进行迭代,对这个帖子看看转换集<Map.Entry的<K,V >>到的HashMap <K,V>其中工程用Java 8.


Mr *_*ody 6

这工作正常。
存储库代码:

@Repository
public interface BookRepository extends CrudRepository<Book,Id> {

    @Query("SELECT b.name, b.author from Book b")
    List<Object[]> findBooks();
}
Run Code Online (Sandbox Code Playgroud)

服务.java

      List<Object[]> list = bookRepository.findBooks();
                for (Object[] ob : list){
                    String key = (String)ob[0];
                    String value = (String)ob[1];
}
Run Code Online (Sandbox Code Playgroud)

链接https://codereview.stackexchange.com/questions/1409/jpa-query-to-return-a-map

  • 这正是 OP 想要避免的 (2认同)
  • @KamilBęben 客户映射者也会在幕后做同样的事情。 (2认同)
  • 嘿伙计,这一点也不坏,如果您的查询返回不止一列,那么您就很好了。 (2认同)

zaw*_*tut -2

这个怎么样 ?

@NamedNativeQueries({
@NamedNativeQuery(
  name="myQuery",
  query="select c.name, c.number from Client c",
  resultClass=RegularClient.class
)
})
Run Code Online (Sandbox Code Playgroud)

     public static List<RegularClient> runMyQuery() {
     return entityManager().createNamedQuery("myQuery").getResultList();
 }
Run Code Online (Sandbox Code Playgroud)