Java Anwendungsentwicklung – Tag 9 von 10
Von Franz-Martin, CTO bei Java Fleet Systems Consulting

Schwierigkeit: 🟡 Fortgeschritten
Voraussetzungen: Tag 8 (JPA Einführung) abgeschlossen


🗺️ Deine Position im Kurs

TagThemaNiveauStatus
1Die Desktop-Ära: Warum GUIs?🟢 Grundlagen✅ Abgeschlossen
2AWT & Swing Grundlagen🟢 Grundlagen✅ Abgeschlossen
3Layouts & Event-Handling🟢 Grundlagen✅ Abgeschlossen
4Komplexe Swing-Komponenten🟡 Fortgeschritten✅ Abgeschlossen
5JavaFX: Die „moderne“ Alternative🔴 KOPFNUSS✅ Abgeschlossen
6JDBC Grundlagen🟢 Grundlagen✅ Abgeschlossen
7JDBC Best Practices🟡 Fortgeschritten✅ Abgeschlossen
8JPA Einführung🟢 Grundlagen✅ Abgeschlossen
→ 9JPA CRUD & Queries🟡 Fortgeschritten👉 DU BIST HIER!
10Integration & Ausblick🔴 KOPFNUSS🔜 Kommt als nächstes

Legende: 🟢 Einsteiger-freundlich | 🟡 Erfordert Grundlagen | 🔴 Optional/Anspruchsvoll


⚡ Das Wichtigste in 30 Sekunden

Heute lernst du:

  • ✅ JPQL – Objekt-orientierte Abfragen
  • ✅ Entity Relationships (@OneToMany, @ManyToOne, @OneToOne)
  • ✅ JOIN FETCH – Das N+1-Problem vermeiden
  • ✅ Das Repository Pattern

Der Unterschied zu SQL:

// SQL: Tabellen und Spalten
SELECT * FROM personen WHERE alter > 25

// JPQL: Entities und Felder
SELECT p FROM Person p WHERE p.alter > 25

🟢 GRUNDLAGEN: JPQL

Was ist JPQL?

JPA CRUD

Abbildung 1: JPQL arbeitet mit Objekten, nicht mit Tabellen


Java Persistence Query Language ist wie SQL, aber:

  • Arbeitet mit Entity-Namen (Person), nicht Tabellennamen (personen)
  • Arbeitet mit Feld-Namen (alter), nicht Spaltennamen
  • Gibt Java-Objekte zurück, nicht Zeilen

Grundlegende JPQL-Abfragen

// Alle laden
List<Person> alle = em.createQuery(
    "SELECT p FROM Person p", 
    Person.class
).getResultList();

// Mit WHERE
List<Person> ergebnis = em.createQuery(
    "SELECT p FROM Person p WHERE p.alter > :minAlter",
    Person.class
).setParameter("minAlter", 25)
 .getResultList();

// LIKE-Suche
List<Person> muellers = em.createQuery(
    "SELECT p FROM Person p WHERE p.name LIKE :name",
    Person.class
).setParameter("name", "%Müller%")
 .getResultList();

// Aggregat-Funktionen
Long anzahl = em.createQuery(
    "SELECT COUNT(p) FROM Person p", 
    Long.class
).getSingleResult();

// Pagination
List<Person> seite1 = em.createQuery("SELECT p FROM Person p", Person.class)
    .setFirstResult(0)   // Offset
    .setMaxResults(10)   // Limit
    .getResultList();

🟡 PROFESSIONALS: Entity Relationships

Die 3 Relationship-Typen

Abbildung 2: @OneToOne, @OneToMany/@ManyToOne, @ManyToMany


@OneToOne: Person → Adresse

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "adresse_id")
    private Adresse adresse;
}

// Verwendung:
Person max = new Person("Max", 28, "max@ex.com");
max.setAdresse(new Adresse("Hauptstr. 1", "12345", "Berlin"));
em.persist(max);  // Adresse wird MIT gespeichert!

