Chr*_*ola 328 java security jdbc prepared-statement in-clause
将SQL IN子句与实例一起使用的最佳解决方法java.sql.PreparedStatement是什么,由于SQL注入攻击安全问题,多个值不支持这种解决方法:一个?占位符代表一个值,而不是值列表.
请考虑以下SQL语句:
SELECT my_column FROM my_table where search_column IN (?)
Run Code Online (Sandbox Code Playgroud)
使用preparedStatement.setString( 1, "'A', 'B', 'C'" );本质上是一种非工作尝试,?首先使用的原因是解决方法.
有哪些变通方法?
Dón*_*nal 186
此处提供了对各种可用选项的分析,以及各自的优缺点.
建议的选项是:
SELECT my_column FROM my_table WHERE search_column = ?,为每个值执行它,并在结果客户端UNION.只需要一份准备好的声明.缓慢而痛苦.SELECT my_column FROM my_table WHERE search_column IN (?,?,?)并执行它.每个IN-list大小需要一个准备好的语句.快速而明显.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...并执行它.[或者用来UNION ALL代替那些分号. - 每个大小的IN列表需要一个准备好的语句.愚蠢的慢,严格的比WHERE search_column IN (?,?,?),所以我不知道为什么博客甚至建议它.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6).任何体面的服务器都会在运行查询之前优化重复值.但是,这些选项都不是超级棒.
在这些地方已经回答了重复的问题,同样有理智的选择,但仍然没有一个非常棒:
正确的答案,如果您使用的是JDBC4和支持的服务器,请按照此处的说明x = ANY(y)使用PreparedStatement.setArray:
但是,似乎没有任何方法可以setArray使用IN列表.
有时,SQL语句在运行时加载(例如,从属性文件中),但需要可变数量的参数.在这种情况下,首先定义查询:
query=SELECT * FROM table t WHERE t.column IN (?)
Run Code Online (Sandbox Code Playgroud)
接下来,加载查询.然后在运行之前确定参数的数量.参数计数一旦知道,运行:
sql = any( sql, count );
Run Code Online (Sandbox Code Playgroud)
例如:
/**
* Converts a SQL statement containing exactly one IN clause to an IN clause
* using multiple comma-delimited parameters.
*
* @param sql The SQL statement string with one IN clause.
* @param params The number of parameters the SQL statement requires.
* @return The SQL statement with (?) replaced with multiple parameter
* placeholders.
*/
public static String any(String sql, final int params) {
// Create a comma-delimited list based on the number of parameters.
final StringBuilder sb = new StringBuilder(
new String(new char[params]).replace("\0", "?,")
);
// Remove trailing comma.
sb.setLength(Math.max(sb.length() - 1, 0));
// For more than 1 parameter, replace the single parameter with
// multiple parameter placeholders.
if (sb.length() > 1) {
sql = sql.replace("(?)", "(" + sb + ")");
}
// Return the modified comma-delimited list of parameters.
return sql;
}
Run Code Online (Sandbox Code Playgroud)
对于某些通过JDBC 4规范传递数组的数据库不受支持,此方法可以帮助将慢速= ?转换为更快的IN (?)子句条件,然后可以通过调用该any方法进行扩展.
Bor*_*ris 117
PostgreSQL的解决方案:
final PreparedStatement statement = connection.prepareStatement(
"SELECT my_column FROM my_table where search_column = ANY (?)"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
while(rs.next()) {
// do some...
}
} finally {
rs.close();
}
Run Code Online (Sandbox Code Playgroud)
要么
final PreparedStatement statement = connection.prepareStatement(
"SELECT my_column FROM my_table " +
"where search_column IN (SELECT * FROM unnest(?))"
);
final String[] values = getValues();
statement.setArray(1, connection.createArrayOf("text", values));
final ResultSet rs = statement.executeQuery();
try {
while(rs.next()) {
// do some...
}
} finally {
rs.close();
}
Run Code Online (Sandbox Code Playgroud)
Vla*_*hev 18
没有简单的方法AFAIK.如果目标是保持语句高速缓存比率高(即不为每个参数计数创建语句),则可以执行以下操作:
使用一些(例如10个)参数创建一个语句:
......在哪里(?,?,?,?,?,?,?,?,?,?)......
绑定所有实际参数
的SetString(1, "富"); 的SetString(2, "巴");
将其余部分绑定为NULL
setNull(3,Types.VARCHAR)... setNull(10,Types.VARCHAR)
NULL永远不会匹配任何内容,因此SQL计划构建器会对其进行优化.
将List传递给DAO函数时,逻辑很容易自动化:
while( i < param.size() ) {
ps.setString(i+1,param.get(i));
i++;
}
while( i < MAX_PARAMS ) {
ps.setNull(i+1,Types.VARCHAR);
i++;
}
Run Code Online (Sandbox Code Playgroud)
Gur*_*ngh 12
您可以使用Collections.nCopies生成占位符集合并使用String.join以下方法加入它们:
List<String> params = getParams();
String placeHolders = String.join(",", Collections.nCopies(params.size(), "?"));
String sql = "select * from your_table where some_column in (" + placeHolders + ")";
try ( Connection connection = getConnection();
PreparedStatement ps = connection.prepareStatement(sql)) {
int i = 1;
for (String param : params) {
ps.setString(i++, param);
}
/*
* Execute query/do stuff
*/
}
Run Code Online (Sandbox Code Playgroud)
Jam*_*hek 10
一个令人不快的解决方法,但肯定是可行的是使用嵌套查询.创建一个临时表MYVALUES,其中包含一列.将值列表插入MYVALUES表.然后执行
select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )
Run Code Online (Sandbox Code Playgroud)
丑陋,但如果您的值列表非常大,那么这是一个可行的选择.
这种技术的另一个优点是,如果数据库不缓存预准备语句,优化器可能会提供更好的查询计划(检查页面是否存在多个值,表格只扫描一次,而不是每个值一次,等等)可以节省开销.您的"插入"需要批量完成,并且可能需要调整MYVALUES表以获得最小的锁定或其他高开销保护.
它适用于琐碎的情况,您可以通过"自动生成准备好的语句"来扩展它,但它始终有其限制.
对于某些情况,in()方法可以很好,但不能防火箭:)
防火解决方案是在单独的调用中传递任意数量的参数(例如,通过传递一些参数),然后使用视图(或任何其他方式)在SQL中表示它们并在您的位置使用标准.
这里有一个蛮力变体http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
但是,如果你可以使用PL/SQL,这个烂摊子会变得非常整洁.
function getCustomers(in_customerIdList clob) return sys_refcursor is
begin
aux_in_list.parse(in_customerIdList);
open res for
select *
from customer c,
in_list v
where c.customer_id=v.token;
return res;
end;
Run Code Online (Sandbox Code Playgroud)
然后,您可以在参数中传递任意数量的逗号分隔客户ID,并且:
这里的诀窍是:
视图看起来像:
create or replace view in_list
as
select
trim( substr (txt,
instr (txt, ',', 1, level ) + 1,
instr (txt, ',', 1, level+1)
- instr (txt, ',', 1, level) -1 ) ) as token
from (select ','||aux_in_list.getpayload||',' txt from dual)
connect by level <= length(aux_in_list.getpayload)-length(replace(aux_in_list.getpayload,',',''))+1
Run Code Online (Sandbox Code Playgroud)
其中aux_in_list.getpayload引用原始输入字符串.
一种可能的方法是传递pl/sql数组(仅由Oracle支持),但是您不能在纯SQL中使用它们,因此总是需要转换步骤.转换不能在SQL中完成,因此,毕竟传递带有所有参数的clob并在视图中转换它是最有效的解决方案.
小智 6
这是我在自己的应用程序中解决它的方式.理想情况下,您应该使用StringBuilder而不是使用+来表示字符串.
String inParenthesis = "(?";
for(int i = 1;i < myList.size();i++) {
inParenthesis += ", ?";
}
inParenthesis += ")";
try(PreparedStatement statement = SQLite.connection.prepareStatement(
String.format("UPDATE table SET value='WINNER' WHERE startTime=? AND name=? AND traderIdx=? AND someValue IN %s", inParenthesis))) {
int x = 1;
statement.setLong(x++, race.startTime);
statement.setString(x++, race.name);
statement.setInt(x++, traderIdx);
for(String str : race.betFair.winners) {
statement.setString(x++, str);
}
int effected = statement.executeUpdate();
}
Run Code Online (Sandbox Code Playgroud)
如果您决定稍后更改查询,则使用上面的x等变量而不是具体数字会有很大帮助.
我从来没有尝试过,但是.setArray()会做你想要的吗?
更新:显然不是.setArray似乎只适用于来自您从先前查询检索到的ARRAY列的java.sql.Array,或者带有ARRAY列的子查询.
小智 5
我的解决方法是:
create or replace type split_tbl as table of varchar(32767);
/
create or replace function split
(
p_list varchar2,
p_del varchar2 := ','
) return split_tbl pipelined
is
l_idx pls_integer;
l_list varchar2(32767) := p_list;
l_value varchar2(32767);
begin
loop
l_idx := instr(l_list,p_del);
if l_idx > 0 then
pipe row(substr(l_list,1,l_idx-1));
l_list := substr(l_list,l_idx+length(p_del));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end split;
/
Run Code Online (Sandbox Code Playgroud)
现在,您可以使用一个变量来获取表中的某些值:
select * from table(split('one,two,three'))
one
two
three
select * from TABLE1 where COL1 in (select * from table(split('value1,value2')))
value1 AAA
value2 BBB
Run Code Online (Sandbox Code Playgroud)
因此,准备好的声明可以是:
"select * from TABLE where COL in (select * from table(split(?)))"
Run Code Online (Sandbox Code Playgroud)
问候,
哈维尔·伊瓦涅斯
| 归档时间: |
|
| 查看次数: |
306817 次 |
| 最近记录: |