Skip to content

5. Introducción a Spring Data JPA

En este capítulo, estudiaremos la siguiente arquitectura:

Se intercala una capa [JPA] (Java Persistence API) entre la capa [DAO] y el controlador JDBC del SGBD. Ahora es la capa JPA la que emite las órdenes SQL destinadas al SGBD. La capa [DAO] ya no maneja órdenes SQL, sino únicamente objetos denominados entidades JPA, que son representaciones de las diferentes tablas de la base de datos en uso. Los campos de estas entidades se asocian de manera única a las columnas de las tablas mediante anotaciones de Java. Esto es lo que permite que la capa JPA traduzca a SQL las operaciones de la capa [DAO] realizadas sobre las entidades JPA.

Spring Data es una rama de Spring dedicada al acceso a los datos, ya sea que estos se encuentren en una base de datos relacional SGBDR, una base NOSQL u otros tipos de repositorios. Aquí nos interesan únicamente los SGBDR y su acceso a través de JPA. Más adelante, en ocasiones escribiremos [Spring JPA] para referirnos, en realidad, a [Spring Data JPA]. En la arquitectura anterior, la capa [Spring Data] proporciona funcionalidades a la capa [DAO] para gestionar las entidades JPA.

JPA es, en realidad, una especificación. Probaremos tres de sus implementaciones:

5.1. Exemple-01

En el sitio web de Spring hay muchos tutoriales para dar los primeros pasos con Spring [http://spring.io/guides]. Usaremos uno de ellos para introducir Spring Data. Para ello, utilizaremos Spring Tool Suite (STS).

  • En [1], importamos uno de los tutoriales de [spring.io/guides];
  • en [2], elegimos el tutorial [Accessing Data Jpa] que muestra cómo acceder a una base de datos con Spring Data;
  • en [3], elegimos un proyecto configurado por Maven;
  • en [4], el tutorial puede presentarse de dos formas: [initial], que es una versión vacía que se completa siguiendo el tutorial, o [complete], que es la versión final del tutorial. Elegimos esta última;
  • en [5], podemos optar por visualizar el tutorial en un navegador;
  • en [6], el proyecto final.

5.1.1. La configuración de Maven del proyecto

Las dependencias de Maven del proyecto se configuran en el archivo [pom.xml]:


    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-data-jpa</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <!-- usar UTF-8 para todo -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <start-class>hello.Application</start-class>
</properties>
  • líneas 5-9: definen un proyecto padre de Maven. Este es el que define la mayor parte de las dependencias del proyecto. Pueden ser suficientes, en cuyo caso no se agregan más, o no, en cuyo caso se agregan las dependencias que faltan;
  • líneas 12-15: definen una dependencia de [spring-boot-starter-data-jpa]. Este artefacto contiene las clases de Spring Data;
  • líneas 16-19: definen una dependencia de SGBD y H2, que permiten crear y administrar bases de datos en memoria.

Veamos las clases que aportan estas dependencias:

Son muchas:

  • algunas pertenecen al ecosistema de Spring (las que comienzan con «spring»);
  • otras pertenecen al ecosistema de Hibernate (hibernate, jboss), de las cuales aquí utilizamos la implementación JPA;
  • otras son bibliotecas de pruebas (junit, hamcrest);
  • otras son bibliotecas de registro (log4j, logback, slf4j);

Las vamos a conservar todas. Para una aplicación en producción, solo se deberían conservar las que sean necesarias.

En la línea 26 del archivo [pom.xml] se encuentra la línea:


<start-class>hello.Application</start-class>

Esta línea está relacionada con las siguientes:


<build>
        <plugins>
            <plugin> 
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

En las líneas 6 a 9, el complemento [spring-boot-maven-plugin] permite generar el archivo JAR ejecutable de la aplicación. La línea 26 del archivo [pom.xml] indica, entonces, la clase ejecutable de este archivo JAR.

5.1.2. La capa [JPA]

El acceso a la base de datos se realiza a través de una capa [JPA], Java Persistence API:

  

La aplicación es básica y maneja clientes [Customer]. La clase [Customer] forma parte de la capa [JPA] y es la siguiente:


package hello;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String firstName;
    private String lastName;

    protected Customer() {
    }

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
    }

}

