Using SpringBatch JdbcCursorItemReader with List as NamedParameters - java

I am using below bean definition to configure a reader to read some data from the database table in a Spring Batch project. It is using a named param in SQL. I am passing A java.util.List as a parameter. However, I am getting Invalid Column type error when it tries to run the SQL.
If I just hard code one single value (namedParameters.put("keys", "138219"); ) instead of passing a list, it works.
#Bean
public JdbcCursorItemReader<MyDTO> myReader() {
JdbcCursorItemReader<MyDTO> itemReader = new JdbcCursorItemReader<>();
itemReader.setDataSource(myDatasource);
itemReader.setRowMapper(return new RowMapper<MyDTO>() {
#Override
public MyDTO mapRow(ResultSet resultSet, int rowNum) throws SQLException {
return toMyDto(resultSet);
}
};);
Map<String, Object> namedParameters = new HashMap<>();
List<Long> keys= //Some List
Map<String, List<Long>> singletonMap = Collections.singletonMap("keys", keys);
namedParameters.putAll(singletonMap);
itemReader.setSql(NamedParameterUtils.substituteNamedParameters("SELECT A FROM MYTABLE WHERE KEY IN (:keys)",new MapSqlParameterSource(namedParameters)));
ListPreparedStatementSetter listPreparedStatementSetter = new ListPreparedStatementSetter();
listPreparedStatementSetter.setParameters(
Arrays.asList(NamedParameterUtils.buildValueArray("SELECT A FROM MYTABLE WHERE KEY IN (:keys)", namedParameters)));
itemReader.setPreparedStatementSetter(listPreparedStatementSetter);
return itemReader;
}
I referred sample code snippet here as a response to one of the questions asked - it is what seems to be working when we pass one value. However, my issue is with passing a list instead of one value in the param. This is where it seems to fail.

The ListPreparedStatementSetter is not aware of parameters types. If a parameter is an array or a collection, it will set it as is to the first placeholder, leaving other placeholders unset. Hence the error. In your example, if List<Long> keys = Arrays.asList(1, 2), your sql statement will be:
SELECT A FROM MYTABLE WHERE KEY IN (?, ?)
If you pass your singletonMap to the ListPreparedStatementSetter, it will set the value of keys (which is of type List) to the first placeholder and that's it. The second placeholder will still be unset and the preparation of the statement will fail.
You can flatten parameters before passing them to the ListPreparedStatementSetter and it should work fine. I added a sample with how to flatten parameters before passing them to the prepared statement setter here (See flatten method).
Hope this helps.

Related

How to query for a single column in NamedJdbcTemplate?

How do I query for a List of Integers using namedParameterJdbcTemplate? I tried following this template, it is not working below .
https://stackoverflow.com/a/40417349/15435022
String customerName = "JOE";
MapSqlParameterSource customerParameters = new MapSqlParameterSource();
customerParameters.addValue("CustomerName", customerName);
private static final String query = "SELECT Customer_Id From dbo.Customers WHERE Customer_Name = :CustomerName";
List<Integer> data = namedParameterJdbcTemplate.queryForList(query, Integer.class);
Error: Cannot resolve method 'queryForList(java.lang.String, java.lang.Class<java.lang.Integer>)'
Why code gives an error
As mentioned in the docs queryForList Method have following implementations available:
queryForList(String sql, Map<String,?> paramMap).
queryForList(String sql, Map<String,?> paramMap, Class<T> elementType)
queryForList(String sql, SqlParameterSource paramSource)
queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType)
None of these implementations matches the parameters used in the given implementation. Thus, we end up with this error:
Error: Cannot resolve method 'queryForList(java.lang.String, java.lang.Class<java.lang.Integer>)'
The idea behind passing missing parameter
The key idea behind passing a Map or ParameterSource is to have a dynamic query where we can put in values later on.
Eg:
String query = "Select :columnName from table";
Map<String,String> map = new HashMap<>();
map.put("columnName", "userName");
When this map is passed along with the query String, internally it is used to replace placeholders with the values from the map.
How to fix the code
There are two ways you can fix this:
Just pass null
This is not the best way of fixing the problem is definitely not recommended for a production code. But, this can be used if there is no placeholder in the query string.
Code:
List<Integer> data = namedParameterJdbcTemplate.queryForList(query, null, Integer.class);
Create and pass an empty Map or SqlParameterSource
You already have a MapSqlParameterSource called customerParameters in your code. Simply pass it while calling queryForList()
Code:
List<Integer> data = namedParameterJdbcTemplate.queryForList(query, customerParameters, Integer.class);

Storing the keysets from a JPQL query result in a java list

