2010-05-18 53 views
34

Nói rằng tôi có một truy vấn có dạngCách tiếp cận tốt nhất khi sử dụng JDBC để tham số hóa mệnh đề IN là gì?

SELECT * FROM MYTABLE WHERE MYCOL in (?) 

Và tôi muốn parameterize các đối số trong.

Có cách nào đơn giản để làm điều này trong Java với JDBC, trong một cách mà có thể làm việc trên nhiều cơ sở dữ liệu mà không sửa đổi chính SQL?

Gần nhất question I've found had to do with C#, tôi tự hỏi liệu có cái gì đó khác với Java/JDBC hay không.

Trả lời

3

Tôi đã giải quyết điều này bằng cách tạo chuỗi SQL với số lượng ? vì tôi có giá trị để tìm kiếm.

SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?) 

Trước tiên tôi tìm kiếm loại mảng mà tôi có thể chuyển vào câu lệnh, nhưng tất cả các loại mảng JDBC đều là nhà cung cấp cụ thể. Vì vậy, tôi ở lại với nhiều ?.

+1

Đó là những gì chúng tôi đang làm ngay bây giờ, nhưng những gì tôi đã hy vọng rằng có một cách thống nhất để làm điều đó mà không cần SQL tùy chỉnh ... – Uri

+0

Ngoài ra, nếu nó giống như Oracle, nó sẽ phải reparse nhất mọi tuyên bố. – orbfish

-1

Một cách tôi có thể nghĩ đến là sử dụng java.sql.PreparedStatement và một chút của ban giám khảo gian lận

PreparedStatement preparedStmt = conn.prepareStatement ("SELECT * FROM MyTable ĐÂU MYCOL trong()?");

... và sau đó ...

prepareStmt.setString (1, [paramged strings)];

http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html

+6

Điều này sẽ không hoạt động vì có thể tạo truy vấn như '... WHERE MYCOL IN ('2,3,5,6')' không phải là thứ bạn đang cố gắng thực hiện. – Progman

1

AFAIK, không có hỗ trợ tiêu chuẩn trong JDBC để xử lý bộ sưu tập như thông số. Nó sẽ là tuyệt vời nếu bạn chỉ có thể vượt qua trong một danh sách và đó sẽ được mở rộng.

truy cập JDBC Spring hỗ trợ thông qua các bộ sưu tập như thông số. Bạn có thể xem cách điều này được thực hiện cho cảm hứng về mã hóa an toàn này.

Xem Auto-expanding collections as JDBC parameters

(Bài báo đầu tiên thảo luận Hibernate, sau đó tiếp tục thảo luận về JDBC.)

36

Có thực sự không có cách nào đơn giản để làm điều này trong JDBC. Một số Trình điều khiển JDBC dường như hỗ trợ PreparedStatement#setArray() theo mệnh đề IN. Tôi chỉ không chắc đó là cái nào.

Bạn chỉ có thể sử dụng phương thức trợ giúp với String#join()Collections#nCopies() để tạo trình giữ chỗ cho mệnh đề IN và một phương thức trợ giúp khác để đặt tất cả các giá trị trong vòng lặp với PreparedStatement#setObject().

public static String preparePlaceHolders(int length) { 
    return String.join(",", Collections.nCopies(length, "?")); 
} 

public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException { 
    for (int i = 0; i < values.length; i++) { 
     preparedStatement.setObject(i + 1, values[i]); 
    } 
} 

Đây là cách bạn có thể sử dụng nó:

private static final String SQL_FIND = "SELECT id, name, value FROM entity WHERE id IN (%s)"; 

public List<Entity> find(Set<Long> ids) throws SQLException { 
    List<Entity> entities = new ArrayList<Entity>(); 
    String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size())); 

    try (
     Connection connection = dataSource.getConnection(); 
     PreparedStatement statement = connection.prepareStatement(sql); 
    ) { 
     setValues(statement, ids.toArray()); 

     try (ResultSet resultSet = statement.executeQuery()) { 
      while (resultSet.next()) { 
       entities.add(map(resultSet)); 
      } 
     } 
    } 

    return entities; 
} 

private static Entity map(ResultSet resultSet) throws SQLException { 
    Enitity entity = new Entity(); 
    entity.setId(resultSet.getLong("id")); 
    entity.setName(resultSet.getString("name")); 
    entity.setValue(resultSet.getInt("value")); 
    return entity; 
} 

Lưu ý rằng một số cơ sở dữ liệu có một giới hạn của khoản chấp nhận các giá trị trong mệnh đề IN. Ví dụ, Oracle có giới hạn trên 1000 mục.

+1

Cách tiếp cận này có dẫn đến SQL Injection không ?? –

+3

@Kaylan: không có dòng mã đơn nào thêm dữ liệu nhập do người dùng kiểm soát vào chuỗi truy vấn SQL. Vì vậy, chắc chắn không có phương tiện của một nguy cơ tiêm SQL. – BalusC

+0

Tốt. Cảm ơn bạn đã làm rõ –

0

Xem bản dùng thử của tôi và Thành công, Người ta nói rằng kích thước danh sách có giới hạn tiềm năng. Danh sách l = Mảng.asList (số nguyên mới [] {12496,12497,12498,12499}); Bản đồ param = Collections.singletonMap ("goodsid", l);

NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource()); 
    String sql = "SELECT bg.goodsid FROM beiker_goods bg WHERE bg.goodsid in(:goodsid)"; 
    List<Long> list = namedParameterJdbcTemplate.queryForList(sql, param2, Long.class); 
0

sormula làm đơn giản này (xem Example 4):

ArrayList<Integer> partNumbers = new ArrayList<Integer>(); 
partNumbers.add(999); 
partNumbers.add(777); 
partNumbers.add(1234); 

// set up 
Database database = new Database(getConnection()); 
Table<Inventory> inventoryTable = database.getTable(Inventory.class); 

// select operation for list "...WHERE PARTNUMBER IN (?, ?, ?)..." 
for (Inventory inventory: inventoryTable. 
    selectAllWhere("partNumberIn", partNumbers))  
{ 
    System.out.println(inventory.getPartNumber()); 
} 
12

Vì không ai trả lời trường hợp cho một lớn tại khoản (hơn 100) tôi sẽ ném tôi, giải pháp cho vấn đề này mà hoạt động tốt cho JDBC. Tóm lại, tôi thay thế IN bằng một số INNER JOIN trên bảng tmp.

Những gì tôi làm là làm cho những gì tôi gọi là một bảng id lô và tùy thuộc vào RDBMS tôi có thể làm cho rằng một bảng tmp hoặc trong bảng bộ nhớ.

Bảng có hai cột. Một cột có id từ IN khoản và cột khác với một id lô mà tôi tạo ra khi đang bay.

SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ? 

Trước khi bạn chọn bạn đẩy id vào bảng với một id lô đã cho. Sau đó, bạn chỉ thay thế mệnh đề IN truy vấn ban đầu của mình bằng kết hợp INNER JOIN trên bảng id của bạn WHERE batch_id bằng với lô hiện tại của bạn. Sau khi thực hiện xong, bạn xóa các mục nhập cho bạn theo lô.

+2

+1 Điều này sẽ khá hiệu quả đối với các tập dữ liệu lớn và không phá vỡ cơ sở dữ liệu của bạn – jasonk

+0

Hmm, sẽ không phải tham gia bán ('IN' hoặc' EXISTS' predicate) có thể hoạt động tốt hơn bên trong? –

+0

@LukasEder YMMV với mã giả SQL của tôi. Như mọi khi kiểm tra/điểm chuẩn. Đó là một ý tưởng thú vị. Phát biểu trong đó tôi nên đi xem những gì SQL thực tế của chúng tôi là khi chúng tôi làm điều này. –

7

Cách tiêu chuẩn để thực hiện việc này là (nếu bạn đang sử dụng Spring JDBC) là sử dụng lớp org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.

Sử dụng lớp này, có thể xác định Danh sách làm tham số SQL của bạn và sử dụng NamedParameterJdbcTemplate để thay thế tham số đã đặt tên. Ví dụ:

public List<MyObject> getDatabaseObjects(List<String> params) { 
    NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); 
    String sql = "select * from my_table where my_col in (:params)"; 
    List<MyObject> result = jdbcTemplate.query(sql, Collections.singletonMap("params", params), myRowMapper); 
    return result; 
} 
0

Có các phương pháp thay thế khác nhau mà chúng tôi có thể sử dụng.

  1. Execute đơn Queries - chậm và không được khuyến khích
  2. Sử dụng Stored Procedure - cơ sở dữ liệu cụ thể
  3. Tạo PreparedStatement Query động - hiệu suất tốt nhưng lợi ích lỏng lẻo của bộ nhớ đệm và cần biên dịch lại
  4. Sử dụng NULL trong PreparedStatement Query - Tôi nghĩ đây là một cách tiếp cận tốt với hiệu suất tối ưu.

Kiểm tra thêm chi tiết về các here này.

2

tôi có câu trả lời từ docs.spring(19.7.3)

Tiêu chuẩn SQL cho phép lựa chọn hàng dựa trên một biểu thức bao gồm một danh sách biến các giá trị. Một ví dụ điển hình sẽ được chọn * từ T_ACTOR trong đó id trong (1, 2, 3). Danh sách biến này không được hỗ trợ trực tiếp cho các câu lệnh đã chuẩn bị theo tiêu chuẩn JDBC; bạn không thể khai báo một số biến chỗ dành sẵn. Bạn cần một số biến thể với số lượng trình giữ chỗ mong muốn được chuẩn bị, hoặc bạn cần phải tạo chuỗi SQL động khi bạn biết có bao nhiêu trình giữ chỗ được yêu cầu. Hỗ trợ tham số được đặt tên được cung cấp trong NamedParameterJdbcTemplate và JdbcTemplate có cách tiếp cận thứ hai. Chuyển các giá trị dưới dạng java.util.List của các đối tượng nguyên thủy. Danh sách này sẽ được sử dụng để chèn các phần giữ chỗ được yêu cầu và chuyển các giá trị trong quá trình thực thi câu lệnh.

Hy vọng điều này có thể giúp bạn.

Các vấn đề liên quan