27

I have an administrative console in my web application that allows an admin to perform a custom SQL SELECT query on our database.

Underneath, the application is using Hibernate, but these queries are not HQL, they're pure SQL, so I'm using a Native Query like this:

protected EntityManager em; public List<Object[]> execute(String query) { Query q = em.createNativeQuery(query); List<Object[]> result = q.getResultList(); return result; } 

This works correctly, but it only returns the rows of data, with no extra information. What I would like is to also get the column names, so when I print the results back to the user I can also print a header to show what the various columns are.

Is there any way to do this?

14
  • how your query look like? Commented Jun 19, 2017 at 9:22
  • 1
    @YCF_L: if I knew before hand, I wouldn't have this problem, as I would know the column names ;) As I wrote in the question, this is for an administrative console where administrators can type any SQL SELECT query and have the results printed on a table. So any SQL query is possible, as long as it is a SELECT. SELECT name, surname FROM users just to make a silly example, but it can be anything. Commented Jun 19, 2017 at 9:28
  • I'm not sure Hibernate supports the retrieval of query metadata. Is plain JDBC an option you can use? If so you can retrieve a ResultSetMetaData object from the ResultSet that contains the column headers. Commented Jun 19, 2017 at 9:31
  • 4
    Set the result class to be Tuple and then you should get column/field name and value. Standard JPA API, so no real reason why a native query could not utilise it datanucleus.org/products/accessplatform_5_1/jpa/… Commented Jun 19, 2017 at 9:31
  • 2
    @BillyFrost: thanks for the suggestion, but I think this only works for mapped entities. As I said, I'm using a native sql query. If I try your method, I get a org.hibernate.MappingException: Unknown entity: javax.persistence.Tuple Commented Jun 19, 2017 at 11:31

11 Answers 11

24
Query query = entityManager.createNamedQuery(namedQuery); NativeQueryImpl nativeQuery = (NativeQueryImpl) query; nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE); List<Map<String,Object>> result = nativeQuery.getResultList(); 

And now you have Map<String,Object> . You can see your Column Names

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you very much after spending whole day I found the solution :) you save my day
This should most definitely be marked as an answer. Works great on my end, however the mapped objects are returned in a different order than I was expecting. Is there any way to manipulate the order of the map internal map items? Perhaps applying some sort of default sorter based on the sys object column id number?
Found an answer to this order of columns thing. Very high level, a custom transformer is applied to the nativequery that just loops over the input and stuffs them into a LinkedHashMap<String, Object>
Sorry but ResultTransformer is deprecated :(
For Hibernate 6: nativeQuery.setResultListTransformer(AliasToEntityMapResultTransformer.INSTANCE);
7

This code worked for me

DTO Class :

 public class ItemResponse<T> { private T item; public ItemResponse() { } public ItemResponse(T item) { super(); this.item = item; } public T getItem() { return item; } public void setItem(T item) { this.item = item; } } 

Service Class is in the below

import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.springframework.stereotype.Service; import org.hibernate.transform.AliasToEntityMapResultTransformer; @Service public class ServiceClass{ @PersistenceContext public EntityManager entityManager; public ItemResponse exceuteQueryResponse(String queryString) { ItemResponse itemResponse=new ItemResponse(); Query jpaQuery = entityManager.createNativeQuery(queryString); org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)jpaQuery).getHibernateQuery(); hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE); List<Map<String,Object>> res = hibernateQuery.list(); itemResponse.setItem(res); return itemResponse; } } 

Comments

6

2020

With hibernate 5.2.11.Final is actually pretty easy. In my example you can see how I get the column names for every row. And how I get values by column name.

Query q = em.createNativeQuery("SELECT columnA, columnB FROM table"); List<Tuple> result = q.getResultList(); for (Tuple row: result){ // Get Column Names List<TupleElement<Object>> elements = row.getElements(); for (TupleElement<Object> element : elements ) { System.out.println(element.getAlias()); } // Get Objects by Column Name Object columnA; Object columnB; try { columnA = row.get("columnA"); columnB= row.get("columnB"); } catch (IllegalArgumentException e) { System.out.println("A column was not found"); } } 

4 Comments

This code works but requires specifically Hibernate 5.2.11.Final. I actually have it running in production. I suspect negative vote might be someone who tested in a different Hibernate version.
The code doesn't answer the question. With your solution you need to know the columns beforehand.
Thanks for your note, @Nicolas. I've just edited my example to do both, print column names, and to get values by column name.
Seems like this does not work with later version like 5.4.2.Final, as what is returned is a List of Objects.
5

Ryiad's answer DTO adds some confusion, you should have kept it away. You should have explained that it works only with hibernate.

If like me you needs to keep the order of columns, you can specify your own transformer. i copied the code from hibernate and changed the HashMap to LinkedHashMap:

import java.util.LinkedHashMap; import java.util.Map; import org.hibernate.transform.AliasedTupleSubsetResultTransformer; import org.hibernate.transform.ResultTransformer; /** * {@link ResultTransformer} implementation which builds a map for each "row", made up of each aliased value where the * alias is the map key. Inspired by {@link org.hibernate.transform.AliasToEntityMapResultTransformer}, but kepping the * ordering of elements. * <p/> * Since this transformer is stateless, all instances would be considered equal. So for optimization purposes we limit * it to a single, singleton {@link #INSTANCE instance}. */ public class AliasToEntityMapResultTransformer extends AliasedTupleSubsetResultTransformer { public static final AliasToEntityMapResultTransformer INSTANCE = new AliasToEntityMapResultTransformer(); /** * Disallow instantiation of AliasToEntityMapResultTransformer. */ private AliasToEntityMapResultTransformer() { } @Override public Object transformTuple(Object[] tuple, String[] aliases) { Map result = new LinkedHashMap<>(tuple.length); for (int i = 0; i < tuple.length; i++) { String alias = aliases[i]; if (alias != null) { result.put(alias, tuple[i]); } } return result; } @Override public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) { return false; } /** * Serialization hook for ensuring singleton uniqueing. * * @return The singleton instance : {@link #INSTANCE} */ private Object readResolve() { return INSTANCE; } } 

