I have a jtable. I run data from mysql database. I use a query with prepared statement:
"select * from customer where city=? and region=? and price>?"
pst.setString(1,"rome")
pst.setstring(2,"italy")
pst.setdouble(3,"1500")
my problem is that one time i need only one parameter (for example city).
i ask you i change every time a query o r i can set dynimicaly number of parameter??
Thanks
(I think seperating them into seperate queries is a better solution, but if you want to do it this way, maybe something like this could help you)
I once wrote something like this
public ArrayList<User> searchUsers(HashMap userProperties)
{
ArrayList<User> foundUsers = new ArrayList<User>();
String searchString = "select * from chatusers where ";
int addedProps = 0;
if (userProperties.get("username") != null && !userProperties.get("username").equals(""))
{
searchString += "nickname like '%" + userProperties.get("username") + "%'";
addedProps++;
}
if (userProperties.get("firstname") != null && !userProperties.get("firstname").equals(""))
{
if (addedProps > 0)
searchString += " AND ";
searchString += "voornaam like '%" + userProperties.get("firstname") + "%'";
addedProps++;
}
if (userProperties.get("lastname") != null && !userProperties.get("lastname").equals(""))
{
if (addedProps > 0)
searchString += " AND ";
searchString += "naam like '%" + userProperties.get("lastname") + "%'";
}
....
Of course you would have to change the "userProperties.get("prop"). to a "?" (I know I need to use preparedStatements, this was just for something silly at university before we had seen preparedStatements.
Furthermore I would go on in a similar fashion to actually bind the variables in the end. (keep track of where you added which variable, at which index, which you know because of the if-statement).
Related
I need to implement a fuzzy search for two fields, businessName and businessAddress. Both of them can be null. If one field is null, search should be based on the other field.
To be specific,
if businessName="name" and businessAddress="address" then execute select * from business where businessName like '%name%' and businessAddress like '%address%'
if businessName=null and businessAddress="address" then execute select * from business where businessAddress like '%address%'
if businessName=null and businessAddress=null then execute select * from business
My code:
StringBuilder sb = new StringBuilder("select * from business where 1=1 ");
if (businessName != null) {
sb.append("and businessName like '%" + businessName + "%' ");
}
if (businessAddress != null) {
sb.append("and businessAddress like '%" + businessAddress + "%' ");
}
try {
con = DBUtil.getConnection();
pst = con.prepareStatement(sb.toString());
rs = pst.executeQuery();
} ...
Apparently it's in danger of SQL-injection attack. I know method prepareStatement.setString() can avoid attack, but number of fields is uncertain before verification.
How can I modify it? Separate method for each case or code like below seem ugly.
if(businessName!=null){
if(businessAddress!=null){
sql = ...;
}else {
sql = ...;
}
else{
...
Never, ever, concatenate values into a query string like that. Always use prepared statements with parameters when executing queries, especially with user-sourced values.
A simple solution for your case is to use a list of values for each parameter you add, and then set the values collected for those parameters before execute:
StringBuilder sb = new StringBuilder("select * from business where 1=1 ");
List<String> parameters = new ArrayList<>();
if (businessName != null) {
sb.append("and businessName like '%' || ? || '%' ");
parameters.add(businessName);
}
if (businessAddress != null) {
sb.append("and businessAddress like '%' || ? || '%' ");
parameters.add(businessAddress)
}
try (Connection con = DBUtil.getConnection();
PreparedStatement pst = con.prepareStatement(sb.toString())) {
int index = 1;
for (String parameter : parameters) {
pst.setString(index++, parameter);
}
try (ResultSet rs = pst.executeQuery()) {
// ...
}
}
If you have parameters of varying types, use a List<Object> and setObject instead.
The solution in the answer by MT0 also works, but not all database systems optimize that type of query well (especially if you have a lot of such conditions), which might affect performance. For only a few conditions, the solution by MT0 is more readable, while having same/similar performance.
You do not need dynamic SQL and can use bind variables in the query:
String query = "select * from business where businessName LIKE '%' || ? || '%' AND businessAddress LIKE '%' || ? || '%'";
(Assuming || is the string concatenation operator for your SQL dialect.)
And then use a prepared statement and bind businessName and businessAddress to the variables.
PreparedStatement st = con.prepareStatement(query);
st.setString(1,businessName);
st.setString(2,businessAddress);
ResultSet rs = st.executeQuery();
(Add exception handling.)
Or, if your SQL dialect requires you to handle NULL separately from the LIKE then :
String query = "select * from business
where (:name IS NULL OR businessName LIKE '%' || :name || '%')
AND (:addr IS NULL OR businessAddress LIKE '%' || :addr || '%')";
and use named bind variables :name and :addr (or use ? pass the values twice).
Except Mark Rotteveel's answer, I think there are additional methods to solve your question.
1.Escape user's input string to avoid sql-injection.But this way is not 100% secure.You can use ESAPI to do this, please refer to SQL_Injection_Prevention_Cheat_Sheet.
2.You can use ORM framework,like MyBatis. For example, MyBatis have dynamic-sql,it allows you generate different sql according to different conditions. In addition, use #{xxx} not ${xxx} to keep sql-injection away.
I have filters in a datatable and when user enters some value it should return a list of results matching that filter. I want it to be case-insensitive.
I create the query string for a prepared statement via Java string concatenation, as in the below:
public static List<Logger> getAll(int from, int to, Map<String, Object> filters, String sortField,
SortOrder sortOrder) {
Connection con = null;
PreparedStatement ps = null;
List<Logger> lista = new ArrayList<>();
String upit = "Select * from (select m.*,rownum r from (";
String upitZaFilterISort = "select m.* from eps_stage.MDM_OSB_LOG m";
try {
con = DataConnect.getConnection();
int upper = from + to;
if (filters.size() > 0) {
upitZaFilterISort = upitZaFilterISort.concat(" where 1=1");
Set<String> keys = filters.keySet();
// To get all key: value
for (String key : keys) {
if (key.equalsIgnoreCase("status") || key.equalsIgnoreCase("mbr")
|| key.equalsIgnoreCase("pib") || key.equalsIgnoreCase("jmbg")
|| key.equalsIgnoreCase("poruka_tip") || key.equalsIgnoreCase("aplikacija")
|| key.equalsIgnoreCase("operacija")) {
upitZaFilterISort = upitZaFilterISort.concat(
" AND UPPER(" + key.toString() + ") LIKE '" + filters.get(key).toString().toUpperCase() + "%'");
}
}
}
}
String sort = "";
ps = con.prepareStatement(upit + upitZaFilterISort + ") m ) where r>=? and r<=?");
ps.setInt(1, from);
ps.setInt(2, upper);
System.out.println(upit+ upitZaFilterISort + sort+") m " + ") where r>=? and r<=?");
ResultSet resultSet = ps.executeQuery();
In this line is a problem:
upitZaFilterISort = upitZaFilterISort.concat(
" AND UPPER(" + key.toString() + ") LIKE '" + filters.get(key).toString().toUpperCase() + "%'");
When I use case-sensitive comparison it works:
upitZaFilterISort = upitZaFilterISort.concat(
" AND " + key.toString() + " LIKE '" + filters.get(key).toString() + "%'");
After concatenation query:
Select * from (select m.*,rownum r from (select m.* from eps_stage.MDM_OSB_LOG m where 1=1 AND UPPER(poruka_tip) LIKE 'V%') m ) where r>=1 and r<=20
It returns the expected result when I run it in Oracle SQL Developer, but in my app it returns an empty result set.
Does Java put quotes somewhere I don't expect? I will provide more info if needed.
Try this:
Check if the user has all the required privileges to make the statements
It may happen that the port has only one open connection. Therefore you can only use java or oracle sql developer. Try disconnecting from sql developer and running your java program. If it doesn't work tell me.
I hope it has been helpful
Re: https://docs.oracle.com/cd/B28359_01/appdev.111/b28843/tdddg_globalization.htm#CCHIJBCG, section "Changing NLS Parameter Values for All Sessions"
Please check your session settings for NLC_COMP, which may be set to LINGUISTIC. The link below can get you there in SQL Developer. If set to LINGUISTIC then your SQL Developer sessions are performing case insensitive searches, possibly explaining differences between the sessions.
Also, concur with Filippo's recomended practices.
I have sql query which has a conditions(in where clause, having clause, or in join). I need to find condition which contains value with brackets like this: {some name} and replace that condition with 1=1(in some cases) with java.
E.g.
Select *
From customer c inner join address a on c.id = a.customer_id
where c.id > {var1} AND (c.name LIKE {var2} OR a.city = {var3}) AND ... (there could be written all posible operaters and conditions which are allowed by sql)
if var1 = 2, var2 = *ALL, var3 = 'aa' then query should looks like
Select *
From customer c inner join address a on c.id = a.customer_id
where c.id > 2 AND (1=1 OR a.city = 'aa')
I thought to split where condition with AND|OR then check if it contains {var2} then replace with 1=1, but this will not work. (in above described example after the split it will be (c.name LIKE {var2} so ( also will be replaced).
Does somebody came across with this situation and how solved.
Is there any open source libraray which will find and replace or how can I do that with regex ?
First I assume you are not able to dynamically build your sql. For this you would go the trivial way and could build your sql using string concats, e.g.
"select ... where c.id > " + myvalue + " ... "
The use of regular expressions for sqls will work, if you can guarantee that your expression will only find the specific parts of your sql and not more. This sounds a bit trivial but is hard to achieve for specific sqls, e.g.:
select '{var2}' from mytable
Should {var2} replaced here or not? I think not, because it is part of a string literal and is not part of a where statement.
Therefore you need a more structured look at your sql which is provided by a sql parser.
Using e.g. JSqlParser (https://github.com/JSQLParser/JSqlParser) you would be able to identify the parts of your where statement and replace the parts of your SQL in a controlled manner.
But to get this parsing going, you have to replace your {val} with something that is SQL conform, e.g. __val__.
So your replacement could be achieved with something like the following code. It assumes, that the replacement is only done within the right expression, but you could easily change that.
String sqlTxt = "Select * from customer c inner join address a on c.id = a.customer_id where c.id > {var1} AND (c.name LIKE {var2} OR a.city = {var3})";
//replace macro constructs
String sql = sqlTxt.replace("{", "__").replace("}", "__");
//build replacement data
Map<String, String> data = new HashMap<>();
data.put("__var1__", "2");
data.put("__var2__", "*ALL");
data.put("__var3__", "'aa'");
//parse sql
Select select = (Select) CCJSqlParserUtil.parse(sql);
StringBuilder b = new StringBuilder(sql);
//rewrite sql to fit your needs
((PlainSelect) select.getSelectBody()).getWhere().accept(new ExpressionVisitorAdapter() {
int delta = 0; //to correct the position due to former replacements
#Override
protected void visitBinaryExpression(BinaryExpression expr) {
if (expr instanceof ASTNodeAccess) {
if (expr.getRightExpression() instanceof Column) {
Column c = ((Column) expr.getRightExpression());
if (data.containsKey(c.getColumnName())) {
if ("__var2__".equals(c.getColumnName())) {
delta = replaceASTNodeWith(b, delta, (ASTNodeAccess) expr, "1=1");
} else {
delta = replaceASTNodeWith(b, delta, (ASTNodeAccess) expr,
expr.getLeftExpression() + expr.getStringExpression() + data.get(c.getColumnName()));
}
}
}
}
super.visitBinaryExpression(expr);
}
});
System.out.println("parsed sql = " + select.toString());
System.out.println("changed sql = " + b.toString());
The replacement in your sql is somewhat tricky due to the position change of former replacements.
//do the text replacement within the sql
private static int replaceASTNodeWith(StringBuilder sql, int delta, ASTNodeAccess node, String expr) {
sql.replace(
node.getASTNode().jjtGetFirstToken().absoluteBegin + delta - 1,
node.getASTNode().jjtGetLastToken().absoluteEnd + delta - 1,
expr);
return delta + expr.length()
- (node.getASTNode().jjtGetLastToken().absoluteEnd - node.getASTNode().jjtGetFirstToken().absoluteBegin);
}
This will not answer your question, but just more a suggestion on how I used the 1=1 approach.
I think you might want to write something like this
String query = "select * from customer c inner join address a on c.id = a.customer_id where 1=1"
if(var1 != null)
query += "and c.id > " + var1
if(var2 != null)
query += "and c.name LIKE " + var2
if(var3 != null)
query += "and a.city = " + var3
for example you want to had that logic, it will be a little tricky that it will look like this.
//you should realize that 1=1 here to make sure that we add "AND " command
String query = "select * from customer c inner join address a on c.id = a.customer_id where 1=1"
if(var1 != null)
query += "and c.id > " + var1
if(var2 != null && var3 != null) { //because if var2 or var3 is all, then return all.
query += "and ( c.name LIKE " + var2;
query += "or a.city = " + var3 + ")";
}
Hope this help
use the following:-
In case of single word variable between {singleWord}
queryString = queryString.replaceAll("\\{[A-Za-z0-9]+\\}", "1=1");
In case of multi word variable between {multi word}
queryString = queryString.replaceAll("\\{[A-Za-z0-9\\s]+\\}", "1=1");
Refer for more details:- https://docs.oracle.com/javase/1.5.0/docs/api/java/util/regex/Pattern.html#sum
I have 2 datebox to make a filter. Their value will determine the 'from' and 'to' in my query (I'm using Oracle now), here is the code.
#Listen("onClick=#btnSaveFilter")
public void saveFilter() throws Exception {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
c_main_filter.detach();
cc.refreshFilter(""
+"[Date of Birth] between '" + formatter.format(dbDateBirthFrom.getValue()).toString() + "' "
+ "and '" + formatter.format(dbDateBirthto.getValue()).toString() + "'");
}
when both datebox has value, the query work. but when they have no value the query is giving no data.
when datebox has value, it's giving data
"[Registration Date] between '2010-09-23' and '2010-09-23' "
when datebox has no value, it's giving no data
"[Registration Date] between '' and '' "
like another filter I expect if the value is '' then all data will appear, but not :D hahaha. the condition is more than this actually, the filter has a lot of parameter one of them is this condition, and some of them use date format so there will be more condition like this.
do you know how to elegantly fix this problem, I've been thinking to use 'if' to determine the datebox has value or no then I will append the text to query text if both of them has value, but then I found another problem how I can add 'and' in query to give another condition,
let say I have 5 conditions so then
"select * from xxxx where 1stcondition and 2ndcondition and
3rdcondition and 4thcondition and 5th condition"
so when the dateboxes of the 5thcondition has no value the query will be wrong like this
"select * from xxxx where 1stcondition and 2ndcondition and
3rdcondition and 4thcondition and"
if I want to use 'if' how can I play with the 'and'? but if you have alternative it will be great cause I don't have to deal with 'if' :D
You can use String.isEmpty() to determine whether you need to put an and:
String where = "";
if (from != null && to != null) {
where += <yourDateCondition>;
}
if (<needToAddSecondCondtion>) {
if (!where.isEmpty()) {
where += " and ";
}
where += <secondCondition>;
}
// continue with other conditions
String query = "select * from xxxx where " + where;
I don't know if you use plain JDBC or ORM framework like hibernate to querying to the database, but you can try something like this :
public void saveFileter(){
StringBuilder sb = new StringBuilder();
sb.append("select * from table ");
if(dbDateBirthFrom.getValue() != null{
sb.append(determineFilterWord(sb.toString()));
sb.append("your condition ");
}
if(dbDateBirthTo.getValue() != null{
sb.append(determineFilterWord(sb.toString()));
sb.append("your condition ")
}
session.createQuery(sb.toString()); //if you using hibernate
}
private string determineFilterWord(String query){
if(query.toLowerCase().indexOf("where") != -1){
return "and ";
}else{
return "where ";
}
}
I have the following code as part of a method which reads from CSV file and store its contents into DB by generating insert statements and executing them.
....
try
{
while( (strLine = br.readLine()) != null)
{
query = baseQuery;
st = new StringTokenizer(strLine, ",");
while(st.hasMoreTokens())
{
token = st.nextToken();
if("TIMESTAMP".equals(values.get(tokenNumber)))
query += "'" + GeneralMethods.dateFormat(token) + "' , ";
else
query += "'" + token + "' , ";
}
query = query.substring(0, query.length()-2) + ")";
ds.insertData(query );
}
} catch (IOException e)
{
logger.error("IOException Occured while trying to read lines of CSV file: \n " + e.getMessage());
status = false;
}
Everything works fine, except that the query is generated with empty values. This is printed into the logs:
Failed to execute query: INSERT INTO c1_ds1 ( TIME , USER ) VALUES ('' , '' )
I believe the problem is in these two lines:
if("TIMESTAMP".equals(values.get(tokenNumber)))
query += "'" + GeneralMethods.dateFormat(token) + "' , ";
else
query += "'" + token + "' , ";
I have printed the (token) variable in the logs, and it's getting the values from the CSV file as it should do.
Anyone knows where is the problem?
I tried this as well:
query += "'"; query += token;
Look into PreparedStatement instead of concatenating your query via Strings. This will let the DB driver handle escaping and converting values for you.
There are exactly two possibilities:
either token is empty; or
GeneralMethods.dateFormat(token) is getting called and returns an empty string.
To find out which it is, either print out more diagnostics, or use a debugger.
P.S. It is a very insecure practice to build SQL queries bit by bit, since you open yourself up for SQL injection attacks and other problems. I would strongly encourage you to rewrite your code to use PreparedStatement.