2010-06-24 31 views
56

Cách đặt giá trị cho trong mệnh đề trong một chuẩn bị sẵn sàng trong JDBC trong khi thực hiện truy vấn.PreparedStatement với danh sách các tham số trong mệnh đề IN

Ví dụ:

connection.prepareStatement("Select * from test where field in (?)"); 

Nếu đây trong điều khoản có thể chứa nhiều giá trị như thế nào tôi có thể làm điều đó. Đôi khi tôi biết danh sách các thông số trước hoặc đôi khi tôi không biết trước. Làm thế nào để xử lý trường hợp này?

+8

lô của giá trị nhân bản: http://stackoverflow.com/questions/2861230/what-is-the-best-approach-using-jdbc-for-parameterizing-an-in- khoản, http://stackoverflow.com/questions/2510083/preparedstatement-question-in-java-against-oracle và http://stackoverflow.com/questions/178479/alternatives-for-java-sql-preparedstatement-in- khoản-vấn đề – BalusC

Trả lời

63

Những gì tôi làm là thêm một "?" cho mỗi giá trị có thể.

Đối với instace:

List possibleValues = ... 
StringBuilder builder = new StringBuilder(); 

for(int i = 0 ; i < possibleValue.size(); i++) { 
    builder.append("?,"); 
} 

String stmt = "select * from test where field in " 
       + builder.deleteCharAt(builder.length() -1).toString(); 
PreparedStatement pstmt = ... 

Và rồi hạnh phúc thiết lập các params

int index = 1; 
for(Object o : possibleValue) { 
    pstmt.setObject( index++, o); // or whatever it applies 
} 
+12

Tùy thuộc vào độ dài tối đa của danh sách, điều này có thể dẫn đến một số lượng lớn các câu lệnh chuẩn bị sẵn sàng, có thể ảnh hưởng đến hiệu suất cơ sở dữ liệu. –

+3

Ngoài ra, có vẻ như có dấu ngoặc đơn bị thiếu ... – Vlasec

+4

Tôi nghe nói về một thực hành tốt khi có một số câu lệnh SQL với số lượng dấu hỏi khác nhau - ví dụ: 10, 40, 160, 800. Phần còn lại được điền bằng 0 không được sử dụng như ID, thông thường) hoặc bất kỳ tham số nào đã cho. Điều này làm giảm số lượng các câu lệnh chuẩn bị được lưu trữ trong bộ nhớ cache của DB. – Vlasec

11

Bạn có thể muốn kiểm tra liên kết này:

http://www.javaranch.com/journal/200510/Journal200510.jsp#a2

Nó giải thích những ưu và nhược điểm của phương pháp khác nhau của việc tạo ra PreparedStatement với in khoản.

EDIT:

Một cách tiếp cận rõ ràng là tạo động '?' một phần trong thời gian chạy, nhưng tôi không muốn chỉ đề xuất phương pháp này vì tùy thuộc vào cách bạn sử dụng, nó có thể không hiệu quả (vì PreparedStatement sẽ cần phải được 'biên dịch' mỗi khi được sử dụng)

7

Bạn không thể thay thế ? trong truy vấn của bạn với một số tùy ý các giá trị. Mỗi ? là một trình giữ chỗ cho một giá trị duy nhất. Để hỗ trợ số lượng giá trị tùy ý, bạn sẽ phải tạo động một chuỗi chứa ?, ?, ?, ... , ? với số lượng dấu hỏi giống với số giá trị bạn muốn trong mệnh đề in của mình.

2

