Spring Boot: Ein Beispiel mit JPA und H2 DB

24.01.2016

Spring Boot: Ein Beispiel mit JPA und H2

Das folgende kleine Beispiel soll zeigen, wie einfach es ist mit SpringBoot eine Anwendung zu erstellen, die JPA verwendet um Entities in einer In-Memory-Datenbank (H2 oder HSQLDB) abzuspeichern. In dem Beispiel werden ein paar Personen mittels eines SQL-Scripts in der Datenbank H2 abgespeichert um sie dann mit Hilfe eine JPA-Repositories auszulesen und verschiedene Berechnungen durchzuführen. Als Buildsystem wird Maven und nicht Gradle verwendet.

Los geht es mit dem Erstellen eine Grundgerüsts. In dem Beispiel wird es auf der Kommodozeile gemacht. Es ist aber auch mit jeder IDE möglich, indem man ein Maven-Projekt erstellt und die Ordner-Struktur manuell anlegt.

Auf der Kommdozeile muss einfach das folgende Kommando ausgeführt werden:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=org.hameister -DartifactId=PersonManager -DinteractiveMode=false

Dadurch wird ein Maven-Projekt in einem Unterverzeichnis PersonManager angelegt. Dieses Projekt sollte nun beispielsweise in IntelliJ geöffnet werden. Mit Eclipse muss es als Maven-Projekt importiert werden.

Damit die Anwendung den SpringBoot-Konventionen entspricht, sollte die Klasse App in Application umbenannt werden.

Im nächsten Schritt wird die Maven-Datei pom.xml so angepasst, dass sie folgendermassen aussieht:

<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.hameister</groupId>
  <artifactId>PersonManager</artifactId>


  <version>1.0-SNAPSHOT</version>

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

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>


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

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

   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>
  </dependencies>

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

    </plugins>
  </build>
</project>

Die Versionsnummern für H2 und JUnit müssen nicht angegeben werden, weil diese im parent-POM definiert sind.

Damit die Applikation zu einer SpringBoot-Anwendung wird muss die Datei Application.java so angepasst werden, dass sie folgendermassen aussieht:

@SpringBootApplication
public class Application
{
    public static void main( String[] args ) throws Exception
    {
        SpringApplication.run(Application.class, args);
        System.out.println( "SpringBoot started!" );
    }
}

Mit der Annotation @SpringBootApplication sagt man, dass es sich um eine Spring-Boot Applikation handelt. Damit der Spring-Context korrekt initialisiert und gestartet wird, muss die Anweisung SpringApplication.run(Application.class, args); ergänzt werden.

Nun kann die Anwendung prinzipiell schon mal gestartet werden. Wenn man auf der Kommandozeile den folgenden Befehl ausführt, dann wird die Anwendung gestartet:

 mvn spring-boot:run

Da in der Anwendung Personendaten gespeichert werden sollen, wird im nächsten Schritt eine Klasse Person im Package org.hameister.personmanager.model angelegt, die den Namen, das Einkommen und das Geburtsdatum speichert.


package org.hameister.personmanager.model;

import javax.persistence.*;
import java.time.LocalDate;

/**
 * Created by hameister on 23.01.16.
 */
@Entity
@Table(name = "Person")
public class Person {

    @Id
    @GeneratedValue
    Long id;

    @Column(name = "name")
    String name;


    @Column(name = "salary")
    Long salary;


    @Column(name = "birthday")
    LocalDate  birthday;

    public Person() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getSalary() {
        return salary;
    }

    public void setSalary(Long salary) {
        this.salary = salary;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public void setBirthday(LocalDate birthday) {
        this.birthday = birthday;
    }
}

Die Annotation @Entity sorgt dafür, dass die Klasse als JPA-Entity erkannt wird. Die Daten werden in der Datenbanktabelle Person abgelegt indem man folgende Annotation verwendet: @Table(name = "Person"). Mit der Annotation @Column sorgt man dafür, dass ein Wert in einer bestimmten Datenbankspalte landet.

Um jetzt Daten vom Typ Person in die Datenbank zu importieren müssen ein paar Konfigurationseinstellungen vorgenommen werden.

Die folgende Abbildung zeigt wo die Dateien abgelegt werden müssen, damit SpringBoot sie automatisch findet.

Wichtig ist, dass der Ordner resouces unter dem Ordner main liegt, da die Konfiguration sonst nicht gefunden wird!

In der Datei application.properies werden die Datenbankeinstellungen für Hibernate hinterlegt und der Pfad zum SQL-Script mit den Daten. Anmerkung: Mit der Einstellung werden die Daten bei jedem Neustart gelöscht und es wird ein neues Datenbankschema angelegt. Für Testzwecke ist das in Ordnung. Im Produktivbetrieb sollte der Wert create-drop geändert werden!

spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
spring.jpa.hibernate.ddl-auto=create-drop

#Import Data
spring.datasource.data=classpath:/data/data.sql

In der SQL-Datei sind die Insert-Statements zu finden, die drei Personen in die Datenbank einfügen:

INSERT INTO Person(name, salary, birthday) values ('Bart Simpson', '100', '1980-04-02');
INSERT INTO Person(name, salary, birthday) values ('Homer Simpson', '200', '1969-01-02');
INSERT INTO Person(name, salary, birthday) values ('Maggie Simpson', '180', '1995-12-31');

Wenn man die Anwendung jetzt startet, erhält man eine Fehlermeldung, dass Probleme beim Import der Daten aufgetreten ist. Das liegt daran, dass ein Converter für die Umwandlung von java.time.LocalDate in java.sql.Date benötigt wird.

package org.hameister.personmanager.repo;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.sql.Date;
import java.time.LocalDate;

/**
 * Created by hameister on 23.01.16.
 */

@Converter(autoApply = true)
public class LocalDateConverter implements AttributeConverter&l;LocalDate, Date> {

