使用 JDBC 参数化 IN 子句的最佳方法是啥? [复制]
Posted
技术标签:
【中文标题】使用 JDBC 参数化 IN 子句的最佳方法是啥? [复制]【英文标题】:What is the best approach using JDBC for parameterizing an IN clause? [duplicate]使用 JDBC 参数化 IN 子句的最佳方法是什么? [复制] 【发布时间】:2010-05-18 21:18:46 【问题描述】:说我有一个表格查询
SELECT * FROM MYTABLE WHERE MYCOL in (?)
我想将参数参数化为 in。
是否有一种直接的方法可以在 Java 中使用 JDBC 执行此操作,并且可以在不修改 SQL 本身的情况下在多个数据库上工作?
最接近的question I've found had to do with C#,我想知道Java/JDBC 是否有不同之处。
【问题讨论】:
【参考方案1】:在 JDBC 中确实没有直接的方法可以做到这一点。 一些 JDBC 驱动程序似乎支持IN
子句中的PreparedStatement#setArray()
。我只是不确定哪些是。
您可以只使用带有String#join()
和Collections#nCopies()
的辅助方法来生成IN
子句的占位符,并使用另一个辅助方法来使用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]);
你可以这样使用它:
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;
请注意,某些数据库在IN
子句中对允许的值数量有限制。例如,Oracle 对 1000 个项目有此限制。
【讨论】:
这种方式会不会导致SQL注入?? @Kaylan:没有单个代码行将用户控制的输入原始添加到 SQL 查询字符串。所以绝对没有 SQL 注入风险。 很好。感谢您的澄清 jtds 驱动程序的最大参数变量列表为 2,000。 在 mysql(驱动程序 5.1.37)和 PostgreSQL(驱动程序 9.1-901)之间,只有 PostgreSQL 对 PreparedStatement#setArray() 有一些支持【参考方案2】:由于没有人回答 大型 IN 子句(超过 100 个),我将提出我的解决方案来解决这个问题,该解决方案非常适用于 JDBC。简而言之,我将 tmp 表上的 IN
替换为 INNER JOIN
。
我所做的是创建我称之为批处理 ids 的表,根据 RDBMS,我可能会创建一个 tmp 表或内存表。
表格有两列。一列带有来自 IN 子句的 ID,另一列带有我即时生成的批次 ID。
SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ?
在您选择之前,将您的 id 放入具有给定批次 id 的表中。 然后,您只需将原始查询 IN 子句替换为与您的 ids 表匹配的 INNER JOIN WHERE batch_id 等于您当前的批次。完成后,您可以批量删除条目。
【讨论】:
+1 这对于大型数据集非常有效,并且不会破坏您的数据库 嗯,半连接(IN
或 EXISTS
谓词)不会比内连接更好吗?
@LukasEder YMMV 和我的 SQL 伪代码。一如既往的测试/基准测试。这是一个有趣的想法。说到这里,当我们这样做时,我应该去看看我们的实际 SQL 是做什么的。
确实如此。在 MySQL 上,半连接仍然有变慢的风险。但是内部连接有不正确的风险;)
@Male,当您需要从包含所有结果的 JDBC 函数(例如,使用商定/固定的 API)输出完整的 ResultSet 时,这将是一个不错的选择:填写 temp-表并进行连接,而不是批量输入,而无法合并 ResultSet。 (注意:当您只有 SELECT 权限时,您将无法创建临时表)【参考方案3】:
执行此操作的标准方法是(如果您使用 Spring JDBC)是使用 org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate 类。
使用这个类,可以定义一个 List 作为你的 SQL 参数,并使用 NamedParameterJdbcTemplate 来替换一个命名参数。例如:
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;
【讨论】:
【参考方案4】:我通过使用尽可能多的 ?
构造 SQL 字符串来解决这个问题。
SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?)
首先,我搜索了可以传递到语句中的数组类型,但所有 JDBC 数组类型都是特定于供应商的。所以我留在了多个?
。
【讨论】:
这就是我们现在正在做的事情,但我希望有一种统一的方法可以在没有自定义 SQL 的情况下做到这一点...... 另外,如果它类似于 Oracle,它必须重新解析大多数语句。【参考方案5】:我从docs.spring(19.7.3)得到了答案
SQL 标准允许基于包含变量值列表的表达式来选择行。一个典型的例子是 select * from T_ACTOR where id in (1, 2, 3)。 JDBC 标准不直接支持准备好的语句使用此变量列表;您不能声明可变数量的占位符。您需要准备好所需数量的占位符的多种变体,或者您需要在知道需要多少占位符后动态生成 SQL 字符串。 NamedParameterJdbcTemplate 和 JdbcTemplate 中提供的命名参数支持采用后一种方法。将值作为原始对象的 java.util.List 传递。此列表将用于在语句执行期间插入所需的占位符并传入值。
希望对你有帮助。
【讨论】:
【参考方案6】:AFAIK,JDBC 中没有标准支持将集合作为参数处理。如果您可以只传入一个 List 并将其扩展,那就太好了。
Spring 的 JDBC 访问支持将集合作为参数传递。您可以看看这是如何完成的,以获取安全编码的灵感。
见Auto-expanding collections as JDBC parameters
(本文先讨论 Hibernate,然后再讨论 JDBC。)
【讨论】:
【参考方案7】:看到我的试用成功了,据说列表大小有潜在的限制。 列表 l = Arrays.asList(new Integer[]12496,12497,12498,12499); 地图参数 = 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);
【讨论】:
【参考方案8】:我们可以使用不同的替代方法。
-
执行单个查询 - 速度慢且不推荐
使用存储过程 - 特定于数据库
动态创建 PreparedStatement 查询 - 性能良好,但缓存的好处松散,需要重新编译
在 PreparedStatement 查询中使用 NULL - 我认为这是一种具有最佳性能的好方法。
查看有关这些here 的更多详细信息。
【讨论】:
【参考方案9】:sormula 让这一切变得简单(参见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());
【讨论】:
【参考方案10】:我能想到的一种方法是使用 java.sql.PreparedStatement 和一些陪审团操纵
PreparedStatement PreparedStmt = conn.prepareStatement("SELECT * FROM MYTABLE WHERE MYCOL in (?)");
...然后...
preparedStmt.setString(1, [你的字符串参数]);
http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html
【讨论】:
这将不起作用,因为它可能会创建像... WHERE MYCOL IN ('2,3,5,6')
这样的查询,而这不是您想要做的。以上是关于使用 JDBC 参数化 IN 子句的最佳方法是啥? [复制]的主要内容,如果未能解决你的问题,请参考以下文章