#4. Integrieren einer Datenbank in die Spring Boot API

Nach den ersten beiden Artikel, die sich darum drehen, Visual Studio Code (folgend VSCode) zu konfigurieren und eine einfache Spring Boot API aufzubauen, bauen wir nun in die API eine Datenbank ein. Voraussetzung zum Verständnis oder Nachbau dieses Artikels ist es, die 3 vorherigen Artikel zu verstehen.

Konfiguration

Wir wollen nun 2 Datenbankverbindungen implementieren. Zum einen soll in die Spring Boot API eine H2 Datenbank integriert werden, um möglichst schnell lokale Tests durchführen zu können, ohne ein Datenbanksystem zur Verfügung stellen zu müssen. Und als produktive Datenbank soll MariaDB integriert werden. Die in Kapitel 3 integrierte Spring API soll dann erweitert werden, um Datensätze direkt aus der Datenbank zu lesen und Datensätze zu schreiben.

Hinzufügen der Abhängigkeiten zu maven

Um mit der MariaDB und H2 Datenbank zu kommunizieren benötigen wir 3 zusätzliche maven dependencies, die wir in die pom.xml eintragen müssen. Eine stellt die JPA Funktionalität her, zwei weitere sind als Übersetzer für H2 und MariaDB notwendig.

<!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>2.7.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>

Ggf. musst du danach die Projekt-Konfiguration aktualisieren.

Wichtig: Maven gibt die Konfigurationsdatei für die H2 Datenbank mit dem Scope “Test” aus. Dies ist auch gut und richtig, da i.d.R. eine H2 Datenbank nicht Teil der produktiven auslieferfähigen Artefakte sein sollte. Dies führt allerdings dazu, das in Folge dieses Artikels die H2 Datenbank beim debuggen über VSCode nicht initialisiert wird. Grund ist, dass der Debugger die Anwendung über java direkt startet. Für jetzt werden den “test-scope” entfernen.

Datenbankeinstellungen

Im nächsten Schritt werden die Einstellungen für die Datenbankverbindungen in die Konfigurationsdaten eingetragen. Im vorigen Kapitel haben wir die Konfiguration für “Default”, “test” und “prod” hinterlegt. In diesem Beispiel werden wir die “Defaultkonfiguration” der Spring Boot API mit der H2 Datenbank bestücken, in der “Test-” und “Produktionskonfiguration” MariaDB konfigurieren.

Zu diesem Zweck öffnen wir als erstes die Datei “application.properties” im Pfad “src/main/resources” und tragen dort die Konfiguration für die H2-Datenbank ein.

spring.datasource.url=jdbc:h2:mem:history_test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 
spring.datasource.username=sa 
spring.datasource.password=password
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Die ersten drei Zeilen definieren den verwendeten Treiber, den Datenbanknamen, Verbindungseinstellungen und die Authentifierungsdetails.

In Zeile 4 & 5 wird konfiguriert, daß SQL Queries bei der Ausführung in der Ausgabe angezeigt werden. In Zeile 6 wird der SQL-Dialekt angegeben, den Hibernate (das ORM Framework was von JPA verwendet wird) vewenden soll.

Nun tragen wir in die Dateien “application-test.properties” und “application-prod.properties” die Datenbankkonfiguration für MariaDB ein.

spring.datasource.url = jdbc:mariadb://localhost/telemetry
spring.datasource.username = telemetryadmin
spring.datasource.password = admin123
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

Im Prinzip ist die Konfiguration identisch zur vorherigen. In der Prod-Datei sollte das Logging in den Zeilen 4-5 natürlich ausgeschaltet sein. Zeile 6 erfordert allerdings etwas mehr Erklärung:
JPA versucht standardmäßig die Datenbank anzulegen, wenn es sich um eine “embedded” Datenbank handelt. Als “embedded” werden die Datenbanktypen hsql, h2, und derby angesehen. Daher benötigen wir diesen Eintrag auch nicht in der default-Konfiguration. Im Falle jeder anderen Datenbank wird JPA diese nicht automatisch anlegen. Hier gibt es im Prinzip 3 Möglichkeiten die Datenbank zu erstellen:

  • JPA erstellt die Datenbank über “ddl-auto=create-drop” (So machen wir es hier).
  • Du erstellst eine “schema.sql” Datei im Verzeichnis “src/main/resources” zum Anlegen der Datenbank und eine “data.sql” zum befüllen der Datenbank mit initialen Daten.
  • Du nutzt ein Datenbankmigrationstool wie Flyway oder Liquibase.

Wir werden in diesem Beispiel die Datenbank über JPA erstellen.
Wichtig: Dies ist nicht für eine Produktionsumgebung empfohlen. Das Datenbankmanagement über ein Migrationstool werde ich in einem späteren Artikel beschreiben.

Anpassen des Codes

Erweitern der System-Klasse

Irgendwie muss JPA nun wissen, wie die zur Klasse zugehörigen Tabellen in der Datenbank heißen sollen. Dies passiert wieder mit Annotations, dieses mal mit “@Entity” und “@Table(…)“. Als erstes definieren wir den Tabellennamen für die im vorherigen Kapitel erstelle Klasse “System“:

@Entity
@Table(name="system")
public class System {
...
...
...
}

Als nächtes fügen wir einen eindeutigen Identifier (Primary Key) als private Eigenschaft ein und legen eine getter-Methode an.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
...
...
@Column(name = "id", nullable = false)
public Long getId() { return this.id; }

