Skip to content

5. Introdução ao Spring Data JPA

Neste capítulo, vamos estudar a seguinte arquitetura:

Inserimos uma camada [JPA] (Java Persistence API) entre a camada [DAO] e o driver JDBC do SGBD. Agora, é a camada JPA que emite os comandos SQL destinados ao SGBD. A camada [DAO] não processa mais ordens SQL, mas apenas objetos chamados entidades JPA, que são representações das diferentes tabelas do banco de dados em uso. Os campos dessas entidades são associados de forma exclusiva às colunas das tabelas por meio de anotações Java. É isso que permite que a camada JPA traduza para SQL as operações da camada [DAO] realizadas nas entidades JPA.

O Spring Data é um ramo do Spring dedicado ao acesso a dados, sejam eles armazenados em um banco de dados relacional SGBDR, em um banco NOSQL ou em outros tipos de repositórios. Aqui, estamos interessados apenas nos SGBDR e em seu acesso por meio do JPA. Posteriormente, por vezes escreveremos [Spring JPA] para nos referirmos, na verdade, ao [Spring Data JPA]. Na arquitetura acima, a camada [Spring Data] fornece recursos à camada [DAO] para gerenciar as entidades JPA.

JPA é, na verdade, uma especificação. Testaremos três de suas implementações:

5.1. Exemple-01

No site do Spring, há vários tutoriais para começar a usar o Spring [http://spring.io/guides]. Vamos usar um deles para apresentar o Spring Data. Para isso, utilizaremos o Spring Tool Suite (STS).

  • no [1], importamos um dos tutoriais do [spring.io/guides];
  • no [2], selecionamos o tutorial [Accessing Data Jpa], que mostra como acessar um banco de dados com o Spring Data;
  • em [3], escolhe-se um projeto configurado pelo Maven;
  • em [4], o tutorial pode ser fornecido de duas formas: [initial], que é uma versão vazia que se preenche seguindo o tutorial, ou [complete], que é a versão final do tutorial. Escolhemos esta última;
  • em [5], é possível optar por visualizar o tutorial em um navegador;
  • em [6], o projeto final.

5.1.1. A configuração do Maven para o projeto

As dependências do Maven do projeto são configuradas no arquivo [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 tudo -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <start-class>hello.Application</start-class>
</properties>
  • linhas 5-9: definem um projeto pai do Maven. É ele que define a maior parte das dependências do projeto. Elas podem ser suficientes, caso em que não se adicionam outras, ou não, caso em que se adicionam as dependências que faltam;
  • linhas 12-15: definem uma dependência do [spring-boot-starter-data-jpa]. Esse artefato contém as classes do Spring Data;
  • linhas 16-19: definem uma dependência do SGBD e do H2, que permitem criar e gerenciar bancos de dados em memória.

Vejamos as classes fornecidas por essas dependências:

São muitas:

  • algumas pertencem ao ecossistema Spring (aquelas que começam com “spring”);
  • outras pertencem ao ecossistema Hibernate (hibernate, jboss), cuja implementação JPA é utilizada aqui;
  • outras são bibliotecas de testes (junit, hamcrest);
  • outras são bibliotecas de logs (log4j, logback, slf4j);

Vamos manter todas elas. Para uma aplicação em produção, seria necessário manter apenas as que são necessárias.

Na linha 26 do arquivo [pom.xml], encontramos a linha:


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

Essa linha está relacionada às seguintes:


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

Nas linhas 6 a 9, o plug-in [spring-boot-maven-plugin] permite gerar o arquivo JAR executável da aplicação. A linha 26 do arquivo [pom.xml] indica, então, a classe executável desse JAR.

5.1.2. A camada [JPA]

O acesso ao banco de dados é feito por meio de uma camada [JPA], Java Persistence API:

  

A aplicação é básica e gerencia clientes [Customer]. A classe [Customer] faz parte da camada [JPA] e é a seguinte:


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);
    }

}

