JDBCTemplate optional parameters - java

I am using spring JDBCTemplate.
I have a scenario, where the parameters that need to be passed into my query function, are conditional/optional. For example, I have the following code:
List<RealTimeDTO> result = jdbcTemplate.query(sql, new Object[] {custId,
number, requestType, startDate, endDate}, new CCCRowMapper());
In the code, I passed in custId, number, requestType, etc. However, requestType is an optional parameter that may come back as null or empty so I don't want it to be passed into the Object[] if it is either null or empty.
What can I do to handle this type of situation?
I could introduce logic where I only pass in the parameters I want into the Object[], however, I was wondering if there is an already built in functionality that handles this instead of me reinventing the wheel.

One option is to use NamedParameterJdbcTemplate, so the parameter "list" (now a Map) doesn't need to be modified, only the SQL does:
List<RealTimeDTO> query(String name) {
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
String sql = "SELECT foo, bar" +
" FROM FooBar" +
" WHERE name" + (name == null ? " IS NULL" : "= :name");
Map<String, Object> params = new HashMap<>();
params.put("name", name);
return jdbcTemplate.query(sql, params, new CCCRowMapper());
}
UPDATE
If you have many conditions that may need to be skipped, and all conditions might be eliminated, then use a StringJoiner to build the WHERE clause:
List<RealTimeDTO> query(String name, String phone, int age) {
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
StringJoiner where = new StringJoiner(" AND ", " WHERE ", "").setEmptyValue("");
if (name != null)
where.add("name = :name");
if (phone != null)
where.add("phone = :phone");
if (age != 0)
where.add("age = :age");
String sql = "SELECT foo, bar" +
" FROM FooBar" +
where;
Map<String, Object> params = new HashMap<>();
params.put("name", name);
params.put("phone", phone);
params.put("age", age);
return jdbcTemplate.query(sql, params, new CCCRowMapper());
}

You can use a static SQL by checking the condition like ? IS NULL OR name = ?. But you have to pass the argument twice AND pass the argument type (at.sql.Types) twice.
String sql = "SELECT foo, bar" +
" FROM FooBar" +
" WHERE (? IS NULL OR name = ?) ";
jdbcTemplate.query(sql, new Object[]{name, name}, new int[]{Types.VARCHAR, Types.VARCHAR}, new CCCRowMapper());
IMO not really better than using conditions.

I'd like to add NamedParameter to #Snozzlebert' answer with the next example.
You have to specify the parameters' type if you want to pass null values otherwise you will get a NullPointerException.
#Autowired
NamedParameterJdbcTemplate namedJdbcTemplate;
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("name", name, Types.VARCHAR);
parameters.addValue("phone", null, Types.VARCHAR);
parameters.addValue("age", null, Types.SMALLINT);
List<RealTimeDTO> list = namedJdbcTemplate.query(
"SELECT foo, bar\n" +
" FROM FooBar\n" +
" WHERE (:name IS NULL OR name = :name) AND \n" +
" (:phone IS NULL OR phone = :phone) AND \n" +
" (:age IS NULL OR age = :age)" +,
parameters,
new RealTimeDTOMapper());

To improve an existing answer, Keep a static query with only bind parameters. Don't 'build' a sql to avoid the pitfall of caching issues and sql injection.
List<RealTimeDTO> query(String name, String phone, int age) {
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
String sql = "SELECT foo, bar FROM FooBar" +
" WHERE (:name IS NULL OR name = :name)" +
" AND (:phone IS NULL OR phone = :phone)" +
" AND (:age = 0 OR age = :age)";
params.put("name", name);
params.put("phone", phone);
params.put("age", age);
return jdbcTemplate.query(sql, params, new CCCRowMapper());
}

Related

NamedParameterJdbcTemplate substituing values with single quotes

