Suggestions for building sql strings with jdbctemplate - java

I currently have this:
MyObject myObject = getJdbcTemplate().queryForObject("select * from my_objects where id = ?",
new Object[]{new Integer(id)},
new MyObjectRowMapper());
Now in my method I want to pass an enumeration:
SortOrder.ASC
SortOrder.DESC
So it will either be:
ORDER BY ID ASC
or
ORDER BY ID DESC
So inside the sql string, do I just add another '?' or do I have to build up the string like:
"select * from abc ORDER BY ID " + sortOrder;
Is there a preferred way?

You have to use the second way. A prepared statement isn't just a "query-replace placeholders by the String I pass". All parameters must be typed values to insert into the syntax tree generated from the query. You can't pass a portion of a query as a parameter.

You can not change the query in PreparedStatements so you can not use ? for asc or desc. I think concatenation is the simplest way you can use.

Related

Get SQL String substituted with parameters using java

Is there any easy way to get a completed SQL statement after parameter substitution?
I am using elasticsearch-sql to query elastic search with sql statements, however I have to submit the query with all the parameters substituted.
I tried Hibernate Query getQueryString, but the parameter substitution is not happening for those sql strings.
The following sql string is produced:
"SELECT * FROM USER WHERE NAME=? AND SURNAME=?"
rather than:
"SELECT * FROM USER WHERE NAME='Selva' AND SURNAME='Esra'
Appreciate any better idea/thoughts?
1. Named parameters
This is the most common and user friendly way. It use colon followed by a parameter name (:example) to define a named parameter. See examples…
String hql = "SELECT * FROM USER WHERE NAME= :userName AND SURNAME= :surName";
Query query = session.createQuery(hql);
query.setParameter("userName ", "userName");
query.setParameter("surName", "SurName");
List results = query.list();
An object-oriented representation of a Hibernate query. A Query instance is obtained by calling Session.createQuery(). This interface exposes some extra functionality beyond that provided by Session.iterate() and Session.find():
a particular page of the result set may be selected by calling setMaxResults(), setFirstResult()
named query parameters may be used
the results may be returned as an instance of ScrollableResults
Named query parameters are tokens of the form :name in the query string. A value is bound to the integer parameter :foo by calling
setParameter("foo", foo, Hibernate.INTEGER);
for example. A name may appear multiple times in the query string.
JDBC-style ? parameters are also supported. To bind a value to a JDBC-style parameter use a set method that accepts an int positional argument (numbered from zero, contrary to JDBC).
You may not mix and match JDBC-style parameters and named parameters in the same query.
2. Positional parameters
It’s use question mark (?) to define a named parameter, and you have to set your parameter according to the position sequence. See example…
Java
String hql = "from Stock s where s.stockCode = ? and s.stockName = ?";
List result = session.createQuery(hql)
.setString(0, "7277")
.setParameter(1, "DIALOG")
.list();
This approach is not support the setProperties function. In addition, it’s vulnerable to easy breakage because every change of the position of the bind parameters requires a change to the parameter binding code.
Java
String hql = "from Stock s where s.stockName = ? and s.stockCode = ?";
List result = session.createQuery(hql)
.setParameter(0, "DIALOG")
.setString(1, "7277")
.list();
Conclusion
In Hibernate parameter binding, i would recommend always go for “Named parameters“, as it’s more easy to maintain, and the compiled SQL statement can be reuse (if only bind parameters change) to increase the performance.

Passing clojure vec to POSTGRES IN statement (?)

I'm trying to pass an array of strings to a select statement and I keep getting the error:
org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of clojure.lang.PersistentVector. Use setObject() with an explicit Types value to specify the type to use.
I know that the column type is correct, it looks as if passing a vector is the culprit. What is the correct way to do this?
The sql statement is formatted like so:
"SELECT * FROM said_table WHERE item_id IN (?)"
This answer assumes you are using jdbc and not korma or something like it and need to generate the sql directly instead of going through some tool:
the in directive requires you to crate one ? for each item in the list. I end up using this pattern when something else requires me to build the SQL manually:
(let [placeholders (s/join ", " (repeat (count things-go-here) "?"))
query "SELECT * FROM said_table WHERE item_id IN (%s)"]
(exec-raw [(format query placeholders) things-go-here] :results)
....)