Những gì bạn có thể làm là tự động xây dựng chuỗi chọn (phần 'IN (?)' Bằng một vòng lặp đơn giản ngay sau khi bạn biết bạn cần đặt bao nhiêu giá trị bên trong mệnh đề IN. Sau đó, bạn có thể khởi tạo PreparedStatement.

+1

Điều đó không đánh bại điểm của PreparedStatement, nếu bạn đang trực tiếp đưa đầu vào của người dùng vào SQL sql injection trở nên dễ dàng –

1

Nhiều DB có khái niệm về bảng tạm thời, thậm chí giả sử bạn không có bảng tạm thời, bạn luôn có thể tạo một bảng có tên duy nhất và thả nó khi bạn hoàn tất. Trong khi chi phí tạo và thả bảng lớn, điều này có thể hợp lý cho các hoạt động rất lớn, hoặc trong trường hợp bạn đang sử dụng cơ sở dữ liệu dưới dạng tệp cục bộ hoặc trong bộ nhớ (SQLite).

Một ví dụ từ một cái gì đó tôi đang ở giữa (sử dụng Java/SqlLite):

String tmptable = "tmp" + UUID.randomUUID(); 

sql = "create table " + tmptable + "(pagelist text not null)"; 
cnn.createStatement().execute(sql); 

cnn.setAutoCommit(false); 
stmt = cnn.prepareStatement("insert into "+tmptable+" values(?);"); 
for(Object o : rmList){ 
    Path path = (Path)o; 
    stmt.setString(1, path.toString()); 
    stmt.execute(); 
} 
cnn.commit(); 
cnn.setAutoCommit(true); 

stmt = cnn.prepareStatement(sql); 
stmt.execute("delete from filelist where path + page in (select * from "+tmptable+");"); 
stmt.execute("drop table "+tmptable+");"); 

này sẽ hiệu quả hơn nếu bạn có thể toreuse bàn.

1
public class Test1 { 
    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     // TODO Auto-generated method stub 
     System.out.println("helow"); 
String where="where task in "; 
     where+="("; 
    // where+="'task1'"; 
     int num[]={1,2,3,4}; 
     for (int i=0;i<num.length+1;i++) { 
      if(i==1){ 
       where +="'"+i+"'"; 
      } 
      if(i>1 && i<num.length) 
       where+=", '"+i+"'"; 
      if(i==num.length){ 
       System.out.println("This is last number"+i); 
      where+=", '"+i+"')"; 
      } 
     } 
     System.out.println(where); 
    } 
} 
+3

Có quá nhiều lệnh gọi 'StringBuilder()' mới. –

1

Hiện tại, MySQL không cho phép đặt nhiều giá trị trong một cuộc gọi phương thức. Vì vậy, bạn phải có nó dưới sự kiểm soát của riêng bạn. Tôi thường tạo một câu lệnh chuẩn bị cho số tham số được xác định trước, sau đó tôi thêm nhiều lô theo ý tôi.

int paramSizeInClause = 10; // required to be greater than 0! 
    String color = "FF0000"; // red 
    String name = "Nathan"; 
    Date now = new Date(); 
    String[] ids = "15,21,45,48,77,145,158,321,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,358,1284,1587".split(","); 

    // Build sql query 
    StringBuilder sql = new StringBuilder(); 
    sql.append("UPDATE book SET color=? update_by=?, update_date=? WHERE book_id in ("); 
    // number of max params in IN clause can be modified 
    // to get most efficient combination of number of batches 
    // and number of parameters in each batch 
    for (int n = 0; n < paramSizeInClause; n++) { 
     sql.append("?,"); 
    } 
    if (sql.length() > 0) { 
     sql.deleteCharAt(sql.lastIndexOf(",")); 
    } 
    sql.append(")"); 

    PreparedStatement pstm = null; 
    try { 
     pstm = connection.prepareStatement(sql.toString()); 
     int totalIdsToProcess = ids.length; 
     int batchLoops = totalIdsToProcess/paramSizeInClause + (totalIdsToProcess % paramSizeInClause > 0 ? 1 : 0); 
     for (int l = 0; l < batchLoops; l++) { 
      int i = 1; 
      pstm.setString(i++, color); 
      pstm.setString(i++, name); 
      pstm.setTimestamp(i++, new Timestamp(now.getTime())); 
      for (int count = 0; count < paramSizeInClause; count++) { 
       int param = (l * paramSizeInClause + count); 
       if (param < totalIdsToProcess) { 
        pstm.setString(i++, ids[param]); 
       } else { 
        pstm.setNull(i++, Types.VARCHAR); 
       } 
      } 
      pstm.addBatch(); 
     } 
    } catch (SQLException e) { 
    } finally { 
     //close statement(s) 
    } 

