Skip to content

5. Inleiding tot Spring Data JPA

In dit hoofdstuk gaan we de volgende architectuur bestuderen:

We voegen een laag [JPA] (Java Persistence API) in tussen de laag [DAO] en de driver JDBC van de SGBD. Het is nu de laag JPA die de opdrachten SQL verzendt die bestemd zijn voor de SGBD. De laag [DAO] verwerkt geen opdrachten SQL meer, maar uitsluitend objecten die JPA-entiteiten worden genoemd en die afspiegelingen zijn van de verschillende tabellen in de gebruikte database. De velden van deze entiteiten worden via Java-annotaties op unieke wijze gekoppeld aan kolommen in tabellen. Hierdoor kan de laag JPA de bewerkingen van de laag [DAO], die op de entiteiten JPA worden uitgevoerd, vertalen naar SQL.

Spring Data is een tak van Spring die zich bezighoudt met gegevenstoegang, of deze gegevens nu zijn opgeslagen in een relationele database SGBDR, een NOSQL-database of andere soorten opslagplaatsen. We richten ons hier uitsluitend op de SGBDR en de toegang daartoe via JPA. Verderop zullen we soms [Spring JPA] schrijven om in feite [Spring Data JPA] aan te duiden. In de bovenstaande architectuur biedt de laag [Spring Data] de laag [DAO] functionaliteit voor het beheer van de entiteiten JPA.

JPA is in feite een specificatie. We zullen drie van de implementaties ervan testen:

5.1. Exemple-01

Op de website van Spring zijn talrijke tutorials te vinden om aan de slag te gaan met Spring [http://spring.io/guides]. We gaan er een gebruiken om Spring Data te introduceren. Hiervoor gebruiken we de Spring Tool Suite (STS).

  • in [1] importeren we een van de tutorials uit [spring.io/guides];
  • in [2] kiezen we de tutorial [Accessing Data Jpa] die laat zien hoe je met Spring Data toegang krijgt tot een database;
  • in [3] kiezen we een door Maven geconfigureerd project;
  • in [4] kan de tutorial in twee vormen worden aangeboden: [initial], een lege versie die je vult terwijl je de tutorial volgt, of [complete], de definitieve versie van de tutorial. We kiezen voor deze laatste;
  • in [5] kun je ervoor kiezen om de tutorial in een browser te bekijken;
  • in [6], het uiteindelijke project.

5.1.1. De Maven-configuratie van het project

De Maven-afhankelijkheden van het project worden geconfigureerd in het bestand [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>
        <!-- gebruik UTF-8 voor alles -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <start-class>hello.Application</start-class>
</properties>
  • regels 5-9: definiëren een bovenliggend Maven-project. Dit project bepaalt het grootste deel van de afhankelijkheden van het project. Deze kunnen voldoende zijn, in welk geval er niets wordt toegevoegd, of onvoldoende, in welk geval de ontbrekende afhankelijkheden worden toegevoegd;
  • regels 12-15: definiëren een afhankelijkheid van [spring-boot-starter-data-jpa]. Dit artefact bevat de klassen van Spring Data;
  • regels 16-19: definiëren een afhankelijkheid van SGBD en H2, waarmee in-memory-databases kunnen worden aangemaakt en beheerd.

Laten we eens kijken naar de klassen die door deze afhankelijkheden worden geleverd:

Het zijn er heel veel:

  • sommige behoren tot het Spring-ecosysteem (die welke beginnen met spring);
  • andere behoren tot het Hibernate-ecosysteem (hibernate, jboss), waarvan we hier de implementatie JPA gebruiken;
  • weer andere zijn testbibliotheken (junit, hamcrest);
  • weer andere zijn logboekbibliotheken (log4j, logback, slf4j);

We zullen ze allemaal behouden. Voor een applicatie in productie zouden we alleen die moeten behouden die nodig zijn.

Op regel 26 van het bestand [pom.xml] staat de volgende regel:


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

Deze regel houdt verband met de volgende regels:


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

Regels 6-9: met de plug-in [spring-boot-maven-plugin] kan het uitvoerbare JAR-bestand van de applicatie worden gegenereerd. Regel 26 van het bestand [pom.xml] verwijst dan naar de uitvoerbare klasse van dit JAR-bestand.

5.1.2. De laag [JPA]

Toegang tot de database verloopt via een laag [JPA], Java Persistence API:

  

De applicatie is eenvoudig en verwerkt klanten [Customer]. De klasse [Customer] maakt deel uit van de laag [JPA] en ziet er als volgt uit:


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

}

Een klant heeft een ID [id], een voornaam [firstName] en een achternaam [lastName]. Elk exemplaar [Customer] vertegenwoordigt een rij in een databasetabel.

  • regel 8: annotatie JPA, waardoor de persistentie van de instanties [Customer] (Create, Read, Update, Delete) wordt beheerd door een implementatie JPA. Uit de Maven-afhankelijkheden blijkt dat de implementatie JPA / Hibernate wordt gebruikt;
  • regels 11-12: JPA-annotaties die het veld [id] koppelen aan de primaire sleutel van de tabel [Customer]. Regel 12 geeft aan dat de implementatie JPA de methode voor het genereren van de primaire sleutel zal gebruiken die eigen is aan de gebruikte SGBD, in dit geval H2;

Er zijn geen andere annotaties voor JPA. Er worden dan standaardwaarden gebruikt:

  • de tabel van [Customer] krijgt de naam van de klasse, d.w.z. [Customer];
  • de kolommen van deze tabel krijgen de namen van de velden van de klasse: [id, firstName, lastName], waarbij hoofdletters en kleine letters in de naam van een tabelkolom niet van belang zijn;

Opgemerkt moet worden dat de gebruikte implementatie JPA op geen enkel moment bij naam wordt genoemd.

5.1.3. De laag [Spring Data]

De klasse [CustomerRepository] implementeert de toegangslaag voor de tabel [Customer]. De code ervan is als volgt:

  

package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}