Un cliente tiene un identificador [id], un nombre [firstName] y un apellido [lastName]. Cada instancia [Customer] representa una fila de una tabla de la base de datos.

  • línea 8: anotación JPA que hace que la persistencia de las instancias [Customer] (Crear, Leer, Actualizar, Eliminar) sea gestionada por una implementación JPA. Según las dependencias de Maven, se observa que se utiliza la implementación JPA / Hibernate;
  • líneas 11-12: anotaciones JPA que asocian el campo [id] con la clave primaria de la tabla [Customer]. La línea 12 indica que la implementación JPA utilizará el método de generación de clave primaria propio de la implementación SGBD utilizada, en este caso H2;

No hay otras anotaciones para JPA. Por lo tanto, se utilizarán los valores por defecto:

  • la tabla de [Customer] llevará el nombre de la clase, es decir, [Customer];
  • las columnas de esta tabla llevarán el nombre de los campos de la clase: [id, firstName, lastName], teniendo en cuenta que no se distingue entre mayúsculas y minúsculas en el nombre de una columna de tabla;

Cabe señalar que en ningún momento se menciona el nombre de la implementación JPA utilizada.

5.1.3. La capa [Spring Data]

La clase [CustomerRepository] implementa la capa de acceso a la tabla [Customer]. Su código es el siguiente:

  

package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}

Por lo tanto, se trata de una interfaz y no de una clase (línea 7). Extiende la interfaz [CrudRepository], una interfaz de Spring Data (línea 5). Esta interfaz se define mediante dos tipos: el primero es el tipo de los elementos gestionados, en este caso el tipo [Customer]; el segundo, el tipo de la clave primaria de los elementos gestionados, en este caso un tipo [Long]. La interfaz [CrudRepository] es la siguiente:


package org.springframework.data.repository;

import java.io.Serializable;

@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

    <S extends T> S save(S entity);

    <S extends T> Iterable<S> save(Iterable<S> entities);

    T findOne(ID id);

    boolean exists(ID id);

    Iterable<T> findAll();

    Iterable<T> findAll(Iterable<ID> ids);

    long count();

    void delete(ID id);

    void delete(T entity);

    void delete(Iterable<? extends T> entities);

    void deleteAll();
}

Esta interfaz define las operaciones CRUD (Crear – Leer – Actualizar – Eliminar) que se pueden realizar sobre un tipo JPA T:

  • línea 8: el método save permite guardar una entidad T en la base de datos. Guarda la entidad con la clave primaria que le asignó el SGBD. También permite actualizar una entidad T identificada por su clave primaria id. La elección de una u otra acción depende del valor de la clave primaria id: si este es nulo, se lleva a cabo la operación de persistencia; de lo contrario, se realiza la operación de actualización;
  • línea 10: lo mismo, pero para una lista de entidades;
  • línea 12: el método findOne permite recuperar una entidad T identificada por su clave primaria id;
  • línea 22: el método delete permite eliminar una entidad T identificada por su clave primaria id;
  • líneas 24-28: variantes del método [delete];
  • línea 16: el método [findAll] permite recuperar todas las entidades T persistentes;
  • línea 18: lo mismo, pero limitado a las entidades cuya lista de identificadores se haya pasado;

Volvamos a la interfaz [CustomerRepository]:


package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}
  • la línea 9 permite recuperar un [Customer] por su nombre [lastName];

Y eso es todo para la capa [DAO]. No existe una clase de implementación de la interfaz anterior. Esta se genera en tiempo de ejecución mediante [Spring Data]. Los métodos de la interfaz [CrudRepository] se implementan automáticamente. En cuanto a los métodos agregados en la interfaz [CustomerRepository], depende. Volvamos a la definición de [Customer]:


    private long id;
    private String firstName;
private String lastName;

El método de la línea 9 es implementado automáticamente por [Spring Data] porque hace referencia al campo [lastName] (línea 3) de [Customer]. Cuando encuentra un método [findBySomething] en la interfaz que se debe implementar, Spring Data lo implementa mediante la siguiente consulta JPQL (Java Persistence Query Language):

select t from T t where t.something=:value

Por lo tanto, el tipo T debe tener un campo llamado [something]. De este modo, el método

List<Customer> findByLastName(String lastName);

se implementará mediante un código similar al siguiente:

return [em].createQuery("select c from Customer c where c.lastName=:value").setParameter("value",lastName).getResultList()

donde [em] hace referencia al contexto de persistencia JPA. Esto solo es posible si la clase [Customer] tiene un campo llamado [lastName], lo cual es el caso.

En conclusión, en los casos sencillos, Spring Data nos permite implementar la capa [DAO] con una simple interfaz.

5.1.4. La capa [console]

  

La clase [Application] es la siguiente:


package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    CustomerRepository repository;
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Override
    public void run(String... strings) throws Exception {
        // guardar un par de clientes
        repository.save(new Customer("Jack", "Bauer"));
        repository.save(new Customer("Chloe", "O'Brian"));
        repository.save(new Customer("Kim", "Bauer"));
        repository.save(new Customer("David", "Palmer"));
        repository.save(new Customer("Michelle", "Dessler"));

        // recoger todos los clientes
        System.out.println("Customers found with findAll():");
        System.out.println("-------------------------------");
        for (Customer customer : repository.findAll()) {
            System.out.println(customer);
        }
        System.out.println();

        // obtener un cliente específico mediante ID
        Customer customer = repository.findOne(1L);
        System.out.println("Customer found with findOne(1L):");
        System.out.println("--------------------------------");
        System.out.println(customer);
        System.out.println();

        // recuperar clientes por apellido
        System.out.println("Customer found with findByLastName('Bauer'):");
        System.out.println("--------------------------------------------");
        for (Customer bauer : repository.findByLastName("Bauer")) {
            System.out.println(bauer);
        }
    }

}
  • línea 9: la clase implementa la interfaz [CommandLineRunner], que a su vez es una interfaz de [Spring Boot] (línea 4). Esta interfaz solo tiene un método, el de la línea 19;
  • línea 8: @SpringBootApplication es una anotación que agrupa varias anotaciones [Spring Boot]:
    • @Configuration: indica que la clase es una clase de configuración;
    • @EnableAutoConfiguration: le pide a [Spring Boot] que cree por sí mismo una cierta cantidad de beans en función de diversas propiedades, en particular el contenido del Classpath del proyecto. Dado que las bibliotecas de Hibernate se encuentran en el Classpath, el bean [entityManagerFactory] se implementará con Hibernate. Dado que la biblioteca de SGBD H2 se encuentra en el Classpath, el bean [dataSource] se implementará con H2. En el bean [dataSource], también hay que definir el usuario y su contraseña. En este caso, Spring Boot utilizará el administrador predeterminado de H2, que no tiene contraseña. Debido a que la biblioteca [spring-tx] se encuentra en el Classpath, se utilizará el administrador de transacciones de Spring;
    • @EnableWebMvc: si en el Classpath se encuentra la biblioteca [spring-mvc]. En este caso, se realiza una configuración automática para la aplicación web;
    • @ComponentScan: que le indica a Spring dónde buscar los demás beans, configuraciones y servicios. Aquí se buscan por defecto en el paquete que contiene la clase etiquetada, es decir, el paquete [hello]. De esta manera, se encontrarán las clases [Customer] y [CustomerRepository]. Como la primera tiene la anotación [@Entity], se catalogará como una entidad que debe gestionar Hibernate. Como la segunda extiende la interfaz [CrudRepository], se registrará como un bean de Spring;
  • líneas 11-12: el bean [CustomerRepository] se inyecta en el código de la clase principal;
  • línea 15: se ejecuta el método estático [run] de la clase [SpringApplication] del proyecto Spring Boot. Su parámetro es la clase que tiene una anotación [Configuration] o [EnableAutoConfiguration]. A continuación, se llevará a cabo todo lo explicado anteriormente. El resultado es un contexto de aplicación de Spring, es decir, un conjunto de beans gestionados por Spring;
  • líneas 19-48: las operaciones siguientes solo utilizan los métodos del bean que implementa la interfaz [CustomerRepository];

Los resultados en la consola son los siguientes:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.2.RELEASE)

