Versiones comparadas

Clave

  • Se ha añadido esta línea.
  • Se ha eliminado esta línea.
  • El formato se ha cambiado.

...

Para el acceso a base de datos, utilizando JpaRepository podemos obtener objetos de base de datos sin necesidad de implementar ningún método, tan solo declararlos con la notación adecuada.

Pre-requisitos

Para poder utilizar JpaRepository es necesario incluir las siguientes dependencias en el pom.xml:

...

Bloque de código
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
spring.jpa.generate-ddl = false
spring.jpa.hibernate.ddl-auto = validate


# Datasource
spring.datasource.url=jdbc:oracle:thin:@hydra-prescan.atica.um.es:1526/ZEUSDESA
spring.datasource.username=USUARIOBD
spring.datasource.password=CONTRASEÑABD

Entidades y DTOs

Una vez hayamos proporcionado a nuestra aplicación acceso a base de datos, debemos hacernos la clase java que mapee los objetos de base de datos que se van a obtener con el JpaRepository. Para auto generar las entidades, podemos consultar esta página de la wiki. También podemos generarlas a mano. Para ilustrar esto, tenemos la clase Usuario:

...

Bloque de código
languagejava
package es.um.atica..--------;
 
import es.um.atica.--------.Usuario;
 
public class UsuarioDTO {
 
    private String login;
 
    public UsuarioDTO() {
    }
 
    public UsuarioDTO(Usuario usuario) {
        this(usuario.getLogin());
    }
 
    public UsuarioDTO( String login ) {
        this.login = login;
    }
 
    //////////////////////////////////// GETTERS & SETTERS ////////////////////////////////////
    public String getLogin() {
        return this.login;
    }
 
    public void setLogin( String login ) {
        this.login = login;
    }
 
}

Secuencias de base de datos

Utilizaremos la etiqueta @SequenceGenerator, a la que tendremos que especificarle el nombre de la secuencia (allocationSize) y el allocationSize. Este último parámetro es importante, pues define el incremento entre cada número de la secuencia, y por defecto tiene un valor de 50, así que lo ponddremos al valor necesario, que normalmente será 1. Esta etiqueta se pone fuera de la clase, y quedaría así:

...

Bloque de código
languagejava
@GeneratedValue(generator = "comunicacionID")

Para evitar dependencias circulares al obtener objetos de base de datos y serializarlos, disponemos de las anotaciones @JsonBackReference y @JsonManagedReference, que utilizaremos sobre los atributos que forman la relación bidireccional. 

...

Bloque de código
languagejson
{
 "id":2,
 "itemName":"book",
 "owner":
    {
        "id":1,
        "name":"John"
    }
}


JpaRepository

Completados los pasos previos podemos definirnos nuestro repositorio. Se ha de crear una interfaz que extienda a JpaRepository parametrizado con el tipo correspondiente, y utilizando la anotación @Repository. Siguiendo con nuestro ejemplo, tendríamos:

...

Bloque de código
languagejava
private final UsuarioRepository usuarioRepo;
 
Usuario usuario = usuarioRepo.findByLogin("login.usuario");

Paginación y reordenación

A la hora de llamar al repositorio, al parámetro, nos ofrece métodos con un argumento Pageable. Esto es para realizar la paginación, y crearemos ese objeto con PageRequest.of, de la siguiente forma:

...

En vez de usar la clase List, que podríamos, es mejor utilizar objetos tipo Page. De estos objetos podemos obtener sus elementos con getContent(), pero además podemos saber el número total de elementos y de páginas con getTotalElements() y getTotalPages(), respectivamente. Esto nos puede servir para, por ejemplo, pasarle el total de elementos a un componente tabla, cosa que no podríamos obtener con List. Los objetos Page por defecto no son serializables, no se pasarán correctamente como JSON.

Custom queries

Aparte de los métodos vistos hasta ahora, podemos definir directamente queries SQL:

...

Utilizamos la anotación @Query. Es importante indicar que ?1 hace referencia al primer parámetro definido en el método (lastname).

Transacciones en JpaRepository

Por defecto, los métodos CRUD de los repositorios son transaccionales, es decir, incluyen la anotación @Transactional por defecto. Sí que tendremos que incluirla en los métodos query que definamos, es decir, en los métodos del repositorio que utilicen la anotación @Query, o en métodos externos que encapsulen varias llamadas a métodos de repositorios. También podríamos incluirla en los métodos CRUD normales de los repositorios para modificar su comportamiento por defecto. Esto podemos leerlo con más detalle en la documentación de Spring Data JPA.

Filtrar utilizando Specification

Otra opción para hacer queries propias es programarlas a través de Criteria y Specification. Specification es una interfaz para definir predicados reutilizables, teniendo métodos que podemos pasar directamente a llamadas a un JpaRepository. La parte negativa es que no se pueden combinar los filtros que aplican los métodos del repositorio con las Specification, pues sólo se pueden utilizar con el método findAll. La interfaz Specification es así:

...

En la implementación del método podemos ver referencias a clases seguidas de un guión bajo, estas son las clases del metamodelo, vamos a ver cómo generarlas.

Generar clases del metamodelo

Podemos generar automáticamente estas clases, incluyendo esta dependencia en nuestro pom.xml:

...

Una vez tengamos esto, tendremos que ejecutar maven install, y las clases se generarán en target/generated-sources/annotations/es/um/.../entities/, dentro de nuestro proyecto. Podemos mover las clases que necesitemos a nuestra estructura de paquetes para tenerlas organizadas junto al resto de clases del proyecto, o también podemos añadir esta carpeta al build path.

Llamadas a métodos de base de datos

Tenemos que diferenciar entre funciones y procedimientos:

  • Las funciones (Function) siempre devuelven un valor utilizando return. Aún así, se pueden especificar parámetros de salida, pero no son recomendables. Las funciones se pueden utilizar en instrucciones típicas como SELECT, INSERT, UPDATE,  DELETE, MERGE, mientras que los procedimientos no. Las funciones se utilizan habitualmente para hacer cálculos.
  • Los procedimientos (Procedure) pueden devolver valores o no, pero si se devuelve algo, se tiene que especificar con parámetros de salida. Los procedimientos se suelen utilizar para ejecutar lógica de negocio.

Funciones

Para llamar a funciones de base de datos, podemos hacerlo directamente desde un JPA Repository, indicándo una custom query con SELECT FUNCION(?1, ?2) FROM DUAL:

...

Hay funciones que desde la propia base de datos se especifica que no se puedan llamar de esta forma, con SELECT FROM DUAL, y en estos casos deberemos hacer la llamada con JdbcTemplate, que podemos ver en el último apartado de esta sección.

Procedimientos

Para llamar a procedimientos desde un JPA Repository, podemos utilizar la anotación @Procedure, de esta manera:

...

Como vemos, utilizamos @Param para indicar a qué parámetro del procedure corresponde cada parámetro de la función java, y se debe indicar el nombre definido en base de datos. En este caso, no se está devolviendo ningún valor. A falta de realizar más investigación y más pruebas en esta vía, podemos utilizar JdbcTemplate, que nos da más flexibilidad.

JdbcTemplate

Para llamar tanto a funciones como a procedimientos de base de datos, podemos utilizar JdbcTemplate. En primer lugar, declararemos la instancia en nuestra clase para el servicio REST:

...

En cuanto a la conexión que se crea, JdbcTemplate se encarga de la obtención y liberación de recursos, como dicha conexión. Por lo tanto, no hace falta cerrarla explícitamente, pues ya lo hace JdbcTemplate.