Het gaat dus om een interface en niet om een klasse (regel 7). Deze interface is een uitbreiding van de interface [CrudRepository], een interface van Spring Data (regel 5). Deze interface wordt gedefinieerd door twee typen: het eerste is het type van de beheerde elementen, in dit geval het type [Customer], het tweede is het type van de primaire sleutel van de beheerde elementen, in dit geval het type [Long]. De interface [CrudRepository] ziet er als volgt uit:


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

Deze interface definieert de bewerkingen CRUD (Create – Read – Update – Delete) die kunnen worden uitgevoerd op een type JPA T:

  • regel 8: met de methode `save` kan een entiteit van type T in de database worden opgeslagen. Hiermee wordt de entiteit opgeslagen met de primaire sleutel die door de methode SGBD is toegekend. Deze methode maakt het ook mogelijk om een entiteit van type T bij te werken die wordt geïdentificeerd door haar primaire sleutel id. De keuze voor de ene of de andere actie hangt af van de waarde van de primaire sleutel id: als deze null is, vindt de opslagbewerking plaats, anders de bijwerkingsbewerking;
  • regel 10: idem, maar dan voor een lijst met entiteiten;
  • regel 12: met de methode findOne kan een entiteit T worden opgehaald die wordt geïdentificeerd door de primaire sleutel id;
  • regel 22: met de methode `delete` kan een entiteit T worden verwijderd die wordt geïdentificeerd door zijn primaire sleutel `id`;
  • regels 24-28: varianten van de methode [delete];
  • regel 16: met de methode [findAll] kunnen alle opgeslagen entiteiten T worden opgehaald;
  • regel 18: idem, maar beperkt tot de entiteiten waarvan de lijst met identificatiecodes is doorgegeven;