Postgres SQL in clause and setArray()

I am using Java 1.7 and JDBC 4 and Postgres. I am trying to use a PreparedStatement with an array to fill a SQL in clause. But, the SQL generated seems to have "{" and "}" in it. Here is the code:
PreparedStatement ptmt =
connection.prepareStatement("select * from foo where id in (?)");
String[] values = new String[3];
values[0] = "a";
values[1] = "b";
values[2] = "c";
ptmt.setArray(1, connection.createArrayOf("text", values));
The resulting SQL looks like this:
select * from foo where id in ('{"a","b","c"}')
Which, can't work. This is how it should look:
select * from foo where id in ("a","b","c")
or
select * from foo where id in ('a','b','c')
What am I missing here?
Use = ANY subquery expression.
PreparedStatement ptmt = connection.prepareStatement("select * from foo where id = ANY(?)");
String[] values = new String[]{"a","b","c"};
ptmt.setArray(1, connection.createArrayOf("text", values));
And if you need to enforce types in the query you can do something like this.
select * from foo where id = ANY(?::text[])
The PostgreSQL documentation has more details. This snippet is worth noting:
SOME is a synonym for ANY. IN is equivalent to = ANY.
When your database field is of type array, then you can use the PreparedStatement.setArray() to send an array to the query. But, in your case, it's not really an array, rather is a variable no of arguments, and you can't do that. i.e.
PreparedStatement ptmt = connection.prepareStatement("select * from foo where id in (?)");
can take only one parameter. If you want 3 parameters to be passed, you have to do
PreparedStatement ptmt = connection.prepareStatement("select * from foo where id in (?, ?, ?)");
And do ptmt.setString(n, "String") thrice.
If your no of arguments aren't constant, then construct the query dynamically, although, you loose the efficiency.
PostgreSQL has a couple of array capabilities that can handle this situation.
First is the unnest function, available since 8.4. A little cludgy, but effective.
select * from foo where id in (SELECT * FROM unnest(?));
Next is the array intersection operator.
select * from foo where ARRAY[id] && ?;
Converts your column value into an array with a single element and then checks for intersection with the array that you set up as a parameter.
As best as I can tell, these are functionally equivalent, but I haven't checked which might be more performant.
I'd consider this a PgJDBC issue. It should really be writing an array constructor or an array literal with an explicit cast, eg:
select * from foo where id in (ARRAY['a','b','c'])
or
select * from foo where id in ('{"a","b","c"}'::text[])
As a workaround you should be able to write:
"select * from foo where id in ( (?)::text[] )"
.. though I haven't tested to verify that.
Please consider writing this up as a unit test for PgJDBC and submit it to the PgJDBC project via github as a bug report. Look at how the existing unit tests work and just add another. It's all pretty simple JUnit.

Using MySQL like for Column name

