Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. Dependencias y configuración
- 4. Usando @NamedStoredProcedureQuery
- 5. Usando createStoredProcedureQuery
- 6. Conclusiones
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
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.
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!