statement query prepared example java security jdbc prepared-statement in-clause

java - query - ¿PreparedStatement IN cláusula alternativas?



java prepared statement in clause example (27)

Las limitaciones del operador in () es la raíz de todo mal.

Funciona para casos triviales, y puede ampliarlo con la "generación automática de la declaración preparada", sin embargo, siempre tiene sus límites.

  • Si está creando una declaración con un número variable de parámetros, eso hará que una sobrecarga de análisis de SQL en cada llamada
  • en muchas plataformas, el número de parámetros del operador in () es limitado
  • en todas las plataformas, el tamaño total del texto SQL es limitado, lo que hace imposible enviar 2000 marcadores de posición para los parámetros en
  • no es posible enviar variables de enlace de 1000-10k, ya que el controlador JDBC tiene sus limitaciones

El enfoque in () puede ser lo suficientemente bueno para algunos casos, pero no a prueba de cohetes :)

La solución a prueba de cohetes es pasar el número arbitrario de parámetros en una llamada separada (pasando un grupo de parámetros, por ejemplo), y luego tener una vista (o cualquier otra forma) para representarlos en SQL y usarlos en su ubicación. criterios

Una variante de fuerza bruta está aquí http://tkyte.blogspot.hu/2006/06/varying-in-lists.html

Sin embargo, si puede usar PL / SQL, este desorden puede volverse bastante limpio.

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;

Luego puede pasar un número arbitrario de ID de clientes separados por comas en el parámetro, y:

  • no obtendrá ningún retraso de análisis, ya que el SQL para seleccionar es estable
  • no hay complejidad de funciones canalizadas - es solo una consulta
  • el SQL está utilizando una combinación simple, en lugar de un operador IN, que es bastante rápido
  • después de todo, es una buena regla general para no acceder a la base de datos con cualquier selección simple o DML, ya que es Oracle, que ofrece años luz de más de MySQL o motores de bases de datos simples similares. PL / SQL le permite ocultar el modelo de almacenamiento de su modelo de dominio de aplicación de una manera efectiva.

El truco aquí es:

  • necesitamos una llamada que acepte la cadena larga, y almacenar en algún lugar donde la sesión db pueda acceder a ella (por ejemplo, variable de paquete simple, o dbms_session.set_context)
  • entonces necesitamos una vista que pueda analizar esto en filas
  • y luego tiene una vista que contiene los identificadores que está consultando, por lo que todo lo que necesita es una simple unión a la tabla consultada.

La vista se ve como:

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

donde aux_in_list.getpayload se refiere a la cadena de entrada original.

Un posible enfoque sería pasar matrices pl / sql (soportadas solo por Oracle), sin embargo, no puede usarlas en SQL puro, por lo tanto, siempre es necesario un paso de conversión. La conversión no se puede hacer en SQL, por lo que, después de todo, pasar un clob con todos los parámetros en cadena y convertirlo en una vista es la solución más eficiente.

¿Cuáles son las mejores soluciones para usar una cláusula SQL IN con instancias de java.sql.PreparedStatement , que no es compatible con múltiples valores debido a problemas de seguridad de ataque de inyección SQL: uno ? el marcador de posición representa un valor, en lugar de una lista de valores.

Considere la siguiente declaración SQL:

SELECT my_column FROM my_table where search_column IN (?)

Utilizando preparedStatement.setString( 1, "''A'', ''B'', ''C''" ); ¿Es esencialmente un intento de no trabajar en una solución de las razones para usar ? en primer lugar.

¿Qué soluciones están disponibles?


Aquí hay una solución completa en Java para crear la declaración preparada para usted:

