Achim Mertens avatar

Java Training Block 6 – Tag 2 JPQL + JPA Vererbung

achimmertens

Published: 21 Jan 2022 › Updated: 21 Jan 2022Java Training Block 6 – Tag 2 JPQL + JPA Vererbung

Java Training Block 6 – Tag 2 JPQL + JPA Vererbung

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 6 gelernt und behalten habe:

JPQL - Java Persistence Query Language

JPQL ist ähnlich wie SQL, sie bildet aber die Objekte aus dem JPA-Framework im Heap-Speicher und nicht Tabellen ab.

Es gibt zwei Möglichkeiten der JPQL-Implementierung: Über NamedQuery oder einem normalen createQuery:

Das Delete holt das Objekt aus dem JPA Context (also ein Detach). Es wird aber nach einem Commit in der Datenbanktabelle gelöscht:

Und so sieht es in der Praxis aus. Jede Query ist auch ein Objekt und erhält jeweils einen Namen:

@NamedQuery hat den Nachteil, dass es hart verdrahtet ist. Will man also die Abfrage ändern, muss neu compliert werden. Bei flexibleren Anfragen sollte man Query (Inline) wählen.

Hier ein Beispiel mit einer Liste und einer Maximalfunktion mit einem Single-Result:

Hier ein Beispiel mit Between und like:

Easy and Lazy Fetching: https://www.baeldung.com/hibernate-lazy-eager-loading

JPA Advanced Mappings

Wir legen in unserem Beispiel die Vererbungsstrategie SINGLE_TABLE fest. Sie wird in der Klasse, von der später geerbt wird, festgelegt. Die Annotation @DiscriminatorValue gilt nur für diese SingleTable-Stategie.

Hibernate erwartet einen leeren Constructor (es gibt eine sehr merkwürdige Fehlermeldung, wenn er fehlt):

Hier die Vererbung (für die Single-Table-Strategie) nebeneinander:

In SaveClient werden die Objekte erstellt:

Strategy: Single Table:

Die Tabellen werden vererbt. In der Spalte "Type" wird definiert, zu welchem Object die Spalte gehört.

Joined Table Strategy

Hier sind die Einzeltabellen kleiner. Sie sind dafür miteinander verknüpft:

Table per class - Strategie

Die Tabellen sind jetzt nicht mehr gejoint. Ich kann anhand der Tabellen nicht mehr die Vererbungsstruktur (Verknüpfungen) erkennen:

Diese Strategie ist zwar für die Verabeitung die schnellste, aber es gehen auch Informationen verloren. Ggf. werden sogar die Ids doppelt geführt.

Beziehungen zwischen Klassen (JPA - Entity relationships)

Many to one (n:1) Beziehung:

Jedem Mitarbeiter wird die Deployment-ID 1 zugeordnet:

One to Many (1:n)

JPQL legt eine Zuordnungstabelle an, wo dem Department die Mitarbeiter zugeordnet werden:

OneToOne (1:1) Relation

In einem der beiden Objekte wird die 1:1 Beziehung eingebaut. Hier bei Employee:

Many to Many Relation (n:m)

Ergebnis:

 (Hier im Code wurde jedem Lehrer die entsprechenden Schulklassen zugeordnet, aber nicht den Schulklassen Lehrern zugeordnet. Dies hätte man z.B. dadurch machen können, dass eine Tabelle "Schulfach" erstellt hätte, in der jeder Schulklasse ein Lehrer zugeordnet worden wäre)

Anderes Beispiel:

Die Gäste führen je eine Liste, in welchen Hotels sie schon waren. Und die Hotels führen je eine Liste, welche Gäste dort waren.

Übung: Ueb02-person-bus-jpa

  • Klassen: Person, Angestellter, Arbeiter, Schueler
  • Klasse: Bus
  • Beziehung 1-n
  • Status eines Busses in DB persistiert
  • Daten Simpson

*)
Appl. Klassen

  1. DB befüllen
  2. DB auslesen

**)
Statt ApplKlassen bitte Test-Klassen

Hier unsere funktionierenden Ergebnis-Klassen. Wir sind gekommen bis DB befüllen und Speichern:

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>de.firma</groupId>
<artifactId>ueb02-person-bus-jpa</artifactId>
<version>1.0-SNAPSHOT</version>

<name>ueb02-person-bus-jpa</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>