I was successfully able to execute a jpql query and print the result which is stored in a queryResults variable. What I want to achieve next is storing just the IDs (primary key column) in a list without the date (value), but I am not too sure if this is possible; perhaps using something like a java map. Is it possible? If yes, how can this be easily achieved?
private static final TestDao Test_DAO = new TestDao();
#Test
public void testById() {
List<TestEntity> queryResults = TEST_DAO.findById(""); //The record from the sql query is stored in queryResults and findById("") is the method that executes the query in a TestDao class and it is called here
for (TestEntity qResult: queryResults) { // looping through the query result to print the rows
System.out.println(qResult.getId());
System.out.println(qResult.getDate());
}
System.out.println("This is the sql result " + queryResults );
}
Output:
This is the result [TestEntity(id=101, date=2020-01-19 15:12:32.447), TestEntity(id=102, date=2020-09-01 11:04:10.0)]// I want to get the IDs 101 and 102 and store in a list without the Dates
I tried using a map this way:
Map<Integer, Timestamp> map= (Map<Integer, Timestamp>) queryResults.get(0); but I got an exception:
java.lang.ClassCastException: TestEntity cannot be cast to java.util.Map
There are some points before the implementation.
Why are you defining DAO as static? I think this is a bad implementation unless I am missing a particular reason you declared it static. You should define this as a member variable and not a static member
The naming of the method - findById() translated in English is - find Something by this Id, but you are fetching a list of Records, so naming is not correct.
Point 2 becomes invalid if ID property is not a Primary Key in your table, then it makes sense, but still naming is bad. Id is something we use to define Primary Key in the Database and should be and will be unique. But your comments suggest that ID is unique and the Primary Key. So read about how Databases work
And even if not unique, if you pass an Id to find some records, why will get different ids in the Records !!!
About implementation:
Changing in your existing code:
private TestDao Test_DAO = new TestDao();
#Test
public void testById() {
List<TestEntity> queryResults = TEST_DAO.findById("");
List<Long> listOfIds = new ArrayList<>(); // Assuming Id is Long type, same logic for any type
for (TestEntity qResult: queryResults) {
System.out.println(qResult.getId());
listOfIds.add(qResult.getId()); // Just add it to the list
System.out.println(qResult.getDate());
}
}
In case you want to be efficient with the query:
You can use JPQL and hibernate
You can then write a query like:
String query = "select te.id from TestEntity te";
// Create the TypedQuery using EntityManager and then get ResultSet back
List<Long> ids = query.getResultList();
In case of using Spring-Data-Jpa, you can define the repository and define the method and pass the query with #Query annotation. Spring Data JPA

Empty ResultSet from query

I'm trying to query for a set and return it as a hash map object with Spring JdbcTemplate. But some reasons I'm getting empty result set from server. It is possible that I have a problem in the overall configuration but the rest of the queries are working without problems.
This is how I query
public Map<Integer, String> getCompanyDataservers() {
return getTemplate().queryForObject("select id, dataserver from company", new RowMapper<Map<Integer,String>>() {
#Override
public Map<Integer, String> mapRow(ResultSet rs, int rowNum)
throws SQLException {
HashMap<Integer, String> toReturn = new HashMap<Integer, String>();
while(rs.next()) {
int id = rs.getInt("id");
toReturn.put(id, dataserver);
}
return toReturn;
}});
}
After some debugging and logging statements I concluded that my resultset seems to be empty of any rows. When I query the same ("select id, dataserver from company") manually directly from DB I get the desired result. Yet this way I get a resultset with 0 rows.
One of my theories is that can't get get this kind of set when querying for a object this way. But isn't there a possibility to be free at you queries and construct a more elaborate object as a query result, or I have to create a purpose built class to be used by "queryForList" to get the desired data and convert it afterwards?
Or am I just missing something?
You are not supposed to invoke rs.next() inside mapRow. JdbcTemplate will invoke mapRow for you for every row in the resultset
All you need to do on mapRow is just return a HashMap which represent one single database row, spring will do the resultset iteration for you

How to best map results from an SQL query to a non-entity Java object using Hibernate?

