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
)