&lt;dependency>
  &lt;groupId>junit&lt;/groupId>
  &lt;artifactId>junit&lt;/artifactId>
  &lt;version>4.12&lt;/version>
  &lt;scope>test&lt;/scope>
&lt;/dependency>

&lt;dependency>
  &lt;groupId>org.hibernate.javax.persistence&lt;/groupId>
  &lt;artifactId>hibernate-jpa-2.1-api&lt;/artifactId>
  &lt;version>1.0.2.Final&lt;/version>
&lt;/dependency>

&lt;dependency>
  &lt;groupId>org.eclipse.persistence&lt;/groupId>
  &lt;artifactId>eclipselink&lt;/artifactId>
  &lt;version>2.7.4&lt;/version>
&lt;/dependency>
&lt;dependency>
  &lt;groupId>mysql&lt;/groupId>
  &lt;artifactId>mysql-connector-java&lt;/artifactId>
  &lt;version>5.1.47&lt;/version>
&lt;/dependency>

&lt;dependency>
  &lt;groupId>org.hsqldb&lt;/groupId>
  &lt;artifactId>hsqldb&lt;/artifactId>
  &lt;version>2.6.1&lt;/version>
&lt;/dependency>

</dependencies>

<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="Eclipselink_JPA"
                      transaction-type="RESOURCE_LOCAL">
        <!--
            <jta-data-source>java:jboss/datasources/MySqlDS</jta-data-source>
            <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
        -->
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <class>de.firma.model.Person</class>
        <class>de.firma.model.Bus</class>
        <class>de.firma.model.Arbeiter</class>
        <class>de.firma.model.Schueler</class>
        <properties>
            <!--
                <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
                <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpadb"></property>
                <property name="javax.persistence.jdbc.user" value="root" />
                <property name="javax.persistence.jdbc.password" value="p@ssw0rd" />
                <property name="javax.persistence.schema-generation.database.action" value="create" />
                <property name="eclipselink.logging.level" value="FINE" />
            -->
            <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbc.JDBCDriver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:file:jpadb"></property>
            <property name="javax.persistence.jdbc.user" value="SA"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.schema-generation.database.action" value="create"/>
            <property name="eclipselink.logging.level" value="FINE"/>
        &lt;property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    &lt;/properties>
&lt;/persistence-unit>

</persistence>

App.java:

package de.firma;

import de.firma.exception.*;
import de.firma.model.Arbeiter;
import de.firma.model.Bus;
import de.firma.model.Geschlecht;
import de.firma.model.Schueler;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.time.LocalDate;

