We are currently building a Map
manually based on the two fields that are returned by a named JPA query because JPA 2.1 only provides a getResultList()
method:
@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); }
But, I feel like a custom mapper or similar would be more performant since this list could easily be 30,000+ results.
Any ideas to build a Map without iterating manually.
(I am using OpenJPA, not hibernate)
The easiest way to map a query result to an entity is to provide the entity class as a parameter to the createNativeQuery(String sqlString, Class resultClass) method of the EntityManager and use the default mapping. The following snippet shows how this is done with a very simple query.
The One-To-One mapping represents a single-valued association where an instance of one entity is associated with an instance of another entity. In this type of association one instance of source entity can be mapped atmost one instance of target entity.
In JPA you can map to a VIEW the same as a table, using the @Table annotation. You can then map each column in the view to your object's attributes. Views are normally read-only, so object's mapping to views are normally also read-only.
Since the JPA 2.2 version, you can use the getResultStream
Query
method to transform the List<Tuple>
result into a 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() ) );
If you're using JPA 2.1 or older versions but your application is running on Java 8 or a newer version, then you can use getResultList
and transform the List<Tuple>
to a Java 8 stream:
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() ) );
Another option is to use the MapResultTransformer
class provided by the Hibernate Types open-source project:
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();
The MapResultTransformer
is suitable for projects still running on Java 6 or using older Hibernate versions.
The OP said:
But, I feel like a custom mapper or similar would be more performant since this list could easily be 30,000+ results.
This is a terrible idea. You never need to select 30k records. How would that fit in the UI? Or, why would you operate on such a large batch of records?
You should use query pagination as this will help you reduce the transaction response time and provide better concurrency.
There is no standard way to get JPA to return a map.
see related question: JPA 2.0 native query results as map
Iterating manually should be fine. The time to iterate a list/map in memory is going to be small relative to the time to execute/return the query results. I wouldn't try to futz with the JPA internals or customization unless there was conclusive evidence that manual iteration was not workable.
Also, if you have other places where you turn query result Lists into Maps, you probably want to refactor that into a utility method with a parameter to indicate the map key property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With