Stored Procedures con Spring

De Dos Ideas.
Saltar a: navegación, buscar

Spring Framework brinda una herramienta bastante práctica para invocar a stored procredures o funciones de forma de evitar el manejo de la conexión y facilitar el seteo de los parámetros.

SimpleJdbcCall

Es una clase que provee Spring que representa la llamada a un stored procedure o store function. Se diferencia de cualquier otra clase utilitaria por leer la meta data de la base de datos, permitiendo que solo tengamos que pasarle en la invocación un map con los nombres de los parámetros y sus respectivos valores. No hace falta especificar los tipos de datos. Adicionalmente encapsula el manejo de la conexión y del statement (internamente usa otra clase mas genérica de Spring para JDBC llamada JdbcTemplate).

Como depende de la información que brinda la meta data, que es obtenida por el driver JDBC, las bases soportadas son Derby, MySQL, Microsoft SQL Server, Oracle y DB2.

Ejemplo invocación

public class MiDao {

   private SimpleJdbcCall miStoreProcedureCall;   
       
   public void buscarDireccion(String pametro1, String parametro2) {  
       SqlParameterSource mapIn = new MapSqlParameterSource()
       .addValue("CODIGO_CLIENTE", pametro1)
       .addValue("NUMERO_DOCUMENTO", parametro2);
       
       Map<String, Object> out = getMiStoreProcedureCall().execute(mapIn);
       String direccionCliente = (String) out.get("DIRECCION");
       
       System.out.println("Invocacion exitosa. Direccion cliente: " + direccionCliente);        
   }

}


Ejemplo de invocacion a un procedure con cursor

Cuando invocamos un procedure que retorna un cursor podemos especificar a que tipo de dato queremos que nos convierta el resultado.

 public Collection<Linea> buscarLineasPorTitular(@NotNull Titular titular) {
       SqlParameterSource parametrosPackage = new MapSqlParameterSource()
               .addValue("P_TIPO_DOCUMENTO", titular.getTipoDocumento(), Types.VARCHAR)
               .addValue("P_NUMERO_DOCUMENTO", titular.getNumeroDocumento().toString(), Types.VARCHAR);
       Map<String, Object> out = getSimpleJdbcCall()
               .withoutProcedureColumnMetaDataAccess()
               .declareParameters(
                       new SqlParameter("P_TIPO_DOCUMENTO", Types.VARCHAR),
                       new SqlParameter("P_NUMERO_DOCUMENTO", Types.VARCHAR))
              .returningResultSet("P_LINEAS",  ParameterizedBeanPropertyRowMapper.newInstance(Linea.class))
              .execute(parametrosPackage);
       return (Collection<Linea>) out.get("P_LINEAS");
   }

En este ejemplo seteamos que los datos del cursor(P_LINEAS) los convierta a la clase Linea y spring mapea los nombres de las columnas con los nombres de los atributos.

Por ejemplo:

-si el nombre de la columna es NUMERO_DOCUMENTO spring va a llamar a linea.setNumeroDocumento(..).
-si el nombre de la columna es sistema spring va a llamar a linea.setSistema(..).

Configuración

En el ejemplo se decidió por inyectar la clase utilitaria, utilizando configuración en xml. De igual manera se podría haber hecho en el mismo código Java.

La clase SimpleJdbcCall necesita como parámetro del constructor el data source asociado a la base de datos en la cual se encuentra el store procedure.

En el ejemplo, el procedure se encuentra dentro de un package y de ahí una propiedad extra que hay que configurar (sino simplemente obviarla). Archivo xml:

<beans>
 <bean id="dataSourceApp" class="org.springframework.jndi.JndiObjectFactoryBean" 
       destroy-method="close">
       <property name="jndiName" value="jdbc/miApp"/>
 </bean>
  
 <bean id="jdbc.miStoreP" class="org.springframework.jdbc.core.simple.SimpleJdbcCall">
       <constructor-arg ref="dataSourceApp"/>
       <property name="procedureName" value="BUSCAR_DIRECCION"/>
       <property name="catalogName" value="PACKAGE_CLIENTES"/>
 </bean>
 <bean id="dao.MiDao" class="com.dosideas.MiDao">
       <property name="miStoreProcedureCall" ref="jdbc.miStoreP"/>        
 </bean>    

</beans>

Schema

Como se menciono anteriormente, esta clase se basa en la obtención de la meta data. Para asegurarnos de que la obtenga correctamente debemos saber mas en detalle como la obtiene. Si corremos el ejemplo con un nivel de log en debug vamos a ver lo siguiente:

Retrieving metadata for PACKAGE_CLIENTES/SCHEMA/BUSCAR_DIRECCION

Si el dueño (owner) del package es diferente al del schema al cual nos estamos conectado, no va a poder obtener la meta data, aunque el mismo tenga un sinónimo publico. Lo importante es que Spring no da error en estos casos, sino que continua con la invocación. En nuestro caso seria algo del tipo:

Compiled stored procedure. Call string is [{call PACKAGE_CLIENTES.BUSCAR_DIRECCION()}]

Luego la base de datos nos dara un error indicando que estamos invocando al procedure con una cantidad incorrecta de parametros (ninguno), ocultado el error que origina todo esto.

Para solucionarlo basta con modificar la llamada al execute de SimpleJdbcCall de la siguiente forma:

Map<String, Object> out = getMiStoreProcedureCall().withSchemaName("MI_ESQUEMA")

                                                  .execute(mapIn);

Ver también