How to query for a single column in NamedJdbcTemplate? - java

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);

Related

Error when passing List/Set of Strings to be Used in an "IN" clause in a query: Can't infer type

I am attempting to pass an array of string values so that said array of strings can be used in an "IN" clause like so with Java/PostgreSQL:
Sample query (contained in a string variable):
private static final String strSQLQueryAcceptingAnArray = "SELECT count(*) FROM sometable WHERE clm IN (:arrStringValues);";
public List<SampleReturnObject> getInClauseResults(List<String> lstStringValues) {
Set<String> setStringValues = new HashSet<String>(lstStringValues);
return this.query(strSQLQueryAcceptingAnArray, previouslyDefinedRowMapper, new Object[] { setStringValues });
}
However, when I run this, I get the following error message:
Can't infer the SQL type to use for an instance of ors.springframework.jdbc.namedparam.MapSQLParameterSource. Use setObject() with an explicit Types value to specify the type to use.
How can I pass a list/array of strings to use in an "IN" clause in PostgreSQL using Java and Spring Framework?
You can use a NamedParameterJdbcTemplate with SqlParameterSource like this:
private static final String strSQLQueryAcceptingAnArray = "SELECT count(*) FROM sometable WHERE clm IN (:arrStringValues)";
#Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public List<SampleReturnObject> getInClauseResults(List<String> lstStringValues) {
Set<String> setStringValues = new HashSet<String>(lstStringValues);
SqlParameterSource sqlParameterSource = new MapSqlParameterSource("arrStringValues", setStringValues);
return namedParameterJdbcTemplate.query(strSQLQueryAcceptingAnArray, sqlParameterSource, previouslyDefinedRowMapper);
}
Append the array values in a string by seperating the values using comma and then use that string in sql query IN clause.

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

Using SpringBatch JdbcCursorItemReader with List as NamedParameters

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.

reuse sql param when not using named parameters

I have a query that gets called with JdbcTemplate but only one param is getting sent and I need to use that one param in two where conditions.
The Query
String sql = "select * from employee where salary > ? and netpay > ?";
The Call
The param here is only one. I.E. if the id is TEST123 the query needs to be
select * from employee where id = TEST123 and name = TEST123 even though one param is getting passed.
getJdbcTemplate().query(sql, new Object[]{"TEST123"}, CustomResultSetExtractor());
Is there any way to do this from the query side instead of passing two params?
NOTE
I do not have access to change the way the query is called, hence I cannot add named params, or just pass an additional parameter.
Use NamedParameterJdbcTemplate, a JdbcTemplate wrapper:
Template class with a basic set of JDBC operations, allowing the use of named parameters rather than traditional '?' placeholders.
This class delegates to a wrapped JdbcTemplate once the substitution from named parameters to JDBC style '?' placeholders is done at execution time.
Your SQL will be with 1 parameter:
select * from employee where id = (:id) and name = (:id)
And code will be :
MapSqlParameterSource args = new MapSqlParameterSource();
args.addValue("id", TEST123);
return new NamedParameterJdbcTemplate(getJdbcTemplate()).query(sql , args, youRowMapper);
If you can't change it, you can change your query to:
select * from employee where id = ? and id = name
I am amazed that you didn't find:
String sql = "select * from employee where id = ? and name = id";
Or did you mean or instead of and?
String sql = "select * from employee where ? in (id, name)";
I would suggest something else, you can repeat your param in Object array for example if your query have two ? then generate the parameters like so :
String param = "TEST123";
Object[] params = IntStream.range(0, 2)
.mapToObj(i -> param)
.toArray();
This will generate an array which hold two time TEST123 :
[TEST123, TEST123]
Then Just send the Object array to your code like you do.
getJdbcTemplate().query(sql, params, CustomResultSetExtractor());
If you don't know the number of hold or parameters in your query you can count them like so :
int numberOfHold = sql.length() - sql.replaceAll("\\?", "").length();

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.

Categories