Implementar la capa de base de datos con Spring Data

En este post voy a montar otro PoC, esta vez es el turno de Spring Data, que junto con Spring Boot y sus autoconfiguraciones nos va a simplificar mucho el código necesario para el acceso a base de datos. El ejemplo lo haremos sobre un ejemplo de servicio REST sobre una aplicación Spring Boot que ya hice en un post anterior.

Concepto:
Spring Data es otro de los proyectos de Spring, cuyo objetivo es simplificar el desarrollo de la capa de base de datos en nuestra aplicación. En concreto, Spring Data JPA realmente es una capa de abstracción más, montada sobre JPA, para evitarnos al máximo posible el código boilerplate necesario en cualquier aplicación que use la especificación JPA para el acceso a base de datos.

La herramienta principal son los repositorios (Repository). Spring nos provee de varias interfaces que simplemente debemos heredar (con una interfaz nuestra), y que contienen los métodos necesarios para el acceso a base de datos. Spring, por su parte, se encarga de implementar esa interfaz que hayamos creado.

En el ejemplo que vamos a seguir en este post, le implementaremos la capa de base de datos al servicio REST que ya implementamos en el post Implementar un servicio Rest con Spring.

Entorno usado:
Java JDK 1.8
Maven 3.2.1
Git 1.9.4
IDE Intellij 14.0.1 Ultimate version

Pasos:

1. Vamos a usar como base el proyecto de ejemplo usado en el post del servicio REST. Antes de empezar, resumo algunos cambios iniciales que le he hecho al proyecto:
– Campo fecha joda-time de User cambiado a Java 8 Date API (JSR310), así como el bean de jackson para ese parseo.
– Modificado la url del servicio a ‘/api/users’ (antes era ‘/user’)
– Modificados nombre de paquetes (estaban en plural)
– Eliminado método ‘update’ del UserService, ahora se usa ‘save’ tanto para actualizar como crear
– Nuevo método ‘existsUser’ en el UserService.
– Otras pequeñas mejoras en la implementación del controller, respecto al update y delete.

2. Como comenté arriba, usaremos como base de datos H2, una base de datos «embebida», perfecta para pocs, test de integración y demás. Para usarla, con añadir la dependencia al pom, es suficiente. La autoconfiguración de Spring Boot, hará el resto (le pasará los parámetros por defecto para el datasource).

...
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.182</version>
    </dependency>
...

Añadimos también al pom la dependencia para spring-data-jpa, en concreto el ‘starter’ de spring boot para la misma:

...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
...

3. Anotamos nuestra entity con las anotaciones básicas para persistencia, quedaría así:

package com.edwise.pocs.springdata.entity;

// imports...

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    private Integer type;
    private String phone;
    private LocalDate birthDate;

    // getter and setters...

    public User copyFrom(User user) {
        if (user.name != null) {
            this.name = user.name;
        }
        if (user.type != null) {
            this.type = user.type;
        }
        if (user.phone != null) {
            this.phone = user.phone;
        }
        if (user.birthDate != null) {
            this.birthDate = user.birthDate;
        }
        return this;
    }

    // equals, hashcode and toString methods...
}

Explicación rápida de las anotaciones usadas:
Entity: marca nuestra entity como eso, una entity de base de datos, para que sea gestionada como tal.
Table: el nombre de la tabla de base de datos. Si no lo ponemos, tiene que ser igual al nombre de la clase.
Id: marcamos el atributo que será la primary key con esta anotación
GeneratedValue: Como se genera la primary key.

4. Creamos un repositorio de Spring Data. Para ello simplemente creamos una interfaz que herede de JpaRepository (como parámetros genéricos, nuestro entity y Long por el tipo de la primary key).

package com.edwise.pocs.springdata.repository;

// imports

public interface UserRepository extends JpaRepository<User, Long> {

}

El código está correcto, es una interfaz vacía. Con esto es suficiente, ya tenemos implementada nuestra capa de base de datos. Al crear esa interfaz, es Spring quien se encargará de implementarla. Simplemente tenemos que usarla, obteniéndola como cualquier bean del contexto de Spring.
JpaRepository hereda, entre otras de CrudRepository, que tiene los métodos básicos para implementar un CRUD (getAll, save, delete…).