2015-03-10 15:35:43.661  INFO 5784 --- [           main] hello.Application                        : Starting Application on Gportpers3 with PID 5784 (started by ST in C:\Users\Serge Tahé\Documents\workspace-sts-3.6.3.RELEASE\gs-accessing-data-jpa-complete)
2015-03-10 15:35:43.708  INFO 5784 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:45.230  INFO 5784 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:45.254  INFO 5784 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2015-03-10 15:35:45.331  INFO 5784 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {4.3.8.Final}
2015-03-10 15:35:45.332  INFO 5784 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2015-03-10 15:35:45.334  INFO 5784 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2015-03-10 15:35:45.651  INFO 5784 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
2015-03-10 15:35:45.754  INFO 5784 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2015-03-10 15:35:45.877  INFO 5784 --- [           main] o.h.h.i.ast.ASTQueryTranslatorFactory    : HHH000397: Using ASTQueryTranslatorFactory
2015-03-10 15:35:46.154  INFO 5784 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:46.169  INFO 5784 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
2015-03-10 15:35:46.779  INFO 5784 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']

Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']

Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
2015-03-10 15:35:47.040  INFO 5784 --- [           main] hello.Application                        : Started Application in 3.623 seconds (JVM running for 4.324)
2015-03-10 15:35:47.042  INFO 5784 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:47.044  INFO 5784 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2015-03-10 15:35:47.046  INFO 5784 --- [       Thread-1] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:47.047  INFO 5784 --- [       Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:47.051  INFO 5784 --- [       Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
  • líneas 1-8: el logotipo del proyecto Spring Boot;
  • línea 9: se ejecuta la clase [hello.Application];
  • línea 10: [AnnotationConfigApplicationContext] es una clase que implementa la interfaz [ApplicationContext] de Spring. Es un contenedor de beans;
  • línea 11: el bean [entityManagerFactory] se implementa con la clase [LocalContainerEntityManagerFactory], una clase de Spring. Este bean administra la capa [JPA];
  • línea 12: aparece [Hibernate]. Se ha elegido esta implementación, JPA;
  • línea 19: un dialecto de Hibernate es la variante SQL que se utilizará con el SGBD. Aquí, el dialecto [H2Dialect] indica que Hibernate trabajará con SGBD y H2;
  • líneas 21-22: se crea la base de datos. Se crea la tabla [CUSTOMER]. Esto significa que Hibernate se ha configurado para generar las tablas a partir de las definiciones JPA; en este caso, la definición JPA de la clase [Customer];
  • líneas 26-30: resultado del método [findAll] de la interfaz;
  • línea 34: resultado del método [findOne] de la interfaz;
  • líneas 38-39: resultados del método [findByLastName];
  • líneas 41 y siguientes: registros del cierre del contexto de Spring.

5.1.5. Configuración manual del proyecto Spring Data

Duplicamos el proyecto anterior en el proyecto [gs-accessing-data-jpa-02]:

  

En este nuevo proyecto, no vamos a recurrir a la configuración automática que realiza Spring Boot. La haremos manualmente. Esto puede ser útil si las configuraciones predeterminadas no nos convienen.

En primer lugar, vamos a especificar las dependencias necesarias en el archivo [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-data-jpa-02</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>
        <!-- Base de datos H2 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <!-- Tomcat JDBC -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <!-- usa UTF-8 para todo -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
        <repository>
            <id>org.jboss.repository.releases</id>
            <name>JBoss Maven Release Repository</name>
            <url>https://repository.jboss.org/nexus/content/repositories/releases</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>
  • líneas 10-14: el proyecto padre de Maven, cuyas bibliotecas vamos a utilizar;
  • líneas 18-21: Spring Data, que se utiliza para acceder a la base de datos;
  • líneas 23-26: la implementación de Hibernate de la especificación JPA;
  • líneas 28-31: SGBD y H2;
  • líneas 33-36: las bases de datos suelen utilizarse con grupos de conexiones abiertas que evitan tener que abrir y cerrar conexiones repetidamente. Aquí, la implementación utilizada es la de [tomcat-jdbc];

En el nuevo proyecto, la entidad [Customer] y la interfaz [CustomerRepository] no cambian. Vamos a modificar la clase [Application], que se dividirá en dos clases:

  • [Config], que será la clase de configuración:
  • [Main], que será la clase ejecutable;
  

La clase ejecutable [Application] ahora es la siguiente:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import repositories.CustomerRepository;
import config.AppConfig;
import entities.Customer;

public class Application {
    public static void main(String[] args) {
        // instanciación del contexto de Spring
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CustomerRepository repository = context.getBean(CustomerRepository.class);

        // guardar un par de clientes
        repository.save(new Customer("Jack", "Bauer"));
        repository.save(new Customer("Chloe", "O'Brian"));
        repository.save(new Customer("Kim", "Bauer"));
        repository.save(new Customer("David", "Palmer"));
        repository.save(new Customer("Michelle", "Dessler"));

        ...

        // cierre del contexto
        context.close();
    }

}
  • línea 9: la clase [Application] ya no tiene anotaciones de configuración;
  • líneas 3-7: cabe destacar que ya no hay importaciones de paquetes [Spring Boot];
  • línea 12: se instancian los beans de Spring. Se obtiene el contexto de Spring que contiene la referencia de los beans así creados;
  • línea 13: se solicita una referencia al bean de tipo [CustomerRepository];

La clase [Config] que configura el proyecto es la siguiente:


package config;

import javax.persistence.EntityManagerFactory;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "repositories" })
@Configuration
// @ComponentScan(basePackages={"paquete1","paquete2"})
public class AppConfig {

    // la base de datos H2
    @Bean
    public DataSource dataSource() {
        // fuente de datos TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuración de acceso JDBC
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:./demo");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        // una conexión abierta inicialmente
        dataSource.setInitialSize(1);
        // resultado
        return dataSource;
    }

    // el proveedor JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(false);
        hibernateJpaVendorAdapter.setGenerateDdl(true);
        hibernateJpaVendorAdapter.setDatabase(Database.H2);
        return hibernateJpaVendorAdapter;
    }

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan("entities");
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    // Administrador de transacciones
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

}
  • línea 17: la anotación [@EnableTransactionManagement] indica que deben interpretarse las anotaciones [@Transactional]. Los métodos de las interfaces [CrudRepository] cuentan con estas anotaciones. Por lo tanto, se ejecutan dentro de una transacción;
  • línea 18: la anotación [@EnableJpaRepositories] permite designar las carpetas donde se encuentran las interfaces Spring Data [CrudRepository]. Estas interfaces se convertirán en componentes de Spring y estarán disponibles en su contexto;
  • línea 19: la anotación [@Configuration] convierte a la clase [Config] en una clase de configuración de Spring;
  • línea 20: la anotación [@ComponentScan] permite enumerar las carpetas en las que se deben buscar los componentes de Spring. Los componentes de Spring son clases etiquetadas con anotaciones de Spring como @Service, @Component, @Controller, etc. Aquí no hay otros más que los definidos dentro de la clase [AppConfig], por lo que la anotación se ha puesto entre comentarios;
  • líneas 24-37: definen la fuente de datos, la base de datos H2. Es la anotación @Bean de la línea 25 la que convierte al objeto creado por este método en un componente administrado por Spring. El nombre del método puede ser cualquiera. Sin embargo, debe llamarse [dataSource] si el método EntityManagerFactory de la línea 51 no existe y se define mediante autoconfiguración;
  • línea 30: la base de datos se llamará [demo] y se generará en la carpeta del proyecto;
  • líneas 40-47: definen la implementación JPA utilizada, en este caso una implementación de Hibernate. El nombre del método puede ser cualquiera;
  • línea 43: no hay registros de SQL;
  • línea 44: la base de datos se creará si no existe;
  • líneas 50-58: definen el método EntityManagerFactory que se encargará de la persistencia de JPA. El método debe llamarse obligatoriamente [entityManagerFactory];
  • línea 51: el método recibe dos parámetros del tipo de los dos beans definidos anteriormente. Estos serán creados y luego inyectados por Spring como parámetros del método;
  • línea 53: establece la implementación JPA que se utilizará;
  • línea 54: establece las carpetas donde se encuentran las entidades JPA;
  • línea 55: establece la fuente de datos que se va a gestionar;
  • líneas 61-66: el administrador de transacciones. El método debe llamarse obligatoriamente [transactionManager]. Recibe como parámetro el bean de las líneas 51-58;
  • línea 64: el administrador de transacciones se asocia con EntityManagerFactory;