Um cliente possui um identificador [id], um nome próprio [firstName] e um sobrenome [lastName]. Cada instância [Customer] representa uma linha de uma tabela do banco de dados.

  • linha 8: anotação JPA que faz com que a persistência das instâncias [Customer] (Create, Read, Update, Delete) seja gerenciada por uma implementação JPA. De acordo com as dependências do Maven, percebe-se que é utilizada a implementação JPA / Hibernate;
  • linhas 11-12: anotações JPA que associam o campo [id] à chave primária da tabela [Customer]. A linha 12 indica que a implementação JPA utilizará o método de geração de chave primária específico do SGBD utilizado, neste caso, o H2;

Não há outras anotações para JPA. Nesse caso, serão utilizados valores padrão:

  • a tabela [Customer] terá o nome da classe, ou seja, [Customer];
  • as colunas dessa tabela terão os nomes dos campos da classe: [id, firstName, lastName], considerando que a distinção entre maiúsculas e minúsculas não é levada em conta no nome de uma coluna de tabela;

Observe-se que, em nenhum momento, a implementação JPA utilizada é nomeada.

5.1.3. A camada [Spring Data]

A classe [CustomerRepository] implementa a camada de acesso à tabela [Customer]. Seu código é o seguinte:

  

package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}

Trata-se, portanto, de uma interface e não de uma classe (linha 7). Ela estende a interface [CrudRepository], uma interface do Spring Data (linha 5). Essa interface é definida por dois tipos: o primeiro é o tipo dos elementos gerenciados, neste caso o tipo [Customer]; o segundo é o tipo da chave primária dos elementos gerenciados, neste caso o tipo [Long]. A interface [CrudRepository] é a seguinte:


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();
}

Essa interface define as operações CRUD (Criar – Ler – Atualizar – Excluir) que podem ser realizadas em um tipo JPA T:

  • linha 8: o método save permite persistir uma entidade T no banco de dados. Ele torna a entidade persistida com a chave primária que lhe foi atribuída pelo SGBD. Ele também permite atualizar uma entidade T identificada por sua chave primária id. A escolha entre uma ou outra ação depende do valor da chave primária id: se este for nulo, ocorre a operação de persistência; caso contrário, ocorre a operação de atualização;
  • linha 10: o mesmo, mas para uma lista de entidades;
  • linha 12: o método findOne permite recuperar uma entidade T identificada por sua chave primária id;
  • linha 22: o método delete permite excluir uma entidade T identificada por sua chave primária id;
  • linhas 24-28: variantes do método [delete];
  • linha 16: o método [findAll] permite recuperar todas as entidades T persistentes;
  • linha 18: o mesmo, mas limitado às entidades cuja lista de identificadores foi passada;

Voltemos à interface [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);
}
  • a linha 9 permite recuperar um [Customer] pelo seu nome [lastName];

E isso é tudo para a camada [DAO]. Não há classe de implementação da interface anterior. Ela é gerada em tempo de execução pelo [Spring Data]. Os métodos da interface [CrudRepository] são implementados automaticamente. Quanto aos métodos adicionados à interface [CustomerRepository], isso depende. Voltemos à definição de [Customer]:


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

O método da linha 9 é implementado automaticamente por [Spring Data] porque faz referência ao campo [lastName] (linha 3) de [Customer]. Quando encontra um método [findBySomething] na interface a ser implementada, o Spring Data o implementa por meio da seguinte consulta JPQL (Java Persistence Query Language):

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

Portanto, é necessário que o tipo T possua um campo chamado [something]. Assim, o método

List<Customer> findByLastName(String lastName);

será implementado por um código semelhante ao seguinte:

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

onde [em] designa o contexto de persistência JPA. Isso só é possível se a classe [Customer] tiver um campo chamado [lastName], o que de fato ocorre.

Concluindo, em casos simples, o Spring Data nos permite implementar a camada [DAO] com uma interface simples.

5.1.4. A camada [console]

  

