Java Web Aufbau – Tag 7 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting

JDBC - Konfiguration

📋 Deine Position im Kurs

TagThemaStatus
1Filter im Webcontainer✅ Abgeschlossen
2Listener im Webcontainer✅ Abgeschlossen
3Authentifizierung über Datenbank✅ Abgeschlossen
4Container-Managed Security & Jakarta Security API✅ Abgeschlossen
5Custom Tags & Tag Handler (SimpleTag)✅ Abgeschlossen
6Custom Tag Handler mit BodyTagSupport✅ Abgeschlossen
→ 7JPA vs JDBC – Konfiguration & Provider👉 DU BIST HIER!
8JPA Relationen (1): @OneToOne & @ManyToOne🔒 Noch nicht freigeschaltet
9JPA Relationen (2): @OneToMany & @ManyToMany🔒 Noch nicht freigeschaltet
10JSF Überblick – Component-Based UI🔒 Noch nicht freigeschaltet

Modul: Java Web Aufbau
Gesamt-Dauer: 10 Arbeitstage
Dein Ziel: JPA verstehen, konfigurieren und erste Entities erstellen


📋 Voraussetzungen für diesen Tag

Du brauchst:

  • ✅ JDK 21 LTS installiert
  • ✅ Apache NetBeans 22 (oder neuer)
  • ✅ Payara Server 6.x konfiguriert
  • ✅ MySQL oder PostgreSQL Datenbank
  • ✅ Tag 1-6 abgeschlossen
  • ✅ JDBC Grundlagen (aus Java SE)

Datenbank-Setup:

docker run --name mysql-jpa -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=jpadb -p 3306:3306 -d mysql:8

Tag verpasst?
Kein Problem! Spring zurück zu Tag 6 für BodyTagSupport Basics.

Setup-Probleme?
Schreib uns: support@java-developer.online


⚡ Das Wichtigste in 30 Sekunden

Heute lernst du:

  • ✅ Was JPA ist und der Unterschied zu JDBC
  • ✅ persistence.xml konfigurieren
  • ✅ EntityManager für CRUD nutzen
  • ✅ Entity-Klassen mit @Entity, @Id, @Column erstellen
  • ✅ JPA-Provider (Hibernate) einrichten

Am Ende des Tages kannst du: Datenbankzugriffe ohne SQL schreiben, Entity-Klassen designen und production-ready Persistence Layer aufbauen.

Schwierigkeitsgrad: Mittel (aber Game-Changer!)


👋 Willkommen zu Tag 7!

Hi! 👋

Elyndra hier. Heute wird’s richtig spannend!

Kurzwiederholung: Challenge von Tag 6

Gestern solltest du BodyTagSupport-Tags mit Iteration erstellen. Falls noch nicht gemacht – die Lösung findest du im GitHub-Projekt.

Was du heute lernst:

Nach 6 Tagen Servlets, JSP und Custom Tags kommt der Game-ChangerJPA – Jakarta Persistence API.

„Warum brauche ich JPA, wenn ich JDBC kann?“, fragst du dich vielleicht?

Real talk: JDBC ist wie mit Schraubenzieher ein Haus bauen. Geht. Aber JPA ist die elektrische Bohrmaschine – schneller, effizienter, weniger fehleranfällig.

Was JPA dir gibt:

  • ✅ Keine SQL-Queries mehr schreiben (meist!)
  • ✅ Object-Relational Mapping (Java-Objekte = DB-Rows)
  • ✅ Automatisches Schema Management
  • ✅ Transaction Management integriert
  • ✅ Caching out-of-the-box

Keine Sorge:
JPA wirkt „magisch“ mit Annotations und XML. Aber ich zeige dir genau, was passiert!

Los geht’s! 🚀


🟢 GRUNDLAGEN: Was ist JPA?

Definition

JPA = Jakarta Persistence API – eine Spezifikation für Object-Relational Mapping (ORM).

Wichtig: JPA ist NICHT eine Implementierung! Es ist ein Standard, ähnlich wie JDBC.

JPA (Spezifikation)
  ↓ implementiert von:
  ├── Hibernate (populärster Provider)
  ├── EclipseLink (Referenzimplementierung)
  └── OpenJPA

JDBC vs. JPA – Der Vergleich

Mit JDBC (der alte Weg):

String sql = "SELECT id, name, email FROM users WHERE id = ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
    stmt.setInt(1, userId);
    ResultSet rs = stmt.executeQuery();
    
    if (rs.next()) {
        User user = new User();
        user.setId(rs.getInt("id"));
        user.setName(rs.getString("name"));
        user.setEmail(rs.getString("email"));
        return user;
    }
}