I want to write a mysql select query using preparedstatement. But theres syntax error at the last part which is concat('%', itemName, '%')"; itemName is a column of table ItemMain.
I already tried 3 queries given below.
String sql ="SELECT * FROM ItemMain WHERE ? = 'All' OR ? like concat('%', itemName, '%')";
String sql ="SELECT * FROM ItemMain WHERE ? = 'All' OR ? like '%'+itemName+'%'";
String sql ="SELECT * FROM ItemMain WHERE ? = 'All' OR ? like '%itemName%'";
You can't use placeholders for field names. The queries would have to be
... WHERE somefield=? OR otherfield LIKE concat('%', ?, '%')
placeholders are for VALUES only. field/table names, function namesm or any of the "Structural" words in SQL are offlimits.
This is a general rule for mysql prepared statements. It is not a java/php/c#/whatever restriction.
Although #Marc B is absolutely right (+1) I would like to add something. I believe that your have a real task where you need such functionality, so I would like to suggest you the following solution.
You can create query dynamically as following. If you are using plain JDBC you can run query like desc YOUR_TABLE_NAME. It will return a easy-to-parse list of fields in your table. You can implement your "like" statement yourself either using regular expression or simple string manipulation methods as startsWith("xyz") instead of like 'xyz%', endsWith("xyz") instead of like '%xyz' and contains("xyz") instead of like '%xyz%'. Now you can create SQL statement dynamically by adding fields the meet your requirements.
Found the answer to my problem.
String sql ="SELECT * FROM ItemMain WHERE ? = 'All' OR itemName like '%"+keyword+"%'";
Object []values ={keyword};
ResultSet res = DBHandller.getData(sql, conn, values);
I swapped the column name, keyword and change the syntax here '%"+keyword+"%'";
Now it works fine. thnx al

How do I sanitize SQL without using prepared statements

For some sql statements I can't use a prepared statment, for instance:
SELECT MAX(AGE) FROM ?
For instance when I want to vary the table. Is there a utility that sanitizes sql in Java? There is one in ruby.
Right, prepared statement query parameters can be used only where you would use a single literal value. You can't use a parameter for a table name, a column name, a list of values, or any other SQL syntax.
So you have to interpolate your application variable into the SQL string and quote the string appropriately. Do use quoting to delimit your table name identifier, and escape the quote string by doubling it:
java.sql.DatabaseMetaData md = conn.getMetaData();
String q = md.getIdentifierQuoteString();
String sql = "SELECT MAX(AGE) FROM %s%s%s";
sql = String.format(sql, q, tablename.replaceAll(q, q+q), q);
For example, if your table name is literally table"name, and your RDBMS identifier quote character is ", then sql should contain a string like:
SELECT MAX(AGE) FROM "table""name"
I also agree with #ChssPly76's comment -- it's best if your user input is actually not the literal table name, but a signifier that your code maps into a table name, which you then interpolate into the SQL query. This gives you more assurance that no SQL injection can occur.
HashMap h = new HashMap<String,String>();
/* user-friendly table name maps to actual, ugly table name */
h.put("accounts", "tbl_accounts123");
userTablename = ... /* user input */
if (h.containsKey(userTablename)) {
tablename = h.get(userTablename);
} else {
throw ... /* Exception that user input is invalid */
}
String sql = "SELECT MAX(AGE) FROM %s";
/* we know the table names are safe because we wrote them */
sql = String.format(sql, tablename);
Not possible. Best what you can do is to use String#format().
String sql = "SELECT MAX(AGE) FROM %s";
sql = String.format(sql, tablename);
Note that this doesn't avoid SQL injection risks. If the tablename is a user/client-controlled value, you'd need to sanitize it using String#replaceAll().
tablename = tablename.replaceAll("[^\\w]", "");
Hope this helps.
[Edit] I should add: do NOT use this for column values where you can use PreparedStatement for. Just continue using it the usual way for any column values.
[Edit2] Best would be to not let the user/client be able to enter the tablename the way it want, but better present a dropdown containing all valid tablenames (which you can obtain by DatabaseMetaData#getCatalogs()) in the UI so that the user/client can select it. Don't forget to check in the server side if the selection is valid because one could spoof the request parameters.
In this case you could validate the table name against the list of available tables, by getting the table listing from the DatabaseMetaData. In reality it would probably just be easier to use a regex to strip spaces, perhaps also some sql reserved words, ";", etc from the string prior to using something liek String.format to build your complete sql statement.
The reason you can't use preparedStatement is because it is probably encasing the table name in ''s and escaping it like a string.

Categories

Resources