spring-data #Query mapping result issue - java

I've created a Repository that extends CrudRepository,
this repository has a method with an #Query notation:
Code:
#Query("select itemType, count(*) as count from Item where User_id = :userId group by itemType")
List<Map<String, Long>> countItemsForUser(#Param("userId") Long userId);
The issue I'm having is that this return a ArrayList of Object(s) and not a List of Map.
I've read somewhere that JPA can't return a Map so that's why I stuff the result in a List>.
I don't know what's the best way to work around this issue or to quickly access the result data.
I've tried casting but that didn't work out either:
for(Object item: items) {
Map<String,Long> castedItem = (HashMap<String,Long>)item;
}

See this example in official documentation of Hibernate.Here
for (Object item:items) {
Object[] tuple = (Object[]) item;
String itemType = (String)tuple[0];
Long count = (Long) tuple[1];
}

Most simple way is to use interface. To let Spring wire query alias
to the interface getter. Example can be found here: https://www.baeldung.com/jpa-queries-custom-result-with-aggregation-functions
also there is #SqlResultSetMapping. See:
JPA- Joining two tables in non-entity class

Related

Hibernate ResultTransformer unable to cast a single column

I have some very complicated SQL (does some aggregation, some counts based on max value etc) so I want to use SQLQuery rather than Query. I created a very simple Pojo:
public class SqlCount {
private String name;
private Double count;
// getters, setters, constructors etc
Then when I run my SQLQuery, I want hibernate to populate a List for me, so I do this:
Query hQuery = sqlQuery.setResultTransformer(Transformers.aliasToBean(SqlCount.class));
Now I had a problem where depending on what the values are for 'count', Hibernate will variably retrieve it as a Long, Double, BigDecimal or BigInteger. So I use the addScalar function:
sqlQuery.addScalar("count", StandardBasicTypes.DOUBLE);
Now my problem. It seems that if you don't use the addScalar function, Hibernate will populate all of your fields with all of your columns in your SQL result (ie it will try to populate both 'name' and 'count'). However if you use the addScalar function, it only maps the columns that you listed, and all other columns seem to be discarded and the fields are left as null. In this instance, it wouldn't be too bad to just list both "name" and "count", but I have some other scenarios where I need a dozen or so fields - do I really have to list them all?? Is there some way in hibernate to say "map all fields automatically, like you used to, but by the way map this field as a Double"?
Is there some way in hibernate to say "map all fields automatically.
No, check the document here, find 16.1.1. Scalar queries section
The most basic SQL query is to get a list of scalars (values).
sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
These will return a List of Object arrays (Object[]) with scalar values for each column in the CATS table. Hibernate will use ResultSetMetadata to deduce the actual order and types of the returned scalar values.
To avoid the overhead of using ResultSetMetadata, or simply to be more explicit in what is returned, one can use addScalar():
sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
i use this solution, I hope it will work with you.
with this solution you can populate what you select from the SQL, and return it as Map, and cast the values directly.
since hibernate 5.2 the method setResultTransformer() is deprecated but its work fine to me and works perfect.
if you hate to write extra code addScalar() for each column from the SQL, you can implement ResultTransformer interface and do the casting as you wish.
ex:
lets say we have this Query:
/*ORACLE SQL*/
SELECT
SEQ AS "code",
CARD_SERIAL AS "cardSerial",
INV_DATE AS "date",
PK.GET_SUM_INV(SEQ) AS "sumSfterDisc"
FROM INVOICE
ORDER BY "code";
note: i use double cote for case-sensitive column alias, check This
after create hibernate session you can create the Query like this:
/*Java*/
List<Map<String, Object>> list = session.createNativeQuery("SELECT\n" +
" SEQ AS \"code\",\n" +
" CARD_SERIAL AS \"cardSerial\",\n" +
" INV_DATE AS \"date\",\n" +
" PK.GET_SUM_INV(SEQ) AS \"sumSfterDisc\"\n" +
"FROM INVOICE\n" +
"ORDER BY \"code\"")
.setResultTransformer(new Trans())
.list();
now the point with Trans Class:
/*Java*/
public class Trans implements ResultTransformer {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
#Override
public Object transformTuple(Object[] objects, String[] strings) {
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < strings.length; i++) {
if (objects[i] == null) {
continue;
}
if (objects[i] instanceof BigDecimal) {
map.put(strings[i], ((BigDecimal) objects[i]).longValue());
} else if (objects[i] instanceof Timestamp) {
map.put(strings[i], dateFormat.format(((Timestamp) objects[i])));
} else {
map.put(strings[i], objects[i]);
}
}
return map;
}
#Override
public List transformList(List list) {
return list;
}
}
here you should override the two method transformTuple and transformList, in transformTuple you have two parameters the Object[] objects its the columns values of the row and String[] strings the names of the columns the hibernate Guaranteed the same order of of the columns as you order it in the query.
now the fun begin, for each row returned from the query the method transformTuple will be invoke, so you can build the row as Map or create new object with fields.

Hibernate - sqlQuery map redundant records while using JOIN on OneToMany

I have #OneToMany association between 2 entities (Entity1 To Entity2).
My sqlQueryString consists of next steps:
select ent1.*, ent2.differ_field from Entity1 as ent1 left outer join Entity2 as ent2 on ent1.item_id = ent2.item_id
Adding some subqueries and writing results to some_field2, some_field3 etc.
Execute:
Query sqlQuery = getCurrentSession().createSQLQuery(sqlQueryString)
.setResultTransformer(Transformers.aliasToBean(SomeDto.class));
List list = sqlQuery.list();
and
class SomeDto {
item_id;
some_filed1;
...
differ_field;
...
}
So the result is the List<SomeDto>
Fields which are highlighted with grey are the same.
So what I want is to group by, for example, item_id and
the List<Object> differFieldList would be as aggregation result.
class SomeDto {
...fields...
List<Object> differFieldList;
}
or something like that Map<SomeDto, List<Object>>
I can map it manually but there is a trouble:
When I use sqlQuery.setFirstResult(offset).setMaxResults(limit)
I retrieve limit count of records. But there are redundant rows. After merge I have less count actually.
Thanks in advance!
If you would like to store the query results in a collection of this class:
class SomeDto {
...fields...
List<Object> differFieldList;
}
When using sqlQuery.setFirstResult(offset).setMaxResults(n), the number of records being limited is based on the joined result set. After merging the number of records could be less than expected, and the data in List could also be incomplete.
To get the expected data set, the query needs to be broken down into two.
In first query you simply select data from Entity1
select * from Entity1
Query.setFirstResult(offset).setMaxResults(n) can be used here to limit the records you want to return. If fields from Entity2 needs to be used as condition in this query, you may use exists subquery to join to Entity2 and filter by Entity2 fields.
Once data is returned from the query, you can extract item_id and put them into a collection, and use the collection to query Entity 2:
select item_id, differ_field from Entity2 where item_id in (:itemid)
Query.setParameterList() can be used to set the item id collection returned from first query to the second query. Then you will need to manually map data returned from query 2 to data returned from query 1.
This seems verbose. If JPA #OneToMany mapping is configured between the 2 entity objects, and your query can be written in HQL (you said not possible in comment), you may let Hibernate lazy load Entity2 collection for you automatically, in which case the code can be much cleaner, but behind the scenes Hibernate may generate more query requests to DB while lazy loading the entity sitting at Many side.
The duplicated records are natural from a relational database perspective. To group projection according to Object Oriented principles, you can use a utility like this one:
public void visit(T object, EntityContext entityContext) {
Class<T> clazz = (Class<T>) object.getClass();
ClassId<T> objectClassId = new ClassId<T>(clazz, object.getId());
boolean objectVisited = entityContext.isVisited(objectClassId);
if (!objectVisited) {
entityContext.visit(objectClassId, object);
}
P parent = getParent(object);
if (parent != null) {
Class<P> parentClass = (Class<P>) parent.getClass();
ClassId<P> parentClassId = new ClassId<P>(parentClass, parent.getId());
if (!entityContext.isVisited(parentClassId)) {
setChildren(parent);
}
List<T> children = getChildren(parent);
if (!objectVisited) {
children.add(object);
}
}
}
The code is available on GitHub.

Map fetch/result from jooq to specific Record

When I currently query with Jooq I am explicitly casting each record-object to the expected record-type.
Result<Record> result = sql.select().from(Tables.COUNTRY).fetch();
for (Record r : result) {
CountryRecord countryRecord = (CountryRecord) r;
//Extract data from countryRecord
countryRecord.getId();
}
Is it, with Jooq, possibly to cast the result straight into the desired record-type?
Such as (this does not compile):
Result<CountryRecord> countryRecords = (Result<CountryRecord>) sql.select().from(Tables.COUNTRY).fetch();
for (CountryRecord cr : countryRecords) {
cr.getNamet();
//etc...
}
#Lukas,
Actually we are using fetchInto() to convert the results to list of object.
For example:
Employee pojo matching database table is employee.
List<Employee> employeeList = sql.select(Tables.Employee)
.from(Tables.EMPLOYEE).fetchInto(Employee.class);
similarly, how could we convert the records we are fetching using joins?
For example:
Customer pojo matching database table is customer.
Employee pojo matching database table is employee.
sql.select(<<IWantAllFields>>).from(Tables.CUSTOMER)
.join(Tables.EMPLOYEE)
.on(Tables.EMPLOYEE.ID.equal(Tables.CUSTOMER.EMPLOYEE_ID))
.fetchInto(?);
You shouldn't be using the select().from(...) syntax when you want to fetch generated record types. Use selectFrom() instead. This is documented here:
http://www.jooq.org/doc/3.1/manual/sql-execution/fetching/record-vs-tablerecord
So your query should be:
Result<CountryRecord> countryRecords = sql.selectFrom(Tables.COUNTRY).fetch();
for (CountryRecord cr : countryRecords) {
cr.getNamet();
//etc...
}

what should be return type of a query which fetches 2 columns in spring data neo4j?

I have following query which i use through #Query annotation with GraphRepository in spring data neo4j. So to get a result i declare return type of method as List
#Query(value = "START user=node:searchByMemberID(memberID=1) MATCH user-[r:FRIENDS_WITH]->member RETURN member")
List<Node> getNodes(int userID);
Now if i want to write a query which returns 2 columns, what will be the return type of its corresponding method. For e.g. what should i write in place of List, as in above query, for the below mentioned query.
START user=node:searchByMemberID(memberID='1') MATCH user-[r:FRIENDS_WITH]->member RETURN member, r.property
In that case queries return an Iterable<Map<String,Object>> which allows you to iterate over the returned rows. Each element is a map which you can access by the name of the returned field and using the neo4jOperations conversion method to cast the value object to its proper class, i.e.:
Iterable<Map<String, Object>> it = getNodes(...);
while (it.hasNext()) {
Map<String, Object> map = it.next();
obj = neo4jOperations.convert(map.get("member"), Node.class);
...
}
You can now do something like this in SDN:
#QueryResult
public class NodeData {
Member member;
String property;
}
And then in the repository:
#Query("The Query...")
public NodeData getNodes(int userId);
This was adapted from an example in the documentation here: https://docs.spring.io/spring-data/neo4j/docs/4.2.x/reference/html/#reference_programming-model_mapresult

Select non-entities with JPA?

Is it possible with JPA to retrieve a instances of a non-entity classes with native queries?
I have a non-entity class that wraps two entities:
class Wrap{
Entity1 ent1;
Entity2 ent2
}
#Entity
class Entity1{
...
}
#Entity
class Entity2{
...
}
How can I do something like that?
Query q = entityManager.createNativeQuery("native select here");
List<Wrap> list = q.getResultList();
Is it possible with JPA to retrieve a instances of a non-entity classes with native queries?
No. Native queries can return entities only (if you tell them to do so by passing the resultClass or a resultSetMapping to the createNativeQuery method; if you don't, you will get collections of raw data).
In JPQL, you can use constructor expressions (SELECT NEW...) whith a non-entity constructor. But this is not supported for native queries, you'll have to do it manually.
JPA native query without entity - especially with complex queries (recursive, multiple joins, etc.) ?
This worked for me, but it's hibernate specific :
import org.hibernate.transform.Transformers;
Query query = entityManagerFactory.createEntityManager().createNativeQuery(SQL);
// Transform the results to MAP <Key, Value>
query.unwrap(org.hibernate.SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
query.setParameter("myNamedParam", myParam);
List<Map<String, Object>> list = query.getResultList();
for (Map<String, Object> map : list) {
System.out.println(map);
}
cf. https://www.programmerall.com/article/89371766511/
I think I found the solution.
There is a way to use the NEW keyword in constructing the query.
What I did to resovle this issue :
public List<ProductType> getProductByName(String productName) {
String sqlQuery = "select DISTINCT **NEW** project1.ProductType(o.name, o.revision) from Lhproduct o where o.name = :prodname";
Query qry = getEntityManager().**createQuery(sqlQuery);**
qry.setParameter("prodname",productName);
return qry.getResultList();
}
The ProductType is a non-entity object, a simple plain object implementing Serialiabale. But you need to define the appropriate constructor.
Happy coding :-)
Thanks and Regards,
Hari

Categories

Resources