Exception while using getJdbcTemplate().queryForList in Spring - java

I am trying to use Spring getJdbcTemplate().queryForList(sql,params) to fetch list of records from the oracle db.
Searched SO as well as google but could not get a proper solution.
The problem is that if i run the same query in the SQL Developer it runs without any issue but if i try to call the same query by passing parameters from my Java code JDBC template its throwing exception.
Dao Code:
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("CODE", Code);
paramMap.put("NAME", Name);
List statusCodesDetails = super.getJdbcTemplate().queryForList(selectSQL,paramMap);
SQL:
SELECT STATUS FROM PROCESS P,TRANSACTION R WHERE P.FILE_ID = R.FILE_ID and P.CODE = :CODE AND R.NAME = :NAME
In the query above both the P.CODE and R.NAME are Varchar.
Exception:
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [
SELECT STATUS FROM PROCESS P,TRANSACTION R WHERE P.FILE_ID = R.FILE_ID and P.CODE = :CODE AND R.NAME = :NAME
]; SQL state [99999]; error code [17004]; Invalid column type; nested exception is java.sql.SQLException: Invalid column type
Can anybody please let me know what is the issue here and what i am doing wrong.
Update:
I was able to get it working by using the ? instead for the named parameters since i was using simplejdbctemplate. I think there is some problem while using named parameters in jdbctemplate.
Modified SQL:
SELECT STATUS FROM PROCESS P,TRANSACTION R WHERE P.FILE_ID = R.FILE_ID and P.CODE = ? AND R.NAME = ?
Modified Code:
List statusCodesDetails = super.getJdbcTemplate().queryForList(selectSQL,new Object[]{CODE , NAME});
Thanks everyone for your help.
Thanks
Vikeng21

please check the datatype in DB and datatype in java compatibility for each attribute you are retreiving.
sometimes varChar(1) will not work so use varchar(2) like this..if possible you can post both datatypes DB and Java

Spring JdbcTemplate will guess the sql type , it seems it not working correctly for your data types.
Instead of using Map<String, Object> use MapSqlParameterSource , specify parameter types using addValue(String paramName, Object value, int sqlType).

Related

Spring JPA and JDBC Template - very slow select query execution with IN clause