/*usage: Util u = new Util(500); //500 items per bracket. String sqlBefore = "select * from myTable where ("; List<Integer> values = new ArrayList<Integer>(Arrays.asList(1,2,4,5)); string sqlAfter = ") and foo = ''bar''"; PreparedStatement ps = u.prepareStatements(sqlBefore, values, sqlAfter, connection, "someId"); */ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Util { private int numValuesInClause; public Util(int numValuesInClause) { super(); this.numValuesInClause = numValuesInClause; } public int getNumValuesInClause() { return numValuesInClause; } public void setNumValuesInClause(int numValuesInClause) { this.numValuesInClause = numValuesInClause; } /** Split a given list into a list of lists for the given size of numValuesInClause*/ public List<List<Integer>> splitList( List<Integer> values) { List<List<Integer>> newList = new ArrayList<List<Integer>>(); while (values.size() > numValuesInClause) { List<Integer> sublist = values.subList(0,numValuesInClause); List<Integer> values2 = values.subList(numValuesInClause, values.size()); values = values2; newList.add( sublist); } newList.add(values); return newList; } /** * Generates a series of split out in clause statements. * @param sqlBefore ""select * from dual where (" * @param values [1,2,3,4,5,6,7,8,9,10] * @param "sqlAfter ) and id = 5" * @return "select * from dual where (id in (1,2,3) or id in (4,5,6) or id in (7,8,9) or id in (10)" */ public String genInClauseSql(String sqlBefore, List<Integer> values, String sqlAfter, String identifier) { List<List<Integer>> newLists = splitList(values); String stmt = sqlBefore; /* now generate the in clause for each list */ int j = 0; /* keep track of list:newLists index */ for (List<Integer> list : newLists) { stmt = stmt + identifier +" in ("; StringBuilder innerBuilder = new StringBuilder(); for (int i = 0; i < list.size(); i++) { innerBuilder.append("?,"); } String inClause = innerBuilder.deleteCharAt( innerBuilder.length() - 1).toString(); stmt = stmt + inClause; stmt = stmt + ")"; if (++j < newLists.size()) { stmt = stmt + " OR "; } } stmt = stmt + sqlAfter; return stmt; } /** * Method to convert your SQL and a list of ID into a safe prepared * statements * * @throws SQLException */ public PreparedStatement prepareStatements(String sqlBefore, ArrayList<Integer> values, String sqlAfter, Connection c, String identifier) throws SQLException { /* First split our potentially big list into lots of lists */ String stmt = genInClauseSql(sqlBefore, values, sqlAfter, identifier); PreparedStatement ps = c.prepareStatement(stmt); int i = 1; for (int val : values) { ps.setInt(i++, val); } return ps; } }


Así es como lo resolví en mi propia aplicación. Idealmente, deberías usar un StringBuilder en lugar de usar + para Cadenas.

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(); }

Usar una variable como x arriba en lugar de números concretos ayuda mucho si decides cambiar la consulta más adelante.


Después de examinar varias soluciones en diferentes foros y no encontrar una buena solución, siento que el truco que aparece a continuación es el más fácil de seguir y codifica:

Ejemplo: Supongamos que tiene varios parámetros para pasar en la cláusula ''IN''. Simplemente coloque una cadena ficticia dentro de la cláusula ''IN'', por ejemplo, "PARAM" denota la lista de parámetros que vendrán en lugar de esta cadena ficticia.

select * from TABLE_A where ATTR IN (PARAM);

Puede recopilar todos los parámetros en una única variable de cadena en su código Java. Esto puede hacerse de la siguiente manera:

String param1 = "X"; String param2 = "Y"; String param1 = param1.append(",").append(param2);

Puede anexar todos sus parámetros separados por comas en una sola variable de cadena, ''param1'', en nuestro caso.

Después de recopilar todos los parámetros en una sola Cadena, simplemente puede reemplazar el texto de relleno en su consulta, es decir, "PARAM" en este caso, con el parámetro Cadena, es decir, param1. Aquí está lo que tú necesitas hacer:

String query = query.replaceFirst("PARAM",param1); where we have the value of query as query = "select * from TABLE_A where ATTR IN (PARAM)";

Ahora puede ejecutar su consulta utilizando el método executeQuery (). Solo asegúrese de no tener la palabra "PARAM" en su consulta en ninguna parte. Puede usar una combinación de caracteres especiales y alfabetos en lugar de la palabra "PARAM" para asegurarse de que no haya ninguna posibilidad de que esa palabra aparezca en la consulta. Espero que tengas la solución.

Nota: Aunque esta no es una consulta preparada, hace el trabajo que quería que hiciera mi código.


En lugar de usar

SELECT my_column FROM my_table where search_column IN (?)

utilizar la declaración de SQL como

select id, name from users where id in (?, ?, ?)

y

preparedStatement.setString( 1, ''A''); preparedStatement.setString( 2,''B''); preparedStatement.setString( 3, ''C'');

o utilice un procedimiento almacenado, esta sería la mejor solución, ya que las sentencias de SQL se compilarán y almacenarán en el servidor DataBase.