5. Enganchamos ahora el repositorio con nuestro UserService. Nuestra implementación era antes un mock (UserServiceMock.java), lo renombramos (como UserServiceImpl) y lo modificamos para que use nuestro repositorio para las distintas operaciones, quedando:

package com.edwise.pocs.springdata.service.impl;

// imports...

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User findById(Long id) {
        return userRepository.findOne(id);
    }

    @Override
    public List<User> findAll() {
        return userRepository.findAll();
    }

    @Override
    public User save(User user) {
        return userRepository.save(user);
    }

    @Override
    public void delete(Long id) {
        userRepository.delete(id);
    }

    @Override
    public boolean existsUser(Long id) {
        return userRepository.exists(id);
    }
}

Simplemente obtenemos con Autowired el repository, y llamamos en cada método de nuestro service al método que corresponda. Nada raro.

6. ¡Terminado! Como siempre, ejecutamos ‘mvn spring-boot:run’ en una consola o desde la tool window de maven en Intellij, para arrancar nuestra aplicación. Si accedemos con un cliente REST a http://localhost:8080/api/users responderá nuestro servicio. Como la base de datos está vacia, lo mejor es hacer algún POST primero para meter algún user, el formato del body de la petición sería algo así:

{
  "name": "Triss",
  "type": 2,
  "phone": "645338822",
  "birthDate": "1985-06-19"
}

7. Más opciones de Spring Data: lo que hemos implementado es lo básico, y lo más sencillo. Quizá, por ejemplo, necesitaríamos un método que nos devuelva los usuarios de un determinado tipo (campo ‘type’). Muy fácil también, añadiríamos el siguiente método en nuestro repository:

public interface UserRepository extends JpaRepository<User, Long> {

    public List<User> findByType(Integer type);    
}

Este método también se encargará el propio Spring de implementarlo. El truco está en que la nomenclatura del método debe ser siempre ‘findByField’ donde ‘Field’ es el nombre del atributo por el que filtramos (siguiendo camelcase), y debe tener un parámetro que sea del tipo del atributo por el que filtrar.

8. Queries más complejas: lo ideal es implementar todos nuestros métodos extra de la manera anterior, pero quizá necesitemos algo más concreto. Por ejemplo, quizá necesitemos un método que devuelva la lista de usuarios para un nombre determinado, sin tener en cuenta mayúsculas o minúsculas. Inicialmente no podemos hacer eso con la estrategia anterior, así que tendríamos que picarnos la query, por ejemplo, con una NamedQuery de JPA añadiendo la anotación correspondiente en nuestra Entity:

@Entity
@Table(name = "users")
@NamedQuery(name = "User.findByNameIgnoreCase", query = "SELECT u FROM User u WHERE LOWER(u.name) = LOWER(?1)")
public class User {
    // all code...
}

Y el siguiente método en nuestro repository:

public interface UserRepository extends JpaRepository<User, Long> {

    public List<User> findByType(Integer type);

    public List<User> findByNameIgnoreCase(String name);   
}

Para que funcione, es necesario, como se ve en el código, que el ‘name’ de la namedQuery contenga el nombre del método del Repository en el que queremos que se ejecute.

9. Similar a lo anterior, podemos usar la anotación Query de Spring Data, directamente modificando solo el repository. Por ejemplo, un método que devuelva la lista de usuarios dado el prefijo de un teléfono, sería añadir el siguiente método a nuestro UserRepository:

public interface UserRepository extends JpaRepository<User, Long> {

    public List<User> findByType(Integer type);

    public List<User> findByNameIgnoreCase(String name);

    @Query("SELECT u FROM User u WHERE u.phone LIKE CONCAT(:phonePrefix, '%')")
    public List<User> findByPhonePrefix(@Param("phonePrefix") String phonePrefix);
}

Como se puede ver, con la anotación @Param marcamos el nombre del parámetro dentro de la query.

Hay más posibilidades: Criteria Queries, QueryDSL…, pero lo vamos a dejar aquí. Si queréis descargaros el proyecto completo, está en mi github, proyecto spring-data-example. También he añadido, comentado, el código necesario en el pom y en un properties para poder hacer la prueba conectando contra un MySQL. Jugad con él 🙂