I have a Hibernate managed Java entity called X and a native SQL function (myfunc) that I call from a Hibernate SQL query along these lines:
SQLQuery q = hibernateSession.createSQLQuery(
"SELECT *, myfunc(:param) as result from X_table_name"
);
What I want to do is to map the everything returned from this query to a class (not necessarily managed by Hibernate) called Y. Y should contain all properties/fields from X plus the result returned by myfunc, e.g. Y could extend class X and add a "result" field.
What I've tried:
I've tried using q.addEntity(Y.class) but this fails with:
org.hibernate.MappingException: Unknown entity com.mycompany.Y
q.setResultTransformer(Transformers.aliasToBean(Y.class)); but this fails with: org.hibernate.PropertyNotFoundException: Could not find setter for some_property. X has a field called someProperty with the appropriate getter and setter but in this case it doesn't seem like Hibernate maps the column name (some_property) to the correct field name.
q.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP); returns a Map but the values are not always of the type expected by the corresponding field in X. For example fields in X of type enum and Date cannot be mapped directly from the Map returned by the SQL query (where they are Strings).
What's the appropriate way to deal with this situation?
See the chapter of the documentation about SQL queries.
You can use the addScalar() method to specify which type Hibernat should use for a given column.
And you can use aliases to map the results with the bean properties:
select t.some_property as someProperty, ..., myfunc(:param) as result from X_table_name t
Or, (and although it require some lines of code, it's my preferred solution), you can simply do the mapping yourself:
List<Object[]> rows = query.list();
for (Object[] row : rows) {
Foo foo = new Foo((Long) row[0], (String) row[1], ...);
}
This avoids reflection, and lets you control everything.
Easy. Cast the rows to Map<String, Object>:
final org.hibernate.Query q = session.createSQLQuery(sql);
q.setParameter("geo", geo);
q.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
final List<Map<String, Object>> src = q.list();
final List<VideoEntry> results = new ArrayList<VideoEntry>(src.size());
for (final Map<String, Object> map:src) {
final VideoEntry entry = new VideoEntry();
BeanUtils.populate(entry, map);
results.add(entry);
}
First of all you need to declare the entity in the hibernate configuration xml file something like this: .....
class="path to your entity"
Or you can do the same thing programatically before you make the query.

How to get Map data using JDBCTemplate.queryForMap

How to load data from JDBCTemplate.queryForMap() and it returns the Map Interface.How the maintained the query data internally in map.I trying to load but i got below exception i.e., org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result
Code:-
public List getUserInfoByAlll() {
List profilelist=new ArrayList();
Map m=new HashMap();
m=this.jdbctemplate.queryForMap("SELECT userid,username FROM USER");
Set s=m.keySet();
Iterator it=s.iterator();
while(it.hasNext()){
String its=(String)it.next();
Object ob=(Object)m.get(its);
log.info("UserDAOImpl::getUserListSize()"+ob);
}
return profilelist;
}
Plz help me
queryForMap is appropriate if you want to get a single row. You are selecting without a where clause, so you probably want to queryForList. The error is probably indicative of the fact that queryForMap wants one row, but you query is retrieving many rows.
Check out the docs. There is a queryForList that takes just sql; the return type is a
List<Map<String,Object>>.
So once you have the results, you can do what you are doing. I would do something like
List results = template.queryForList(sql);
for (Map m : results){
m.get('userid');
m.get('username');
}
I'll let you fill in the details, but I would not iterate over keys in this case. I like to explicit about what I am expecting.
If you have a User object, and you actually want to load User instances, you can use the queryForList that takes sql and a class type
queryForList(String sql, Class<T> elementType)
(wow Spring has changed a lot since I left Javaland.)
I know this is really old, but this is the simplest way to query for Map.
Simply implement the ResultSetExtractor interface to define what type you want to return. Below is an example of how to use this. You'll be mapping it manually, but for a simple map, it should be straightforward.
jdbcTemplate.query("select string1,string2 from table where x=1", new ResultSetExtractor<Map>(){
#Override
public Map extractData(ResultSet rs) throws SQLException,DataAccessException {
HashMap<String,String> mapRet= new HashMap<String,String>();
while(rs.next()){
mapRet.put(rs.getString("string1"),rs.getString("string2"));
}
return mapRet;
}
});
This will give you a return type of Map that has multiple rows (however many your query returned) and not a list of Maps. You can view the ResultSetExtractor docs here: http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/jdbc/core/ResultSetExtractor.html
To add to #BrianBeech's answer, this is even more trimmed down in java 8:
jdbcTemplate.query("select string1,string2 from table where x=1", (ResultSet rs) -> {
HashMap<String,String> results = new HashMap<>();
while (rs.next()) {
results.put(rs.getString("string1"), rs.getString("string2"));
}
return results;
});
You can do something like this.
List<Map<String, Object>> mapList = jdbctemplate.queryForList(query));
return mapList.stream().collect(Collectors.toMap(k -> (Long) k.get("userid"), k -> (String) k.get("username")));
Output:
{
1: "abc",
2: "def",
3: "ghi"
}

Categories