working with variable number of parameters in a preparedStatement - java

I'm creating a search form for my application.
In it user selects fields that should be used in filtering data.
the number fields is variable so I don't know how many ? should be in where clause of SQL query.
How can I use preparedStatement with variable number of conditions in where clause?
Thanks

if you want to add variable number of conditions in where clause use
StringBuilder (StringBuffer if its multithreaded env.) and and run time depending upon your conditions concate/append to your string.
like
StringBuilder query = new StringBuilder("Select id, name from Student ");
if(args >0)
{
query.append(" where "); //and add more args.
and later create prepared statement with this query by converting it to string
PrepareStatement(query.toString());

PrepardStatements don't support variable numbers of conditions. What some frameworks do, is they cache each PreparedStatement in a Map where the key is the query.
So each time you want to run a query, you need to build the string to create the PreparedStatement, check if you have it in the map (and reuse it) or create a new one, and add it to the map.

Related

Is there a better way to dynamically implement SQL statements in java?

I have been working with a JDBC for the past couple of weeks and I have come upon a problem that I figure will have subjective answers. Let's suppose we want to create a method that dynamically constructs a SQL statement that is to be executed based upon what a user inputs into some GUI. Based on what the user has put into the GUI, we need to gather that information, validate it, then pass it into the database. That is, if the user has left any field empty, we simply do not add any extra conditionals to the SQL statement.
For example if the user left the hypothetical column "name" blank (and the table automatically generates primary keys) we might write
INSERT INTO <tableName>;
to add a new row to the table.
Alternatively if the user has given a name, we write,
INSERT INTO <tableName> (name) VALUES (?);
.
With that context given lets suppose I construct a method that dynamically creates this SQL statement:
public void addToDatabase(){
Connection connection = createConnectionToDatabase();
String str = "INSERT INTO <tableName>";
if(!name.isBlank()){
str += " (name) VALUES (?)"
}
str += ";";
PreparedStatement statement = connection.prepareStatement(str);
if(!name.isBlank()){
statement.setString(1, name);
}
statement.execute();
connection.close();
If you notice, we check if name is blank twice - which I find rather annoying since it should only be checked once in my opinion. The first time we check if name is blank is to construct the proper string to be placed into the SQL statement. The second time we check if the name is blank is to confirm if we need to pass the parameter into the prepared statement. This creates a sort of catch-22 that forces us to check if the name is blank twice which I do not like. Are there any better ways of handling this situation to only have to check for the name once?
I found a few other answers stating that there is no better way of doing this kind of dyamic SQL statements but I don't like that answer and am hoping for a better one. Thanks.
what you want is equivalent to
String str = "INSERT INTO <tableName> name values (?)";
PreparedStatement statement = connection.prepareStatement(str);
if(!name.isBlank()){
statement.setString(1, name);
}
else {
statement.setNull(1, Types.VARCHAR);
}

What are the risks when using StringBuilder and JPA native queries?

I need to create a native sql query and I plan to use StringBuilder for creating it.
StringBuilder sqlQuery = new StringBuilder();
sqlQuery.append("SELECT ");
sqlQuery.append("b.\"id\", ");
sqlQuery.append("b.\"title\", ");
sqlQuery.append("a.\"author_name\" as author ");
sqlQuery.append("FROM ");
sqlQuery.append(":dbName b ");
sqlQuery.append("JOIN ");
sqlQuery.append(":dbName2 a");
sqlQuery.append(" ON a.\"id\" = b.\"author\"");
ObjectQuery query = objectManager.createQuery(sqlQuery.toString());
query.setParameter("dbName", "Book");
query.setParameter("dbName2", "Author");
Is it safe to use the append of StringBuilder? Will this not result in SQL injection from an attacker? Will attacker be able to append a part of query that will drop the entire database? Any suggestions?
I know this query is simple and can be written in a single string but I have bigger queries with if statements and loops that append more lines depending on parameters
I know the named query is safer but in my case I don't know what the query will be like until the last moment.
If you set the parameter values with Query.setParameter() as in your sample code :
query.setParameter("dbName", "Book");
query.setParameter("dbName2", "Author");
you should not have SQL injection issues.
SQL injection happens as you concatenate yourself the user data in the created query.
But you are not in this case as the query created by the StringBuilder object is mastered totally by you and doesn't take any values from an external client.
Is it safe to use the append of StringBuilder?
While the StringBuilder variable (beware : StringBuilder is not thread-safe) is created and manipulated in a method scope, it is not more or less safe than another solution.
It doesn't have any relationship with safety but as #HRgiger suggested, a good alternative would be using Criteria API.
It makes sense as you explain that the construction of your queries is very dynamic :
I have much bigger query with if statements that decides what to
append
It would produce a much more maintainable and readable code.
In this case, there is no risk to get SQL injections attacks, simply because of using setParameter, but you have to note that StringBuilder is not thread safe type.

How to execute a SQL statement in Java with many values in a single variable in where in clause

I have to execute below query through JDBC call
select primaryid from data where name in ("abc", adc", "anx");
Issue is inside in clause I have to pass 11000 strings. Can I use prepared statement here? Or any other solution any one can suggest. I dont want to execute the query for each record, as it is consuming time. I need to run this query in very less time.
I am reading the strings from an XML file using DOMParser. and I am using sql server db.
I'm just wondering why you would need to have a manual set of 11,000 items where you need to specify each item. It sounds like you need to bring the data into a staging table
(surely it's not been selected from the UI..?), then join to that to get your desired resultset.
Using an IN clause with 11k literal values is a really bad idea - off the top of my head, I know one major RDBMS (Oracle) that doesn't support more than 1k values in the IN list.
What you can do instead:
create some kind of (temporary) table T_NAMES to hold your names; if your RDBMS doesn't support "real" (session-specific) temporary tables, you'll have to add some kind of session ID
fill this table with the names you're looking for
modify your query to use the temporary table instead of the IN list: select primaryid from data where name in (select name from T_NAMES where session_id = ?session_id) or (probably even better) select primaryid from data join t_names on data.name = t_names.name and t_names.session_id = ?session_id (here, ?session_id denotes the bind variable used to pass your session id)
A prepared statement will need to know the number of arguments in advance - something along the lines of :
PreparedStatement stmt = conn.prepareStatement(
"select id, name from users where id in (?, ?, ?)");
stmt.setInt(1);
stmt.setInt(2);
stmt.setInt(3);
11,000 is a large number of parameters. It may be easiest to use a 'batch' approach as described here (in summary - looping over your parameters, using a prepared statement
each time)
Note - if your 11,000 strings are the result of an earlier database select, then the best approach is to write a stored procedure to do the whole calculation in the database (avoiding passing the 11,000 strings back and forth with your code)
You can merge all your parameter strings into one bitg string separating by ';' char
bigStrParameter=";abc;adc;anx;"
And use LOCATE to find substring.
select primaryid from data where LOCATE(concat(';',name,';'),?)>=0;

How to make PreparedStatement.setXXX() dynamic based on type of column values

I have to update table using values from a data file which contains all the rows. Now I am using JDBC batches. Data files contains 100s of columns and millions of rows.
For e.g. data file contains 3 columns two rows to make it simple
1,ABC,DEF
2.GHI,JKL
PreparedStatement pstmt = connection.prepareStatement(insert);
//how to find type
pstmt.setInt(1, 2);
pstmt.setString(2, "GHI");
pstmt.setString(3, "JKL");
pstmt.addBatch();
pstmt.executeBatch();
Now my question is at run time based on the data coming from data file how do I find that I need to call setInt or setString and more importantly how many times I need to call setXXX for that addBatch(). This seems like for each table I need to have dedicated preparedStatements. More importantly I need to find how many times I should call setObject at run based on the number of columns which is in data ile? Is there anyway I can make this generic?
I am new to JDBC please guide. Thanks in advance.
You can use setObject(int index, Object obj). JDBC then determines the correct type.
The PreparedStatement has a method setObject(int, Object). The documentation states
If arbitrary parameter type conversions are required, the method setObject should be used with a target SQL type.
If you have an SQL statement like
Select * From table Where value1 = ? and value2 = ?
you have to call the setXXX methods two times. They are used to specify the wildcard values (marked ny ?) for the SQL statement that the PreparedStatement instance represents. The number of calls is therefore dependent of your SQL statement that is referenced by your variable insert. The int argument of the setXXX methods refers to the position of the variable in the SQL statement with setXXX(1, object) referring to the first wildcard and so on.
Of course, you have to repeat the same amount of calls to setXXX for each query you add the the batch that you want to execute at the end.
You can use like below snippet. Check statement.setObject documentation for further details. The "rs" in the below snippet is a resultset got by executing some query from one table. The "query" in the below snippet is some insert or update query into different table. The below example states selection from one table and insertion into some other table while dynamically identifying the column type. Note: Table column types should match else exception will be thrown.
PreparedStatement statement = connection.prepateStatement( query );
ResultSetMetaData rsmd = rs.getMetaData();
while( rs.next() )
{
for( int i = 1 ; i <= rsmd.getColumnCount() ; i++ )
{
statement.setObject( i, rs.getObject(i), rsmd.getColumnType(i) );
}
}

How to do a search based on number of fields that has been filled

I am implementing a search in which users need to fill different fields to do the search, but I am not sure how to search if user does not fill some of the fields.
Lets say I am asking them to fill name,family,age,city and country
User may leave any of these blank so I should make a search based on the filled one.
For example if name and family are filled just search for name AND family and if name, family and country are filled, search for records that has all of these three.
I am using prepared statement, How about if I use Persitance API?
ps.setString(1, "Name");
ps.setString(2, "Family");
ps.setint(3, "age");
ps.setString(4, "city");
ps.setString(5,"country");
Query
SELECT * FROM Customer WHERE name = ? AND family = ? AND age = ? AND city = ? AND country = ?
You'll want to remove the expressions that reference unspecified fields.
You can have a SortedMap<String, Object> which contains only the requested fields. Iterate over that and build the SQL where clause using a StringBuilder along with setting the parameter values.
Some ORM's will do this for you.
If very complex, this sort of scenario seems to call for something like the Hibernate Criteria Query API.
The Squiggle SQL Builder might work well for you for simpler cases.

Categories