Laten we teruggaan naar de 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);
}
  • met regel 9 kan een [Customer] worden opgezocht op basis van de naam [lastName];

En dat is alles voor de laag [DAO]. Er is geen implementatieklasse voor de vorige interface. Deze wordt tijdens de uitvoering gegenereerd door [Spring Data]. De methoden van de interface [CrudRepository] worden automatisch geïmplementeerd. Voor de methoden die aan de interface [CustomerRepository] zijn toegevoegd, hangt het ervan af. Laten we teruggaan naar de definitie van [Customer]:


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

De methode op regel 9 wordt automatisch geïmplementeerd door [Spring Data], omdat deze verwijst naar het veld [lastName] (regel 3) van [Customer]. Wanneer Spring Data een methode [findBySomething] tegenkomt in de te implementeren interface, implementeert het deze via de volgende JPQL-query (Java Persistence Query Language):

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

Het type T moet dus een veld hebben met de naam [something]. Zo wordt de methode

List<Customer> findByLastName(String lastName);

zal worden geïmplementeerd met code die er ongeveer zo uitziet:

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

waarbij [em] verwijst naar de persistentiecontext JPA. Dit is alleen mogelijk als de klasse [Customer] een veld heeft met de naam [lastName], wat het geval is.

Kortom, in eenvoudige gevallen stelt Spring Data ons in staat om de laag [DAO] te implementeren met een eenvoudige interface.

5.1.4. De laag [console]

  

De klasse [Application] ziet er als volgt uit:


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 {
        // een paar klanten opslaan
        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"));

        // alle klanten ophalen
        System.out.println("Customers found with findAll():");
        System.out.println("-------------------------------");
        for (Customer customer : repository.findAll()) {
            System.out.println(customer);
        }
        System.out.println();

        // een individuele klant ophalen via ID
        Customer customer = repository.findOne(1L);
        System.out.println("Customer found with findOne(1L):");
        System.out.println("--------------------------------");
        System.out.println(customer);
        System.out.println();

        // klanten op achternaam ophalen
        System.out.println("Customer found with findByLastName('Bauer'):");
        System.out.println("--------------------------------------------");
        for (Customer bauer : repository.findByLastName("Bauer")) {
            System.out.println(bauer);
        }
    }

}
  • regel 9: de klasse implementeert de interface [CommandLineRunner], die een interface is van [Spring Boot] (regel 4). Deze interface heeft slechts één methode, namelijk die op regel 19;
  • regel 8: @SpringBootApplication is een annotatie die meerdere [Spring Boot]-annotaties groepeert:
    • @Configuration: geeft aan dat de klasse een configuratieklasse is;
    • @EnableAutoConfiguration: vraagt [Spring Boot] om zelf een bepaald aantal beans aan te maken op basis van diverse eigenschappen, met name de inhoud van het classpath van het project. Omdat de Hibernate-bibliotheken in het Classpath staan, zal de bean [entityManagerFactory] met Hibernate worden geïmplementeerd. Omdat de bibliotheek van SGBD H2 in de Classpath staat, zal de bean [dataSource] worden geïmplementeerd met H2. In de bean [dataSource] moeten ook de gebruiker en het wachtwoord worden gedefinieerd. Hier zal Spring Boot de standaardbeheerder van H2 gebruiken, die geen wachtwoord heeft. Omdat de bibliotheek [spring-tx] in het classpath staat, wordt de transactiebeheerder van Spring gebruikt;
    • @EnableWebMvc: als de bibliotheek [spring-mvc] in het classpath staat. In dat geval wordt de webapplicatie automatisch geconfigureerd;
    • @ComponentScan: deze geeft aan Spring aan waar de andere beans, configuraties en services moeten worden gezocht. Hier worden ze standaard gezocht in het pakket dat de gemarkeerde klasse bevat, d.w.z. het pakket [hello]. Zo zullen de klassen [Customer] en [CustomerRepository] worden gevonden. Omdat de eerste de annotatie [@Entity] heeft, wordt deze gecatalogiseerd als een entiteit die door Hibernate moet worden beheerd. Omdat de tweede de interface [CrudRepository] uitbreidt, wordt deze geregistreerd als een Spring-bean;
  • regels 11-12: de bean [CustomerRepository] wordt geïnjecteerd in de code van de hoofdklasse;
  • regel 15: de statische methode [run] van de klasse [SpringApplication] van het Spring Boot-project wordt uitgevoerd. De parameter ervan is de klasse die een annotatie [Configuration] of [EnableAutoConfiguration] heeft. Alles wat hierboven is uitgelegd, zal dan plaatsvinden. Het resultaat is een Spring-applicatiecontext, d.w.z. een verzameling beans die door Spring worden beheerd;
  • regels 19-48: de volgende bewerkingen maken alleen gebruik van de methoden van de bean die de interface [CustomerRepository] implementeert;

