简体   繁体   中英

prepare statement : create method using an ArrayList of parameters

I'm trying to modify some Java existing code (was not done by me) and I found the queries not done using prepare statement. Because I have alot of code I'm trying to modify it as less as possible. So instead of having a method like

public void executeInsertStmt(String strQuery, String param1, String param2) {

...
...
PreparedStatement preparedStatement = cnx.prepareStatement(stringRequest);
preparedStatement.setString(1, param1);
preparedStatement.setString(2, param2);
}

I would like to do something like that (I'm not sure that is the best solution).

public void executeInsertStmt(String strQuery, ArrayList<ArrayList<Object>> parameters) {

PreparedStatement preparedStatement = cnx.prepareStatement(stringRequest);

int counter=1;
            for (final ArrayList<Object> eachParam : parameters) {

                switch(DataTypes.valueOf(eachParam.get(0).toString().toUpperCase()))
                {
                    case STRING:
                        preparedStatement.setString(counter, (String)eachParam.get(1));
                    break;
                    case DATE:
                        preparedStatement.setDate(counter, (Date)eachParam.get(1));
                    break;
                    case INT:
                        preparedStatement.setInt(counter, (Integer)eachParam.get(1));
                    break;
                    default:
                        preparedStatement.setString(counter, (String)eachParam.get(1));
                    break;
                }

                counter++;

            }
}

And then to have something like this:

strQuery = "insert into toto values (?,?)";
ArrayList<Object> paramToPass1 = new ArrayList<Object>();

paramToPass1.add("String");
paramToPass1.add("TheValueForTheString");

ArrayList<Object> paramToPass2 = new ArrayList<Object>();

paramToPass2.add("String");
paramToPass2.add("TheValueForTheString2");

ArrayList<ArrayList<Object>> paramToPass = new ArrayList<ArrayList<Object>>();

paramToPass.add(paramToPass1);
paramToPass.add(paramToPass2);


executeInsertStmt(strQuery,paramToPass);

Because I have many queries with different number of parameters this kind of aproach will be the best for me. I don'y have to do a method for each type of query.

What do you think.

Is something wrong about that ? Is the best way to go ?

Thanks for any idea.

You should use an object representation for your parameters, rather than an ArrayList<Object> of size 2. Also, you don't need your own DataType enumeration, there is already a java.sql.Types class. Best of all, there's a setObject() method on PreparedStatement that recognizes these types values, so you don't need a switch statement or calls to type-specific PreparedStatement.set..() methods.

Here's a solution that has an object representation for the parameters, takes advantage of java.sql.Types and preparedStatement.setObject() support for those types, and also insulates you from the type constant values.

First, the object representation for the parameters:

import java.sql.Types;

public class ParamDescriptor {
    // Constructor itself is private, we are encapsulating so that
    // you don't need to write java.sql.Types constants yourself
    private ParamDescriptor(int dataType, Object value) {
        _dataType = dataType;
        _value = value;
    }

    // Factory methods for actual instantiation
    public static ParamDescriptor forInt (int paramVal) {
        return new ParamDescriptor (Types.INTEGER, paramVal);
    }

    public static ParamDescriptor forString (String paramVal) {
        return new ParamDescriptor (Types.VARCHAR, paramVal);
    }

    public static ParamDescriptor forDate (Date paramVal) {
        return new ParamDescriptor (Types.DATE, paramVal);
    }
    // Add more here to support more data types . . . .    


    public int getDataType() {
        return _dataType;
    }

    public Object getValue() {
        return _value;
    }

    private int     _dataType;
    private Object  _value;
}

Next, the new version of executeInsertStmt() . We've reduced it to just a few lines, and it never needs to change again, regardless of future support for parameter types:

  public void executeInsertStmt(String strQuery, List<ParamDescriptor> parameters) throws SQLException {    
        PreparedStatement preparedStatement = cnx.prepareStatement(strQuery);

        int counter = 1;
        for (ParamDescriptor paramDescriptor : parameters) {
            preparedStatement.setObject(counter, 
                                        paramDescriptor.getValue(),
                                        paramDescriptor.getDataType());
            counter++;
        }
    }

Finally, code that would call the new executeInsertStmt() :

String strQuery = "insert into toto values (?,?)";

ParamDescriptor paramToPass1 = ParamDescriptor.forString("TheValueForTheString");
ParamDescriptor paramToPass2 = ParamDescriptor.forString("TheValueForTheString2");

List<ParamDescriptor> parameters = new ArrayList<ParamDescriptor>();
parameters.add(paramToPass1);
parameters.add(paramToPass2);

executeInsertStmt(strQuery, parameters);

Note that if you ever plan on handling decimals, there is another version of PreparedStatement.setObject() that supports specifying decimal scale. You may need to add some sort of support for that into your executeInsertStmt() .

Well, your idea is not bad... just some pointers.

Don't pass an ArrayList of ArrayList... rather use instanceof or a DataHolder class.

Method 1: instanceof

Simple add types Integer, String, Double, etc and then in your method:

public void executeInsertStmt(String strQuery, ArrayList<Object> parameters) {

    PreparedStatement preparedStatement = cnx.prepareStatement(stringRequest);
    int counter=1;
    for (final Object eachParam : parameters) {
        if (eachParam instanceof String) {
            preparedStatement.setString(counter, (String)eachParam); 
        } else if (eachParam instanceof Integer) {
            preparedStatement.setInt(counter, (Integer)eachParam); 
        } else ...

        counter++;

    }
}

The idea with the DataHolder is that the one field will keep the type and the other the actual data. You can even make use of generic to make it even nicer. However, I would go for instanceof in this case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM