Hibernate ResultTransformer unable to cast a single column - java

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.

Related

How to map the POJO with the database ResultSet if the columnNames and fieldNames is different using java Reflection?

I am working in dynamically mapping the values from Result Set to POJO using Java reflection. It is working, but if the column name is different from the field in pojo it is not getting mapped.
For ex: if my column name is ORD_ID and in my pojo it is orderId, the ord_id is not getting mapped with order id. Here is the logic i'm using below.Kindly suggest a solution or an idea. Thanks in advance !
int colCount = resultSet.getMetaData().getColumnCount();
for (int i = 0; i < colCount; i++)
{
columnNames.put(resultSet.getMetaData().getColumnName(i + 1).toLowerCase(), i);
}
List<T> results = new ArrayList<>();
while(resultSet.next())
{
T newObj = clazz.newInstance();
for (Field field : clazz.getDeclaredFields())
{
String fieldName = field.getName().toLowerCase();
if (columnNames.containsKey(fieldName))
{
final int index = columnNames.get(fieldName);
field.setAccessible(true);
field.set(newObj, resultSet.getObject(index+1));
}
}
results.add(newObj);
}
but if the column name is different from the field in pojo it is not
getting mapped
Obviously, that wouldn't work since your code is written for that very purpose when both names match so I don't understand the surprise element.
Only way out that I can think of is a secondary global map of field name to DB Column name and you refer to it once columnNames.containsKey(fieldName) is false. That map is a manual work and that manual work will always be there since only you as a developer know that which column maps to which field in the POJO. That can't be automated if both are different & external mapping needs to be fed to Java progarm.
That mapping can be kept in an external property file.
There are APIs like apache-commons-dbutils but again manual mapping wouldn't go away as you will have to provide that in your custom - org.apache.commons.dbutils.BeanProcessor
Something else can be done the lines of JPA entity generation tools where we attach something like below to POJO fields -
#Column(name = "ADDRESS_IND")
private String addressInd;
but that is again a manual work as far as mapping specification is concerned. I think, you can retrieve annotation value and construct your mapping.
How to get annotation class name, attribute values using reflection
I did something similar recently, it's very crude but it works.
// finalData format will be [List<String>,List<List<String>>] i.e [column names, column data]
List<Object> finalData = new ArrayList<Object>();
List<String> columnNames = new ArrayList<>();
// columnData will be list of lists, where inner list comprised of each row data and outer list comprised of such row objects.
List<List<String>> columnData = new ArrayList<List<String>>();
ResultSet rs = serviceDao.generateData(query);//returns result set based on query
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++ ) {
String columnName = rsmd.getColumnName(i);
columnNames.add(columnName);
}
finalData.add(columnNames);// first object of finalData object
//Iterate through result set for each row, get all the columns data which are present in column names
while(rs.next()) {
List<String> rowData = new ArrayList<String>();
for(int i=0;i<columnNames.size();i++) {
rowData.add(rs.getString(columnNames.get(i)));
}
columnData.add(rowData);
}
finalData.add(columnData); // Second object of finalData object
Edit 1: you can use rs.getString("column name") to retrieve any datatype as Strings.
Oracle docs says
Note that although the method getString is recommended for retrieving the SQL types CHAR and VARCHAR, it is possible to retrieve any of the basic SQL types with it. Getting all values with getString can be very useful, but it also has its limitations. For instance, if it is used to retrieve a numeric type, getString converts the numeric value to a Java String object, and the value has to be converted back to a numeric type before it can be operated on as a number. In cases where the value is treated as a string anyway, there is no drawback. Furthermore, if you want an application to retrieve values of any standard SQL type other than SQL3 types, use the getString method.

How to compare list of records against database in Java?

How to compare list of records against database? I have more than 1000 records in list and need to validate against database. How to validate each record from list to database? Select all the data from database and stored in list, then have to compare the values? Please advise...
The below code lists values to validate against database.
private void validatepart(HttpServletRequest req, Vector<String> errors) {
Parts Bean = (Parts)req.getAttribute("partslist");
Vector<PartInfo> List = Bean.getPartList();
int sz = partList.size();
for (int i = 0; i < sz; i++) {
PartInfo part = (PartInfo)partList.elementAt(i);
System.out.println(part.getNumber());
System.out.println(part.getName());
}
}
This depends on what you mean by compare. If it's just one field then executing a query such as select * from parts_table where part_number = ?. It's not that much of a stretch to add more fields to that query. If nothing is returned you know it doesn't exist.
If you need to compare and know exactly which values are different then you can try something like this
List<String> compareObjects(PartInfo filePart, PartInfo dbPart) {
List<String> different = new LinkedList<String>();
if (!filePart.getNumber().equals(dbPart.getNumber())) {
different.add("number");
}
//repeat for all your fields
return different;
}
If your list of objects that you need to validate against the database includes a primary key, then you could just build a list of those primary key values and run a query like:
SELECT <PRIMARY KEY FIELD> FROM <TABLE> WHERE <PRIMARY_KEY_FIELD> IN <LIST OF PRIMARY KEYS> SORT BY <PRIMARY KEY FIELD> ASC;
Once you get that list back, you can compare the results. My instinct would be to put your data (and the query results too) into a Set object and then call removesAll() to get the items not in the database (reverse this for items in the database but not in your set):
yourDataSet.removeAll(queryResults);
This assumes that you have an equals() method implemented in your PartInfo object. You can see the Java API documentation for more details.

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

Any collection object to hold list of combination of more than 2 elements?

Is there a collection object or a approach to hold a combination of elements?
For instance, I need to create a list that contains the combination of the elements name, age, height and weight.
Creating an object for this is not a good idea in my case. Because the number of fields keep changing.
I need to create this list to pass to a query.
Any solution?
class MyContainer {
String someString;
int someInt;
}
List <MyContainer> myList = new List<>();
Something like that!?
I donĀ“t know exactly, what you mean by "Creating an object for this is not a good idea in my case". You could as an alternative create a List<Object> and put in whatever you have or even a List<List<Object>> if you want to have a List of a number of grouped objects.
The best approach would be to make an Object with all the possible elements in it.
class myObject {
String name;
Integer age;
Float weight;
// Etc
}
Or have a base class then have another class which extends this with additional elements
class myExtendedObject extends myObject{
String streetAddress;
String city;
// etc;
}
Then if you don't have an element set it to null... you could always build your query from the object itself by including a method to return your query, juct check if its null and not include in your query (Assuming you mean an sql style query)
public String buildQuery{
String query = "Select * from blahtable Where ";
query += (name != null)?" name = " + name : "";
// etc, or what ever your query needs to be
return query
}
Other wise you could just have a method which returns a map of your elements then you know what the type of each element is based on the key
public Map<String, Object> getElements{
Map<String, Object> myMap = new HashMap<String, Object>();
if(name != null)
myMap.put("Name", name);
// etc
return myMap
}
What about just using a Map for that and use attribute name as key (e.g. Weight )?
You can use any combination of attributes you want and it would be convenient to pass such collection to the query
Consider Enum map should you require more column names type safety

spring-data #Query mapping result issue

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

Categories

Resources