Existen diferentes enfoques alternativos que podemos usar para la cláusula IN en PreparedStatement.

  1. Uso de consultas individuales: rendimiento más lento y uso intensivo de recursos
  2. Uso de StoredProcedure: el más rápido pero específico de la base de datos
  3. Creación de consultas dinámicas para PreparedStatement: buen rendimiento, pero no se beneficia del almacenamiento en caché y PreparedStatement se compila cada vez.
  4. Use NULL en las consultas de PreparedStatement. El rendimiento óptimo funciona muy bien cuando conoce el límite de los argumentos de la cláusula IN. Si no hay límite, entonces puede ejecutar consultas en lote. Fragmento de código de ejemplo es;

    int i = 1; for(; i <=ids.length; i++){ ps.setInt(i, ids[i-1]); } //set null for remaining ones for(; i<=PARAM_SIZE;i++){ ps.setNull(i, java.sql.Types.INTEGER); }

Puede consultar más detalles sobre estos enfoques alternativos here .


Genere la cadena de consulta en PreparedStatement para que un número de? Coincida con el número de elementos en su lista. Aquí hay un ejemplo:

public void myQuery(List<String> items, int other) { ... String q4in = generateQsForIn(items.size()); String sql = "select * from stuff where foo in ( " + q4in + " ) and bar = ?"; PreparedStatement ps = connection.prepareStatement(sql); int i = 1; for (String item : items) { ps.setString(i++, item); } ps.setInt(i++, other); ResultSet rs = ps.executeQuery(); ... } private String generateQsForIn(int numQs) { String items = ""; for (int i = 0; i < numQs; i++) { if (i != 0) items += ", "; items += "?"; } return items; }


Me encontré con una serie de limitaciones relacionadas con la declaración preparada:

  1. Las declaraciones preparadas se almacenan en caché solo dentro de la misma sesión (Postgres), por lo que realmente funcionará solo con la agrupación de conexiones
  2. Una gran cantidad de declaraciones preparadas según lo propuesto por @BalusC puede hacer que la memoria caché se llene en exceso y se eliminarán las declaraciones previamente almacenadas en caché
  3. La consulta debe ser optimizada y usar índices. Suena obvio, sin embargo, por ejemplo, la declaración ANY (ARRAY ...) propuesta por @Boris en una de las mejores respuestas no puede usar índices y la consulta será lenta a pesar del almacenamiento en caché
  4. La declaración preparada también almacena en caché el plan de consulta y los valores reales de los parámetros especificados en la declaración no están disponibles.

Entre las soluciones propuestas, elegiría la que no disminuya el rendimiento de las consultas y reduzca el número de consultas. Este será el # 4 (agrupando pocas consultas) desde el enlace @Don o especificando valores NULL para ''?'' Innecesarios? marcas según lo propuesto por @Vladimir Dyuzhev


Mi solución es:

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; /

Ahora puedes usar una variable para obtener algunos valores en una tabla:

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

Entonces, la declaración preparada podría ser:

"select * from TABLE where COL in (select * from table(split(?)))"

Saludos,

Javier ibáñez


No hay forma simple de AFAIK. Si el objetivo es mantener alta la proporción de caché de sentencias (es decir, no crear una sentencia por cada recuento de parámetros), puede hacer lo siguiente:

  1. crear una declaración con unos pocos parámetros (por ejemplo, 10):

    ... ¿DONDE ESTÁ EN (?,?,?,?,?,?,?,?,?,?) ...

  2. Enlazar todos los parametros de actuall

    setString (1, "foo"); setString (2, "bar");

  3. Enlazar el resto como NULL

    setNull (3, Types.VARCHAR) ... setNull (10, Types.VARCHAR)

NULL nunca coincide con nada, por lo que el generador de planes SQL lo optimiza.

La lógica es fácil de automatizar cuando se pasa una Lista a una función DAO:

while( i < param.size() ) { ps.setString(i+1,param.get(i)); i++; } while( i < MAX_PARAMS ) { ps.setNull(i+1,Types.VARCHAR); i++; }


Nunca lo he intentado, pero .setArray () haría lo que estás buscando?

Actualización : Evidentemente no. Parece que setArray solo funciona con un java.sql.Array que proviene de una columna ARRAY que ha recuperado de una consulta anterior, o una subconsulta con una columna ARRAY.


