Especificar información de tipo SQL para parámetros

Normalmente, Spring determina el tipo SQL de parámetros según el tipo de parámetro pasado. Puede especificar explícitamente el tipo de SQL que se utilizará al configurar los valores de los parámetros. A veces, esto es necesario para establecer los valores NULL correctamente.

Puede especificar información de tipo SQL de varias maneras:

  • Muchas Los métodos de actualización y consulta JdbcTemplate aceptan un parámetro adicional en forma de matriz int. Esta matriz se utiliza para especificar el tipo SQL del parámetro correspondiente utilizando valores constantes de la clase java.sql.Types. Especifique una entrada para cada parámetro.

  • Puede utilizar la clase SqlParameterValue para ajustar el valor de un parámetro que requiere esta información adicional. Para hacer esto, cree una nueva instancia para cada valor y pase el tipo SQL y el valor del parámetro en el constructor. También puede especificar un parámetro de escala opcional para valores numéricos.

  • Para métodos que funcionan con parámetros con nombre, puede usar las clases SqlParameterSource, BeanPropertySqlParameterSource o MapSqlParameterSource. Ambos tienen métodos para registrar el tipo SQL para cualquiera de los valores de parámetros nombrados.

Manejo de objetos BLOB y CLOB

La base de datos puede almacenar imágenes, otros datos binarios y grandes fragmentos de texto. En el caso de datos binarios, estos objetos de gran tamaño se denominan objetos grandes binarios, o abreviadamente BLOB (Binary Large OBject), y objetos grandes de caracteres, o CLOB (Character Large OBject), en el caso de datos de caracteres. En Spring, puede trabajar con objetos tan grandes usando JdbcTemplate directamente, así como usando abstracciones de nivel superior proporcionadas por objetos RDBMS y clases SimpleJdbc. Todos estos enfoques utilizan una implementación de la interfaz LobHandler para gestionar realmente los datos LOB (Large OBject). LobHandler proporciona acceso a la clase LobCreator a través del método getLobCreator, que se utiliza para crear nuevos objetos LOB para su inserción.

LobCreator y LobHandler proporcionan el siguiente soporte para entrada y salida LOB:

  • BLOB

    • byte[]: getBlobAsBytes y setBlobAsBytes

    • InputStream: getBlobAsBinaryStream y setBlobAsBinaryStream

  • CLOB

    • String: getClobAsString y setClobAsString

    • InputStream: getClobAsAsciiStream y setClobAsAsciiStream

    • Lector: getClobAsCharacterStream y setClobAsCharacterStream

El siguiente ejemplo muestra cómo crear e insertar un BLOB. Más adelante demostraremos cómo volver a leerlo desde la base de datos.

Este ejemplo utiliza JdbcTemplate y la implementación AbstractLobCreatingPreparedStatementCallback. Implementa un método, setValues. Este método proporciona un LobCreator que se utiliza para establecer valores para las columnas LOB en la instrucción de inserción SQL.

En este ejemplo, asumimos que la variable lobHandler existe, que ya está configurado para la instancia DefaultLobHandler. Normalmente, este valor se establece mediante inyección de dependencia.

El siguiente ejemplo muestra cómo crear e insertar un BLOB:

Java
final File blobIn = nuevo archivo("primavera2004.jpg"); final InputStream blobIs = nuevo FileInputStream(blobIn); Archivo final clobIn = nuevo archivo("large.txt"); final InputStream clobIs = nuevo FileInputStream(clobIn); final InputStreamReader clobReader = nuevo InputStreamReader(clobIs); jdbcTemplate.execute( "INSERTAR EN lob_table (id, a_clob, a_blob) VALORES (?, ?, ?)", nuevo AbstractLobCreatingPreparedStatementCallback(lobHandler) {  setValues vacío protegido(PreparedStatement ps, LobCreator lobCreator) lanza SQLException { ps.setLong(1, 1L); lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length());  lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length());  } } ); blobIs.close(); clobReader.close();
  1. Pasamos lobHandler, que (en este ejemplo) es un DefaultLobHandler normal.
  2. Uso del método setClobAsCharacterStream para pasar el contenido del CLOB.
  3. Usar el método setBlobAsBinaryStream para pasar el contenido de un BLOB.
Kotlin
val blobIn = File("spring2004. jpg") val blobIs = FileInputStream(blobIn) val clobIn = File("large.txt") val clobIs = FileInputStream(clobIn) val clobReader = InputStreamReader(clobIs) jdbcTemplate.execute( "INSERT INTO lob_table (id, a_clob, a_blob) VALORES (?, ?, ?)", objeto: AbstractLobCreatingPreparedStatementCallback(lobHandler) {  anular fun setValues(ps: PreparedStatement, lobCreator: LobCreator) { ps.setLong(1, 1L) lobCreator.setClobAsCharacterStream(ps, 2, clobReader, clobIn.length().toInt())  lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, blobIn .length().toInt())  } } ) blobIs.close() clobReader .close()
  1. Pasamos lobHandler, que (en este ejemplo) es el habitual DefaultLobHandler .
  2. Usar el método setClobAsCharacterStream para pasar el contenido de un CLOB.
  3. Usar el setBlobAsBinaryStream método para pasar el contenido de un BLOB.

Si llama al método setBlobAsBinaryStream, setClobAsAsciiStream o setClobAsCharacterStream Para el LobCreator devuelto por DefaultLobHandler.getLobCreator(), usted Opcionalmente, puede establecer el argumento contentLength en un valor negativo. Si la longitud del contenido especificada es negativa, DefaultLobHandler utiliza variantes de los métodos de configuración de subprocesos JDBC 4.0 sin un parámetro de longitud. De lo contrario, pasa la longitud especificada al controlador.

Consulte la documentación del controlador JDBC que está utilizando para asegurarse de que admita la transmisión LOB sin especificar la longitud del contenido.

Ahora es el momento de leer los datos LOB de la base de datos. Nuevamente, JdbcTemplate se usa con la misma variable de instancia lobHandler y una referencia a DefaultLobHandler. El siguiente ejemplo muestra cómo hacer esto:

Java
Lista<Mapa<Cadena, Objeto>> l = jdbcTemplate.query("seleccione id, a_clob, a_blob de lob_table", new RowMapper<Map<String, Object>>() { public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException { Map< ;Cadena, Objeto> resultados = nuevo HashMap<Cadena, Objeto>(); Cadena clobText = lobHandler.getClobAsString(rs, "a_clob");  resultados.put("CLOB", clobText); byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob");  results.put("BLOB", blobBytes); devolver resultados; } });
  1. Uso Método getClobAsString para obtener el contenido de un CLOB.
  2. Utilice el método getBlobAsBytes para obtener el contenido de un BLOB.
Kotlin
val l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table") { rs, _ -> val clobText = lobHandler.getClobAsString(rs, "a_clob")  val blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob")  mapOf("CLOB" a clobText, "BLOB" a blobBytes) }
  1. Usando el método getClobAsString para obtener el contenido de un CLOB.
  2. Usando el getBlobAsBytes método para obtener el contenido de un BLOB.

Pasar listas de valores para la expresión "IN"

El estándar SQL le permite seleccionar filas basándose en un Expresión que incluye una lista de valores para una variable. Un ejemplo típico es select * from T_ACTOR donde id está en (1, 2, 3). Esta lista de variables no está directamente respaldada por el estándar JDBC para declaraciones preparadas. Puede declarar un número variable de marcadores de posición. Debe preparar varias opciones con la cantidad requerida de marcadores de posición, o debe generar la cadena SQL dinámicamente una vez que sepa cuántos marcadores de posición se requieren. El soporte de parámetros con nombre proporcionado en NamedParameterJdbcTemplate y JdbcTemplate adopta el último enfoque. Puede pasar valores como una lista de objetos primitivos java.util.List. Esta lista se utiliza para insertar los marcadores de posición necesarios y pasar valores durante la ejecución de la declaración.

Tenga cuidado al pasar una gran cantidad de valores. El estándar JDBC no garantiza que pueda utilizar más de 100 valores para una lista de expresiones in. Varias bases de datos le permiten superar esta cifra, pero normalmente tienen un límite estricto en la cantidad de valores válidos. Por ejemplo, el límite de Oracle es 1000.

Además de los valores primitivos en una lista de valores, puede crear java.util.List a partir de matrices de objetos. Esta lista puede admitir múltiples expresiones definidas para la expresión in, por ejemplo select * from T_ACTOR donde (id, apellido) en ((1, 'Johnson'), (2, 'Harrop' )). Esto, por supuesto, requiere que su base de datos admita esta sintaxis.

Manejo de tipos complejos para llamadas a procedimientos almacenados

Al llamar a procedimientos almacenados, a veces puede usar tipos complejos específicos de la base de datos. Para trabajar con estos tipos, Spring proporciona SqlReturnType para manejarlos si se devuelven desde una llamada a un procedimiento almacenado, y SqlTypeValue si se pasan como parámetro al procedimiento almacenado.

La interfaz SqlReturnType tiene un método único (llamado getTypeValue) que debe implementarse. Esta interfaz se utiliza como parte de la declaración del parámetro SqlOutParameter. El siguiente ejemplo muestra el retorno del valor de un objeto Oracle llamado STRUCT de tipo ITEM_TYPE declarado por el usuario:

Java
 clase pública TestItemStoredProcedure extiende StoredProcedure { público TestItemStoredProcedure(DataSource dataSource) { // ... declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE", (CallableStatement cs, int colIndx, int sqlType , String typeName) -> { STRUCT struct = (STRUCT) cs.getObject(colIndx); Object[] attr = struct.getAttributes(); TestItem item = new TestItem(); item.setId(((Number) attr[ 0]).longValue( )); item.setDescription((String) attr[1]); item.setExpirationDate((java.util.Date) attr[2]); devolver elemento; })); // ... }
Kotlin
class TestItemStoredProcedure(dataSource: DataSource): StoredProcedure() { init { // ... declaraParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName -> val struct = cs.getObject(colIndx) as STRUCT val attr = struct.getAttributes () TestItem((attr[0] como largo, attr[1] como cadena, attr[2] como fecha) }) // ... } }

Puede utilizar SqlTypeValue para pasar el valor de un objeto Java (como TestItem) a un procedimiento almacenado. La interfaz SqlTypeValue tiene un único método (llamado createTypeValue) para implementar. La conexión activa se pasa y puede usarla para crear objetos específicos de la base de datos, como instancias StructDescriptor o ArrayDescriptor. código de instancias>. El siguiente ejemplo crea una instancia de un StructDescriptor:

Java
final TestItem testItem = new TestItem(123L, "Un elemento de prueba", nuevo SimpleDateFormat ("aaaa-M-d").parse("2010-12-31"); Valor SqlTypeValue = nuevo AbstractSqlTypeValue() { Objeto protegido createTypeValue(Conexión conexión, int sqlType, Cadena tipoNombre) lanza SQLException { StructDescriptor itemDescriptor = nuevo StructDescriptor(tipoNombre, conexión); Elemento de estructura = new STRUCT(itemDescriptor, conn, new Object[] { testItem.getId(), testItem.getDescription(), new java.sql.Date(testItem.getExpirationDate().getTime()) }); Devolver objeto; } };
Kotlin
val (id, descripción, fecha de vencimiento) = TestItem(123L, "Una prueba item", SimpleDateFormat("yyyy-M-d").parse("2010-12-31")) val value = object: AbstractSqlTypeValue() { anular fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Cualquier { val itemDescriptor = StructDescriptor(typeName, conn) return STRUCT(itemDescriptor, conn, arrayOf(id, descripción, java.sql.Date(expirationDate.time))) } }

Ahora puede agregar este valor SqlTypeValue al Map que contiene los parámetros de entrada para llamar al procedimiento almacenado execute.

Otro uso de SqlTypeValue es pasar una matriz de valores a un procedimiento almacenado de Oracle. Oracle tiene su propia clase interna ARRAY que debe usarse en En este caso, puede usar SqlTypeValue para crear una instancia de ARRAY de Orcale y completarla con valores de ARRAY de Java, como se muestra a continuación. ejemplo:

Java
final Long[] ids = new Long[] {1L, 2L}; Valor SqlTypeValue = nuevo AbstractSqlTypeValue() { Objeto protegido createTypeValue(Conexión conexión, int sqlType, Nombre de tipo cadena) lanza SQLException { ArrayDescriptor arrayDescriptor = nuevo ArrayDescriptor(tipoNombre, conexión); ARRAY idArray = nuevo ARRAY(arrayDescriptor, conn, ids); devolver matriz de identificación; } };
Kotlin
clase TestItemStoredProcedure(dataSource: DataSource): StoredProcedure() { init { val ids = arrayOf(1L, 2L) val value = object: AbstractSqlTypeValue() { anular fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Cualquiera { val arrayDescriptor = ArrayDescriptor(typeName, conn) return ARRAY(arrayDescriptor, conn, ids) } } } }