With this transformer you can used Ryiad's solution with Hibernate:

 Query jpaQuery = entityManager.createNativeQuery(queryString); org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)jpaQuery).getHibernateQuery(); hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE); List<Map<String,Object>> res = hibernateQuery.list(); 

Comments

2

This worked for me:

 final Query emQuery = em.createNativeQuery(query, Tuple.class); final List<Tuple> queryRows = emQuery.getResultList(); final List<Map<String, Object>> formattedRows = new ArrayList<>(); queryRows.forEach(row -> { final Map<String, Object> formattedRow = new HashMap<>(); row.getElements().forEach(column -> { final String columnName = column.getAlias(); final Object columnValue = row.get(column); formattedRow.put(columnName, columnValue); }); formattedRows.add(formattedRow); }); return formattedRows; 

Comments

1

After a long time without any answer, And based on my own further research, It seems that it can not be possible, Unfortunately.

8 Comments

Are those libs an option for you: javax.persistence.Query, org.eclipse.persistence.config.QueryHints, org.eclipse.persistence.config.ResultType? You could set your query to QueryHints.RESULT_TYPE, ResultType.Map: myEntityMananger.createNativeQuery("select * from mytable").setHint(QueryHints.RESULT_TYPE, ResultType.Map);. Then access the column names via the map keys
@woodz hey there woodz, I am trying to use setHint() as you said in your comment, I am creating a query first, then setting the hint. What kind of result does it return? Could you provide an example?
good day @vc73: I am not sure what I can show more. Have you done filling my given code template above with your concrete query conditions? Then, did you have a look onto your result list? I am showing you the steps to process again: //your resulting list List myResult = res; //your query via the entity manager res = myEntityMananger.createNativeQuery("select * from mytable") .setHint(QueryHints.RESULT_TYPE, ResultType.Map) .getResultList(); have a look onto your res variable you can cast each entry of the list: int i = 1; List<Map> data = (List)res.get(i);
@WileE.Genius: glad to help you here. This platform is not intended to help people, it is to comply to a system of law and order. Since I am not sure, how or whether eclipse link fits to hibernate, folks here definitively will complain that "eclipse link" isn't a tag of the original question and thus will tell me "this desn't answer the question" plus put devaluation on it.
Hi @woodz! Thanks a lot for your answer as it saved me plenty of time. I had to adjust it a bit, but it works flawless. Here is my starting point: List<DatabaseRecord> res = this.em.createNativeQuery(selectStmt).setHint(QueryHints.RESULT_TYPE, ResultType.Map).getResultList(); DatabaseRecord seems to be the class to use, as it doesn't reside in the internal package of Eclipselink.
|
0

If the JPA provider does not support the retrieval of query metadata, another solution could be the use of a SQL parser like JSQLParser, ZQL or General SQL Parser (comercial), which extracts the fields from the SELECT statement.

Comments

0

cast query to hibernate query, then use hibernate method

 //normal use, javax.persistence.Query interface Query dbQuery = entityManager.createNativeQuery(sql); //cast to hibernate query org.hibernate.Query hibernateQuery =((org.hibernate.jpa.HibernateQuery)dbQuery) .getHibernateQuery(); hibernateQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE); List<Map<String,Object>> res = hibernateQuery.list(); List<TxTestModel> txTestModels = new ArrayList<>(); res.forEach(e->{ TxTestModel txTestModel = new ObjectMapper().convertValue(e, TxTestModel.class); // txTestModels.add(new TxTestModel().setIdd((Integer) e.get("idd")).setMmm((String) e.get("mmm")).setDdd((Date) e.get("ddd"))); txTestModels.add(txTestModel); }); System.out.println(txTestModels.size()); 

2 Comments

Error on ((org.hibernate.jpa.HibernateQuery).getHibernateQuery();. org.hibernate.query.internal.NativeQueryImpl cannot cast to org.hibernate.jpa.HibernateQuery.
@AliSohrabi I uesd this code in springboot1.5.1.RELEASE, it depens on hibernate-core:5.0.11.Final
0
 This is the working solution Below example return the objectlist from the query. Looping the same and from the first object cast it to hasmap, and hashmap.keyset will give you all the coulmn names in a set. List dataList = session.createSQLQuery("SLECT * FROM EMPLOYEETABLE").setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list(); for (Object obj : dataList) { HashMap<String, Object> hashMap = (HashMap<String, Object>) obj; Set<String> keySet = hashMap.keySet(); break; } 

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
-1

I also faced a similar problem working with JPA. There is no direct way in JPA to access the resultset metadata. The solution can be extracting column names from the query itself or use JDBC to get the metadata.

Comments

-1

To enforce em.createNativeQuery(..).getResultList() to return List<Tuple> specify it with Tuple.class when creating native queries :

Query q = em.createNativeQuery("SELECT columnA, columnB FROM table", Tuple.class ); List<Tuple> result = q.getResultList(); for (Tuple row: result){ // Get Column Names List<TupleElement<Object>> elements = row.getElements(); for (TupleElement<Object> element : elements ) { System.out.println(element.getAlias()); } // Get Objects by Column Name Object columnA; Object columnB; try { columnA = row.get("columnA"); columnB= row.get("columnB"); } catch (IllegalArgumentException e) { System.out.println("A column was not found"); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.