Para algunas situaciones, la expresión regular puede ayudar. Aquí hay un ejemplo que he comprobado en Oracle, y funciona.

select * from my_table where REGEXP_LIKE (search_column, ''value1|value2'')

Pero hay una serie de inconvenientes con él:

  1. Cualquier columna que aplique debe convertirse a varchar / char, al menos implícitamente.
  2. Hay que tener cuidado con los caracteres especiales.
  3. Puede ralentizar el rendimiento; en mi caso, la versión IN utiliza el índice y la exploración de rango, y la versión REGEXP realiza la exploración completa.

Podría usar el método setArray como se menciona en este javadoc :

PreparedStatement statement = connection.prepareStatement("Select * from emp where field in (?)"); Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"E1", "E2","E3"}); statement.setArray(1, array); ResultSet rs = statement.executeQuery();


PreparedStatement no proporciona ninguna buena manera de lidiar con la cláusula SQL IN. Según here "No puede sustituir las cosas que están destinadas a formar parte de la declaración SQL. Esto es necesario porque si el propio SQL puede cambiar, el el controlador no puede precompilar la declaración. También tiene el efecto secundario agradable de prevenir los ataques de inyección SQL ". Terminé usando el siguiente enfoque:

String query = "SELECT my_column FROM my_table where search_column IN ($searchColumns)"; query = query.replace("$searchColumns", "''A'', ''B'', ''C''"); Statement stmt = connection.createStatement(); boolean hasResults = stmt.execute(query); do { if (hasResults) return stmt.getResultSet(); hasResults = stmt.getMoreResults(); } while (hasResults || stmt.getUpdateCount() != -1);


SetArray es la mejor solución pero no está disponible para muchos controladores antiguos. La siguiente solución puede usarse en java8

String baseQuery ="SELECT my_column FROM my_table where search_column IN (%s)" String markersString = inputArray.stream().map(e -> "?").collect(joining(",")); String sqlQuery = String.format(baseSQL, markersString); //Now create Prepared Statement and use loop to Set entries int index=1; for (String input : inputArray) { preparedStatement.setString(index++, input); }

Esta solución es mejor que otras soluciones de bucle feo donde la cadena de consulta se construye mediante iteraciones manuales


Siguiendo la idea de Adán. Haga que su declaración preparada seleccione my_column desde my_table donde search_column en (#) Cree una cadena x y llénela con un número de "?,?,?" dependiendo de su lista de valores Luego, simplemente cambie el # en la consulta para su nueva Cadena x una población


Solo para estar completo y porque no vi a nadie más sugerirlo:

Antes de implementar cualquiera de las sugerencias complicadas anteriores, considere si la inyección de SQL es realmente un problema en su escenario.

En muchos casos, el valor proporcionado a IN (...) es una lista de identificadores que se han generado de manera que puede estar seguro de que no es posible la inyección ... (por ejemplo, los resultados de una selección anterior some_id from some_table where alguna condicion.

Si ese es el caso, simplemente puede concatenar este valor y no usar los servicios o la declaración preparada para ello o usarlos para otros parámetros de esta consulta.

query="select f1,f2 from t1 where f3=? and f2 in (" + sListOfIds + ");";


Solo para estar completo: siempre que el conjunto de valores no sea demasiado grande, también podría simplemente construir una declaración como string

... WHERE tab.col = ? OR tab.col = ? OR tab.col = ?

que luego podría pasar a prepare (), y luego usar setXXX () en un bucle para establecer todos los valores. Esto parece asqueroso, pero muchos sistemas comerciales "grandes" hacen este tipo de cosas de manera rutinaria hasta que alcanzan los límites específicos de la base de datos, como 32 KB (creo que lo es) para las declaraciones en Oracle.

Por supuesto, debe asegurarse de que el conjunto nunca será excesivamente grande, o realizar una captura de errores en caso de que lo sea.


Solución para 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(); }

o

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(); }


Spring permite pasar java.util.Lists a NamedParameterJdbcTemplate , que automatiza la generación de (?,?,?, ...,?), Según corresponda para el número de argumentos.

Para Oracle, esta publicación de blog analiza el uso de oracle.sql.ARRAY (Connection.createArrayOf no funciona con Oracle). Para esto tienes que modificar tu sentencia SQL:

SELECT my_column FROM my_table where search_column IN (select COLUMN_VALUE from table(?))

