为 PreparedStatements 动态设置参数

Posted

技术标签:

【中文标题】为 PreparedStatements 动态设置参数【英文标题】:Setting parameters dynamically for PreparedStatements 【发布时间】:2016-11-09 04:48:42 【问题描述】:

我很好奇,我们可以动态输入参数吗?例如,我有一个对象:

class Fields 
  private Integer id;
  private String name;

  public Fields(Integer id, String name) 
    this.id = id;
    this.name = name;
  
  //Getter - setter here

现在,我准备声明:

public void main() 
  PreparedStatement ps = conn.PrepareStatement("SELECT * FROM table WHERE id = ? AND name = ?");`

然后我设置

Fields param = new Fields(2, "xxx");

好吧..叫我懒惰,我怎样才能将参数发送到 PrepareStatement 之类的

ps.setParameters(param)

或者也许:

ps.setParameters(param.getAsArray())

为了更好地说明我的观点,我想做这样的事情:

for (Fields f: fieldList) 
  sql += f + " = ?";

请原谅我缺少上述代码的“AND”/“OR”运算符。我的最终目标是,我可以通过仅填充部分字段来传递实体类来创建整个字段和相应的参数。然后代码将跳过空字段并将填充的字段作为参数。

好吧,只是为了好奇。

谢谢

【问题讨论】:

AFAIK 您可以传递给PreparedStatement 的内容受到限制。通常,只允许您在数据库查询中使用的类型,例如int、varchar、blob 等。Fields 类是什么? 上面的字段类将被限制为一个字符串,因此不是很有用。同样在您有一个连接并且将重新使用相同的变量时,不能重新使用相同的 Field 对象。当然,您可以从 PreparedStatement 扩展并自己实现。 添加信息以更好地说明我的观点。简而言之,我想从实体对象动态创建条件子句,并从实体对象传递我的字段和参数。但这不仅限于实体,我可以创建一些特定的类并部分填充相关字段,跳过不需要的字段,并将它们传递给 prepareStatement。 您将需要使用 Hibernate 或其他包装 JDBC 的库,或者自己编写。 PreparedStatement 本身没有任何东西支持你想要的。 我不知道您是否必须使用PreparedStatement,但如果不需要,您可以使用 Java 持久性查询并使用位置参数。查看docs.oracle.com/javaee/6/tutorial/doc/bnbrg.html 【参考方案1】:

不用Field类,你可以直接把值放入参数中。

ps.setParameters(2, "xxx");

试试这些,希望对你有用。

    Field field1 = new Field(0,1,"id");
    Field field2 = new Field(1,"Nathan","name");

    List<Field> fields = new ArrayList<>();
    fields.add(field1);
    fields.add(field2);

    StringBuilder strBuilder = new StringBuilder("SELECT * FROM table WHERE ");

    // Dynamically Construct your SQL Query String
    for(int i = 0 ; i < fields.size() ; i++) 
        String strTemp = " AND ";
        String strTempEquals = " = ?";
        if(fields.size()-1 == i) 
            strTemp = ";";
        
        strBuilder.append(fields.get(i).getColumnName());
        strBuilder.append(strTempEquals);
        strBuilder.append(strTemp);
    

        PreparedStatement ps = conn.PrepareStatement(strBuilder);

    // Dynamically pass your values from your Field class to the parameters of PreparedStatement
    for (Field tempField : fields) 
        ps.setParameter(tempField.getIndex(),tempField.getValue());
    

我对您的 Field 类进行了一些更改。

class Field 
private Integer index;
private Object value;
private String columnName;

// Constructor
//Getters and Setter

【讨论】:

事实上,我想避免这种情况。【参考方案2】:

尝试使用像这样的参数化构造函数,我希望这是你需要的

class Fields 
private Integer id;
private String name;
 public Fields(int id,String name)
 
    this.id=id;
    this.name=name;
    setParameter();
 
 public  void setParameter()
 
    PreparedStatement ps = conn.PrepareStatement("SELECT * FROM table WHERE id = ? AND name = ?");
    ps.setInt(1,id);
    ps.setString(2,name);
 

【讨论】:

相反,我不想为每个参数指定setSomething()。 哦,你应该通过休眠/弹簧 Spring太重了,不能直接从Entity中取标准; 对..如果您找到您的查询的解决方案,请分享【参考方案3】:

好的.. 这是一个非常粗略的解决方案。我仍然不喜欢我必须重复所有字段两次才能获得字段类型 String 或其他类型的事实,但请随意进一步完善它,因为这仍然非常粗糙。此外,您需要在 ps.setObject() 故障保护之前扩展下面的 ps.setSomething() 参数。您还需要预测没有任何条件的查询,例如跳过那些条件对象扫描并直接执行。但我希望你能明白。

public static String addPrefix(String prefix, String field) 
    return new StringBuilder(prefix)
            .append(Character.toUpperCase(field.charAt(0)))
            .append(field.substring(1))
            .toString();


public static <T> List<T> query(Connection conn, T criteria, String operator) throws SQLException 
    List<T> list = null;
    Class<?> targetClass = criteria.getClass();
    if (targetClass.getAnnotation(Table.class) == null) throw new SQLException("ERROR: Table not defined at entity class " + targetClass.getName());
    StringBuilder SQL = new StringBuilder("SELECT * FROM ").append(targetClass.getAnnotation(Table.class).name());
    List<Object> parameters = new ArrayList<>();

    try 
        Field[] fields = targetClass.getDeclaredFields();
        for (Field field : fields) 
            if (field.getAnnotation(Column.class) == null) continue;
                Method m = targetClass.getMethod(addPrefix("get", field.getName()).toString());
                Object o = m.invoke(criteria);

                if (o == null) continue;

                if (parameters.isEmpty()) SQL.append(" WHERE"); else SQL.append(operator);
                SQL.append(" ").append(field.getAnnotation(Column.class).name()).append(" = ?");
                parameters.add(o);
        

        try (Connection connection = IwiPrivate.getInstance().getConnection()) 
            try (PreparedStatement ps = connection.prepareStatement(SQL.toString())) 
                Integer x = 1;
                for (Field field : fields) 
                    String type = field.getType().getName();
                    Method m = targetClass.getMethod(addPrefix("get", field.getName()));
                    Object o = m.invoke(criteria);

                    if (o == null) continue;
                    if (type ==  "java.lang.String") ps.setString(x, (String) parameters.get(x));
                    else if (type == "java.lang.Integer") ps.setInt(x, (Integer) parameters.get(x));
                    else ps.setObject(x, parameters.get(x)); //Put more set traps here.
                
                try (ResultSet rs = ps.executeQuery();) 
                    while (rs.next())
                        list.add((T) Database.mapSingle(rs, targetClass));
                
            
        
     catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) 
        Logger.getLogger(QueryExperiment.class.getName()).log(Level.SEVERE, null, ex);
    
    return list;

现在,要使用它,只需像这样创建您的实体对象

@Table(name = "testTable")
public class Entity 
    @Column(name = "id")
    private Integer id;

    @Column(name = "name")
    private String name;

    public Integer getId() 
        return id;
    

    public void setId(Integer id) 
        this.id = id;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

然后把它作为标准

public void testQuery() 
    Entity criteria = new Entity();
    criteria.setId(7777);
    try (Connection connection = yourDatabase.getConnection()) 
        List<Entity> assets = QueryTest.query(connection, criteria, "AND");
     catch (SQLException ex) 
        Logger.getLogger(IwiPrivateTest.class.getName()).log(Level.SEVERE, null, ex);
    

它将创建如下 SQL: SELECT * FROM testTable WHERE id = ? 并使用 setInt 发送 7777 作为参数

如果您想避免 ORM 并创建简单的查询,我相信这种方法可以正常工作。

【讨论】:

【参考方案4】:

在Java代码中,使用spring jdbc也可以实现。

下面的示例代码供参考: 目前,我们正在循环更新 100 个参数。如果我们知道参数的数量,那么我们就这样做。 如果您的要求是具有 100 个参数的表,那么您也可以按照以下方法进行一些修改。

  public Class MappingDTO
     private String attribute1;
     private String attribute2;
     private String attribute3;
     private String attribute4;
     .
     .
     .
     .
     .
     private String attribute100;

     public getAttribute1()
     return attribute1;
     
     .
     .
     .
     public getAttribute100()
     return attribute1;
     

  updated=jdbcTemplate.batchUpdate("our query", new BatchPreparedStatementSetter() 

                        @Override
                        public void setValues(PreparedStatement ps, int index) throws SQLException 
                                        MappingDTO mappingDTO = mappingDTOList.get(index);
                                        Class<?> tClass = mappingDTO.getClass();
                                        int i=1;
                                        for (i = 1; i <= 100; i++)  //100 max attributes can be updated //dynamic preparestatemetn setter
                                            Method method=null;
                                            try 
                                                method = tClass.getMethod("getAttribute"+(i), new Class[] );
                                             catch (NoSuchMethodException e) 
                                                LOG.error("Exception thrown | uploadStatementTemporaryTableUpdate | No such method exception | while trying to find methods", e);
                                             catch (SecurityException e) 
                                                LOG.error("Exception thrown | uploadStatementTemporaryTableUpdate |Security Exception | while trying to find methods", e);
                                            
                                            String str1=null;
                                            try 
                                                str1 = (String)method.invoke(mappingDTO, new Object[] );
                                             catch (IllegalAccessException e) 

                                             catch (IllegalArgumentException e) 
                                                // TODO Auto-generated catch block

                                             catch (InvocationTargetException e) 

                                            
                                            ps.setString(i, str1);

                                        


                                    

                                    @Override
                                    public int getBatchSize() 
                                        // TODO Auto-generated method stub
                                        return mappingDTOList.size();
                                    
                                );

【讨论】:

【参考方案5】:

PreparedStatement 有一个setObject(int, java.lang.Object) 方法。您可以在接受数组/字段列表并将名称附加到 sql 并使用 setObject 方法设置参数的类或方法中使用它。

请记住,这种方法极易受到 sql 注入攻击,因此请谨慎使用。

【讨论】:

嗯……这给了我一些想法……等等。我很快就会给一些东西。 对我来说真正的问题是你为什么需要这个?为什么不创建与您的表对应的 POJO 类并将 POJO 对象读入/从相应的表中读取/写入? 正如我所说,我不使用任何 ORM。 JPA或休眠或任何东西。我只是不喜欢他们。它们太重了,而且缺乏优化。因此我更喜欢从数据库服务器进行优化。我所需要的只是从视图或存储过程中进行简单检索。但是这个问题只是一个简单的实验,可以加快我的工作速度,但仍然占用资源。

以上是关于为 PreparedStatements 动态设置参数的主要内容,如果未能解决你的问题,请参考以下文章

如何在多线程环境中生成 PreparedStatements?

PreparedStatements 是不是由 CQL 字符串缓存? Datastax PreparedStatement 是不是保留语句 ID?

PostgreSQL JDBC - 使用 PreparedStatements 创建表时已存在关系

Java PreparedStatements 中的通配符

不关闭我的 JDBC PreparedStatements 是不是会导致内存泄漏?

从搜索表单动态构建 WHERE 子句时如何防止 SQL 注入?