A classe [Application] é a seguinte:


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 {
        // salvar alguns 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"));

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

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

        // buscar clientes pelo sobrenome
        System.out.println("Customer found with findByLastName('Bauer'):");
        System.out.println("--------------------------------------------");
        for (Customer bauer : repository.findByLastName("Bauer")) {
            System.out.println(bauer);
        }
    }

}
  • linha 9: a classe implementa a interface [CommandLineRunner], que é uma interface [Spring Boot] (linha 4). Essa interface possui apenas um método, o da linha 19;
  • linha 8: @SpringBootApplication é uma anotação que agrupa várias anotações [Spring Boot]:
    • @Configuration: indica que a classe é uma classe de configuração;
    • @EnableAutoConfiguration: solicita que [Spring Boot] crie por conta própria um determinado número de beans com base em várias propriedades, especialmente o conteúdo do Classpath do projeto. Como as bibliotecas do Hibernate estão no Classpath, o bean [entityManagerFactory] será implementado com o Hibernate. Como a biblioteca do SGBD H2 está no Classpath, o bean [dataSource] será implementado com o H2. No bean [dataSource], também é necessário definir o usuário e sua senha. Aqui, o Spring Boot utilizará o administrador padrão do H2, que não possui senha. Como a biblioteca [spring-tx] está no Classpath, será utilizado o gerenciador de transações do Spring;
    • @EnableWebMvc: se a biblioteca [spring-mvc] estiver no Classpath. Nesse caso, é realizada uma configuração automática para a aplicação web;
    • @ComponentScan: que indica ao Spring onde procurar os demais beans, configurações e serviços. Aqui, eles são procurados por padrão no pacote que contém a classe marcada, ou seja, o pacote [hello]. Assim, as classes [Customer] e [CustomerRepository] serão encontradas. Como a primeira possui a anotação [@Entity], ela será catalogada como uma entidade a ser gerenciada pelo Hibernate. Como a segunda estende a interface [CrudRepository], ela será registrada como um bean do Spring;
  • linhas 11-12: o bean [CustomerRepository] é injetado no código da classe principal;
  • linha 15: o método estático [run] da classe [SpringApplication] do projeto Spring Boot é executado. Seu parâmetro é a classe que possui uma anotação [Configuration] ou [EnableAutoConfiguration]. Tudo o que foi explicado anteriormente será então executado. O resultado é um contexto de aplicação Spring, ou seja, um conjunto de beans gerenciados pelo Spring;
  • linhas 19-48: as operações a seguir limitam-se a utilizar os métodos do bean que implementa a interface [CustomerRepository];

Os resultados na console são os seguintes:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
  • linhas 1-8: o logotipo do projeto Spring Boot;
  • linha 9: a classe [hello.Application] é executada;
  • linha 10: [AnnotationConfigApplicationContext] é uma classe que implementa a interface [ApplicationContext] do Spring. Trata-se de um contêiner de beans;
  • linha 11: o bean [entityManagerFactory] é implementado pela classe [LocalContainerEntityManagerFactory], uma classe do Spring. Ele gerencia a camada [JPA];
  • linha 12: surge o [Hibernate]. Foi essa implementação, JPA, que foi escolhida;
  • linha 19: um dialeto do Hibernate é a variante SQL a ser usada com o SGBD. Aqui, o dialeto [H2Dialect] indica que o Hibernate trabalhará com o SGBD e o H2;
  • linhas 21-22: o banco de dados é criado. A tabela [CUSTOMER] é criada. Isso significa que o Hibernate foi configurado para gerar as tabelas a partir das definições JPA; neste caso, a definição JPA da classe [Customer];
  • linhas 26-30: resultado do método [findAll] da interface;
  • linha 34: resultado do método [findOne] da interface;
  • linhas 38-39: resultados do método [findByLastName];
  • linhas 41 e seguintes: registros do encerramento do contexto Spring.

5.1.5. Configuração manual do projeto Spring Data

Duplicamos o projeto anterior no projeto [gs-accessing-data-jpa-02]:

  

