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

    • Reader: 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 = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);
jdbcTemplate.execute(
    "INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
    new AbstractLobCreatingPreparedStatementCallback(lobHandler) { 
        protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws 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) VALUES (?, ?, ?)",
        object: AbstractLobCreatingPreparedStatementCallback(lobHandler) { 
            override 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

List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
    new RowMapper<Map<String, Object>>() {
        public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
            Map<String, Object> results = new HashMap<String, Object>();
            String clobText = lobHandler.getClobAsString(rs, "a_clob"); 
            results.put("CLOB", clobText);
            byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob"); 
            results.put("BLOB", blobBytes);
            return results;
        }
    });
  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" to clobText, "BLOB" to 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

public class TestItemStoredProcedure extends StoredProcedure {
    public 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]);
                return item;
            }));
        // ...
    }
Kotlin

class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
    init {
        // ...
        declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
            val struct = cs.getObject(colIndx) as STRUCT
            val attr = struct.getAttributes()
            TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
        })
        // ...
    }
}

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, "A test item",
        new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
        Struct item = new STRUCT(itemDescriptor, conn,
        new Object[] {
            testItem.getId(),
            testItem.getDescription(),
            new java.sql.Date(testItem.getExpirationDate().getTime())
        });
        return item;
    }
};
Kotlin

val (id, description, expirationDate) = TestItem(123L, "A test item",
        SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
val value = object : AbstractSqlTypeValue() {
    override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
        val itemDescriptor = StructDescriptor(typeName, conn)
        return STRUCT(itemDescriptor, conn,
                arrayOf(id, description, 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 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};
SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
        ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
        return idArray;
    }
};
Kotlin

class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
    init {
        val ids = arrayOf(1L, 2L)
        val value = object : AbstractSqlTypeValue() {
            override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
                val arrayDescriptor = ArrayDescriptor(typeName, conn)
                return ARRAY(arrayDescriptor, conn, ids)
            }
        }
    }
}