Nếu bạn không muốn đặt NULL khi không còn tham số, bạn có thể sửa đổi mã để tạo hai truy vấn và hai câu lệnh chuẩn bị. Đầu tiên là như nhau, nhưng câu thứ hai cho phần còn lại (modulus). Trong ví dụ cụ thể này sẽ là một truy vấn cho 10 thông số và một cho 8 thông số. Bạn sẽ phải thêm 3 đợt cho truy vấn đầu tiên (30 tham số đầu tiên), sau đó một lô cho truy vấn thứ hai (8 thông số).

3

Bạn cần jdbc4 thì bạn có thể sử dụng setArray!

Trong trường hợp của tôi, nó không hoạt động, vì kiểu dữ liệu UUID trong postgres dường như vẫn còn những điểm yếu, nhưng đối với các kiểu thông thường thì nó hoạt động.

ps.setArray(1, connection.createArrayOf("$VALUETYPE",myValuesAsArray)); 

Tất nhiên thay thế $ VALUETYPE và myValuesAsArray với giá trị chính xác.

Ghi chú sau Đánh dấu nhận xét:

Cơ sở dữ liệu và trình điều khiển của bạn cần hỗ trợ điều này! Tôi đã thử Postgres 9.4 nhưng tôi nghĩ rằng điều này đã được giới thiệu trước đó. Bạn cần trình điều khiển jdbc 4, nếu không setArray sẽ không có sẵn. Tôi đã sử dụng postgresql lái xe 9,4-1201-jdbc41 mà tàu với khởi động mùa xuân

+4

Việc đó có phụ thuộc nhiều vào cơ sở dữ liệu hay không. Bạn có thể muốn bao gồm trình điều khiển cơ sở dữ liệu + bạn đã sử dụng. –

0

public static void main (String arg []) {

Connection connection = ConnectionManager.getConnection(); 
    PreparedStatement pstmt = null; 
      //if the field values are in ArrayList 
     List<String> fieldList = new ArrayList(); 

    try { 

     StringBuffer sb = new StringBuffer(); 

     sb.append(" SELECT *   \n"); 
     sb.append(" FROM TEST   \n"); 
     sb.append(" WHERE FIELD IN ( \n"); 

     for(int i = 0; i < fieldList.size(); i++) { 
      if(i == 0) { 
       sb.append(" '"+fieldList.get(i)+"' \n"); 
      } else { 
       sb.append(" ,'"+fieldList.get(i)+"' \n"); 
      } 
     } 
     sb.append("   )  \n"); 

     pstmt = connection.prepareStatement(sb.toString()); 
     pstmt.executeQuery(); 

    } catch (SQLException se) { 
     se.printStackTrace(); 
    } 

} 
+0

Đừng làm điều này! Điều này mở ra chương trình của bạn để tiêm SQL. Nếu bạn đang làm điều này từ đầu vào của người dùng, họ có thể làm cho truy vấn làm bất cứ điều gì họ muốn. – DavidBittner

23

Bạn có thể sử dụng phương pháp setArray như đã đề cập trong javadoc dưới đây:

http://docs.oracle.com/javase/6/docs/api/java/sql/PreparedStatement.html#setArray(int, java.sql.Array)

Code:

PreparedStatement statement = connection.prepareStatement("Select * from test where field in (?)"); 
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"A1", "B2","C3"}); 
statement.setArray(1, array); 
ResultSet rs = statement.executeQuery(); 
+4

giải pháp này nghe có vẻ tuyệt vời, nhưng tôi nhận được một ** java.sql.SQLFeatureNotSupportedException ** từ trình điều khiển mysql 5.1.39 – benez

+0

Tôi nhận được 'SQLException: Tính năng không được hỗ trợ' khi sử dụng' createArrayOf'. –

+1

có thể bạn đang sử dụng MySQL, MySQL không hỗ trợ tính năng "setArray". – Laily

2

Bạn không muốn sử dụng PreparedStatment với các truy vấn động sử dụng mệnh đề IN ít nhất là chắc chắn bạn luôn dưới 5 biến hoặc một giá trị nhỏ như thế nhưng ngay cả như tôi nghĩ đó là một ý tưởng tồi (không khủng khiếp, nhưng xấu). Vì số lượng các phần tử lớn, nó sẽ tệ hơn (và khủng khiếp).