#Autowired
NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public List<Contact> findByPhoneWithNamedParameters(String phone) {
MapSqlParameterSource namedParameters = new
MapSqlParameterSource().addValue("phone", phone);
String sqlQ = getSqlQuery(namedParameters);
namedParameters.addValue("anotherCondition", sqlQ);
String sql = "select * from Contact c where c.phone=:phone
:anotherCondition";
return namedParameterJdbcTemplate.query(sql, namedParameters, new ContactRowMapper());
}
private String getSqlQuery(MapSqlParameterSource namedParameters) {
return " and c.name='Saman'";
}
The above one is generating the query like below:
select * from Contact c where c.phone='09137390432' ' and c.name=''Saman'''
getSqlQuery() return value is embeddigng within single quotes, with that query is not working as expected.
I tried to concatenate the value directyle instead of namedParams;
But in my case, I have to avoid the SQL Injection.
How to resolve this?
You should only use named parameters with the actual values you want to pass to the query, not with whole SQL sub-parts. Otherwise it will escape also the valid SQL quotes.
So you should change your code to something like this:
private String getAdditionalSqlConditions(MapSqlParameterSource namedParameters) {
namedParameters.add("name", "Saman");
return "c.name = :name";
}
public List<Contact> findByPhoneWithNamedParameters(String phone) {
MapSqlParameterSource namedParameters = new MapSqlParameterSource().addValue("phone", phone);
String conditions = getAdditionalSqlConditions(namedParameters);
String sql = "select * from Contact c where c.phone=:phone and " + conditions);
return namedParameterJdbcTemplate.query(sql, namedParameters, new ContactRowMapper());
}

Get PSQL Exception when passing List<String> as a Query parameter in "IN" clause using JDBCTemplate

I am creating the Spring Boot application with a PostgreSQL database. and I am using JDBCTemplate to perform DB operations. as per my requirement, I want a count of the row from CONTRACT_VIEW_2 table where the value of LICENSE_PLATE = "xxxxxx" and ZONE_CODE is IN ("x","y","z") but I am getting PSQL Exception.
I tried using MapSQLParameterSource but still, I facing the issue.
#Override
public Integer getAllZoneForLp(String lp,List<String> zones) {
MapSqlParameterSource zoneIds = new MapSqlParameterSource();
zoneIds.addValue("zoneIds",zones);
String sql = "select " +
"count(*) " +
"from contract_view_2 " +
"where license_plate = ? and zone_code IN (?)";
return jdbcTemplate.queryForObject(sql,Integer.class,lp,zoneIds);
}
I expect the row count in the result but I am getting PSQL Exception. I am attaching the image of the exception which I am getting.
Thanks in advance.
The problem with yours is you have added Namedparameter to JdbcTemplate.
So in case you are using NamedParameterJdbcTemplate
#Override
public Integer getAllZoneForLp(String lp,List<String> zones) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("lp", lp);
parameters.addValue("zoneIds",zones);
String sql = "select " +
"count(*) " +
"from contract_view_2 " +
"where license_plate = :lp and zone_code IN (:zoneIds)";
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
return namedParameterJdbcTemplate.queryForObject(sql, parameters, Integer.class);
}
In case you want to use jdbcTemplate
#Override
public Integer getAllZoneForLp(String lp,List<String> zones) {
String sql = "select " +
"count(*) " +
"from contract_view_2 " +
"where license_plate = ? and zone_code IN (?)";
return jdbcTemplate.queryForObject(sql, Integer.class, new Object[] { lp, zones});
}
Go with NameParameterJdbcTemplate so that you won't miss any params.
Please change your query to
String sql = "select " +
"count(*) " +
"from contract_view_2 " +
"where license_plate = ? and zone_code IN (:zoneIds)";
Changes: changed ? to :zoneIds
Line zoneIds.addValue("zoneIds",zones); is using named parameter to bind it.

hibernate parameter with that name [<name>] did not exist