@OneToMany / @ManyToOne: Person ↔ Bestellungen

@Entity
public class Person {
    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
    private List<Bestellung> bestellungen = new ArrayList<>();
    
    // Helper-Methode
    public void addBestellung(Bestellung b) {
        bestellungen.add(b);
        b.setPerson(this);  // Beide Seiten verknüpfen!
    }
}

@Entity
public class Bestellung {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "person_id")
    private Person person;
}

Das N+1-Problem und JOIN FETCH

// ❌ SCHLECHT: N+1 Queries!
List<Person> personen = em.createQuery("SELECT p FROM Person p").getResultList();
for (Person p : personen) {
    p.getBestellungen().size();  // → Extra Query pro Person!
}

// ✅ GUT: 1 Query mit JOIN FETCH
List<Person> personen = em.createQuery(
    "SELECT DISTINCT p FROM Person p LEFT JOIN FETCH p.bestellungen",
    Person.class
).getResultList();

🟡 Die 3 Query-Methoden

Abbildung 3: JPQL, Criteria API, Native SQL


MethodeWann verwenden?
JPQLDie meisten Fälle – lesbar, datenbankunabhängig
Criteria APIDynamische Filter (z.B. Suchformulare)
Native SQLKomplexe Reports, DB-spezifische Features
// JPQL (empfohlen für 90% der Fälle)
em.createQuery("SELECT p FROM Person p WHERE p.alter > :min", Person.class)
  .setParameter("min", 25);

// Native SQL (wenn nötig)
em.createNativeQuery("SELECT * FROM personen WHERE alter > ?1", Person.class)
  .setParameter(1, 25);

🟡 Das Repository Pattern

// Manuell implementiert (wie heute)
public class PersonRepository {
    public List<Person> findAll() { ... }
    public Optional<Person> findById(Long id) { ... }
    public Person save(Person person) { ... }
    public void deleteById(Long id) { ... }
}

// Mit Spring Data JPA (morgen):
public interface PersonRepository extends JpaRepository<Person, Long> {
    List<Person> findByNameContaining(String name);
}
// Das ist ALLES - Spring generiert die Implementation!

💬 Real Talk: Das N+1-Problem

Java Fleet Büro, Donnerstag 14:30 Uhr.


Code Sentinel: „Nova, warum ist deine Query so langsam? 500ms für 10 Benutzer?“

Nova: „Keine Ahnung, ich lade doch nur die Benutzer und ihre Bestellungen…“

Code Sentinel: „Zeig mal den Code.“

List<Person> personen = em.createQuery("SELECT p FROM Person p").getResultList();
for (Person p : personen) {
    System.out.println(p.getName() + ": " + p.getBestellungen().size());
}

Code Sentinel: „Da ist dein Problem. Das N+1-Problem.“

Nova: „N+1?“

Code Sentinel: „Du machst 1 Query für die Personen. Dann für JEDE Person noch eine Query für die Bestellungen. 10 Personen = 11 Queries.“

Nova: „Oh… und mit 1000 Personen?“

Code Sentinel: „1001 Queries. Das wird nicht schneller. Die Lösung: JOIN FETCH.“

List<Person> personen = em.createQuery(
    "SELECT DISTINCT p FROM Person p LEFT JOIN FETCH p.bestellungen",
    Person.class
).getResultList();

Nova: „Eine Query statt 1001?“

Code Sentinel: „Genau. Merk dir: Wenn du über Collections iterierst, immer prüfen ob du ein N+1-Problem hast. Hibernate zeigt dir die Queries wenn show_sql=true ist.“


✅ Checkpoint

📝 Quiz

Frage 1: Was ist der Unterschied zwischen SQL und JPQL?

A) JPQL ist schneller
B) JPQL arbeitet mit Entity-Namen, SQL mit Tabellennamen
C) SQL kann mehr als JPQL
D) Es gibt keinen Unterschied


Frage 2: Was bedeutet mappedBy in @OneToMany(mappedBy = "person")?