Mit JPA (der moderne Weg):

User user = entityManager.find(User.class, userId);

Das war’s! Eine Zeile statt 15. JPA macht das SQL, Mapping und Connection-Handling für dich.

Wann JDBC? Wann JPA?

Nutze JDBC für:

  • Bulk-Operations (10.000+ Rows löschen)
  • Komplexe Reporting-Queries
  • Performance-kritische Abschnitte

Nutze JPA für:

  • Enterprise-Anwendungen (90% aller Fälle!)
  • CRUD-Operationen
  • Komplexe Objektbeziehungen
  • Weniger Code schreiben

🟢 GRUNDLAGEN: Deine erste Entity

Entity-Klasse erstellen

package com.javafleet.model;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username", nullable = false, unique = true)
    private String username;
    
    @Column(name = "email", nullable = false)
    private String email;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    // Default Constructor (PFLICHT für JPA!)
    public User() {}
    
    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
    }
    
    // Getters & Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public LocalDateTime getCreatedAt() { return createdAt; }
}

Annotations erklärt:

@Entity – „Diese Klasse ist eine Entity (= DB-Tabelle)“
@Table(name = „users“) – „Sie mappt zur Tabelle ‚users'“
@Id – „Das ist der Primary Key“
@GeneratedValue – „Wert wird automatisch generiert (Auto-Increment)“
@Column – „Spalten-Details: Name, Constraints“
@PrePersist – „Vor persist() ausführen (für Timestamps)“

persistence.xml konfigurieren

Speicherort: src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             version="3.0">
    
    <persistence-unit name="myPU" transaction-type="JTA">
        
        <jta-data-source>jdbc/myDB</jta-data-source>
        
        <properties>
            <!-- Hibernate Dialect -->
            <property name="hibernate.dialect" 
                      value="org.hibernate.dialect.MySQL8Dialect"/>
            
            <!-- SQL-Logging (nur Development!) -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            
            <!-- Schema-Generation -->
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
        
    </persistence-unit>
    
</persistence>

Wichtige Properties:

hibernate.dialect – SQL-Dialekt für deine Datenbank
hibernate.show_sql – Zeigt generiertes SQL (Debug)
hibernate.hbm2ddl.auto – Schema-Management:

  • none – Production (nichts machen)
  • validate – Schema prüfen
  • update – Schema aktualisieren (Development)
  • create – Schema neu erstellen (LÖSCHT DATEN!)

EntityManager nutzen

import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

@Stateless
public class UserService {
    
    @PersistenceContext
    private EntityManager em;
    
    // CREATE
    public void createUser(String username, String email) {
        User user = new User(username, email);
        em.persist(user);
        // INSERT beim Transaction-Commit!
    }
    
    // READ
    public User findUser(Long id) {
        return em.find(User.class, id);
    }
    
    // UPDATE
    public void updateEmail(Long id, String newEmail) {
        User user = em.find(User.class, id);
        if (user != null) {
            user.setEmail(newEmail);
            // Automatisches UPDATE dank Dirty Checking!
        }
    }
    
    // DELETE
    public void deleteUser(Long id) {
        User user = em.find(User.class, id);
        if (user != null) {
            em.remove(user);
        }
    }
    
    // QUERY
    public List<User> findUsersByEmail(String pattern) {
        return em.createQuery(
            "SELECT u FROM User u WHERE u.email LIKE :pattern",
            User.class
        )
        .setParameter("pattern", "%" + pattern + "%")
        .getResultList();
    }
}

EntityManager Methoden:

persist(entity) – INSERT (Entity wird MANAGED)
find(Class, id) – SELECT per Primary Key
merge(entity) – UPDATE (Detached → Managed)
remove(entity) – DELETE
createQuery(jpql) – JPQL-Queries


🟡 PROFESSIONALS: Production-Ready Setup

DataSource konfigurieren

Im Payara Server:

  1. JDBC Connection Pool erstellen:
    • Admin Console → Resources → JDBC → Connection Pools
    • Pool Name: MySQLPool
    • Database: MySQL
    • Server: localhost:3306
    • Database: jpadb
    • User/Password: root/secret
  2. JDBC Resource erstellen:
    • Admin Console → Resources → JDBC Resources
    • JNDI Name: jdbc/myDB
    • Pool: MySQLPool

Warum DataSource statt hardcoded Connection?

  • ✅ Connection Pooling
  • ✅ Zentrale Konfiguration
  • ✅ Umgebungs-spezifisch (DEV/TEST/PROD)
  • ✅ Keine Credentials im Code