De console-uitvoer is als volgt:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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
  • regels 1-8: het logo van het Spring Boot-project;
  • regel 9: de klasse [hello.Application] wordt uitgevoerd;
  • regel 10: [AnnotationConfigApplicationContext] is een klasse die de Spring-interface [ApplicationContext] implementeert. Dit is een bean-container;
  • regel 11: de bean [entityManagerFactory] wordt geïmplementeerd met de klasse [LocalContainerEntityManagerFactory], een Spring-klasse. Deze beheert de laag [JPA];
  • regel 12: [Hibernate] verschijnt. Deze implementatie, JPA, is gekozen;
  • regel 19: een Hibernate-dialect is de variant SQL die moet worden gebruikt met SGBD. Hier geeft het dialect [H2Dialect] aan dat Hibernate gaat werken met de SGBD en H2;
  • regels 21-22: de database wordt aangemaakt. De tabel [CUSTOMER] wordt aangemaakt. Dit betekent dat Hibernate is geconfigureerd om tabellen te genereren op basis van de definities JPA, in dit geval de definitie JPA van de klasse [Customer];
  • regels 26-30: resultaat van de methode [findAll] van de interface;
  • regel 34: resultaat van de methode [findOne] van de interface;
  • regels 38-39: resultaten van de methode [findByLastName];
  • regels 41 en volgende: logboeken van het afsluiten van de Spring-context.

5.1.5. Handmatige configuratie van het Spring Data-project

We dupliceren het vorige project in het project [gs-accessing-data-jpa-02]:

  

In dit nieuwe project gaan we niet vertrouwen op de automatische configuratie door Spring Boot. We gaan dit handmatig doen. Dit kan handig zijn als de standaardconfiguraties niet aan onze eisen voldoen.

Allereerst gaan we de benodigde afhankelijkheden expliciet opgeven in het bestand [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>
        <!-- H2-database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <!-- Tomcat JDBC -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <!-- gebruik UTF-8 voor alles -->
        <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>
  • regels 10-14: het bovenliggende Maven-project waarvan we de bibliotheken gaan gebruiken die daarin zijn gedefinieerd;
  • regels 18-21: Spring Data, gebruikt om toegang te krijgen tot de database;
  • regels 23-26: de Hibernate-implementatie van de specificatie JPA;
  • regels 28-31: de SGBD H2;
  • regels 33-36: databases worden vaak gebruikt met pools van open verbindingen, waardoor herhaaldelijk openen en sluiten van verbindingen wordt voorkomen. Hier wordt de implementatie van [tomcat-jdbc] gebruikt;

In het nieuwe project blijven de entiteit [Customer] en de interface [CustomerRepository] ongewijzigd. We gaan de klasse [Application] aanpassen door deze op te splitsen in twee klassen:

  • [Config], de configuratieklasse:
  • [Main], de uitvoerbare klasse;
  

De uitvoerbare klasse [Application] ziet er nu als volgt uit:


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) {
        // instantiëren van Spring-context
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CustomerRepository repository = context.getBean(CustomerRepository.class);

        // een paar klanten opslaan
        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"));

        ...

        // context afsluiten
        context.close();
    }

}
  • regel 9: de klasse [Application] heeft geen configuratie-annotaties meer;
  • regels 3-7: er zijn geen imports meer van het pakket [Spring Boot];
  • regel 12: de Spring-beans worden geïnstantieerd. We verkrijgen de Spring-context die de verwijzing naar de aldus aangemaakte beans bevat;
  • regel 13: er wordt een verwijzing aangevraagd naar de bean van het type [CustomerRepository];

