65

我们目前正在Map基于命名 JPA 查询返回的两个字段手动构建一个,因为 JPA 2.1 仅提供一种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);
}

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

无需手动迭代即可构建地图的任何想法。

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

4

11 回答 11

34

使用 JPA Query 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()
    )
);

使用 JPA Query 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()
    )
);

使用特定于 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();

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

避免返回大型结果集

OP说:

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

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

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

于 2020-01-21T11:49:23.573 回答
19

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

查看相关问题:JPA 2.0 native query results as map

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

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

于 2011-11-19T20:34:07.357 回答
10

您可以检索 java.util 的列表。Map.Entry代替。因此,您的实体中的集合应建模为 Map:

@OneToMany
@MapKeyEnumerated(EnumType.STRING)
public Map<PhoneType, PhoneNumber> phones;

在示例中,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();
}

您现在已准备好检索条目并开始使用它:

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

如果您仍然需要地图中的条目但不想手动遍历条目列表,请查看此帖子Convert Set<Map.Entry<K, V>> to HashMap<K, V>哪个有效使用 Java 8。

于 2015-12-29T15:38:40.060 回答
6

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

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

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

服务.java

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

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

于 2018-09-25T19:50:53.287 回答
4
Map<String,Object> map = null;
    try {
        EntityManager entityManager = getEntityManager();
        Query query = entityManager.createNativeQuery(sql);
            query.setHint(QueryHints.RESULT_TYPE, ResultType.Map);
        map = (Map<String,Object>) query.getSingleResult();
    }catch (Exception e){ }

 List<Map<String,Object>> list = null;
    try {
        EntityManager entityManager = getEntityManager();
        Query query = entityManager.createNativeQuery(sql);
            query.setHint(QueryHints.RESULT_TYPE, ResultType.Map);
            list = query.getResultList();
    }catch (Exception e){  }
于 2019-04-03T14:59:19.097 回答
2

JPA v2.2

虽然我在这里迟到了,但是如果有人到达这里寻求解决方案,这是我为多行选择的多个列的自定义工作解决方案:

Query query = this.entityManager.createNativeQuery("SELECT abc, xyz, pqr,...FROM...", Tuple.class);
.
.
.
List<Tuple> lst = query.getResultList();
List<Map<String, Object>> result = convertTuplesToMap(lst);

实施convertTuplesToMap()

public static List<Map<String, Object>> convertTuplesToMap(List<Tuple> tuples) {
    List<Map<String, Object>> result = new ArrayList<>();
    for (Tuple single : tuples) {
        Map<String, Object> tempMap = new HashMap<>();
        for (TupleElement<?> key : single.getElements()) {
            tempMap.put(key.getAlias(), single.get(key));
        }
        result.add(tempMap);
    }
    return result;
}
于 2020-12-23T05:55:00.737 回答
1

如果 java 8 有内置条目“CustomEntryClass”

  • 因为return是流,所以调用者函数(repoistory层)必须有@Transactional(readonly=true|false)注解,否则会抛出异常

  • 确保您将使用类 CustomEntryClass 的完整限定名...

    @Query("select new CustomEntryClass(config.propertyName, config.propertyValue) " +
                        "from ClientConfigBO config where config.clientCode =:clientCode ")
                Stream<CustomEntryClass<String, String>> getByClientCodeMap(@Param("clientCode") String clientCode);
    
于 2019-12-30T17:52:07.097 回答
0

使用自定义结果class和一点番石榴,这是我的方法,效果很好:

public static class SlugPair {
    String canonicalSlug;
    String slug;

    public SlugPair(String canonicalSlug, String slug) {
        super();
        this.canonicalSlug = canonicalSlug;
        this.slug = slug;
    }

}

...

final TypedQuery<SlugPair> query = em.createQuery(
    "SELECT NEW com.quikdo.core.impl.JpaPlaceRepository$SlugPair(e.canonicalSlug, e.slug) FROM "
      + entityClass.getName() + " e WHERE e.canonicalSlug IN :canonicalSlugs",
    SlugPair.class);

query.setParameter("canonicalSlugs", canonicalSlugs);

final Map<String, SlugPair> existingSlugs = 
    FluentIterable.from(query.getResultList()).uniqueIndex(
        new Function<SlugPair, String>() {
    @Override @Nullable
    public String apply(@Nullable SlugPair input) {
        return input.canonicalSlug;
    }
});
于 2013-11-28T08:51:01.540 回答
0

使用 java 8 (+),您可以通过休眠实体管理器将结果作为数组对象列表(选择中的每一列将在结果数组上具有相同的索引),然后从结果列表到流,将结果映射到条目(键,值),然后将它们收集到相同类型的地图中。

 final String sql = "SELECT ID, MODE FROM MODES";
     List<Object[]> result = entityManager.createNativeQuery(sql).getResultList();
        return result.stream()
                .map(o -> new AbstractMap.SimpleEntry<>(Long.valueOf(o[0].toString()), String.valueOf(o[1])))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
于 2019-10-30T16:17:05.630 回答
-1

请参考,JPA 2.0 原生查询结果为 map

在你的情况下Postgres,它会是这样的,

List<String> list = em.createNativeQuery("select cast(json_object_agg(c.number, c.name) as text) from schema.client c")
                   .getResultList();

//handle exception here, this is just sample
Map map = new ObjectMapper().readValue(list.get(0), Map.class);

请注意,我只是在与 Postgres 分享我的解决方法。

于 2017-09-13T06:44:43.437 回答
-2

这个怎么样 ?

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

     public static List<RegularClient> runMyQuery() {
     return entityManager().createNamedQuery("myQuery").getResultList();
 }
于 2010-12-07T06:39:56.173 回答