Hibernate Properties (Production)

<properties>
    <!-- WICHTIG: In Production NIEMALS "update" oder "create"! -->
    <property name="hibernate.hbm2ddl.auto" value="none"/>
    
    <!-- Kein SQL-Logging in Production -->
    <property name="hibernate.show_sql" value="false"/>
    
    <!-- Performance -->
    <property name="hibernate.jdbc.batch_size" value="20"/>
    <property name="hibernate.order_inserts" value="true"/>
    
    <!-- Cache -->
    <property name="hibernate.cache.use_second_level_cache" value="true"/>
</properties>

Schema-Changes in Production: Nutze Flyway oder Liquibase, NIEMALS hbm2ddl.auto!

Transaction Management

@Stateless
public class UserService {
    
    @PersistenceContext
    private EntityManager em;
    
    // Automatische Transaction durch @Stateless!
    public void createUser(User user) {
        em.persist(user);
        // Commit beim Method-Ende
    }
    
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void createUserNewTransaction(User user) {
        em.persist(user);
        // Eigene Transaction, unabhängig vom Caller
    }
}

Rollback bei Exception:

public void createUser(User user) throws Exception {
    em.persist(user);
    
    if (user.getEmail().contains("invalid")) {
        throw new Exception("Invalid email");
        // Automatischer Rollback!
    }
}

🔵 BONUS: Advanced Features

Lazy vs. Eager Loading

@Entity
public class User {
    
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Order> orders;  // Nicht sofort geladen!
}

LAZY: Daten werden erst geladen wenn benötigt
EAGER: Daten werden sofort mitgeladen

Best Practice: Default belassen, bei Bedarf JOIN FETCH nutzen:

User user = em.createQuery(
    "SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id",
    User.class
).setParameter("id", id).getSingleResult();

N+1 Problem vermeiden

Problem:

List<User> users = em.createQuery("SELECT u FROM User u", User.class)
    .getResultList();  // 1 Query

for (User user : users) {
    user.getOrders().size();  // N Queries!
}

→ Bei 100 Users: 101 Queries!

Lösung: JOIN FETCH

List<User> users = em.createQuery(
    "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders",
    User.class
).getResultList();  // Nur 1 Query!

Named Queries

@Entity
@NamedQuery(
    name = "User.findByEmail",
    query = "SELECT u FROM User u WHERE u.email = :email"
)
public class User { ... }

Verwendung:

User user = em.createNamedQuery("User.findByEmail", User.class)
    .setParameter("email", "test@example.com")
    .getSingleResult();

Vorteil: Query wird beim Start validiert, Fehler früh erkannt!


💬 Real Talk: JPA in der Praxis

Java Fleet Büro, 15:00 Uhr. Nova sitzt frustriert vor Laptop, Elyndra kommt vorbei.


Nova: „Elyndra, warum soll ich JPA lernen wenn JDBC funktioniert?“

Elyndra: „Zeig mal. Was machst du?“

Nova: „User mit Orders laden. Mit JDBC ein JOIN. Jetzt hab ich @OneToManyLazyInitializationException… das ist Overkill!“

Elyndra (schmunzelt): „Lass mich raten – user.getOrders() außerhalb der Transaction?“

Nova: „Ja! Warum lädt JPA die Orders nicht wenn ich sie brauche?“

Elyndra: „Kann es – mit JOIN FETCH:“

em.createQuery(
    "SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id",
    User.class
).setParameter("id", id).getSingleResult();

Nova: „Oh! Das ist clever. Aber warum nicht alles EAGER?“

Elyndra: „Dann lädst du 1000 Orders, auch wenn du nur den Namen brauchst. Performance-Killer!“

Nova: „Macht Sinn. Aber diese persistence.xml nervt.“

Elyndra: „Einmal Setup, dann fertig. Und du bekommst:

  • Automatisches Mapping
  • Dirty Checking
  • Transaktionen
  • Caching
  • Keine SQL-Injection

Mit JDBC schreibst du 20 Zeilen pro CRUD. Mit JPA eine Zeile.“

Nova (grinst): „Lowkey überzeugt. Hibernate oder EclipseLink?“

Elyndra: „Hibernate. Bessere Community, mehr Tutorials.“


✅ Checkpoint: Hast du es verstanden?

Quiz:

Frage 1: Was ist der Hauptunterschied zwischen JDBC und JPA?

Frage 2: Welche 4 Hauptmethoden hat der EntityManager für CRUD?

Frage 3: Was bedeutet @Entity auf einer Klasse?