Wie du siehst, wird an der getter-Methode die Spalten-Definition festgelegt. Über die Annotation “Column” setzen wir hier den Namen und eventuelle weitere Eigenschaften, (wie hier das Attribut “nullable“). Mehr Informationen erhälst du hier. Natürlich muss nun auch jede weitere getter-Methode mit der “@Column” Notation versehen und somit mit der entsprechenden Tabellenspalte verknüpft werden.

Zu guter letzt muss noch ein leerer Konstruktor angelegt werden. Dieser ist zwingend notwendig, damit JPA mit der Klasse umgehen kann.

Anlegen eines Repository

Nun benötigen wir noch ein kleines bisschen Funktionalität zum lesen und schreiben der Datenbank. Natürlich müssen wir das nicht selber schreiben, sondern können dafür ein sogenanntes “JpaRepository” anlegen.

Wir legen dafür ein neues package “repository” und in diesem eine neue Datei “SystemRepository.java” an. Diesmal allerdings nicht als Klasse sondern als Interface, welches von “JpaRepository” erbt, Objekte vom Typ “System” verarbeitet und einen “Long“-Datentyp als Identifier verwendet.

package de.frickeldave.fleckerlteppich.telemetry.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import de.frickeldave.fleckerlteppich.telemetry.entity.System;

public interface SystemRepository  extends JpaRepository<System, Long> {
    
}

Integration des JpaRepository in den Controller

Als letzte Aktion wird der Spring Boot Controller um das JpaRepository erweitert um auf die Datenbank zuzugreifen. Füge als erstes die private Eigenschaft “heatingRepository” ein.

Die Annotation “@Autowired” nimmt uns den üblichen Initialisierungsaufwand ab.

@Autowired
private HeatingRepository heatingRepository;

Erweitern des Controllers um die CRUD Methoden

Nun geht es darum, die Methoden für die 4 klassischen CRUD Operationen zu integrieren. Pro Operation muss eine Methode integriert werden.

Lesen von Daten (READ)

Wir erweitern die Methode “getHeating“, die wir im vorherigen Artikel erstellt haben, wie folgt:

public List<Heating> getHeatings() {
    List<Heating> heatingList = heatingRepository.findAll();
    return heatingList;    
}

Ein guter Moment für einen ersten Test. Klar wird die Datenbank keine Inhalte zurückgeben, aber wir haben ja unsere SQL-Log-Ausgabe in der Konfigurationdatei aktiviert, mit der wir super sehen können was passiert.

Erster Test eines Datenbankabrufes

Senden von Daten (CREATE)

Zum speichern der Daten implementieren wir ein eine post-Methode mit einem RequestMapping für POST-Requests. In dieser rufen wir die “save” Methode des Repository auf.

@RequestMapping(
    method = RequestMethod.POST,
    path = "/add",
    produces = MediaType.APPLICATION_JSON_VALUE, 
    consumes = MediaType.APPLICATION_JSON_VALUE
)
public System postSystem(@RequestBody System system) {
    return systemRepository.save(system);
}

Das war im Prinzip schon alles. Legen wir nun ein paar testrequests in unserer Datei “testrequests.http” an (Trenne den request vom vorherigen mit “###“):

POST http://localhost:8080/systems/add
content-type: application/json

{
    "hostname": "h1",
    "lastboot": "2020-01-01T19:01",
    "timestamp": "2020-01-01T20:01",
    "serial": "serial1",
    "mac": "111111111111",
    "primaryIPv4Address": "192.168.66.1",
    "fqdn": "h1.fleckerlteppich.frickeldave.de"
}

Wichtig: Bei den Eigenschaften wird Groß- und Kleinschreibung beachtet. Würdest du “primaryipv4address” schreiben, würde nichts in der Datenbank ankommen.

Löschen von Daten (DELETE)

Wie zuvor legen wir eine entsprechende Methode zum löschen von Objekten an und verknüpfen diese mit einem DELETE-Request.

@RequestMapping(
        value = "/{id}",
        method = RequestMethod.DELETE
    )
    public void deleteSystem(@PathVariable final String id) {
        Long deleteId = Long.parseLong(id, 10);
        systemRepository.deleteById(deleteId);
    }

Der entsprechende Eintrag in der testrequests.http wird wie folgt notiert:

DELETE http://localhost:8080/systems/1

Aktualisieren eines Datensatzes (UPDATE)

Um einen Datensatz in der Datenbank zu aktualisieren, schreibst du nun eine PUT Methode, die ein System-Objekt, als auch ein ID-Wert entgegennimmt.

    @RequestMapping(
        value = "/{id}",
        method = RequestMethod.PUT,
        produces = MediaType.APPLICATION_JSON_VALUE
    )
    public System updateSystem(@RequestBody System system, @PathVariable final String id) {
        Long systemId = Long.parseLong(id, 10);
        if (systemRepository.existsById(systemId)) {
            return systemRepository.save(system); }
        else
            return null;
    }

Es gibt natürlich verschiedene Möglichkeiten das Update zu implementieren. Die “ID” kannst du z.B. direkt aus dem Objekt lesen. Auch den Umgang mit dem Objekt wenn der Eintrag nicht in der Datenbank vorhanden ist, kannst du beliebig verändern.

Tip: Gibt es eine einzelne Objekteigenschaft die regelmäßig aktualisiert werden muss, kannst du neben dem “PUT” noch eine “PATCH” Methode implementieren. Einen guten Artikel dazu findest du hier.

Fazit

15 Minuten bis zur funktionstüchtigen CRUD API? Mit Spring Boot ist das problemlos möglich. Ich hoffe dieser Guide hilt Dir schnell zu deinem Ziel zu kommen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.