/**

  • Hello world!

*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello Bus!" );

    EntityManagerFactory emfactory = Persistence.createEntityManagerFactory( "Eclipselink_JPA" );
    EntityManager entitymanager = emfactory.createEntityManager( );
    entitymanager.getTransaction( ).begin( );

    Arbeiter a1 = new Arbeiter("Homer", "Simpson", 35, 120, LocalDate.of(1969, 11, 17),
            Geschlecht.MAENNLICH, "AKW Springfield", 3400.0);
    Arbeiter a2 = new Arbeiter("Otto","Man",27,110,LocalDate.now(),Geschlecht.MAENNLICH,"öpnv",2000);

    Schueler s1 = new Schueler("Bart", "Simpson", 12, 90, LocalDate.of(1995, 03, 05),
            Geschlecht.MAENNLICH,"Springfield Elementary Schule",10.0);

// Store Person
entitymanager.persist(a1);
entitymanager.persist(s1);
entitymanager.persist(a2);
System.out.println("a1 "+ a1.toCSV());

    System.out.println("s1 "+ s1.toCSV());
    System.out.println("-----------------------");
    Bus b1 = new Bus(15,5, a2);
    try {

        b1.einsteigen(a1, 1);
        b1.einsteigen(s1, 2);
        b1.aussteigen(a1);
       // b1.aussteigen(null);

    } catch (PersonNotExistException e) {
        System.err.println(e.getMessage());
    } catch (BusVollException e) {
        System.err.println(e.getMessage());
    } catch (PersonAlreadyInBusException e) {
        System.err.println(e.getMessage());
    } catch (SeatAlreadyUsedException e) {
        System.err.println(e.getMessage());
    } catch (ArrayIndexOutOfBoundsException e) {
        System.err.println(e.getMessage());
    }

    b1.showStatus();

    //Store Bus
    entitymanager.persist(b1);

    entitymanager.getTransaction( ).commit( );
    entitymanager.close( );
    emfactory.close( );
}

}

Bus.java:

package de.firma.model;

import de.firma.exception.*;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Bus extends Object {
// Eigenschaften
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
private int busNR;
private int anzahlSitze;
private int anzahlFahrgaeste;
private Person fahrer;
private Person[] fahrgaeste;

@OneToMany(targetEntity=Person.class)
private List&lt;Person> gaeste;

//Konstruktoren
public Bus(int busNR, int anzahlSitze) {
    this.busNR = busNR;
    this.anzahlSitze = anzahlSitze;
    this.anzahlFahrgaeste = 0;
    this.fahrer = null;
    this.fahrgaeste = new Person[anzahlSitze];
    this.gaeste = new ArrayList&lt;>();
}

public Bus(int busNR, int anzahlSitze, Person fahrer) {
    this(busNR, anzahlSitze);
    this.fahrer = fahrer;
}

public Bus() {  // wegen doofer Fehlermeldung
}
// Getter- und Setter Methoden

public void setGaeste(List&lt;Person> gaeste) {
    this.gaeste = gaeste;
}

public int getBusNR() {
    return busNR;
}

public void setBusNR(int busNR) {
    this.busNR = busNR;
}

public int getAnzahlSitze() {
    return anzahlSitze;
}

public int getAnzahlFahrgaeste() {
    return anzahlFahrgaeste;
}

public Person getFahrer() {
    return fahrer;
}

public void setFahrer(Person fahrer) {
    this.fahrer = fahrer;
}

//Business Methoden
public void einsteigen(Person p) throws PersonNotExistException, BusVollException, PersonAlreadyInBusException {
    if (p == null) {
        throw new PersonNotExistException("Person existiert nicht!");
    }

    if (this.anzahlFahrgaeste >= this.anzahlSitze) {
        throw new BusVollException("Du kommst hier nicht rein, " + p.getVorname());
    }

    for (Person seatp : this.fahrgaeste) {
        if (p == seatp) {
            throw new PersonAlreadyInBusException("Die Person " + p.getVorname() + " darf nicht rein, weil sie schon drin ist.");
        }
    }

    for (int i = 0; i &lt; this.anzahlSitze; i++) {
        if (this.fahrgaeste[i] == null) {
            this.fahrgaeste[i] = p;
            this.anzahlFahrgaeste++;
            break;
        }
    }
    this.gaeste.add(p);
}

public void einsteigen(Person p, int sitznr) throws SeatAlreadyUsedException, PersonNotExistException, BusVollException, PersonAlreadyInBusException, ArrayIndexOutOfBoundsException {

    if (sitznr >= this.anzahlSitze || sitznr &lt; 0) {
        throw new ArrayIndexOutOfBoundsException("Bitte gib eine gültige Sitznummer an, du Honk!");
    }


    if (p == null) {
        throw new PersonNotExistException("Person existiert nicht!");
    }

    if (this.anzahlFahrgaeste >= this.anzahlSitze) {
        throw new BusVollException("Du kommst hier nicht rein, " + p.getVorname());
    }

    for (Person seatp : this.fahrgaeste) {
        if (p == seatp) {
            throw new PersonAlreadyInBusException("Die Person " + p.getVorname() + " darf nicht rein, weil sie schon drin ist.");
        }
    }

    if (this.fahrgaeste[sitznr] == null) {
        this.fahrgaeste[sitznr] = p;
        this.anzahlFahrgaeste++;
    } else {
        throw new SeatAlreadyUsedException("Da sitzt leider schon jemand, lieber " + p.getVorname());
    }
    this.gaeste.add(p);
}


public void aussteigen(Person p) throws PersonNotExistException {
    boolean found = false;
    for (int i = 0; i &lt; this.anzahlSitze; i++) {
        if (this.fahrgaeste[i] == p) {
            this.fahrgaeste[i] = null;
            this.anzahlFahrgaeste--;
            found = true;
        }
    }
    if (!found){
        throw new PersonNotExistException("Die Person sitzt nicht im Bus");
    }

    if (p == null) {
        throw new PersonNotExistException("Person existiert nicht!");
    }
    this.gaeste.remove(p);
}


public void aussteigen(int sitzplatz){

    fahrgaeste[sitzplatz] = null;

}


public void showStatus() {
    System.out.println("Fahrer: " + this.fahrer.getVorname() + " " + this.fahrer.getNachname());
    System.out.println("BusNr: " + this.busNR);
    System.out.println("Anzahl Fahrgäste: " + this.anzahlFahrgaeste);
    System.out.println("------------------------");


    for (int i = 0; i &lt; this.anzahlSitze; i++) {
        if (fahrgaeste[i] != null) {
            System.out.println("Fahrgast: " + fahrgaeste[i].getVorname() + " " + fahrgaeste[i].getNachname() + " Sitzplatz:" + i);
        }
    }
}

}

Person.java

package de.firma.model;

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

@Entity
@Table
@Inheritance(strategy = InheritanceType.JOINED)
public class Person extends Object {

//Eigenschaften
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
private long pid;
private String vorname;
private String nachname;
private int alter;
private int puls;
private LocalDate gebDate;
@Enumerated(EnumType.STRING)
private Geschlecht geschlecht;

//Konstruktoren
public Person() {  // Default Constructor
    super(); // Ruft den Konstruktoren der Obertklasse auf    ->  Object()
    this.vorname = "Erwin";
    this.nachname = "Mustermann";
    this.alter = 37;
    this.setPuls(140);
    this.geschlecht = Geschlecht.DIVERS;
    //this.gebDate = new LocalDate(); Das ginge auch, aber erzeugt immer wieder neue Objekte
    this.gebDate = LocalDate.now(); //Hier wird die Methode des einzig existiernden Objektes aufgerufen
}

public Person(String vorname, String nachname) {
    //super();   // Ruft den Konstruktoren der Obertklasse auf    ->  Object()
    this();      // Ruft den Konstruktoren der eigenen Klasse mit der Signatur () auf    ->  Person()
    this.vorname = vorname;
    this.nachname = nachname;
}

public Person(String vorname, String nachname, int alter, int puls, LocalDate gebDate, Geschlecht geschlecht) {
    this.vorname = vorname;
    this.nachname = nachname;
    this.alter = alter;
    this.puls = puls;
    this.gebDate = gebDate;
    this.geschlecht = geschlecht;
}

// Getter- und Setter-Methoden
public String getVorname() {
    return vorname;
}

public void setVorname(String vorname) {
    this.vorname = vorname;
}

public String getNachname() {
    return nachname;
}

public void setNachname(String nachname) {
    this.nachname = nachname;
}

public int getAlter() {
    return this.alter;
}

public void setAlter(int alter) {

    this.alter = alter;


}

public int getPuls() {
    return puls;
}

public void setPuls(int puls) {
    if(puls &lt;= 0) {
        System.out.println("Wiederbelebung " + this.vorname);
        this.puls = 80;
    } else {
        this.puls = puls;
    }
}

public LocalDate getGebDate() {
    return gebDate;
}

public void setGebDate(LocalDate gebDate) {
    this.gebDate = gebDate;
}

public Geschlecht getGeschlecht() {
    return geschlecht;
}

public void setGeschlecht(Geschlecht geschlecht) {
    this.geschlecht = geschlecht;
}




// Business-Logik
/*
@Override
public String toString() {
   return this.vorname + " " + this.nachname + " " +this.alter + " " +this.puls;
}
*/