I am running the following code.
UserService.java
String alias = "u";
String select = "SELECT u.email";
String where = "u.userId = :id";
Map<String, Object> params = new HashMap<>();
params.put("id", userId);
List<User> users = db.findRecords(User.class, alias, select, where, params);
DB.java
public <T> List<T> findRecords(Class<T> entityClass, String entityAlias, String select, String where, Map<String, Object> params) {
String sql = select + " FROM " + entityClass.getName() + " " + entityAlias;
if (where != null) {
sql = sql + " WHERE " + where;
}
Query query = entityManager.createQuery(sql);
System.out.println(sql);
if (!params.isEmpty()) {
Iterator<Entry<String, Object>> iterator = params.entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, Object> entry = iterator.next();
System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
query.setParameter((String) entry.getKey(), (Long) entry.getValue());
}
}
return query.getResultList();
}
I am getting the following error log.
SELECT u.email FROM com.catalog.user.User u WHERE u.userId = :id
key: id, value: 28636907
Caused by: java.lang.IllegalArgumentException: Parameter with that name [id] did not exist
If the parameter is getting printed in the console then what is causing the illegal arguments exception to come up?
Please help!
1) check that it is not a typo in the parameter name :id. It's case sensitive.
2) try to execute query without parameters. It might be a problem in the entity mapping.
3) try to set parameter directly in the query without HashMap.
I think the problem here is caused by the way you are getting the params from the Map, with the Map.entrySet() method the iterated values are only available within the iteration and will be undefined outside of it, that's why the param is correctly printed during the loop and doesn't exist in the query.
If you take a look at the Map.entry documentation it says:
These Map.Entry objects are valid only for the duration of the
iteration;
I suggest that you change the way you store and use the parameters , you can simply use a List<Object> to store the parameters or just pass the id param directly in the method call:
public <T> List<T> findRecords(Class<T> entityClass, String entityAlias, String select, String where, Long id){
Then directly append this value in the query:
query.setParameter("id", id);

How to convert query object into a List of Strings?

I got a Login class:
if((!(name.equals(null) || name.equals("")) && !(password.equals(null) || password.equals(""))))
{
try{
loggedUser = checkLogin(name, hexPass.toString());
if(!loggedUser.isEmpty())
{
userdbId = loggedUser.get(0);
userdbName = loggedUser.get(1);
userdbPsw = loggedUser.get(2);
userdbType = loggedUser.get(3);
...
And a user DAO:
public List<String> checkLogin(String uname, String password) {
Session session = null;
List<String> lst = new ArrayList<String>();
try {
session = getSession();
String sql = "select * from user where uname='" + uname + "' and password='" + password + "'";
Query query = session.createSQLQuery(sql)
.addScalar("Id", StringType.INSTANCE)
.addScalar("uname", StringType.INSTANCE)
.addScalar("password", StringType.INSTANCE)
.addScalar("utype", StringType.INSTANCE);
lst = query.list();
}
catch (Exception e){
e.printStackTrace();
}
return lst;
}
The userdbId = loggedUser.get(0); generates error: 500
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.lang.String
com.se.pl.actions.LoginAction.execute(LoginAction.java:95)
I do not understand why this is happening since list of 4 strings is going to be put into 4 variables.
The error means that you have an Object and you are doing an implicit cast to String, so that's the error.
userdbId = loggedUser.get(0);
You can fix that by adding toString() method like:
userdbId = loggedUser.get(0).toString();
Of course, you are going to get the string representation for that object.
Note that if you have your own object you are going to get the following representation:
getClass().getName() + '#' + Integer.toHexString(hashCode())
In case you have this, you will need to override the toString() method in your class
You should use TypedQuery<T> instead of query and create objects of user entity type(if you have it defined - you definitely should have one) - it should look something like this (do not take this code literaly! I mainly use OpenJPA)
TypedQuery<UserEntity> query= session.createQuery("SELECT u FROM UserEntity u WHERE name=:name AND password=:password");
query.addParam("name",name);
query.addParam("password",password);
This probably should help:
Query query = session.createSQLQuery(sql)
.addScalar("Id", StringType.INSTANCE)
.addScalar("uname", StringType.INSTANCE)
.addScalar("password", StringType.INSTANCE)
.addScalar("utype", StringType.INSTANCE)
.setResultTransformer(org.hibernate.Criteria.ALIAS_TO_ENTITY_MAP);
Map row = (Map) query.uniqueResult();
lst.add((String) row.get("Id"));
lst.add((String) row.get("uname"));
lst.add((String) row.get("password"));
lst.add((String) row.get("utype"));

Two JdbcTemplate update statements in one method

I am trying to do two JdbcTemplate updates in same method, but only the first one executes. How can I do two update statements in one method? EduId and timeId returns correct values and I guess the SQL syntax is valid so the problem isn't there.
EDIT: I am using MySQL.
#Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.SERIALIZABLE, readOnly=false)
public void moveEdu(int eduId, int timeId) {
final String sql = "UPDATE timeslot ts"
+ " INNER JOIN eduevent ee ON ts.edu_id = ee.edu_id"
+ " SET ts.edu_id = null"
+ " WHERE ts.edu_id = ?;";
Object[] parameters = new Object[] {eduId};
final String sql2 = "UPDATE timeslot ts"
+ " INNER JOIN eduevent ee ON ts.edu_id = ee.edu_id"
+ " SET ts.edu_id = ?"
+ " WHERE ts.time_id = ?;";
Object[] parameters2 = new Object[] {eduId, timeId};
jdbcTemplate.update(sql, parameters);
jdbcTemplate.update(sql2, parameters2);
}
You code looks fine.
You can try to do all your updates in one
...
SET ts.edu_id=CASE
WHEN ts.time_id=? THEN ? <-- time_id=timeID then eduID
ELSE null
END
WHERE ts.time_id = ? OR ts.edu_id = ?
and params should be
Object[] parameters = new Object[] {timeId, eduId, timeId, eduId};
I solved the problem by removing "INNER JOIN" from that SQL2 string.

Categories