De klasse [Config] die het project configureert, ziet er als volgt uit:


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 {

    // de database H2
    @Bean
    public DataSource dataSource() {
        // gegevensbron TomcatJdbc
        DataSource dataSource = new DataSource();
        // toegangscontrole JDBC
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:./demo");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        // een aanvankelijk geopende verbinding
        dataSource.setInitialSize(1);
        // resultaat
        return dataSource;
    }

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

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

}
  • regel 17: de annotatie [@EnableTransactionManagement] geeft aan dat de annotaties [@Transactional] moeten worden geïnterpreteerd. De methoden van de interfaces [CrudRepository] hebben deze annotaties. Ze worden dan uitgevoerd binnen een transactie;
  • regel 18: de annotatie [@EnableJpaRepositories] maakt het mogelijk om de mappen aan te wijzen waarin de Spring Data-interfaces [CrudRepository] zich bevinden. Deze interfaces worden Spring-componenten en zijn beschikbaar in de Spring-context;
  • regel 19: de annotatie [@Configuration] maakt van de klasse [Config] een Spring-configuratieklasse;
  • regel 20: met de annotatie [@ComponentScan] kunnen de mappen worden opgegeven waarin naar Spring-componenten moet worden gezocht. Spring-componenten zijn klassen die zijn gemarkeerd met Spring-annotaties zoals @Service, @Component, @Controller, ... Hier zijn er geen andere dan die welke zijn gedefinieerd binnen de klasse [AppConfig], dus is de annotatie uitgecommentarieerd;
  • regels 24-37: definiëren de gegevensbron, de database H2. Het is de @Bean-annotatie op regel 25 die ervoor zorgt dat het door deze methode aangemaakte object een door Spring beheerde component wordt. De naam van de methode kan hier willekeurig zijn. Deze moet echter [dataSource] heten als EntityManagerFactory uit regel 51 ontbreekt en via autoconfiguratie wordt gedefinieerd;
  • regel 30: de database krijgt de naam [demo] en wordt gegenereerd in de projectmap;
  • regels 40-47: definiëren de gebruikte implementatie JPA, in dit geval een Hibernate-implementatie. De naam van de methode kan hier willekeurig zijn;
  • regel 43: geen logbestanden voor SQL;
  • regel 44: de database wordt aangemaakt als deze nog niet bestaat;
  • regels 50-58: definiëren de EntityManagerFactory die de persistentie van JPA zal beheren. De methode moet verplicht [entityManagerFactory] heten;
  • regel 51: de methode ontvangt twee parameters van het type van de twee eerder gedefinieerde beans. Deze worden vervolgens geconstrueerd en door Spring als parameters van de methode geïnjecteerd;
  • regel 53: stelt de gebruikte implementatie JPA vast;
  • regel 54: hier worden de mappen opgegeven waarin de entiteiten JPA te vinden zijn;
  • regel 55: stelt de te beheren gegevensbron vast;
  • regels 61-66: de transactiebeheerder. De methode moet verplicht [transactionManager] heten. Deze ontvangt als parameter de bean uit de regels 51-58;
  • regel 64: de transactiebeheerder wordt gekoppeld aan EntityManagerFactory;