Hãy tưởng tượng hàng trăm hoặc hàng ngàn khả năng tại khoản IN:

  1. Đó là phản tác dụng, bạn bị mất hiệu suất và bộ nhớ vì bạn nhớ cache mỗi khi một yêu cầu mới, và PreparedStatement không chỉ dành riêng cho SQL injection, đó là về hiệu suất. Trong trường hợp này, Tuyên bố là tốt hơn.

  2. Hồ bơi của bạn có giới hạn PreparedStatment (-1 defaut nhưng bạn phải giới hạn nó) và bạn sẽ đạt đến giới hạn này! và nếu bạn không có giới hạn hoặc giới hạn rất lớn, bạn có một số rủi ro bị rò rỉ bộ nhớ, và trong trường hợp cực kỳ lỗi OutofMemory. Vì vậy, nếu nó cho dự án personnal nhỏ của bạn được sử dụng bởi 3 người sử dụng nó không ấn tượng, nhưng bạn không muốn điều đó nếu bạn đang ở trong một công ty lớn và ứng dụng của bạn được sử dụng bởi hàng nghìn người và hàng triệu yêu cầu.

Một số đọc. IBM : Memory utilization considerations when using prepared statement caching

1

Bạn có thể sử dụng:

for(int i = 0 ; i < listField.size(); i++) { 
    i < listField.size() - 1 ? request.append("?,") : request.append("?"); 
} 

Sau đó:

int i = 1; 
for (String field : listField) { 
    statement.setString(i++, field); 
} 

dụ:

List<String> listField = new ArrayList<String>(); 
listField.add("test1"); 
listField.add("test2"); 
listField.add("test3"); 

StringBuilder request = new StringBuilder("SELECT * FROM TABLE WHERE FIELD IN ("); 

for(int i = 0 ; i < listField.size(); i++) { 
    request = i < (listField.size() - 1) ? request.append("?,") : request.append("?"); 
} 


DNAPreparedStatement statement = DNAPreparedStatement.newInstance(connection, request.toString); 

int i = 1; 
for (String field : listField) { 
    statement.setString(i++, field); 
} 

ResultSet rs = statement.executeQuery(); 
2
public static ResultSet getResult(Connection connection, List values) { 
    try { 
     String queryString = "Select * from table_name where column_name in"; 

     StringBuilder parameterBuilder = new StringBuilder(); 
     parameterBuilder.append(" ("); 
     for (int i = 0; i < values.size(); i++) { 
      parameterBuilder.append("?"); 
      if (values.size() > i + 1) { 
       parameterBuilder.append(","); 
      } 
     } 
     parameterBuilder.append(")"); 

     PreparedStatement statement = connection.prepareStatement(queryString + parameterBuilder); 
     for (int i = 1; i < values.size() + 1; i++) { 
      statement.setInt(i, (int) values.get(i - 1)); 
     } 

     return statement.executeQuery(); 
    } catch (Exception d) { 
     return null; 
    } 
} 
-1
Using Java 8 APIs, 

    List<Long> empNoList = Arrays.asList(1234, 7678, 2432, 9756556, 3354646); 

    List<String> parameters = new ArrayList<>(); 
    empNoList.forEach(empNo -> parameters.add("?")); //Use forEach to add required no. of '?' 
    String commaSepParameters = String.join(",", parameters); //Use String to join '?' with ',' 

StringBuilder selectQuery = new StringBuilder().append("SELECT COUNT(EMP_ID) FROM EMPLOYEE WHERE EMP_ID IN (").append(commaSepParameters).append(")"); 
0

thử với mã này

String ids[] = {"182","160","183"}; 
      StringBuilder builder = new StringBuilder(); 

      for(int i = 0 ; i < ids.length; i++) { 
       builder.append("?,"); 
      } 

      String sql = "delete from emp where id in ("+builder.deleteCharAt(builder.length() -1).toString()+")"; 

      PreparedStatement pstmt = connection.prepareStatement(sql); 

      for (int i = 1; i <= ids.length; i++) { 
       pstmt.setInt(i, Integer.parseInt(ids[i-1])); 
      } 
      int count = pstmt.executeUpdate(); 
Các vấn đề liên quan