@Override
public String toString() {
    return "Person{" +
            "vorname='" + vorname + '\'' +
            ", nachname='" + nachname + '\'' +
            ", alter=" + alter +
            ", puls=" + puls +
            ", gebDate=" + gebDate +
            ", geschlecht=" + geschlecht +
            '}';
}

public String toCSV() {
    return toCSV(';');
}

public String toCSV(char seperator) {
    // return this.getVorname() + seperator + this.getNachname() + seperator + this.getAlter();  //geht, aber ist langsam, bzw. blockiert Speicher
    StringBuffer stringBuffer = new StringBuffer(50);
    stringBuffer.append(this.vorname);
    stringBuffer.append(seperator);
    stringBuffer.append(this.nachname);
    stringBuffer.append(seperator);
    stringBuffer.append(this.alter);
    stringBuffer.append(seperator);
    stringBuffer.append(this.puls);
    stringBuffer.append(seperator);
    stringBuffer.append(this.gebDate);
    stringBuffer.append(seperator);
    stringBuffer.append(this.geschlecht);

    return stringBuffer.toString();
}

}

Arbeiter.java:

package de.firma.model;

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

@Entity
@PrimaryKeyJoinColumn(referencedColumnName = "pid")
public class Arbeiter extends Person {
//Eigenschaften
private String arbeitgeber;
private double lohn;

// Konstruktoren

public Arbeiter() {
    this.arbeitgeber = "nn";
    this.lohn = 0.0;
}

public Arbeiter(String vorname, String nachname, int alter, int puls, LocalDate gebDate, Geschlecht geschlecht, String arbeitgeber, double lohn) {
    super(vorname, nachname, alter, puls, gebDate, geschlecht);
    this.arbeitgeber = arbeitgeber;
    this.lohn = lohn;
}


//Getter und Setter Methoden

public String getArbeitgeber() {
    return arbeitgeber;
}

public void setArbeitgeber(String arbeitgeber) {
    this.arbeitgeber = arbeitgeber;
}

public double getLohn() {
    return lohn;
}

public void setLohn(double lohn) {
    this.lohn = lohn;
}

// Business Methoden

@Override
public String toCSV(char seperator) {
    StringBuffer stringBuffer = new StringBuffer(50);
    stringBuffer.append(super.toCSV(seperator)); //hol dir den gesamten CSV-Datensatz aus der Oberklasse
    stringBuffer.append(seperator);
    stringBuffer.append(this.arbeitgeber);
    stringBuffer.append(seperator);
    stringBuffer.append(this.lohn);
    return stringBuffer.toString();
}

}