Frage 4: Warum braucht eine Entity einen Default Constructor?

Frage 5: Was macht @PrePersist?

Frage 6: Wann nutzt du GenerationType.IDENTITY vs. SEQUENCE?

Frage 7: Was ist „Dirty Checking“?

Frage 8: Warum sollte hibernate.hbm2ddl.auto in Production „none“ sein?

Frage 9: Was ist das N+1 Problem und wie löst du es?

Frage 10: Wann würdest du JDBC statt JPA nutzen?


🎯 Mini-Challenge

Aufgabe: Erstelle ein JPA Product-Management

Requirements:

  1. Entity Product:
    • Auto-generated ID
    • Name (max 100 Zeichen, NOT NULL)
    • Price (BigDecimal, NOT NULL)
    • Stock (Integer, default 0)
    • CreatedAt Timestamp
  2. Service ProductService:
    • createProduct()
    • findProductById()
    • findAllProducts()
    • updateStock()
    • deleteProduct()
  3. persistence.xml:
    • Hibernate als Provider
    • MySQL Dialect
    • Auto-update für Development
  4. Bonus:
    • @PrePersist für createdAt
    • Named Query für findAll

Lösung:
Findest du am Anfang von Tag 8! 🚀

GitHub: [Link folgt]


Geschafft? 🎉


❓ Häufig gestellte Fragen

Frage 1: Muss ich für jede Tabelle eine Entity erstellen?

Für Tabellen, die du per JPA verwalten willst – ja. Aber du kannst Native SQL für komplexe Queries nutzen und DTOs für Read-Only Views.


Frage 2: Wie migriere ich von JDBC zu JPA?

Schrittweise! Phase 1: JPA parallel einführen. Phase 2: CRUD zu JPA. Phase 3: Komplexe Queries. Nicht alles auf einmal!


Frage 3: Wie teste ich JPA-Code?

Option 1: H2 In-Memory DB für Tests
Option 2: Testcontainers mit echter DB

Empfehlung: H2 für Unit-Tests, Testcontainers für Integration-Tests.


Frage 4: Wie handle ich Enums?

@Enumerated(EnumType.STRING)  // Speichert "ADMIN", "USER"
private UserRole role;

IMMER EnumType.STRING! Nie ORDINAL (bricht bei Reihenfolge-Änderung).


Frage 5: Kann ich andere DBs als MySQL nutzen?

Klar! JPA ist datenbank-agnostisch. Nur Dialect ändern:

<!-- PostgreSQL -->
<property name="hibernate.dialect" 
          value="org.hibernate.dialect.PostgreSQLDialect"/>

Frage 6: Wie handle ich Optimistic Locking?

@Version
private int version;  // JPA verwaltet automatisch!

Bei Concurrent Updates wirft JPA OptimisticLockException.


Frage 7: Bernd meinte, „JPA ist overthinking für simple Apps“. Recht?

Lowkey ja für 3-Tabellen-Apps. Aber sobald du 10+ Tabellen, Relationen und Business-Logik hast, zahlt sich JPA aus. In Enterprise-Projekten ist JPA Standard.

Real talk: Upfront mehr Setup, langfristig weniger Arbeit. Für Production fast immer besser.


📚 Quiz-Lösungen

Hier sind die Antworten zum Quiz:


Frage 1: Was ist der Hauptunterschied zwischen JDBC und JPA?

Antwort:

JDBC: Low-Level, manuelles SQL, manuelles Mapping
JPA: High-Level ORM, automatisches SQL, automatisches Mapping

Kern: JDBC ist „imperativer“ Zugriff (WIE), JPA ist „deklarativer“ Zugriff (WAS).


Frage 2: Welche 4 Hauptmethoden hat der EntityManager für CRUD?

Antwort:

  1. persist(entity) – CREATE/INSERT
  2. find(Class, id) – READ/SELECT
  3. merge(entity) – UPDATE
  4. remove(entity) – DELETE

Frage 3: Was bedeutet @Entity auf einer Klasse?

Antwort:

„Diese Klasse ist eine JPA-Entity und repräsentiert eine Datenbank-Tabelle.“ JPA scannt beim Start alle @Entity-Klassen.


Frage 4: Warum braucht eine Entity einen Default Constructor?

Antwort:

JPA nutzt Reflection zum Instanziieren von Entities beim Laden aus der DB. Der Default Constructor (kann private sein) wird dafür benötigt.


Frage 5: Was macht @PrePersist?

Antwort:

Lifecycle Callback – wird automatisch VOR persist() aufgerufen. Perfekt für Timestamps oder UUID-Generierung:

@PrePersist
protected void onCreate() {
    createdAt = LocalDateTime.now();
}

Frage 6: Wann nutzt du GenerationType.IDENTITY vs. SEQUENCE?

Antwort:

IDENTITY: MySQL (AUTO_INCREMENT), SQL Server
SEQUENCE: PostgreSQL, Oracle

MySQL hat keine Sequences → IDENTITY
PostgreSQL hat Sequences → SEQUENCE (bessere Performance)


Frage 7: Was ist „Dirty Checking“?

Antwort:

JPA tracked automatisch Änderungen an MANAGED Entities. Vor Commit vergleicht JPA Entity mit Snapshot und generiert UPDATE nur für geänderte Felder.

User user = em.find(User.class, 1L);  // MANAGED
user.setEmail("new@email.com");
// Kein merge() nötig! Automatisches UPDATE beim Commit!

Frage 8: Warum sollte hibernate.hbm2ddl.auto in Production „none“ sein?

Antwort:

create = LÖSCHT ALLE DATEN!
update = Kann schiefgehen, kein Rollback

In Production: Schema-Changes mit Flyway/Liquibase. Niemals auto-generation!

<property name="hibernate.hbm2ddl.auto" value="none"/>

Frage 9: Was ist das N+1 Problem und wie löst du es?

Antwort:

Problem: 1 Query für Entities + N Queries für Lazy-loaded Relations = 1+N Queries!

Lösung: JOIN FETCH

em.createQuery(
    "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders",
    User.class
).getResultList();  // Nur 1 Query statt 1+N!

Frage 10: Wann würdest du JDBC statt JPA nutzen?

Antwort:

JDBC für:

  • Bulk-Operations (10.000+ Rows)
  • Komplexe Reporting-Queries
  • Performance-kritische Abschnitte

JPA für:

  • Enterprise-Apps (90% aller Fälle)
  • CRUD auf Domain-Modell
  • Komplexe Objektbeziehungen

Best Practice: Hybrid – JPA für 90%, JDBC für spezielle 10%!


🎉 Tag 7 geschafft!

Slay! Du hast es geschafft! 🚀

Das hast du heute gerockt:

  • ✅ JPA vs JDBC verstanden
  • ✅ persistence.xml konfiguriert
  • ✅ Erste Entity erstellt
  • ✅ EntityManager CRUD gemeistert
  • ✅ Production-Ready Setup gelernt

Von JDBC-Queries zu automatischem ORM!

Main Character Energy: Unlocked! ✨

Real talk:
JPA ist am Anfang overwhelming – aber du hast es geschafft! Das ist ein massiver Skill-Boost für deine Karriere.


🚀 Wie geht’s weiter?

Morgen (Tag 8): JPA Relationen (1): @OneToOne & @ManyToOne

Was dich erwartet:

  • Relationen zwischen Entities (@OneToOne, @ManyToOne)
  • Foreign Keys und JOINs
  • Bidirektionale Relationen
  • Cascade-Types – dein Durchbruch für komplexe Domain-Modelle! 🔥

Brauchst du eine Pause?
Absolut! JPA ist intensive Kost. Lass es sacken, spiel mit Entities.

Tipp für heute Abend:
Erweitere die User-Entity:

  • Füge Role-Enum hinzu
  • Erstelle Named Queries
  • Teste alle CRUD-Operationen

Learning by doing! 🔧


🔧 Troubleshooting

Problem: LazyInitializationException

Ursache: LAZY Collection außerhalb Transaction.

Lösung: JOIN FETCH oder FetchType.EAGER


Problem: No Persistence provider

Ursache: Hibernate nicht gefunden.

Lösung:

<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

Prüfe: persistence.xml unter META-INF/!


Problem: Table doesn’t exist

Lösung Development:

<property name="hibernate.hbm2ddl.auto" value="update"/>

Production: Tabelle manuell erstellen oder Flyway nutzen!


Offizielle Docs:

Tutorials:

Books:

  • „Pro JPA 2“ by Mike Keith
  • „High-Performance Java Persistence“ by Vlad Mihalcea (must-read!)

💬 Feedback?

War Tag 7 zu theoretisch? Mehr Praxis gewünscht?

Schreib uns: feedback@java-developer.online


Bis morgen! 👋

Elyndra

elyndra@java-developer.online
Senior Developer bei Java Fleet Systems Consulting


Java Web Aufbau – Tag 7 von 10
© 2025 Java Fleet Systems Consulting
Website: java-developer.online

Autor

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.