A) Der Tabellenname
B) Das Feld in der ANDEREN Entity, das die Beziehung „besitzt“
C) Der Spaltenname für den Foreign Key
D) Die Mapping-Strategie


Frage 3: Was ist das N+1-Problem?

A) Eine Query gibt N+1 Ergebnisse zurück
B) Für N Datensätze werden N+1 Queries ausgeführt
C) Die Pagination funktioniert nicht
D) JOIN nicht möglich


Frage 4: Wie vermeidet man das N+1-Problem?

A) Mehr RAM
B) @Lazy Annotation
C) JOIN FETCH in der JPQL-Query
D) Kleinere Batches


📝 Quiz-Lösungen

Frage 1:B – JPQL arbeitet mit Entity-Namen
JPQL: SELECT p FROM Person p. SQL: SELECT * FROM personen.

Frage 2:B – Das Feld in der anderen Entity
mappedBy = "person" bedeutet: „Die Bestellung-Entity hat ein Feld person, das diese Beziehung definiert.“

Frage 3:B – N+1 Queries werden ausgeführt
1 Query für die Hauptentität, dann N Queries für die verknüpften Entities.

Frage 4:C – JOIN FETCH
SELECT p FROM Person p LEFT JOIN FETCH p.bestellungen lädt alles in einer Query.


❓ FAQ

Wann @OneToMany, wann @ManyToMany?
@OneToMany: Eine Bestellung gehört zu EINER Person. @ManyToMany: Ein Student kann in MEHREREN Kursen sein, ein Kurs hat MEHRERE Studenten.

Was ist CascadeType.ALL?
Operationen werden weitergegeben. persist(person) speichert auch die Adresse. remove(person) löscht auch die Bestellungen.

Was ist orphanRemoval = true?
Wenn eine Bestellung aus der Liste entfernt wird, wird sie auch aus der DB gelöscht.

JPQL oder Criteria API?
JPQL für 90% der Fälle. Criteria API nur wenn du dynamische Queries brauchst (z.B. Suchformulare mit optionalen Filtern).

Muss ich das alles selbst schreiben?
Nein! Spring Data JPA generiert alles automatisch. Das zeigen wir morgen in Tag 10!


📦 Downloads

ProjektInhaltDownload
java-anwendungsentwicklung-tag9.zipJPQL, Relationships, Repository⬇️ Download

Quick Start:

mvn exec:java                # JPQL Demo
mvn exec:java -Prelations    # Relationship Demo
mvn exec:java -Prepository   # Repository Pattern

🔗 Weiterführende Links

RessourceBeschreibung
JPQL ReferenceOffizielle Spezifikation
Hibernate N+1N+1 Problem im Detail
Spring Data JPADie Zukunft des Repository Patterns

🎉 Tag 9 geschafft!

Was du heute gelernt hast:

✅ JPQL für objektorientierte Abfragen
✅ Entity Relationships (@OneToOne, @OneToMany, @ManyToOne)
✅ Das N+1-Problem und JOIN FETCH
✅ Repository Pattern mit JPA
✅ Wann JPQL, Criteria API oder Native SQL

Morgen – Tag 10: Integration & Ausblick (🔴 KOPFNUSS)

Der Abschluss! GUI + Datenbank zusammenbringen. Plus: Spring Boot Vorschau – wie das alles VIEL einfacher wird.


Fragen? franz-martin@java-developer.online

© 2026 Java Fleet Systems Consulting | java-developer.online

Autor

  • Franz-Martin

    65 Jahre alt, CTO und Gründer von Java Fleet Systems Consulting. Franz-Martin ist erfahrener Java-Entwickler, Tutor und Dozent, der das Unternehmen gegründet hat, um sein Wissen weiterzugeben und echte Java-Probleme zu lösen. Er moderiert Team-Diskussionen, mentoriert alle Crew-Mitglieder und sorgt dafür, dass technische Exzellenz mit Business-Realität kombiniert wird.