Neste novo projeto, não vamos utilizar a configuração automática feita pelo Spring Boot. Vamos fazê-la manualmente. Isso pode ser útil caso as configurações padrão não sejam adequadas para nós.

Primeiramente, vamos especificar as dependências necessárias no arquivo [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>
        <!-- Banco de dados 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>
        <!-- use UTF-8 para tudo -->
        <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>
  • linhas 10-14: o projeto Maven pai, cujas bibliotecas vamos utilizar;
  • linhas 18-21: o Spring Data usado para acessar o banco de dados;
  • linhas 23-26: a implementação do Hibernate da especificação JPA;
  • linhas 28-31: o SGBD H2;
  • linhas 33-36: os bancos de dados costumam ser usados com pools de conexões abertas, o que evita a abertura e o fechamento repetidos de conexões. Aqui, a implementação utilizada é a de [tomcat-jdbc];

No novo projeto, a entidade [Customer] e a interface [CustomerRepository] permanecem inalteradas. Vamos alterar a classe [Application], que será dividida em duas classes:

  • [Config], que será a classe de configuração:
  • [Main], que será a classe executável;
  

A classe executável [Application] agora é a seguinte:


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) {
        // instanciação do contexto Spring
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CustomerRepository repository = context.getBean(CustomerRepository.class);

        // salvar alguns 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"));

        ...

        // fechamento do contexto
        context.close();
    }

}
  • linha 9: a classe [Application] não possui mais anotações de configuração;
  • linhas 3-7: observe que não há mais importações de pacotes [Spring Boot];
  • linha 12: instanciamos os beans do Spring. Obtemos o contexto do Spring, que contém a referência aos beans assim criados;
  • linha 13: solicita-se uma referência ao bean do tipo [CustomerRepository];

A classe [Config] que configura o projeto é a seguinte:


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={"package1","package2"})
public class AppConfig {

    // banco de dados H2
    @Bean
    public DataSource dataSource() {
        // fonte de dados TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuração de acesso JDBC
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:./demo");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        // uma conexão inicialmente aberta
        dataSource.setInitialSize(1);
        // resultado
        return dataSource;
    }

    // o provedor 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();
    }

    // Gerenciador de transações
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

}
  • linha 17: a anotação [@EnableTransactionManagement] indica que as anotações [@Transactional] devem ser interpretadas. Os métodos das interfaces [CrudRepository] possuem essas anotações. Eles, portanto, são executados dentro de uma transação;
  • linha 18: a anotação [@EnableJpaRepositories] permite indicar as pastas onde se encontram as interfaces Spring Data [CrudRepository]. Essas interfaces se tornarão componentes do Spring e estarão disponíveis em seu contexto;
  • linha 19: a anotação [@Configuration] transforma a classe [Config] em uma classe de configuração do Spring;
  • linha 20: a anotação [@ComponentScan] permite listar as pastas onde os componentes Spring devem ser procurados. Os componentes Spring são classes marcadas com anotações Spring, como @Service, @Component, @Controller, etc. Aqui, não há outros além daqueles definidos na classe [AppConfig]; por isso, a anotação foi colocada entre comentários;
  • linhas 24-37: definem a fonte de dados, o banco de dados H2. É a anotação @Bean na linha 25 que torna o objeto criado por esse método um componente gerenciado pelo Spring. O nome do método pode ser qualquer um. No entanto, ele deve ser chamado de [dataSource] se o EntityManagerFactory da linha 51 estiver ausente e for definido por autoconfiguração;
  • linha 30: o banco de dados se chamará [demo] e será gerado na pasta do projeto;
  • linhas 40-47: definem a implementação JPA utilizada, neste caso, uma implementação do Hibernate. O nome do método pode ser qualquer um;
  • linha 43: sem logs SQL;
  • linha 44: o banco de dados será criado caso não exista;
  • linhas 50-58: definem o EntityManagerFactory que irá gerenciar a persistência do JPA. O método deve se chamar obrigatoriamente [entityManagerFactory];
  • linha 51: o método recebe dois parâmetros com o tipo dos dois beans definidos anteriormente. Estes serão então instanciados e injetados pelo Spring como parâmetros do método;
  • linha 53: define a implementação JPA a ser utilizada;
  • linha 54: define as pastas onde se encontram as entidades JPA;
  • linha 55: define a fonte de dados a ser gerenciada;
  • linhas 61-66: o gerenciador de transações. O método deve se chamar obrigatoriamente [transactionManager]. Ele recebe como parâmetro o bean das linhas 51-58;
  • linha 64: o gerenciador de transações é associado ao EntityManagerFactory;