Schueler.java (man achte auf die merkwürdige Besonderheit bei der Vererbung):

package de.firma.model;

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

@Entity
@PrimaryKeyJoinColumn(referencedColumnName = "pid")
public class Schueler extends Person implements Serializable { //Erbt von der Klasse Person
// implements Serializable wird hier gebraucht, sonst gibt es eine Fehlermeldung
// Eigenschaften
private String schule;
private double taschengeld;

// Konstruktoren
public Schueler() { //Konstruktoren heissen wie die Klasse
    super (); // Das passiert sowieso
    this.setSchule("Springfield Elementary School");
    this.taschengeld = 2.0; //geht, schöner ist aber:
    this.setTaschengeld(2.0);
}

public Schueler(String vorname, String nachname, int alter, int puls, LocalDate gebDate, Geschlecht geschlecht, String schule, double taschengeld) {
   super(vorname, nachname, alter, puls, gebDate, geschlecht);
    this.schule = schule;
    this.taschengeld = taschengeld;
}

// Getter und Setter Methoden:
public String getSchule() {
    return schule;
}

public void setSchule(String schule) {
    this.schule = schule;
}

public double getTaschengeld() {
    return taschengeld;
}

public void setTaschengeld(double taschengeld) {
    this.taschengeld = taschengeld;
}

//Business Logik


@Override
public String toString() {
    return "Schueler{" +
            "schule='" + schule + '\'' +
            ", taschengeld=" + taschengeld +
            "} " + super.toString();
}

@Override
public String toCSV(char seperator) {
    StringBuffer stringBuffer = new StringBuffer(50);
    stringBuffer.append(super.toCSV(seperator)); //hol dir den gesamten CSV-Datensatz aus der Oberklasse
    stringBuffer.append(seperator);
    stringBuffer.append(this.schule);
    stringBuffer.append(seperator);
    stringBuffer.append(this.taschengeld);
    return stringBuffer.toString();
}

}

Geschlecht.java

package de.firma.model;

import javax.persistence.Entity;

public enum Geschlecht {
DIVERS,
MAENNLICH,
WEIBLICH
}

BusVollException.java

package de.firma.exception;

public class BusVollException extends Exception {
public BusVollException(String message) {
super(message);
}
}


Disclaimer

Alles was ich mitschrieb und verstanden habe ist ohne Gewähr.

Besten Dank an unseren sehr empfehlenswerten

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

In den nächsten Tagen und Wochen geht’s weiter, so: stay tuned!

Gruß, Achim Mertens

Leave Java Training Block 6 – Tag 2 JPQL + JPA Vererbung to:

Written by

Love also your next but one and create more than you destroy.

Read more #java posts


Best Posts From Achim Mertens

We have not curated any of achimmertens's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.

More Posts From Achim Mertens