jdbctemplate return multple rows with dynamic sql - java

I have a method which needs to return a list of Strings from a DB.
private static List<String> getUserForAccount(final String account) {
String sql = "SELECT NAME FROM CLIENTS WHERE ACCOUNT = ?";
}
I am unsure which jdbcTemplate method is the best to use for this situation. I don't think I can use queryForList() because the SQL has a parameter.
Any suggestions would be welcome .
Thanks

Check out the JdbcTemplate API. You argument that "I don't think I can use queryForList() because the SQL has a parameter" really doesn't make much sense. I think all the queryForList() takes parameters, which is the parameter Object[] args, as in
List<Map<String,Object>> queryForList(String sql, Object[] args, int[] argTypes
There are many overloaded methods for queryForList() but I think this is the one you want. Basically just pass your query, an array of arguments, which will only be account in your case, and pass the db Types (int) argument. Something like
public class SomeDaoImpl extends JdbcTemplate implements SomeDao {
private static final String NAME_BY_ACCOUNT =
"SELECT NAME FROM CLIENTS WHERE ACCOUNT = ?"
public List<Map<String, Object>> getUsernameByAccount(String account) {
return (List<Map<String, Object>>) queryForList(
NAME_BY_ACCOUNT,
new Object[] { account },
new int[] { Types.VARCHAR });
}
}
The key returns will be the name of the column. Then you can iterate the list of maps like
List<Map<String, Object>> result = someDao.getUsernameByAccount(account);
for (Map map : result) {
System.out.println(map.get("NAME"));
}
You may also find some interest in some of the overloaded query() methods, which take variations of PreparedStatementSetter and RowMapper. Look at the API I linked above

Related

How do I properly convert List to ArrayList of my type of Object?

I have a JdbcTemplate query that fetches all the records from the database table using queryForList. I have a class that operates on ArrayLists mainly and would like to convert the List to ArrayList<Customer>. In the debugger it retrieves the records fine, but I'm having trouble figuring out how to cast the Object to a Customer and create an ArrayList of type Customer. Here is my code:
public static ArrayList<Customer> getCustomers(){
String query = "SELECT customerId,lastName,firstName,email,address,city,state,zip FROM " +
"Customers";
List customerList = jdbcTemplate.queryForList(query);
ArrayList<Customer> customerArrayList = new ArrayList(customerList);
return customerArrayList;
}
Is there any reason List is naked instead of List<Customer>? I believe that's all you need to fix.
Note ArrayList supports the constructor
ArrayList(Collection<? extends E> c)
Which you are trying to use.
From Spring's JDBC Template documentation it looks like you need to "hint" the return type:
<T> List<T> queryForList(String sql, Class<T> elementType)
i.e. call
List<Customer> customerList = jdbcTemplate.queryForList(query, Customer.class);

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.

Best practice for method with Map return

Suppose i have a method which has map as return type and uses generics.
I would like to know what is the best practice of filling that Map object.
Please see the snippet.
public Map<String,?> getEmployeeInfo(String query) {
Map<String,Object> dataMap = new HashMap<String,Object>();
// do some op.
String empId = "abc123";
List<Long> projectIds = new ArrayList<Long>();
List<String> performanceGoals = new ArrayList<String>();
dataMap.put("empId",empId);
dataMap.put("projectIds",projectIds);
dataMap.put("performanceGoals",performanceGoals);
return dataMap;
}
The best practise is: Don't use this.
Make a Class Employee with members
public class Employee {
String id;
List<Long> projectIds;
List<String> performanceGoals;
...
}
And you method changes to:
public Employee getEmployeeInfo(String query) {
...
update for clarification why returning Map is bad in general
If your method returns:
Map<String,?> or Map<String,? extends Object> you say (in slang):
Hey look here, I am returning something. Store it in a variable called "something", because I don't say anything about the value.
If you write this method, you have to ensure, that you know every single line of code, where you work with your Map.
Lets say I would like to change employeeId from String to Integer. This will lead to really bad RuntimeExceptions and ClassCastExceptions.

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

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

Categories

Resources