    @Override
    public java.sql.Date convertToDatabaseColumn(java.time.LocalDate attribute) {
        return attribute == null ? null : java.sql.Date.valueOf(attribute);
    }

    @Override
    public java.time.LocalDate convertToEntityAttribute(java.sql.Date dbData) {
        return dbData == null ? null : dbData.toLocalDate();
    }
}

Nach dem Hinzufügen des Converter fährt die Applikation fehlerfrei hoch und das SQL-Skript wird ausgeführt und die Daten importiert.

Als nächstes wird ein JPA-Repository angelegt, um auf die Daten zuzugreifen.

package org.hameister.personmanager.repo;

import org.hameister.personmanager.model.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}

Um das Repository nun anzusprechen um beispielsweise das Druchschnittseinkommen aller Personen zu berechnen, wird ein Service erstellt, der dies macht. Zum Testen werden außerdem noch ein paar Ausgaben ergänzt, die das Geburtsdatum und das Gehalt anzeigen.


package org.hameister.service;

import org.hameister.model.Person;
import org.hameister.repo.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * Created by hameister on 23.01.16.
 */
@Service
public class PersonService {


    @Autowired
    PersonRepository personRepository;

    public double  averageSalary() {

        double salarySum = 0;
        List<Person> personList= personRepository.findAll();

        for( Person person : personList) {

            salarySum =  salarySum +person.getSalary();


            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");

            if(person.getBirthday()!=null) {
                String birthday = person.getBirthday().format(formatter);
                System.out.println("Der Geburtstag von " + person.getName() + " ist: " + birthday+ " und der Verdienst ist:"+ person.getSalary());
            }
        }

        return  salarySum / personList.size();
    }

    public Person  addPerson(Person person) {

        return personRepository.save(person);
    }
}

Die Annotation @Service sorgt dafür, dass die Klasse als Service erkannt wird.

Da später eine weiterere Person programmatisch hinzugefügt werden soll, existiert auch eine Funktion addPerson

Anschliessend fügen wird noch eine Komponente (@Component) hinzu, die als eine Art Kern dient und den Service und das Repository initialisiert.

Die Klasse wird einfach Analyzer genannt.

package org.hameister;

import org.hameister.personmanager.model.Person;
import org.hameister.personmanager.repo.PersonRepository;
import org.hameister.personmanager.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Created by hameister on 09.01.16.
 */

@Component
public class Analyzer {

    @Autowired
    PersonRepository personRepository;

    @Autowired
    PersonService personService;

    @Autowired
    public Analyzer(PersonRepository repository, PersonService service) {
        this.personRepository = repository;
        this.personService = service;

        if (personRepository != null) {
            System.out.println("Number of Persons in DB:" + personRepository.findAll().size());


            System.out.println("Avg. Salary:" + personService.averageSalary());


            // Insert new Person into the Database
            Person person = new Person();
            person.setName("Lisa Simpson");
            person.setBirthday(LocalDate.of(1980, 12, 24));
            person.setSalary(400l);

            Person newPerson = personService.addPerson(person);


            if (newPerson != null) {

                System.out.println("Number of Persons in DB:" + personRepository.findAll().size());

                // Recalculate Average Salary
                System.out.println("Avg. Salary:" + personService.averageSalary());

            }

        }
    }
}

In der Klasse werden das Repository und der Service mittels @Autowired verbunden. Anschliessend können sie benutzt werden. Es wird beispielsweise die Anzahl der Personen in der Datenbank ausgegeben und das durchschnittliche Gehalt beim Service abgefragt und anschliessend auch ausgegeben.

Abschliessend wird eine weitere Person programatisch in die Datenbank eingefügt und überprüft, ob das Einfügen erfolgreich war und sich das Durchschnittseinkommen verändert hat.

Wenn alles richtig ist dann sollte folgende Ausgabe zu sehen sein:

Number of Persons in DB:3
Der Geburtstag von Bart Simpson ist: 02.04.1980 und der Verdienst ist:100
Der Geburtstag von Homer Simpson ist: 02.01.1969 und der Verdienst ist:200
Der Geburtstag von Maggie Simpson ist: 31.12.1995 und der Verdienst ist:180
Avg. Salary:160.0
Number of Persons in DB:4
Der Geburtstag von Bart Simpson ist: 02.04.1980 und der Verdienst ist:100
Der Geburtstag von Homer Simpson ist: 02.01.1969 und der Verdienst ist:200
Der Geburtstag von Maggie Simpson ist: 31.12.1995 und der Verdienst ist:180
Der Geburtstag von Lisa Simpson ist: 24.12.1980 und der Verdienst ist:400
Avg. Salary:220.0

Der komplette Quellcode ist bei GitHub unter folgender URL zu finden: SpringBootPersonManager

Achtung: Die Version mit dem Tag V1 verwenden. (Auf der Kommandozeile git checkout V1 )