La función de tabla de Oracle transforma la matriz pasada en una tabla como valor utilizable en la instrucción IN .


Supongo que podría (utilizando la manipulación básica de cadenas) generar la cadena de consulta en la PreparedStatement para tener un número de ? ''s que coincide con el número de elementos en su lista.

Por supuesto, si estás haciendo eso, estás a un paso de generar un OR encadenado gigante en tu consulta, pero sin tener el número correcto de ? en la cadena de consulta, no veo de qué otra manera puedes solucionar esto.


Un análisis de las diversas opciones disponibles, y los pros y los contras de cada una está disponible here .

Las opciones sugeridas son:

  • Prepare SELECT my_column FROM my_table WHERE search_column = ? , ejecútelo para cada valor y UNION los resultados del lado del cliente. Requiere solo una declaración preparada. Lento y doloroso.
  • Prepare SELECT my_column FROM my_table WHERE search_column IN (?,?,?) Y ejecútelo. Requiere una declaración preparada por tamaño de lista. Rápido y obvio.
  • Prepare SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ... y ejecutarlo. [O usa UNION ALL en lugar de esos puntos y comas. --ed] Requiere una declaración preparada por tamaño de lista IN. Estúpidamente lento, estrictamente peor que WHERE search_column IN (?,?,?) , Así que no sé por qué el blogger lo sugirió.
  • Utilice un procedimiento almacenado para construir el conjunto de resultados.
  • Prepare N diferentes consultas de tamaño de lista; Digamos, con 2, 10 y 50 valores. Para buscar una lista de entrada con 6 valores diferentes, SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6) consulta de tamaño 10 para que se vea como SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6) . Cualquier servidor decente optimizará los valores duplicados antes de ejecutar la consulta.

Sin embargo, ninguna de estas opciones es super grande.

Se han respondido preguntas duplicadas en estos lugares con alternativas igualmente sensatas, pero ninguna de ellas es muy buena:

La respuesta correcta, si está utilizando JDBC4 y un servidor que admite x = ANY(y) , es usar PreparedStatement.setArray como se describe aquí:

Sin embargo, no parece haber ninguna manera de hacer que setArray funcione con IN-lists.


Una solución alternativa desagradable, pero ciertamente factible es utilizar una consulta anidada. Crea una tabla temporal MYVALUES con una columna en ella. Inserte su lista de valores en la tabla MYVALUES. Entonces ejecuta

select my_column from my_table where search_column in ( SELECT value FROM MYVALUES )

Feo, pero una alternativa viable si su lista de valores es muy grande.

Esta técnica tiene la ventaja adicional de que los planes de consulta mejorados del optimizador (compruebe una página para varios valores, las tablas solo pueden ser una vez por valor, etc.) pueden ahorrar en la sobrecarga si su base de datos no almacena en antemano las declaraciones preparadas. Sus "INSERTOS" deberían realizarse por lotes y la tabla MALVALUES puede necesitar ser modificada para tener un bloqueo mínimo u otras protecciones de alta sobrecarga.


intenta usar la función instr?

select my_column from my_table where instr(?, '',''||search_column||'','') > 0

entonces

ps.setString(1, ",A,B,C,");

Es cierto que esto es un poco sucio, pero reduce las oportunidades de inyección de SQL. Funciona en el oráculo de todos modos.


Sormula admite el operador SQL IN permitiéndole proporcionar un objeto java.util.Collection como parámetro. Se crea una declaración preparada con un? Para cada uno de los elementos de la colección. Vea el Ejemplo 4 (en el ejemplo, SQL es un comentario para aclarar lo que se crea pero no es utilizado por Sormula).


Mi solución (JavaScript)

var s1 = " SELECT " + "FROM table t " + " where t.field in "; var s3 = ''(''; for(var i =0;i<searchTerms.length;i++) { if(i+1 == searchTerms.length) { s3 = s3+''?)''; } else { s3 = s3+''?, '' ; } } var query = s1+s3; var pstmt = connection.prepareStatement(query); for(var i =0;i<searchTerms.length;i++) { pstmt.setString(i+1, searchTerms[i]); }

SearchTerms es la matriz que contiene su entrada / claves / campos, etc.


Puede utilizar Collections.nCopiespara generar una colección de marcadores de posición y unirse a ellos utilizando 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 */ }