Java Training Block 8 – Tag 2 JPA Spring Data

Ich nehme derzeit an einer Akademie zum Java Fullstack Software Engineer teil. In den kommenden Wochen möchte ich hier meine Mitschrift, so gut es geht, aufzeichnen und mitteilen. Hier ist das, was ich vom zweiten Tag in Block 8 gelernt und behalten habe:

Nachtrag zu Donnerstag

Code first vs. Contract first:

Transaktionssicherheit:

Die Transaktionssicherheit mit @Transactional läuft auch über mehrere Server im Cluster. Gesteuert wird das über die persistence.xml. Hier ist die Transaktionssicherheit nur lokal:

JPA Springdata

ueb-kugbuchservice-springdata

Aufgabe: Wir wollen alle Bücher aus der KuG-Datenbank via Webservice sehen und verwalten

Erstellung des Spring-Servers inkl. SQL-Daten

Wir laden das Zipfile runter und öffnen es in IntelliJ. Wir fügen in der pom.xml noch die Dependency für Jackson mit rein:

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

Wir kopieren die sql-Daten vom kugbuch-schema-data ins Resource Verzeichnis

Inhalt:

Wir passen die Application.properties an:

server.port=8099
# http://localhost:8080/h2-console
spring.h2.console.enabled=true
# ===============================
# DATABASE
# ===============================
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:kugbuch
spring.datasource.username=sa
spring.datasource.password=
# ===============================
# JPA / HIBERNATE
# ===============================
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

Wir starten die Anwendung:

Wir greifen auf die lokale h2 SQL-DB zu:

Die Daten werden im Hintergrund aus den Resources hochgeladen. Data.sql und schema.sql sind default Dateien. Sie werden von Spring-data automatisch beim Starten gelesen. (Jetzt mal ehrlich - das ist schon ziemlich beeindruckend, wie schnell man einen DB-Server inkl. Daten aufsetzen kann :-))

Wenn wir andere Dateien haben wollen, können wir das in den application.properties anpassen. Details:

https://docs.spring.io/spring-boot/docs/1.1.5.RELEASE/reference/html/common-application-properties.html

Erstellung der Logik und des Web-Zugriffes

Jetzt wollen wir auch einen Webzugriff auf die Daten haben. Das ist dann nicht mehr ganz so trivial, da wir erst eine Logik und Mapping aufsetzen müssen. Wir haben oben somit den Inhalt für das Repository erstellt. Jetzt fehlt uns dessen Einbindung sowie der mittlere und linke Teil des Bildes:

Wir bauen Packages: controller, dto (könnte auch modell heißen), entity, repository, services, configuration:

Jetzt bauen wir in dto die Klasse KuGBuchDTO (wir sind also in der Mitte vom Flipchart, wo als erstes das Datenformat definiert wird):

Es gibt Tools, die aus der SQL-Datei die Klasse bauen. Wir füllen die DTO-Klasse hier mit copy (vom Trainer) + paste. (Letztendlich werden nur die Tabellenspalten eingetragen):

public class KuGBuchDTO {
private String isbn;
private String titel;
private String autor;
private int jahr;
private double preis;

public KuGBuchDTO() {
}

public KuGBuchDTO(String isbn, String titel, String autor, int jahr, double preis) {
    this.isbn = isbn;
    this.titel = titel;
    this.autor = autor;
    this.jahr = jahr;
    this.preis = preis;
}

public String getIsbn() {
    return isbn;
}

public void setIsbn(String isbn) {
    this.isbn = isbn;
}

public String getTitel() {
    return titel;
}

public void setTitel(String titel) {
    this.titel = titel;
}

public String getAutor() {
    return autor;
}

public void setAutor(String autor) {
    this.autor = autor;
}

public int getJahr() {
    return jahr;
}

public void setJahr(int jahr) {
    this.jahr = jahr;
}

public double getPreis() {
    return preis;
}

public void setPreis(double preis) {
    this.preis = preis;
}

}

