Spring Framework 006 - Ejemplo con spring data jpa

De ChuWiki

Acabamos de ver Como configurar Spring JPA. No hemos utilizado en ese tutorial las ventajas que nos da spring data jpa. Y la principal ventaja es que nos ahorra escribir el código de las operaciones básicas de inserción, consulta, modificación y borrado en base de datos. Veamos un ejemplo.

Tienes el código completo de ejemplo en Ejemplo con spring data jpa

Dependencia de spring-data-jpa[editar]

Partiendo del tutorial anterior de spring jpa, necesitamos añadir la dependencia de spring-data-jpa.

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.7.13</version>
        </dependency>

Configuración de spring para spring-data-jpa[editar]

Ahora, en el fichero XML de configuración de spring debemos añadir un par de cosas. No ponemos el fichero completo, sólo lo que ha cambiado respecto al tutorial anterior

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
            http://www.springframework.org/schema/data/jpa
            http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.0.xsd">

...

<!-- Eliminamos la instanciacion del bean UserDao, ya no nos hace falta hacerlo aquí -->
<!-- bean class="com.chuidiang.examples.spring_jpa.UserDao"/-->
<!-- En su lugar ponemos esto -->
<jpa:repositories base-package="com.chuidiang.examples.spring_jpa" />

Como hemos comentado, con spring data jpa, no necesitamos hacer la clase UserDao con las sentencias básicas, así que no necesitamos instanciarla con Spring. En su lugar, tendremos que crear una interface java, que también llamaremos UserDao, pero de la que spring se encargará de implementar e instanciar automáticamente su implementación. No te preocupes, lo explicamos más adelante.

Y para que spring sepa dónde buscar este tipo de interfaces, necesitamos decirle dónde buscarlas con jpa:repositories. Para ello, añadimos en la cabecera del XML las líneas marcadas con color y la linea con jpa:repositories

spring data jpa repositories[editar]

En nomenclatura Spring, un repository es una interface java que nos permite acceder a la base de datos. Spring se encarga automáticamente de implementar e instanciar la clase de implementación. Para ello, tenemos que hacer una interface Java que extienda de alguna de las interfaces que Spring entiende como repository. Por ejemplo

public interface UserDao extends CrudRepository<User, Integer> {
}

CrudRepository<User, Long> es una de estas interfaces de spring. Si miramos su código

public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S entity);
    <S extends T> Iterable<S> saveAll(Iterable<S> entities);
    Optional<T> findById(ID id);
    boolean existsById(ID id);
    Iterable<T> findAll();
    Iterable<T> findAllById(Iterable<ID> ids);
    long count();
    void deleteById(ID id);
    void delete(T entity);
    void deleteAllById(Iterable<? extends ID> ids);
    void deleteAll(Iterable<? extends T> entities);
    void deleteAll();
}

Vemos que:

  • Hereda de Repository. Esto hace que spring sepa que es una clase de acceso a base de datos.
  • Tiene muchos método para base de datos, como findById(), delete(), save(), etc.
  • Para la entidad usa el tipo genérico T (nuestro ejemplo sería User) y para el identificador de esa entidad usa el tipo genérico ID, en nuestro caso, el id dentro de la clase User.

Con el último punto, vemos que nuestro UserDao debe hererdar de CrudRepository indicando que T es User y que ID es de tipo Integer.

Y listo, no tenemos que hacer más. Spring se encarga por nosotros de darnos todos esos métodos implementados. Nuestro código de ejemplo podría ser como este

    public static void main(String[] args) {
        BeanFactory factory = new ClassPathXmlApplicationContext("spring-context.xml");
        final UserDao userDao = factory.getBean(UserDao.class);

        User newUser = new User();
        newUser.setName("Pedro");
        newUser.setEmail("pedro@email.com");
        userDao.save(newUser);
        newUser = new User();
        newUser.setName("Juan");
        newUser.setEmail("juan@email.com");
        userDao.save(newUser);

        System.out.println("Find All");
        Iterable<User> users = userDao.findAll();
        users.forEach(user -> {
            System.out.println(user.getId() + ", "+ user.getName() + ", "+ user.getEmail());
        });

Creamos la factory de spring y le pedimos UserDao. Spring nos devuelve una clase ya implementada de esa interface. Solo nos queda usar los métodos save() para meter un par de usuarios y el método findAll() para consultarlo.

Ampliar los métodos del spring data jpa repository[editar]

Spring no solo nos da CrudRepository. Tiene otras interfaces posibles. Por ejemplo, para tener consultas ordenadas con paginación.

Si esto no es suficiente, podemos hacer más métodos a medida.

Añadir métodos sin implementarlos[editar]

Si son consultas más o menos estándar, no nos hace falta implementarlos, spring lo hará por nosotros siempre que sigamos una nomenclatura determinada para dichos métodos. Por ejemplo

public interface UserDao extends CrudRepository<User, Integer> {
    User findByEmail(String email);
    List<User> findByEmailOrName(String email, String name);
}

En nuestra interface UserDao hemos metido un par de métodos más: findByEmail() y findByNameOrName(). Por el nombre es bastante intuitivo lo que deben hacer. Spring también entiende ese formato y es capaza de generar esas consultas. Ahora podemos usar UserDao para buscar un usuario por email, o bien que tenga un determinado email o name. Por supuesto, email y name son atributos de nuestra clase User.

Un ejemplo de cómo usar esto, continuación el main anterior.

        System.out.println("Find by Email:");
        User juan = userDao.findByEmail("juan@email.com");
        System.out.println(juan.getId() + ", "+ juan.getName() + ", "+ juan.getEmail());

        System.out.println("Find By Email or Name");
        users = userDao.findByEmailOrName("juan@email.com","Pedro");
        users.forEach(user -> {
            System.out.println(user.getId() + ", "+ user.getName() + ", "+ user.getEmail());
        });

Implementar nuestros propios métodos[editar]

Si nuestra consulta es más compleja, podemos implementar nosotros nuestra propia consulta. Por ejemplo, imagina que quieres buscar usuario por nombre pero sin tener en cuenta mayúsculas y minúsculas. Spring no nos da esa opción, así que la implementamos nosotros. Una forma de hacerlo es usar las @NamedQuery. En nuestra clase User poenemos la consulta.

@Getter @Setter
@Entity
@Table(name="user_table")
@NamedQuery(name = "User.findByNameIgnoreCase", query = "SELECT u FROM User u WHERE LOWER(u.name) = LOWER(?1)")
public class User {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private int id;

    private String name;
    private String email;
}

Le hemos puesto nombre findByNameIgnoreCase indicando que es de User y luego la consulta en lenguaje de JPA para buscar poniendo tanto lo que nos pasan como primer parámetro ?1 como el nombre en base de datos en minúsculas. El siguiente paso es añadir el método, con el mismo nombre y un parámetro, en la interface UserDao

public interface UserDao extends CrudRepository<User, Integer> {
    User findByEmail(String email);
    List<User> findByEmailOrName(String email, String name);
    User findByNameIgnoreCase(String name);
}

y lo último, usarlo para ver que funciona, en el main anterior

        System.out.println("Find by Name Ignore Case:");
        User pedro = userDao.findByNameIgnoreCase("pEDRo");
        System.out.println(pedro.getId() + ", "+ pedro.getName() + ", "+ pedro.getEmail());