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
- DB befüllen
- 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>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <version>1.0.2.Final</version> </dependency> <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.7.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.6.1</version> </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"/><property name="eclipselink.ddl-generation" value="drop-and-create-tables"/> </properties> </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<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<>(); } 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<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 < 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 < 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 < 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 < 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 <= 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