Os métodos anteriores podem ser definidos em qualquer ordem.

A execução do projeto produz os mesmos resultados. Um novo arquivo aparece na pasta do projeto, o do banco de dados H2:

  

5.1.6. Criação de um arquivo executável

Para criar um arquivo executável do projeto, pode-se proceder da seguinte forma:

  • em [1]: cria-se uma configuração de execução;
  • em [2]: do tipo [Java Application]
  • em [3]: indica o projeto a ser executado (use o botão Browse);
  • em [4]: indica a classe a ser executada;
  • em [5]: o nome da configuração de execução – pode ser qualquer um;
  • em [6]: exporta-se o projeto;
  • em [7]: na forma de um arquivo executável JAR;
  • em [8]: indica o caminho e o nome do arquivo executável a ser criado;
  • em [9]: o nome da configuração de execução criada em [5];
10
  
  • em [10], o arquivo compactado criado;

Feito isso, abra um terminal na pasta que contém o arquivo executável:

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

O arquivo executável é executado da seguinte maneira:


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

Os resultados obtidos no terminal são os seguintes:

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 mais detalhes.
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. Criação de um projeto [Spring Data]

Para criar um esqueleto de projeto Spring Data, pode-se proceder da seguinte maneira:

  • em [1], cria-se um novo projeto;
  • em [2]: do tipo [Spring Starter Project];
  • o projeto gerado será um projeto Maven. Em [3], indica-se o nome do grupo do projeto;
  • no [4]: indica-se o nome do artefato (um jar, neste caso) que será criado durante a compilação do projeto;
  • em [5]: o nome do projeto no Eclipse – pode ser qualquer um (não precisa ser idêntico a [4]);
  • em [7]: indica-se que será criado um projeto com uma camada [JPA], contendo os arquivos SGBD e MySQL. As dependências necessárias para tal projeto serão, então, incluídas no arquivo [pom.xml];
  • no [8], forneça o nome da pasta do projeto;
  • no [9], conclua o assistente;
  • em [10]: o projeto criado;

O arquivo [pom.xml] inclui as dependências necessárias para um projeto 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/> <!-- pesquisar pai no repositório -->
    </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>
  • linhas 14-19: o projeto Maven pai;
  • linhas 28-31: a dependência necessária para o JPA – incluirá o [Spring Data];
  • linhas 32-36: a dependência do driver JDBC do MySQL;
  • linhas 37-41: as dependências necessárias para os testes JUnit integrados com o Spring;

A classe executável [Application] não faz nada, mas está pré-configurada:


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);
    }
}
  • a anotação [@SpringBootApplication] torna a classe uma classe de autoconfiguração do projeto;

A classe de testes [ApplicationTests] não executa nenhuma ação, mas está pré-configurada:


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() {
    }

}
  • linha 9: a anotação [@SpringApplicationConfiguration] permite utilizar o arquivo de configuração [IntroSpringData01Application]. A classe de teste passará, assim, a dispor de todos os beans definidos por esse arquivo;
  • linha 8: a anotação [@RunWith] permite a integração do Spring com JUnit: a classe poderá ser executada como um teste JUnit. [@RunWith] é uma anotação JUnit (linha 4), enquanto a classe [SpringJUnit4ClassRunner] é uma classe Spring (linha 6);

Agora que temos uma estrutura básica da aplicação JPA, podemos completá-la.