Wir kopieren das KUGbuch und pasten es in das entity Package (rechter Teil vom Flipchart):

Jetzt fügen wir die Annotationen in der Entity-Klasse hinzu:

Wir erstellen das JPA Interface KugBuchRepository. Es soll sich auf die KuGBuchEntity beziehen und der primary Key ist vom Typ String:

Jetzt kommt der linke Teil vom Flipchart dran. Wir bauen eine (Rest-) Controller-Klasse und fügen schon mal ein paar Annotationen ein:

Bevor wir hier weiter machen, erstellen wir erst einmal ein Interface KugBuchService:

Wir erben von JPARepository, damit wir gleich (im SimpleKuGBuchService) das "findAll" nutzen können:

Unser Controller sieht jetzt so aus:

Spring baut eine Instance vom Typ SimpleKugBuchService. Beim Autowired wird gefragt: "Wer ist vom Typ KuGBuchService". Wir deklarieren im Controller ein Objekt "kugBuchService" vom Typ "KugBuchService", aber der Inhalt wird von Spring gefüllt, welches, wie erwähnt, eine Instance von SimpleKugBuchService erstellt:

SimplKuGBuchService:

Um die Objekte, die jetzt aus der Entity-Klasse über das KuGBuchRepository-Interface Spring zur Verfügung gestellt werden, in die DTO-Objekte zu kopieren, benutzen wir den Modellmapper. Diesen laden wir über die pom.xml:

        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>2.3.5</version>
        </dependency>

Mit folgendem Code-Schnipsel finden wir alle Bücher und erzeugen die entsprechenden DTO-Objekte:

     List<KuGBuchEntity> dbListe = kugBuchRepository.findAll();
        List<KuGBuchDTO> kuGBuchDTOList = new ArrayList<KuGBuchDTO>();
        for (KuGBuchEntity a: dbListe){
            KuGBuchDTO aDTO = modelMapper.map(a, KuGBuchDTO.class);
            kuGBuchDTOList.add(aDTO);
        }
        return kuGBuchDTOList;

Die SimplKuGBuchService Klasse sieht dann wie folgt aus:

Das @Autowired kann derzeit den modelMapper noch nicht finden, weil dieses Bean in der Spring-Wolke noch nicht bekannt ist.
Daher bauen wir in der Configurations-Package die ModellMapperConfiguration Klasse:

Schauen wir noch mal in den SimpleKuGBuchService:

Details zu ModelMapper: http://modelmapper.org/

Jetzt starten wir den Server und kontrollieren mit curl:

curl -X GET http://localhost:8099/kugcontroller/buecherliste

Hier mal eine Zusammenfassung des Spring-Servers soweit. Allerbesten Dank an unseren Kollegen Michael Schneider für dieses Bild:

Einbau von etwas Logik:

Jetzt wollen wir den Durchschnittspreis der Bücher haben. Wir fügen in den KuGBuchController folgendes hinzu:

Und in das Interface bauen wir die Methode getAVGPreis:

Und jetzt im SimplKuGBuchService die Logik einbauen, zumindest den Rumpf davon:

Es fehlt noch die Eigenschaft im Repository. Spring erledigt das Befüllen der Eigenschaft mit einer JQL-Abfrage:

Jetzt können wir SimplKuGBuchService vervollständigen:

Server einmal durchstarten und das Ergebnis überprüfen (passt):

Hier alle beteiligten Dateien:

Pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>de.firma</groupId>
    <artifactId>ueb-kugbuchservice-springdata</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ueb-kugbuchservice-springdata</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    &lt;dependency>
        &lt;groupId>com.h2database&lt;/groupId>
        &lt;artifactId>h2&lt;/artifactId>
        &lt;scope>runtime&lt;/scope>
    &lt;/dependency>
    &lt;dependency>
        &lt;groupId>com.fasterxml.jackson.dataformat&lt;/groupId>
        &lt;artifactId>jackson-dataformat-xml&lt;/artifactId>
    &lt;/dependency>

    &lt;dependency>
        &lt;groupId>org.modelmapper&lt;/groupId>
        &lt;artifactId>modelmapper&lt;/artifactId>
        &lt;version>2.3.5&lt;/version>
    &lt;/dependency>


    &lt;dependency>
        &lt;groupId>org.springframework.boot&lt;/groupId>
        &lt;artifactId>spring-boot-starter-test&lt;/artifactId>
        &lt;scope>test&lt;/scope>
    &lt;/dependency>
&lt;/dependencies>

&lt;build>
    &lt;plugins>
        &lt;plugin>
            &lt;groupId>org.springframework.boot&lt;/groupId>
            &lt;artifactId>spring-boot-maven-plugin&lt;/artifactId>
        &lt;/plugin>
    &lt;/plugins>
&lt;/build>

</project>

Application.properties:

server.port=8099
# http://localhost:8080/h2-console
spring.h2.console.enabled=true
# ===============================
# DATABASE
# ===============================
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:kugbuch
spring.datasource.username=sa
spring.datasource.password=
# ===============================
# JPA / HIBERNATE
# ===============================
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

KuGBuchController.java:

package de.firma.controller;

import de.firma.dto.KuGBuchDTO;
import de.firma.services.KuGBuchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@CrossOrigin("*")
@RequestMapping("/kugcontroller")
public class KuGBuchController {

@Autowired
KuGBuchService kuGBuchService;  // Das Objekt kuGBuchService wird jetzt aus dem Interface gebaut und an Spring Restfull übergeben.
//Genauer: Suche etwas in der Spring-Wolke
//Spring baut eine Instance vom Typ @Service = SimpleKugBuchService

@GetMapping(value="/buecherliste")  // curl -X GET http://8099/kugcontroller/buecherliste
public List&lt;KuGBuchDTO> getAllBooks() {
    return kuGBuchService.getAllBooks();
}

//curl -X GET http://localhost:8099/kugcontroller/avgpreis
@GetMapping(value = "/avgpreis")
public double getAVGPreis() {
    return kuGBuchService.getAVGPreis();
}

}

UebKugbuchserviceSpringdataApplication.java (Haupt-App mit Main Methode):

package de.firma;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UebKugbuchserviceSpringdataApplication {

public static void main(String[] args) {
    SpringApplication.run(UebKugbuchserviceSpringdataApplication.class, args);
}

}

ModellMapperConfiguration.java:

package de.firma.configuration;

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapperConfiguration {

@Bean
public ModelMapper buildModelMapper() {
    return new ModelMapper();
}

KugBuchDTO.java:

package de.firma.dto;

public class KuGBuchDTO {

private String isbn;
private String titel;
private String autor;
private int jahr;
private double preis;

public KuGBuchDTO() {
}

public KuGBuchDTO(String isbn, String titel, String autor, int jahr, double preis) {
    this.isbn = isbn;
    this.titel = titel;
    this.autor = autor;
    this.jahr = jahr;
    this.preis = preis;
}

public String getIsbn() {
    return isbn;
}

public void setIsbn(String isbn) {
    this.isbn = isbn;
}

public String getTitel() {
    return titel;
}

public void setTitel(String titel) {
    this.titel = titel;
}

public String getAutor() {
    return autor;
}

public void setAutor(String autor) {
    this.autor = autor;
}

public int getJahr() {
    return jahr;
}

public void setJahr(int jahr) {
    this.jahr = jahr;
}

public double getPreis() {
    return preis;
}

public void setPreis(double preis) {
    this.preis = preis;
}

}

KuGBuchEntity.java:

package de.firma.entity;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="kug") //Weil die Tabelle in der Datenbank so heißt müssen wir @Table hinzufügen

public class KuGBuchEntity {
@Id // In der Schema-Datei finden wir den Primary Key
private String isbn;
private String titel;
private String autor;
private int jahr;
private double preis;

public KuGBuchEntity() {
}

public KuGBuchEntity(String isbn, String titel, String autor, int jahr, double preis) {
    this.isbn = isbn;
    this.titel = titel;
    this.autor = autor;
    this.jahr = jahr;
    this.preis = preis;
}

public String getIsbn() {
    return isbn;
}

public void setIsbn(String isbn) {
    this.isbn = isbn;
}

public String getTitel() {
    return titel;
}

public void setTitel(String titel) {
    this.titel = titel;
}

public String getAutor() {
    return autor;
}

public void setAutor(String autor) {
    this.autor = autor;
}

public int getJahr() {
    return jahr;
}

public void setJahr(int jahr) {
    this.jahr = jahr;
}

public double getPreis() {
    return preis;
}

public void setPreis(double preis) {
    this.preis = preis;
}

}

KuGBuchRepository.java:

package de.firma.repository ;

import de.firma.entity.KuGBuchEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface KugBuchRepository extends JpaRepository <KuGBuchEntity, String> {
//Basis-Funktionalität: count, delete, deleteALL, ..., save

@Query("select avg(a.preis) FROM KuGBuchEntity a")      // -> DB:          select avg(a.preis) FROM KuGBuchEntity a"
public double getAVGPreis();                            // &lt;- Service:     double getAVGPreis()

}

KuGBuchService.Java

package de.firma.services;

import de.firma.dto.KuGBuchDTO;

import java.util.List;

public interface KuGBuchService {
public List<KuGBuchDTO> getAllBooks();
public double getAVGPreis();
}

SimplKuGBuchService.java:

package de.firma.services;

import de.firma.dto.KuGBuchDTO;

import java.util.List;

public interface KuGBuchService {
public List<KuGBuchDTO> getAllBooks();
public double getAVGPreis();
}


Zusammenfassung Block 8 Spring Data


Block 08 - SpringData

SpringData
-> programming against interfaces

Installation:
pom.xml
org.springframework.boot spring-boot-starter-data-jpa

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

Konfiguration

  • JPA persistence.xml
    application.properties
    # (TREIBER,URL,USER,PASSWORD)
    spring.datasource.url=jdbc:h2:mem:testdb #Grafik-Konsole
    spring.h2.console.enabled=true #Zugriff:
    http://localhost:8080/h2-console

Entity:

@Entity
public class Employee {

@Id @GeneratedValue 
private Long id;
private String name;
private String role;

Repository = DAO

@Repository
public interface EmployeeRepository extends CrudRepository {…}

@Repository
public interface EmployeeRepository extends JpaRepository {…}

extends CrudRepository (org.springframework.data.repository)
-> https://docs.spring.io/spring-data/commons/docs/current/api/index.html?org/springframework/data/repository/CrudRepository.html

   Basis-Funktionalität (CRUD):
   count, delete, deleteAll, deleteAll, deleteAllById, deleteById, existsById, findById, save

extends JpaRepository (org.springframework.data.jpa.repository)
-> https://docs.spring.io/spring-data/jpa/docs/current/api/index.html?org/springframework/data/jpa/repository/JpaRepository.html

   Erweiterte-Funktionalität:
    count, delete, deleteAll, deleteAll, deleteAllById, deleteById, existsById, findById, save
    exists, findAll, findBy, findOne

Disclaimer

Alles was ich mitschrieb und verstanden habe ist ohne Gewähr. Die Bilder stammen teilweise aus dem Internet und wir haben keine Urheberansprüche darauf.

Besten Dank an unseren sehr empfehlenswerten

Trainer: Hans-Joachim Blanke blanke@4point.de

Ich habe diese Woche Recap-Phase (die ich dringend brauche). In den nächsten Wochen geht’s aber weiter, so: stay tuned!

Gruß, Achim Mertens

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now