De voorgaande methoden kunnen in willekeurige volgorde worden gedefinieerd.

Het uitvoeren van het project levert dezelfde resultaten op. Er verschijnt een nieuw bestand in de projectmap, namelijk het databasebestand H2:

  

5.1.6. Een uitvoerbaar archief aanmaken

Om een uitvoerbaar archief van het project aan te maken, kunt u als volgt te werk gaan:

  • in [1]: maak een uitvoerconfiguratie aan;
  • in [2]: van het type [Java Application]
  • in [3]: geef het uit te voeren project aan (gebruik de knop Browse);
  • in [4]: geeft de uit te voeren klasse aan;
  • in [5]: de naam van de uitvoeringsconfiguratie – kan willekeurig zijn;
  • in [6]: het project wordt geëxporteerd;
  • in [7]: in de vorm van een uitvoerbaar JAR-archief;
  • in [8]: geeft het pad en de naam aan van het uit te voeren bestand dat moet worden aangemaakt;
  • in [9]: de naam van de uitvoerconfiguratie die in [5] is aangemaakt;
10
  
  • in [10]: het aangemaakte archief;

Zodra dit is gebeurd, openen we een console in de map die het uitvoerbare archief bevat:

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

Het archief wordt als volgt uitgevoerd:


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

De resultaten die in de console worden weergegeven, zijn als volgt:

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 voor meer informatie.
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. Een project aanmaken [Spring Data]

Om een Spring Data-projectsjabloon aan te maken, kunt u als volgt te werk gaan:

  • in [1] maakt u een nieuw project aan;
  • in [2]: van het type [Spring Starter Project];
  • het gegenereerde project is een Maven-project. In [3] geef je de naam van de projectgroep op;
  • in [4]: geef je de naam op van het artefact (hier een jar) dat bij het bouwen van het project wordt aangemaakt;
  • in [5]: de Eclipse-naam van het project – deze kan willekeurig zijn (hoeft niet identiek te zijn aan [4]);
  • in [7]: hier wordt aangegeven dat er een project wordt aangemaakt met een laag [JPA] met de SGBD MySQL. De benodigde afhankelijkheden voor een dergelijk project worden vervolgens opgenomen in het bestand [pom.xml];
  • in [8], geef de naam van de projectmap op;
  • in [9], de wizard afsluiten;
  • in [10]: het aangemaakte project;

Het bestand [pom.xml] bevat de benodigde afhankelijkheden voor een project 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/> <!-- bovenliggende entiteit opzoeken in de repository -->
    </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>
  • regels 14-19: het bovenliggende Maven-project;
  • regels 28-31: de afhankelijkheid die nodig is voor JPA – zal [Spring Data] opnemen;
  • regels 32-36: de afhankelijkheid van de driver JDBC van MySQL;
  • regels 37-41: de vereiste afhankelijkheden voor de in Spring geïntegreerde tests JUnit;

De uitvoerbare klasse [Application] doet niets, maar is vooraf geconfigureerd:


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);
    }
}
  • de annotatie [@SpringBootApplication] maakt van de klasse een zelfconfigurerende klasse van het project;

De testklasse [ApplicationTests] doet niets, maar is vooraf geconfigureerd:


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

}
  • regel 9: de annotatie [@SpringApplicationConfiguration] maakt het mogelijk om het configuratiebestand [IntroSpringData01Application] te gebruiken. De testklasse zal zo profiteren van alle beans die in dit bestand zijn gedefinieerd;
  • regel 8: de annotatie [@RunWith] maakt de integratie van Spring met JUnit mogelijk: de klasse kan worden uitgevoerd als een test JUnit. [@RunWith] is een annotatie JUnit (regel 4), terwijl de klasse [SpringJUnit4ClassRunner] een Spring-klasse is (regel 6);

Nu we een applicatieskelet JPA hebben, kunnen we dit aanvullen.