我们目前正在根据命名查询返回的两个字段手动构建一个映射,因为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);
}
但是我觉得自定义映射器或者类似物会更有效率,因为这个列表很容易就会产生30,000多个结果。
无需手动迭代即可构建Map的任何想法。
(我正在使用OpenJPA,而不是休眠)
答案 0 :(得分:16)
由于这是一个常见问题,所以我决定就此主题写this article。
从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 2.1或更早版本,但是您的应用程序在Java 8或更高版本上运行,则可以使用getResultList
并将List<Tuple>
转换为Java 8流:< / p>
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 Types开源项目提供的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
适用于仍在Java 6上运行或使用较旧的Hibernate版本的项目。
OP说:
但是,我觉得自定义映射器或类似的工具会更有效 因为此列表很可能是30,000多个结果。
这是一个糟糕的主意。您无需选择30k条记录。如何适应用户界面?或者,为什么您要处理这么大的记录呢?
您应该使用query pagination,因为这将帮助您减少事务响应时间并提供更好的并发性。请查看本文,以获取有关best way to paginate query result sets的更多详细信息。
答案 1 :(得分:15)
没有标准方法让JPA返回地图。
请参阅相关问题:JPA 2.0 native query results as map
手动迭代应该没问题。相对于执行/返回查询结果的时间,在内存中迭代列表/映射的时间将变得很小。除非有确凿的证据表明手动迭代不可行,否则我不会尝试使用JPA内部或定制。
此外,如果您将查询结果列表转换为地图的其他位置,您可能希望将其重构为带有参数的实用程序方法,以指示地图键属性。
答案 2 :(得分:8)
您可以检索java.util。 Map.Entry 的列表。 因此,您实体中的集合应该建模为地图:
@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()
}
如果您仍然需要地图中的条目但又不想手动遍历您的条目列表,请查看适用于Java 8的帖子Convert Set<Map.Entry<K, V>> to HashMap<K, V>。
答案 3 :(得分:4)
这很好。
仓库代码:
@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
答案 4 :(得分:1)
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;
}
答案 5 :(得分:0)
这个怎么样?
@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();
}
答案 6 :(得分:0)
使用自定义结果class
和一点Guava,这是我的方法,效果很好:
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;
}
});
答案 7 :(得分:0)
请参阅JPA 2.0 native query results as map
在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);
的案例中,它就像是,
var result = from role in db.LookUpRoles
join ur in db.UserRole
on { role.LookUpRoleId, accountId } equals new {ur.LookUpRoleId, ur.UserAccountId } into j
from ur in j.DefaultIfEmpty()
where ur == null
select role.RoleName;
请注意,我只是与Postgres分享我的解决方法。
答案 8 :(得分:0)
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){ }
答案 9 :(得分:0)
使用Java 8(+),您可以通过休眠实体管理器以数组对象列表的形式获取结果(select的每一列将在result数组上具有相同的索引),然后从结果列表到流中,将结果映射到条目中(键,值),然后将它们收集到相同类型的地图中。
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));
答案 10 :(得分:0)
如果Java 8 内置条目“ CustomEntryClass”
由于返回是流,因此调用方函数(存储层)必须具有@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);