Los métodos anteriores se pueden definir en cualquier orden.

La ejecución del proyecto arroja los mismos resultados. Aparece un nuevo archivo en la carpeta del proyecto, el de la base de datos H2:

  

5.1.6. Creación de un archivo ejecutable

Para crear un archivo ejecutable del proyecto, se puede proceder de la siguiente manera:

  • en [1]: se crea una configuración de ejecución;
  • en [2]: de tipo [Java Application]
  • en [3]: se designa el proyecto que se ejecutará (utilice el botón Browse);
  • en [4]: indica la clase que se ejecutará;
  • en [5]: el nombre de la configuración de ejecución; puede ser cualquiera;
  • en [6]: se exporta el proyecto;
  • en [7]: en forma de un archivo ejecutable JAR;
  • en [8]: indica la ruta y el nombre del archivo ejecutable que se creará;
  • en [9]: el nombre de la configuración de ejecución creada en [5];
10
  
  • en [10], el archivo comprimido creado;

Una vez hecho esto, abrimos una consola en la carpeta que contiene el archivo ejecutable:

.....\dist>dir
12/06/2014  09:11        15 104 869 gs-accessing-data-jpa-02.jar

El archivo se ejecuta de la siguiente manera:


.....\dist>java -jar gs-accessing-data-jpa-02.jar