I am trying to execute the following query from my Java project.
I am using MySQL and data store and have configured Hikari CP as Datasource.
SELECT iv.* FROM identifier_definition id
INNER JOIN identifier_list_values iv on id.definition_id = iv.definition_id
where
id.status IN (:statuses)
AND id.type = :listType
AND iv.identifier_value IN (:valuesToAdd)
MySQL connection String:
jdbc:mysql://hostname:3306/DBNAME?useSSL=true&allowPublicKeyRetrieval=true&useServerPrepStmts=true&generateSimpleParameterMetadata=true
When I execute this same query from MySQL workbench it returns results in 0.5 sec.
However when I do the same from JPA Repository or Spring JDBC Template its taking almost 50 secs to execute.
This query has 2 IN clauses, where statuses collection has 3 only items whereas identifierValues collection has 10000 items.
When I execute raw SQL query without named params using JDBC template it got results in 2 secs. However, this approach is suseptible to SQL injection.
Both JPA and JDBC Templete under the hood makes used of Java PreparedStatement. My hunch is the underlying PreparedStatement while adding large params set is causing performance issue.
How do I improve my query performance?
Following is the JDBC template code that I am using:
#Component
public class ListValuesDAO {
private static final Logger LOGGER = LoggerFactory.getLogger(ListValuesDAO.class);
private final NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
public ListValuesDAO(DataSource dataSource) {
jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public void validateListOverlap(List<String> valuesToAdd, ListType listType) {
String query = "SELECT iv.* FROM identifier_definition id " +
"INNER JOIN identifier_list_values iv on id.definition_id = iv.definition_id where " +
"id.status IN (:statuses) AND id.type = :listType AND iv.identifier_value IN (:valuesToAdd)";
List<String> statuses = Arrays.stream(ListStatus.values())
.map(ListStatus::getValue)
.collect(Collectors.toList());
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("statuses", statuses);
parameters.addValue("listType", listType.toString());
parameters.addValue("valuesToAdd", valuesToAdd);
List<String> duplicateValues = jdbcTemplate.query(query, parameters, new DuplicateListValueMapper());
if (isNotEmpty(duplicateValues)) {
LOGGER.info("Fetched duplicate list value entities");
} else {
LOGGER.info("Could not find duplicate list value entities");
}
}
EDIT - 1
I came across this post where other's faced similar issue while running select query using PreparedStatement on MS SQL Server. Is there any such property like "sendStringParametersAsUnicode" available in MySQL?
EDIT - 2
Tried enabling few MySQL Performance related properties. Still the same result.
jdbc:mysql://localhost:3306/DBNAME?useSSL=true&allowPublicKeyRetrieval=true&useServerPrepStmts=true&generateSimpleParameterMetadata=true&rewriteBatchedStatements=true&cacheResultSetMetadata=true&cachePrepStmts=true&cacheCallableStmts=true
I think should enable "show_sql" to true in JPA and then try, I think its running multiple queries because of lazy loading because of which it may be taking time.
Composite indexes to add to the tables:
id: INDEX(type, status, definition_id)
id: INDEX(definition_id, type, status)
iv: INDEX(identifier_value, definition_id)
iv: INDEX(definition_id, identifier_value)
For jdbc, the connection parameters should include something like
?useUnicode=yes&characterEncoding=UTF-8
For further discussion, please provide SHOW CREATE TABLE for each table and EXPLAIN SELECT... for any query in question.
Instead passing the list to IN clause, pass the list as comma seperated string and split it in the query using
select value from string_split(:valuesToAdd, ',')
So your query will look like this
SELECT iv.* FROM identifier_definition id
INNER JOIN identifier_list_values iv on id.definition_id = iv.definition_id
where id.status IN (:statuses) AND id.type = :listType AND iv.identifier_value
IN (select value from string_split(:valuesToAdd, ','))
string_split is a function in SQL Server, MySQL might have similar one

JPA Stored Procedure throwing exception: could not extract ResultSet

I am trying to call a basic stored procedure from azure sql which is just returning the number 1, which looks something like this
CREATE PROCEDURE [dbo].[testProc]
#TableName varchar(100)
AS
BEGIN
SET NOCOUNT ON
SELECT 1
END
I have a spring boot app trying call the stored procedure using the #Query annotation
#Repository
#Transactional
public interface TestDAO extends JpaRepository<TestEntity, Long> {
#Query(value = "CALL testProc(:TableName)", nativeQuery = true)
Long invokeTestProc(#Param("TableName") String TableName);
}
however, I get an exception which says
"Incorrect syntax near '#P0'"
and SQLGrammarException: could not extract ResultSet.
I am not sure how to fix this, I tried using the #Procedure with #NamedStoredProcedureQueries annotations and it threw another exception saying "Cannot mix positional parameter with named parameter registrations;"
According to the documentation for Azure Sql, you have to call the stored procedure with the following
EXECUTE HumanResources.uspGetEmployeesTest2 N'Ackerman', N'Pilar';
-- Or
EXEC HumanResources.uspGetEmployeesTest2 #LastName = N'Ackerman', #FirstName = N'Pilar';
So in your case this would be translated as
#Query(value = "EXECUTE testProc :TableName", nativeQuery = true)
Long invokeTestProc(#Param("TableName") String TableName);
or
#Query(value = "EXEC testProc #TableName = :TableName", nativeQuery = true)
Long invokeTestProc(#Param("TableName") String TableName);
So considering that you use native queries, the
exception which says "Incorrect syntax near '#P0'" and
SQLGrammarException: could not extract ResultSet.
reffers to the wrong use of sql grammar where you don't use execute or exec and you also pass the parameter in a way not expected for Azure SQL.

Error while creating SQL query with bind variables - JDBCTemplate in Spring

I am very beginner to Spring - JDBC .
I am trying to retrieve the employee_id from a table using the query having bind variables and also with IN condition in it .
I'm getting SQLException that
" invalid column type" - Caused by:
org.springframework.jdbc.UncategorizedSQLException:
PreparedStatementCallback; uncategorized SQLException for SQL [select
employee_id from table_employee where age=:varTwo and marks in
(:varOne) and name =:varThree]; SQL state [99999]; error code [17004];
Invalid column type; nested exception is java.sql.SQLException:
Invalid column type
Can you please tell , where I'm wrong .
I have tried using the types as Long , Integer , String but still i'm getting "invalid column type"
age is - NUMBER
marks is - NUMBER
name is - VARCHAR
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("varOne", varOne);
parameters.addValue("varTwo", Long.parseLong(varTwo));
parameters.addValue("varThree", varThree);
Long employeeId = jdbcTemplate.queryForObject("select employee_id from table_employee where age=:varTwo and marks in (:varOne) and name =:varThree" , Long.class , parameters);
I should be getting the result of this SQL as the "employee id".
I think the type of varOne may be Collection.
When you want to use a variable in SQL query especially with IN, you should make sure the variable is a correct type.
Thanks for your responses. I was able to proceed further by making the list to string using join method.

SQL Server, HQL: How to compare SQL server datetime column field to date

I have following SQL query which i need to make it work in HQL, I tried couple of solutions but none of them work.(Database : SQL server)
select t.createdTimeStamp FROM ServiceData t , UserInfo C where C.UserId = t.UserId and CAST(t.createdTimeStamp AS DATE) = '2016-05-30'
I tried following in HQL but none worked, (createdTimeStamp is of datetime datatype in sql server )
Query:
select t.createdTimeStamp FROM ServiceData t , UserInfo C where C.UserId = t.UserId and CAST(t.createdTimeStamp AS DATE) = '2016-05-30'
Error:
org.hibernate.QueryException: Could not resolve requested type for CAST : DATE
any suggestion on how to make it work would be helpful.
you have to write the cast command as followed, then it will work
cast(t.createdTimeStamp as date)
so "cast", "as" and "date" in small caps
I found answer from below StackOverflow URL
StackOverflow-date-is-not-a-recognized-built-in-function-name
I used CONVERT instead of CAST and it worked
Query:select t.createdTimeStamp FROM ServiceData t , UserInfo C where C.UserId = t.UserId and CONVERT(date,t.createdTimeStamp) = '2016-05-30'
but not sure why CAST didn't work, as per Hibernate Doc CAST is supported

H2 database does not accept a table name as named parameter

I'm having trouble when trying to run the following query against an in memory H2 (version 1.4.181) table:
Object result = hibernateSession
.createSQLQuery("show columns from :myTable")
.setString("myTable", "some_table")
.list();
This query causes the following exception:
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "SHOW COLUMNS FROM ?[*] "; expected "identifier"; SQL statement: show columns from ? [42001-181]
...
...
...
I had done some debbuging and I found that during parse of query, the character "?" is tested to check if it is a valid identififer and it fails, causing the rise of exception (class org.h2.command.Parser, line 3027):
//currentToken is "?" at this point
if (currentTokenType != IDENTIFIER) {
throw DbException.getSyntaxError(sqlCommand, parseIndex,
"identifier");
}
I think it is a bug. What you think?
No, it is quite normal. Hibernate could not possibly make a PreparedStatement of it.
Standard JDBC has many possibilities to query schemata and such, in a database vendor independant way.
DatabaseMetaData dbMeta = connection.getMetaData();
Then getColumns can be used to receive a ResultSet of miscellaneous information.
You can try creating the required query instead of setting table name as named-parameter which won't work.
String sqlQuery = "show columns from " + tableName;
Class<?> entity = Class.forName(entityName);
session.createSQLQuery(sqlQuery);
Get the metadata information & then can retrieve required details from it.
String[] properties =
sessionFactory.getClassMetadata(entityClass).getPropertyNames();
There are several other methods available to get meta information, can refer ClassMetaData
[I haven't checked Criteria API, will update if found anything relevant, you can try it]

Categories

Resources