2

I know jdbcTemplate can be used to create PreparedStatements if you set it up to do so:

i.e.

private JdbcTemplate jdbcTemplate;
String sqlQuery = "Select * from table where column = ?";
String value = "value";
this.jbdcTemplate.query(sqlQuery, new Object[] { value }, rs, rowNum -> {
  System.out.println(rs.getString("column"));
});

However, I have a query with many AND operators in the Where clause, and depending on some condition, a specific AND statement may or may not be added to the query string.

i.e.

private JdbcTemplate jdbcTemplate;
StringBuilder sqlQuery = new StringBuilder("Select * from table where 1=1 "); //base query
if(someCondition)
   sqlQuery.append("And column1 = '" + value1 + "'");
if(someCondition2)
   sqlQuery.append("And column2 = '" + value2 + "'");
if(someCondition3)
   sqlQuery.append("And column3 = '" + value3 + "'");
//etc...

With this type of dynamic query, I am unable to know exactly how many values to place in the new Object[] {} field at compile time, because the someCondition fields are always changing at runtime.

Is there a way to write the this.jdbcTemplate.query(sqlQuery.toString(), new Object[] {}... to accommodate these dynamic AND statements?

bscott
  • 311
  • 2
  • 5
  • 19
  • Surely you can create the `Object[]` at runtime with the appropriate size. What have you tried so far? – mustaccio Aug 13 '16 at 18:40
  • @mustaccio Thanks for that advice! That really helped. Since you can't resize arrays in Java at runtime, I was able to come up with a pretty simple solution to dynamically initialize the `Object[]` with the appropriate size at runtime. I still have another issue I need to fix before I post my solution though – bscott Aug 14 '16 at 23:50
  • The issue is that one of the dynamic `AND` statements is written like so: `AND column1 IN ('x','y','z','etc..')`, where the values inside of the parentheses will also be dynamic at runtime. I can't write the preparedStatement like `AND column1 IN (?)` and then plug the comma separated string value (i.e. `String values = "x,y,z,etc...";`) in because it will treat the comma separated string value as a string literal, which leads to errors. I'm in the process of fixing this issue and I'll come back when I have fixed it. – bscott Aug 14 '16 at 23:58

2 Answers2

1

I found a solution after taking into consideration what @mustaccio said in his comment to my original question. I also took part of the solution from this stackoverflow question and used it in my own solution.

The main issue I was having was dynamically creating an Object[] array at runtime, since you can't dynamically add elements to an Object[] array. They must have a defined size when initialized.

First, I create an arraylist of strings called queryArgs. Every time one of the if conditions proves true and we add an AND statement to the query, I also add another line of code that adds the value to be plugged in the preparedStatement to the queryArgs arraylist. Once that's done, I create a new Object[] array whose size is initialized to the size of the queryArgs arraylist. Lastly, I loop through each element in the Object[] array, setting them equal to the values in queryArgs.

private JdbcTemplate jdbcTemplate;

List<QueryResults> jdbcQuery(QueryParams queryParams) {
    /* base query */
    StringBuilder sqlQuery = new StringBuilder("Select * from table where 1=1 ");
    /* stores the dynamic preparedStatement arguments */
    List<String> queryArgs = new ArrayList<>();

    if(someCondition){
       sqlQuery.append("And column1 = ? ");
       queryArgs.add(queryParams.value1);
    }
    if(someCondition2){
       sqlQuery.append("And column2 = ? ");
       queryArgs.add(queryParams.value2);
    }
    if(someCondition3){
       sqlQuery.append("And column3 = ? ");
        queryArgs.add(queryParams.value3);
    }
    //etc...

    /* this is the part I used from the above stackoverflow question */
    Object[] preparedStatementArgs = new Object[queryArgs.size()];
    for(int i = 0; i < preparedStatementArgs.length; i++){
        preparedStatementArgs[i] = queryArgs.get(i);
    }

    /* Lastly, execute the query */
    return this.jdbcTemplate.query(sqlQuery.toString(),
    preparedStatementArgs, (rs, rowNum) -> {

        QueryResults result = new QueryResults();
        /* store the results of the query... */
    });
}

The outlier is that one of the dynamic AND statements above is written like this:

AND column4 IN ('x','y','z','etc..'‌​)

, where the values inside of the parentheses are also dynamic at runtime. My service receives a string value that looks like this:

String queryParams.value4 = "x,y,z,etc...";

I can't write the preparedStatement like this: AND column4 IN (?) and then simply plug in queryParams.value4 because it will treat queryParams.value4 as a string literal, which leads to errors.

To solve this issue, I create another arraylist of strings called value4Array. I loop through every character in queryParams.value4, and I check if the current character in the loop is equal to a comma, our delimiter. If it is, then I create a substring of all the characters leading up to that comma, and add that newly created string to value4Array.

The next step is to create the dynamic AND column4 IN (?) statement. I do this by looping through each string value in the value4Array arraylist we just created, and doing a sql.append("?"), based on how many strings are in value4Array. After this, the rest of the logic is the same as my solution above.

/* this function takes the comma delimited string literal (value4 : "x,y,z,etc...")
   and parses it into an array of strings. */
private List<String> parseValue4(String value4){
    int valueIndex= 0;
    List<String> value4Array = new ArrayList<>();
    for(int i = 0; i < value4.length(); i++){
        if(value4.charAt(i) == ','){
            value4Array.add(value4.substring(valueIndex, i));
            valueIndex = i + 1;
        }
        else if(i == value4.length() - 1){
            value4Array.add(value4.substring(valueIndex, value4.length()));
        }
    }
    return value4Array;
}
if(someCondition4){
   List<String> value4Array = parseValue4(queryParams.value4);
   sqlQuery.append("And column4 IN ("); /* base AND statement */
    for(int i = 0; i < value4Array.size(); i++){
        if(i == value4Array.size() - 1)
            sqlQuery.append("?)");
        else                       /* dynamically appending ?'s */
            sqlQuery.append("?,");
        queryArgs.add(value4Array.get(i));
    }
}
bscott
  • 311
  • 2
  • 5
  • 19
  • ...I would not be surprised if there is a library for this, which might make things easier for you, but don't know of any offhand. `ArrayList` _does_ have a [`toArray()`](http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html#toArray()) method, which should simplify that part of the code. For dealing with the `IN` parameter, the `String` methods `split` and `join` will be helpful, as will `ArrayList`'s `addAll`. – Clockwork-Muse Aug 15 '16 at 17:14
  • @Clockwork-Muse I wasn't sure how to make use of the `ArrayList` `addAll` method or the `String` `join` methods in this situation. I did however, remove the `Object[]` logic all together by using `toArray()` as you suggested on the `List queryArgs` arrayList. I also made good use of the `split` method instead of writing that unnecessary `parseValue4` function for creating the array of values since there's no point reinventing the wheel. – bscott Aug 18 '16 at 17:30
0

You are taking list of string type (List<String> queryArgs = new ArrayList<>();). What if the parameter is an integer type? Is there any way to store multiple typle of data?

ouflak
  • 2,458
  • 10
  • 44
  • 49
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 01 '22 at 12:31