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);
Related
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());
}
I want to execute the query
"SELECT sd.id, sd.word, sd.desc FROM word_data sd WHERE sd.word = ANY (VALUES :words)"
Prepare arguments
for(String element : key){
listElement.add("('" + element + "')");
}
String finalString = StringUtils.join(listElement.iterator(), ",");
Map<String, Object> arg = new HashMap<>();
arg.put("words", finalString);
Execute queries
namedParameterJdbcTemplate.query(sql, arg,
(rs, rowNum) -> new Word(rs.getInt("id"), rs.getString("word"), rs.getString("desc")));
Exception is returned
org.postgresql.util.PSQLException: ERROR: Syntax error (approximate position: "$1")
How to correctly fill in the arguments for such a query? When the arguments were inserted manually and the query was executed successfully.
I'm using Hibernate4 + PostgresSql.
I need to get a list of ids from my DB without getting a whole object.
I use a native query (because it's hierarchical) and it looks like:
String s = hierachical_part_skipped +"select id " +
"from search_hierarchy " +
"order by id";
Query q = getEntityManager().createNativeQuery(s);
List<Long> resList = new ArrayList<>();
List<BigInteger> biglist = q.getResultList();
for (Object id : biglist) {
System.out.print(id+",");
resList.add(id.longValue());
}
Well, 1 time out of three my bigList is filled with wrong values!
I have
10,20,30,40,50,60,70,80,90,100 ... 27350
instead of
1, 2... 2735
(a zero is added to each value).
Native query executed manually not through Hibernate returns correct values.
I've tried retrieving result as List of Object, but the result was the same
I have found a sufficient workaround
s.append("select id \\:\\:numeric " + //that's a hack
"from search_department " +
"order by id");
Query q = getEntityManager().createNativeQuery(s);
List<Long> resList = new ArrayList<>();
List<BigDecimal> biglist = q.getResultList();
for (BigDecimal id : biglist) {
System.out.print(id + ",");
resList.add(id.longValue());
}
Still, I'm wondering why BigInteger works incorrectly
My database table looks like:
ID ACCESSID ENTITYGUID NAME NAMEID OWNERGUID TIMECREATED VALUEID VALUETYPE
I want to only retrieve a list with two fields, name AND valueId, not the whole record. I'm attempting this as follows:
via projections:
public void getAllEntityMetadata(int GUID) {
Criteria c = session.createCriteria(RevMetadata.class)
.add(Restrictions.eq("entityGUID", GUID))
.setProjection(Projections.property("name"))
.setProjection(Projections.property("nameId"));
List<RevMetadata> m = (List<RevMetadata>) (RevMetadata) c.list();
for (RevMetadata item : m) {
System.out.println("-> All entity metadata for GUID: " + item.getName() + " : " + item.getValueId());
}
}
I get the error:
Caused by: java.lang.ClassCastException: java.util.ArrayList cannot be cast to wakiliproject.Persistence.RevMetadata
at wakiliproject.Persistence.PersistRevMetadata.getAllEntityMetadata(PersistRevMetadata.java:112)
at wakiliproject.HomeController.showMetastring(HomeController.java:492)
... 40 more
Via query by example, I'm only getting the output below but no data. No error gets called:
public void getAllEntityMetadataEg(int GUID) {
RevMetadata m = new RevMetadata();
m.setEntityGUID(GUID);
Example e = Example.create(m);
Criteria c = session.createCriteria(RevMetadata.class).add(e);
for (Iterator it = c.list().iterator(); it.hasNext();) {
RevMetadata item = (RevMetadata) it.next();
System.out.println("-> All entity metadata for GUID: " + item.getName() + " : " + item.getValueId());
}
}
The output:
Hibernate: select this_.id as id1_4_0_, this_.accessId as accessId2_4_0_, this_.entityGUID as entityGU3_4_0_, this_.name as name4_4_0_, this_.nameId as nameId5_4_0_, this_.ownerGUID as ownerGUI6_4_0_, this_.timeCreated as timeCrea7_4_0_, this_.valueId as valueId8_4_0_, this_.valueType as valueTyp9_4_0_ from METADATA this_ where (this_.accessId=? and this_.entityGUID=? and this_.nameId=? and this_.ownerGUID=? and this_.timeCreated=? and this_.valueId=?)
None of the approaches work. Please help me make it work so that I can get a list with two data objects of name and nameId
Update:
The database table entity class:
public class RevMetadata implements Serializable {
private String name;
private int valueId;
}
If only some specified columns will be retrieved, the result will be List<Object[]>.
In your case, Object[] will be populated with name value => index 0 and nameId => index 1. Need to cast it to target value's type.
Criteria criteria = session.createCriteria(RevMetadata.class);
criteria.add(Restrictions.eq("entityGUID", GUID));
criteria.setProjection(Projections.projectionList()
.add(Projections.property("name"))
.add(Projections.property("nameId")));
List<Object[]> criteriaResult = criteria.list();
List<RevMetadata> results = new ArrayList<RevMetadata>();
//then get result into the class properties
for (Object[] object : criteriaResult) {
//cast to target value type
String name = (String)object[0];
int nameId = ((Integer)object[1]).intValue();
RevMetadata result = new RevMetadata();
result.setName(name);
result.setValueId(nameId);
results.add(result);
}
//now results of List<RevMetadata> is ready
The projections needs to be added to projectList if you want to get multiple columns:
Projections.projectionList()
.add(Projections.property("name"))
.add(Projections.property("valueId"))
Also the c.list() returns a List and this list contains the fields added to your projection list so it contains an object array with first field as your name property and second field as your valueId property. So your c.list() should be like:
List<Object[]> rows = (List<Object[]>) c.list();
So here are the changes that works:
Criteria c = session.createCriteria(RevMetadata.class)
.add(Restrictions.eq("entityGUID", GUID));
c.setProjection(Projections.projectionList()
.add(Projections.property("name"))
.add(Projections.property("valueId")));
List<Object[]> rows = (List<Object[]>) c.list();
System.out.println(rows);
for (Object[] row : rows) {
System.out.println(row[0] + " and " + row[1]);
}
It seems that the Problem is the double casting
(List<RevMetadata>) (RevMetadata) c.list();
Try to remove the cast
(RevMetadata)
List m = c.list();
for(Iterator it = c.iterator(); it.hasNext()) {
Object[] row = (Object[]) it.next();
System.out.println("-> All entity metadata for GUID: " + row[0] + " : " +row[1]);
}
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"));