Aimed at preventing SQL injection attacks, all the SQL Statement code in my project should transformed to Parameterized Query. But I got a problem when the query condition includes a 'IN' case. Like this (Using DB2 database):
String employeeId = 'D2309';
String name = "%brady%";
List<Integer> userRights = new ArrayList<Integer>();
userRights.add(1);
userRights.add(2);
userRights.add(3);
String sql = "SELECT * FROM T_EMPLOYEE WHERE EMPLOYEE_ID = ? AND NAME LIKE ?
AND RIGHT IN (?)";
jdbcTemplate.query(sql, new Object[] {employeeId, name, userRights}, new
EmployeeRowMapper());
The above code runs failed with the exception:
org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad
SQL grammar [SELECT * FROM T_EMPLOYEE WHERE EMPLOYEE_ID = ? AND NAME LIKE ? AND
RIGHT IN (?)]; nested exception is com.ibm.db2.jcc.am.io: [jcc][1091][10824]
[3.57.82] .... ERRORCODE=-4461, SQLSTATE=42815
The question here is that does not JdbcTemplate support Parameterized Query for IN case? and I know this work can be done by NamedParameterJdbcTemplate, and whether only NamedParameterJdbcTemplate can do IN case query?
Thanks a lot.
As I already mentioned in the comments, I'm not happy with this solution as it dynamically generates a number of SQL statements. Given the number of userRights is between 1 and n, it requires up to n prepared statements in the cache.
The below should work (I did not try it).
String employeeId = 'D2309';
String name = "%brady%";
List<Integer> userRights = new ArrayList<Integer>();
userRights.add(1);
userRights.add(2);
userRights.add(3);
// build the input string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < userRights.size; i++) {
sb.append("?");
if (i < userRights.size() - 1) {
sb.append(", ");
}
}
// build the SQL
String sql = "SELECT * FROM T_EMPLOYEE WHERE EMPLOYEE_ID = ?" +
" AND NAME LIKE ?" +
" AND RIGHT IN (" + sb.toString() + ")";
// init the object array
// size is employeeId + name + right
Object[] param = new Object[2 + userRights.size()];
// fill it
param[0] = employeeId;
param[1] = name;
for (int i = 0; i < userRights.size(); i++) {
param[i + 2] = userRights.get(i);
}
jdbcTemplate.query(sql, param, new EmployeeRowMapper());
Related
Use jpa nativequery multiple columns in object list array
List<Object []> queryList = new ArrayList<>();
String[] arr = {"val1", "val2"};
queryList.add(arr);
String sql = SELECT * FROM TABLE A WHERE (A.COL1, A.COL2) IN (:queryList)
Query query = entityManager.createNativeQuery(sql);
query.setParameter("queryList", queryList);
In postgresql like this
SELECT * FROM TABLE A WHERE (A.COL1, A.COL2) IN (('val1', 'val2'), ('val3', 'val4'));
Here is the Exception
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: record = bytea
建議:No operator matches the given name and argument types. You might need to add explicit type
Is this possible?
I would try to restructure the query as follows:
SELECT * FROM TABLE A
WHERE (A.COL1 = 'val1' and A.COL2 = 'val2')
OR (A.COL1 = 'val3' and A.COL2 = 'val4')
This would allow the query to be constructed as follows:
List<String[]> queryList = new ArrayList<>();
String[] arr = {"val1", "val2"};
String[] arr = {"val3", "val4"};
queryList.add(arr);
String sql = "SELECT * FROM TABLE A "; //dont forget space at end
if (!queryList.isEmpty()){
sql = sql + "WHERE "; //dont forget space at end
for (String[] queryParam : queryList ){
sql = sql + " (A.COL1 = '"+ queryParam[0] + "' and A.COL2 = '" + queryParam[1] + "') OR "; //dont forget space at end and simple colons for param
}
//finally remove the last OR.
Integer indexLastOR = sql.lastIndexOf("OR");
sql = sql.substring(0, indexLastOR);
}
Query query = entityManager.createNativeQuery(sql);
This will also allow the query to be implemented without being native, which is advisable to maintain the JPA philosophy.
I have data in following format:
HashMap<PageID, Set<SubscriberIDS>>
What I need to check is how many SubscriberIDS for each of the PageIDs do not exist in a MySQL table already. MySQL table has PageID and SubscriberID columns.
This is what I have so far:
String NEW_SUBSCRIBER_COUNT = "SELECT ? - COUNT(*) as new_subscribers from FB_SUBSCRIPTIONS WHERE PAGEID=? AND SUBSCRIBERID IN (?)";
First parameter being numberOFSubscriberIDs, Second being PageId and Third being SubscriberIds
but this will need to be hit for each pageId. How do I modify it to give me number of new subscribers for each PageID using single query.
Is there any specific need to do it in one query? Because while it can, it might actually be more readable to use your original solution and invoke a query for each page id. In any case, what you want can't be done in a single line, so you need to expect to loop at a given point.
// Build dynamic query
StringBuilder whereClause = new StringBuilder();
Iterator<PageID> it = yourMap.keySet().iterator();
while(it.hasNext()){
PageID key = it.next();
Set<SubscriberIDS> value = yourMap.get(key);
// You need to fill the 'IN' clause with multiple parameters, one for each subscriber id
StringBuilder inClause = new StringBuilder();
for(SubscriberIDS subId : value){
if(inClause.length > 0){
inClause.append(", ");
}
inClause.append("?");
preparedStatement.setInt(paramIndex++, subId.getId());
}
// For each page id we append a new 'OR' to our query
if(whereClause.lenght > 0){
whereClause.append(" OR ");
}
whereClause.append("(PAGEID=? AND SUBSCRIBERID IN (").append(inClause.toString()).append("))");
}
String query = "SELECT PAGEID, COUNT(SUBSCRIBERID) AS SUBSCRIBERS FROM FB_SUBSCRIPTIONS WHERE " + whereClause.toString() + " GROUP BY PAGEID";
// Create prepared statement and set parameters
PreparedStatement preparedStatement = connection.prepareStatement(query);
int paramIndex = 0;
it = yourMap.keySet().iterator();
while(it.hasNext()){
PageID key = it.next();
Set<SubscriberIDS> value = yourMap.get(key);
preparedStatement.setInt(paramIndex++, key.getId());
for(SubscriberIDS subId : value){
preparedStatement.setInt(paramIndex++, subId.getId());
}
}
// Execute query, loop over result and calculate new subscriptions
ResultSet rs = preparedStatement.executeQuery();
while(rs.next()){
int pageId = rs.getInt("PAGEID");
int newSubscriptions = yourMap.get(pageId).size() - rs.getInt("SUBSCRIBERS");
System.out.println(pageId + ", " + newSubscriptions);
}
Given following data in your map:
PAGEID SUBSCRIBERIDS
1 1,3,4,5,9
2 3,4,5,6,8,9
3 2,5,6
And following data in the DB:
PAGEID SUBSCRIBERIDS
1 3,4,10,11
2 1,2,5,7
3 1,2,5,6,7,8,9
This should give following output:
1,3
2,6
3,0
I haven't actually ran the code, so it might need some adjustments, but it gives you the general idea...
Question
With MySQL and using a JDBC template, is there a way to build parameters from Java Lists so that the SQL request matches a couple of values with couples of values in a given set?
Details
The values should match only if the couple of values is present in the list.
It should not match if one is present in a couple of values and the other in another couple further into the list of couples.
That is to say, given that JDBC parametrized query:
SELECT *
FROM TABLE_1
WHERE (COL_1, COL_2) IN (:valuesSet)
Caution: valuesSet is a set of couples
And that Java code:
public void daoMethod(List<MyObject> values1, List<MyObject> values2) {
String query = "";
query = "SELECT *\n" +
"FROM TABLE_1\n" +
"WHERE (COL_1, COL_2) IN (:valuesSet)";
MapSqlParameterSource parameters = new MapSqlParameterSource();
// Build valuesSet here
parameters.addValue("valuesSet", valuesSet);
namedParameterJdbcTemplate.query(query, parameters);
}
Is there an elegant way to build a JDBC template without having to "manually" create the string?
The inserted :valuesSet should be something like:
"(values1.get(0), values2.get(0)), (values1.get(1), values2.get(1)), ..."
But how should I build that string?
Current Track
Currently, my first draft solution is to build the string by Java code like this:
List<String> valuesSet = new ArrayList<String>();
for (int i = 0; i < values1.size(); i++) {
String value1 = StringEscapeUtils.escapeSql(values1.get(i).toString());
String value2 = StringEscapeUtils.escapeSql(values2.get(i).toString());
valuesSet.add("('" + value1 + "','" + value2 + "')");
}
But it keeps escaping the result list to make it a String and adds ' around it. Therefore, it's not working.
TL;DR
Input:
List<Object> objects
Output:
SELECT *
FROM TABLE_1
WHERE (COL_1, COL_2) IN (
('object_1_val1', 'object_1_val2'),
('object_2_val1', 'object_2_val2'),
('object_3_val1', 'object_3_val2'),
('object_4_val1', 'object_4_val2'),
...
)
Mean:
NamedParameterJdbcTemplate
Use every value separately.
List<String> valuesSet = new ArrayList<>();
StringBuilder sqlIn = new SqlBuilder();
for (int i = 0; i < values1.size(); i++) {
sqlIn.append("(?, ?),");
valuesSet.add(values1.get(i).toString());
valuesSet.add(values2.get(i).toString());
}
I found out a workaround using CONCAT():
SELECT *
FROM TABLE_1
WHERE CONCAT(COL_1, ',', COL_2) IN (:valuesSet);
That way, valuesSet can be a simple list of strings and is passed as the following:
List<String> valuesSet = new ArrayList<String>();
for (int i = 0; i < values1.size(); i++) {
String value1 = StringEscapeUtils.escapeSql(values1.get(i).toString());
String value2 = StringEscapeUtils.escapeSql(values2.get(i).toString());
valuesSet.add(value1 + "," + value2;
}
parameters.addValue("valuesSet", valuesSet);
Then, the executed query is something like:
SELECT *
FROM TABLE_1
WHERE CONCAT(COL_1, ',', COL_2) IN (
'object_1_val1,object_2_val1',
'object_1_val2,object_2_val2',
'object_1_val3,object_2_val3',
'object_1_val4,object_2_val4',
...
);
I am currently using ResultSet but want to switch over to Hibernate queries.
I have this code:
Statement stmt = nfCon.createStatement();
stmt = nfCon.createStatement();
ResultSet foo = stmt.executeQuery(sql);
String str = " " + foo.getTimestamp("time").getTime() + ", " + foo.getInt("len");
How can I change it if I am using
Query query = session.createQuery(hql);
List<String> list = query.list();
Obviously, I can't do list.getTimestamp("time");. My SQL/HQL is a select statement. Any help would be appreciated!
select xxx as time, yyy as len from zzz where ...
List<Object[]> list = query.list();
for (Object[] row : list) {
Date date = (Date) row[0];
int len = ((Number) row[1]).intValue();
String str = " " + date.getTime() + ", " + len;
}
This is a general solution. If len is a mapped integer field of an entity, then you can cast it directly to int (or better Integer, especially if it is nullable).
You might also want to look at constructor invocation from HQL, as it could help reduce the boilerplate code involved when representing projected columns as Object arrays.
I have several tables. I have a query also. My problem is to generate the SQL query dynamically using Java.
I have the following fields in a separate table:
Collumn name status
po_number, Y
unit_cost, Y
placed_date , Y
date_closed, Y
scheduled_arrival_date Y
date_closed Y
order_quantity Y
roll_number N
product_sku N
product_category_name N
rec_vendor_quantity Y
vendor_name Y
et_conversion_unit_quantity Y
from which i have to generate a query when the status is Y, the problem here is some time the above columns
The following query is the out put of the above :
here i have inculded all the columns but i have to exculde the column which has the status of N, please help me to construt the query using java.
select
pi.po_number,poi.unit_cost,pi.placed_date CreateDate,
case when isnull(pi.date_closed) then pi.scheduled_arrival_date
else pi.date_closed end as ReceviedDate,
poi.order_quantity,poi.roll_number,p.product_sku product_name,
pc.product_category_name,poi.rec_vendor_quantity,pv.vendor_name,p.et_conversion_unit_quantity,pi.note
from
purchase_order as pi,
purchase_order_inventory as poi,
product_vendors as pv,
products AS p,
product_categories AS pc
where
pi.purchase_order_id=poi.purchase_order_id and
pc.product_category_id=p.product_category_id and
poi.product_id = p.product_id and
poi.product_category_id=pc.product_category_id and
pi.vendor_id=pv.product_vendor_id and
( ( pi.date_closed >= '2012-01-01' and pi.date_closed <='2012-09-05 23:59:59' )
or ( pi.scheduled_arrival_date >= '2012-01-01' and pi.scheduled_arrival_date <='2012-09-05 23:59:59') ) and
pi.po_type=0
and pi.status_id = 0 and poi.transaction_type = 0
order by pi.po_number
UPDATE :
QUERY : STEP 1:
SELECT rcm.id,rcm.tablename,rcm.columnname,rcm.size,rcm.displayorder,rcm.isactive FROM report_customise_master rcm where rcm.tablename !='employee' and rcm.isactive='Y' order by rcm.displayorder;
STEP 2 :
Java method to construct the query :
public Map getComplexReportQuery() {
String query = "SELECT rcm.id,rcm.tablename,rcm.columnname,rcm.size,rcm.displayorder,rcm.isactive FROM report_customise_master rcm where rcm.tablename !='employee' and rcm.isactive='Y' order by rcm.displayorder;";
String tableName = "", from = "", select = "";
StringBuffer sb = new StringBuffer();
Map<String, List<String>> resultsMap = new LinkedHashMap<String, List<String>>();
Map<String, String> displayOrderMap = new LinkedHashMap<String, String>();
Map queryMap = new LinkedHashMap();
if (!query.isEmpty() || query.length() > 0) {
sb.append(query);
}
Connection connection = getConnection();
if (connection != null) {
try {
PreparedStatement reportQueryPS = connection.prepareStatement(sb.toString());
ResultSet reportQuery_rst = reportQueryPS.executeQuery();
List<String> tables = new ArrayList<String>();;
if (reportQuery_rst != null) {
StringBuilder selectQuery = new StringBuilder(" SELECT ");
StringBuilder fromQuery = new StringBuilder(" FROM ");
while (reportQuery_rst.next()) {
tableName = reportQuery_rst.getString("tablename");
List<String> columns = resultsMap.get(tableName);
if (columns == null) {
columns = new ArrayList<String>();
resultsMap.put(tableName, columns);
}
columns = resultsMap.get(tableName);
String columnName = reportQuery_rst.getString("columnname");
columns.add(columnName);
}
tableName = "";
for (Entry<String, List<String>> resultEntry : resultsMap.entrySet()) {
tableName = resultEntry.getKey();
List<String> columns = resultEntry.getValue();
int i = 0;
for (String column : columns) {
selectQuery.append(tableName + "." + column);
if (i != columns.size()) {
selectQuery.append(",");
} else {
selectQuery.append("");
}
i++;
}
if (!tables.contains(tableName)) {
tables.add(tableName);
}
}
//to remove comma at the end of line
select = selectQuery.toString().replaceAll(",$", "");
tableName = "";
int i = 0;
for (String table : tables) {
fromQuery.append(table);
fromQuery.append(" ");
fromQuery.append(table);
if (i != tables.size()) {
fromQuery.append(",");
} else {
fromQuery.append("");
}
i++;
}
from = fromQuery.toString().replaceAll(",$", "");
queryMap.put("query", select + from);
}
//from = from+"ORDER BY "+orderbyColumn+" "+sort+" ";
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
closeConnection(connection, null, null);
} catch (Exception ex) {
ex.printStackTrace();
}
}
} else {
System.out.println("Connection not Established. Please Contact Vendor");
}
return queryMap;// return the map/ list which contains query and sory and display order
}
STEP 3 : Result query
{query= SELECT purchase_order.po_number,purchase_order.placed_date,purchase_order.date_closed,purchase_order.scheduled_arrival_date,purchase_order_inventory.unit_cost,purchase_order_inventory.order_quantity,purchase_order_inventory.roll_number,purchase_order_inventory.rec_vendor_quantity,products.product_sku,products.et_conversion_unit_quantity,product_categories.product_category_name ,product_vendors.vendor_name FROM purchase_order purchase_order,purchase_order_inventory purchase_order_inventory,products products,product_categories product_categories,product_vendors product_vendors}
but this not what i wanted, Please help me to construct the query i have given.
Two queries
You need to make two queries:
Query which fields are enabled
Build the second query string (the one you want to build dinamically)
It's this way because a SQL query has to tell which columns will be included before querying any data. In fact it will be used to build the internal DB query plan, it is, the way the DB motor will use to retrieve and organize the data you ask.
Query all columns
Is it necesary to query only that fields? Can't you query everything and use the relevant data?
Joins
Looking at the updated question I guess you need to dynamically add where conditions to join tables correctly. What I should do is have a reference telling me what coindition to add when a table is present.
There are at least two options:
Based on table pairs present (by example: "if A and B are present then add A.col1 = B.col2")
Based on tables present ("if B is present, then add A.col1 = B.col2; A should be present"
Based on your example I think the second option is more suitable (and easy to implement).
So I should have some static Map<String, JoinInfo> where JoinInfo has at least:
JoinInfo
+ conditionToAdd // by example "A.col1 = B.col2"
+ dependsOnTable // by example "A" to indicate that A must be present when B is present
So you can use:
that info to add tables that should be (by example: even if A has no selected cols, must be present to join with B)
include the conditionToAdd to the where clause
Anyway... I think you are getting into much trouble. Why so dynamic?
You have to approach the thing step by step.
Firstly you have to create a query that will return all rows that have status='Y'
Then you will put the COLUMN_NAME in a list of Strings.
List<String> list = new List<String>();
while(rs.next()){
list.add(rs.getString(columnNumber));
}
And then you have to loop the List and generate dynamically your second sql statement
String sqlSelect = "SELECT ";
String sqlFrom = " FROM SOME_OTHER_TABLE "
String sqlWhere = " WHERE SOME_CONDITION = 'SOME_VALUE' "
for(String x : list){
sqlFrom += x +" , "+;
}
//here make sure that you remove the last comma from sqlFrom because you will get an SQLException
String finalSql = sqlSelect + sqlFrom + sqlWhere ;