Los resultados obtenidos en la consola son los siguientes:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder para más detalles.
mars 10, 2015 5:27:20 PM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
mars 10, 2015 5:27:20 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.3.8.Final}
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
mars 10, 2015 5:27:22 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
mars 10, 2015 5:27:22 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
mars 10, 2015 5:27:22 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']

Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']

Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']

5.1.7. Creación de un proyecto [Spring Data]

Para crear un esqueleto de proyecto de Spring Data, se puede proceder de la siguiente manera:

  • en [1], se crea un nuevo proyecto;
  • en [2]: de tipo [Spring Starter Project];
  • el proyecto generado será un proyecto Maven. En [3], se indica el nombre del grupo del proyecto;
  • en [4]: se indica el nombre del artefacto (en este caso, un archivo .jar) que se creará al compilar el proyecto;
  • en [5]: el nombre del proyecto en Eclipse; puede ser cualquiera (no tiene que ser idéntico a [4]);
  • en [7]: se indica que se creará un proyecto con una capa [JPA] que contenga SGBD y MySQL. Las dependencias necesarias para dicho proyecto se incluirán entonces en el archivo [pom.xml];
  • en [8], indique el nombre de la carpeta del proyecto;
  • en [9], finalice el asistente;
  • en [10]: el proyecto creado;

El archivo [pom.xml] incorpora las dependencias necesarias para un proyecto JPA:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>istia.st.springdata</groupId>
    <artifactId>intro-spring-data-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>intro-spring-data-01</name>
    <description>démo spring data avec table de produits</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath/> <!-- búsqueda del elemento padre en el repositorio -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.IntroSpringData01Application</start-class>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • líneas 14-19: el proyecto padre de Maven;
  • líneas 28-31: la dependencia necesaria para JPA —incluirá [Spring Data]—;
  • líneas 32-36: la dependencia del controlador JDBC de MySQL;
  • líneas 37-41: las dependencias necesarias para las pruebas JUnit integradas con Spring;

La clase ejecutable [Application] no hace nada, pero está preconfigurada:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class IntroSpringData01Application {

    public static void main(String[] args) {
        SpringApplication.run(IntroSpringData01Application.class, args);
    }
}
  • la anotación [@SpringBootApplication] convierte a la clase en una clase de autoconfiguración del proyecto;

La clase de pruebas [ApplicationTests] no hace nada, pero está preconfigurada:


package demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = IntroSpringData01Application.class)
public class IntroSpringData01ApplicationTests {

    @Test
    public void contextLoads() {
    }

}
  • línea 9: la anotación [@SpringApplicationConfiguration] permite utilizar el archivo de configuración [IntroSpringData01Application]. De esta manera, la clase de prueba se beneficiará de todos los beans definidos en ese archivo;
  • línea 8: la anotación [@RunWith] permite la integración de Spring con JUnit: la clase podrá ejecutarse como una prueba JUnit. [@RunWith] es una anotación JUnit (línea 4), mientras que la clase [SpringJUnit4ClassRunner] es una clase de Spring (línea 6);

Ahora que tenemos un esqueleto de aplicación JPA, podemos completarlo.