Invocar a procedimientos usando Spring Boot Data JPA

2
53003

Índice de contenidos

1. Introducción

En este tutorial vamos a ver las distintas posibilidades que tenemos a la hora de invocar a procedimientos o métodos almacenados en ORACLE usando Spring Boot Data JPA.

A modo de introducción previa al tutorial os dejo otra forma de hacerlo con Spring JDBC como nos indicaba Daniel en su tutorial.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3, 500GB Flash Storage)
  • Sistema Operativo: Mac OS Sierra 10.13.4
  • Entorno de desarrollo: IntelliJ IDEA 2018.1
  • JDK 1.8
  • Apache Maven 3.5.0

3. Dependencias y configuración

Lo primero que haremos será crear un proyecto Maven Spring Boot y añadiremos las siguientes dependencias en el fichero pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc8</artifactId>
        <version>12.2.0.1</version>
    </dependency>
</dependencies>

Tras añadir las dependencias habrá que configurar el datasource en el fichero application.yml:

spring:
    datasource:
        url: jdbc:oracle:thin:@//localhost:5000/dbname
        username: user
        password: pass
        driver-class-name: oracle.jdbc.OracleDriver

4. Usando @NamedStoredProcedureQuery

La primera posibilidad que nos da Spring es definir el acceso mediante anotaciones de Java de la siguiente manera:

@Entity
@NamedStoredProcedureQueries({
    @NamedStoredProcedureQuery(
        name = "java_procedure_name",
        procedureName = "SCHEMA_NAME.PACKAGE_NAME.METHOD_OR_PROCEDURE_NAME",
        parameters = {
          @StoredProcedureParameter(mode=ParameterMode.IN, name="inputParam1", type=String.class),
          @StoredProcedureParameter(mode=ParameterMode.IN, name="inputParam2", type=String.class),
          @StoredProcedureParameter(mode=ParameterMode.OUT, name="outputParam", type=String.class)
    })
})
public class TableName implements Serializable {
}

Tras definir el procedimiento podremos invocarlo usando CrudRepository:

public interface TableNameRepository extends CrudRepository<TableName, Long> {
    @Procedure(name = "java_procedure_name")
    String procedureName(@Param("inputParam1") String inputParam1, @Param("inputParam2") String inputParam2);
}

5. Usando createStoredProcedureQuery

La siguiente forma que tenemos para invocar a los procedimientos es usar el método createStoredProcedureQuery(String procedureName) que expone javax.persistence.EntityManager.
De esta forma podremos definir la invocación de los procedimientos de forma dinámica:

@Repository
public class ProcedureInvoker {

    private final EntityManager entityManager;

    @Autowired
    public ProcedureInvoker(final EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    ...
        StoredProcedureQuery storedProcedureQuery = entityManager.createStoredProcedureQuery("SCHEMA_NAME.PACKAGE_NAME.METHOD_OR_PROCEDURE_NAME");
        
        // Registrar los parámetros de entrada y salida
        storedProcedureQuery.registerStoredProcedureParameter("INPUT_PROCEDURE_PARAMETER_NAME", String.class, ParameterMode.IN);
        storedProcedureQuery.registerStoredProcedureParameter("OUTPUT_PROCEDURE_PARAMETER_NAME1", String.class, ParameterMode.OUT);
        storedProcedureQuery.registerStoredProcedureParameter("OUTPUT_PROCEDURE_PARAMETER_NAME2", Long.class, ParameterMode.OUT);

        // Configuramos el valor de entrada
        storedProcedureQuery.setParameter("INPUT_PROCEDURE_PARAMETER_NAME", "value")

        // Realizamos la llamada al procedimiento
        storedProcedureQuery.execute();

        // Obtenemos los valores de salida
        final String outputValue1 = (String) query.getOutputParameterValue("OUTPUT_PROCEDURE_PARAMETER_NAME1");
        final Long outputValue2 = (Long) query.getOutputParameterValue("OUTPUT_PROCEDURE_PARAMETER_NAME2");
    ...
}

También podremos invocar a procedimientos que devuelven un SYS_REFCURSOR y almacenar el resultado en un objecto List de la siguiente manera:

    ...
    storedProcedureQuery.registerStoredProcedureParameter(2, Class.class, ParameterMode.REF_CURSOR)

    storedProcedureQuery.execute();

    // Obtenemos el resultado del cursos en una lista
    List<Object[]> results = storedProcedure.getResultList();

    // Recorremos la lista con map y devolvemos un List<BusinessObject>
    storedProcedureResponse.stream().map(result -> new BusinessObject(
        (String) result[0],
	    (Long) result[1]
    )).collect(Collectors.toList());

6. Conclusiones

Usar anotaciones hará que tengamos un código más limpio y orientado a JPA pero tiene dos puntos en contra:

  • Solo podremos devolver un único parámetro o un cursor.
  • A nivel conceptual, un procedimiento no pertenece a una tabla sino a la base de datos. Pero Spring nos obliga a anotar la clase @NamedStoredProcedureQuery con @Entity cuando puede que el procedimiento que va a ser invocado devuelva valores que no representan ninguna tabla dentro de nuestro modelo de datos.

Usando createStoredProcedureQuery tendremos muchas más flexibilidad a la hora de obtener todo tipo de valores pero también hará que el código se pueda descontrolar sino tenemos cuidado a la hora de usar este tipo de estructuras.

Un saludo.

Alejandro

aalmazan@autentia.com

2 COMENTARIOS

  1. Buenas,
    Personalmente sigo prefiriendo un autogenerador frente a la solución que indicas. El uso de la anotación es muy «cool» pero vas a encontrarte una cantidad de problemas en hacer el mapping de tipos desde java al storedProcedureQuery que dependiendo del número de procedimientos que debas consumir, puede ser tu muerte.

    Saludos.

  2. Muy agradecido por el post, me ayudo mucho y muy interesante las dos formas de consumir los sp por mi parte estoy usando ambas.

    Saludos crack!

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad