JPA 查询能以 JavaMap 的形式返回结果吗?

我们目前正在基于命名 JPA 查询返回的两个字段手动构建 Map,因为 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多个结果。

任何构建 Map 而不需要手动迭代的想法。

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

141706 次浏览

这个怎么样?

@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();
}

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

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

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

另外,如果还有其他地方可以将查询结果 List 转换为 Maps,那么可能需要将其重构为带有参数的实用程序方法,以指示映射键属性。

自定义结果 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;
}
});

您可以检索 java.util.地图,入口的列表。 因此,实体中的集合应该建模为 Map:

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

在示例 PhoneType 是一个简单的枚举,PhoneNumber 是一个实体。在查询中使用 JPA 2.0中引入的 ENTRY关键字进行映射操作:

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()
}

如果您仍然需要映射中的条目,但是不想手动遍历条目列表,那么可以看看这篇使用 Java8的文章 转换 Set < Map. Entry < K,V > > 到 HashMap < K,V >

请参阅 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 分享我的工作方式。

这样挺好的。
存储库代码:

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


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

Service.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

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){  }

使用 java 8(+) ,你可以通过 hibernate 实体管理器得到一个数组对象列表的结果(每个列的 select 将在结果数组上有相同的索引) ,然后从结果列表到流,将结果映射到条目(键,值) ,然后收集到相同类型的映射。

 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));

以防万一 有内置条目“ CustomEntryClass”

  • 由于 return 是流,因此调用方函数(存储层)必须有@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);
    

使用 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 查询 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 结果

另一种选择是使用 Hibernate Type 开源项目提供的 MapResultTransformer类:

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适用于仍在 Java6上运行或使用旧版 Hibernate 的项目。

避免返回大的结果集

《观察家报告》称:

但是,我觉得自定义映射程序或类似程序的性能会更好 因为这个列表很容易就会有超过30,000个结果。

这主意糟透了。你永远不需要选择3万记录。这在用户界面中有什么用?或者,你为什么要对这么大批的唱片进行操作?

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

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;
}

对我来说,最简单易行的方法是:

String[] columns = {"id","name","salary","phone","address", "dob"};
String query = "SELECT id,name,salary,phone,address,dob from users ";


List<Object[]> queryResp = em.createNativeQuery(query).getResultList();


List<Map<String,String>> dataList = new ArrayList<>();
for(Object[] obj : queryResp) {
Map<String,String> row = new HashMap<>(columns.length);
for(int i=0; i<columns.length; i++) {
if(obj[i]!=null)
row.put(columns[i], obj[i].toString());
else
row.put(columns[i], "");
}
dataList.add(row);
}