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

Schwierigkeit: 🟢 Grundlagen
Dauer: ~6-8 Stunden
Voraussetzungen: Tag 1-5 abgeschlossen (GUI-Teil)


🗺️ 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👉 DU BIST HIER!
7JDBC Best Practices🟡 Fortgeschritten🔜 Kommt als nächstes
8JPA Einführung🟢 Grundlagen🔒 Gesperrt
9JPA CRUD & Queries🟡 Fortgeschritten🔒 Gesperrt
10Integration & Ausblick🔴 KOPFNUSS🔒 Gesperrt

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


⚡ Das Wichtigste in 30 Sekunden

Was ist JDBC? Java Database Connectivity – die Standard-API um von Java aus mit Datenbanken zu sprechen.

Heute lernst du:

  • ✅ Die JDBC-Architektur (Driver, Connection, Statement, ResultSet)
  • ✅ Deine erste Datenbankverbindung
  • ✅ CRUD-Operationen (Create, Read, Update, Delete)
  • ✅ Warum PreparedStatement PFLICHT ist (SQL Injection!)
  • ✅ try-with-resources für sauberes Ressourcen-Management

Die goldene Regel:

NIEMALS String-Konkatenation für SQL!
IMMER PreparedStatement mit Parametern!


🟢 GRUNDLAGEN: Die JDBC-Architektur

Wie Java mit Datenbanken spricht

Abbildung 1: JDBC – Die Schichten zwischen Java und Datenbank


JDBC funktioniert wie ein Dolmetscher:

  1. Dein Code spricht JDBC (java.sql.*)
  2. JDBC API ist die standardisierte Schnittstelle
  3. JDBC Driver übersetzt für die spezifische Datenbank
  4. Datenbank versteht ihre eigene Sprache

Der Clou: Dein Code bleibt gleich – nur der Treiber wechselt!

// MySQL
String url = "jdbc:mysql://localhost:3306/mydb";

// PostgreSQL
String url = "jdbc:postgresql://localhost:5432/mydb";

// H2 (In-Memory)
String url = "jdbc:h2:mem:testdb";

// Der Rest deines Codes ändert sich NICHT!
Connection conn = DriverManager.getConnection(url, user, pass);

🟢 Die 4 Schritte jeder JDBC-Operation

JDBC

Abbildung 2: Die 4 Schritte – Connection → Statement → ResultSet → Close


Schritt 1: Connection herstellen

String url = "jdbc:h2:mem:testdb";
String user = "sa";
String password = "";

Connection conn = DriverManager.getConnection(url, user, password);

Schritt 2: Statement vorbereiten

// ❌ NIEMALS SO (SQL Injection!)
Statement stmt = conn.createStatement();
stmt.executeQuery("SELECT * FROM users WHERE id=" + userId);

// ✅ IMMER SO (PreparedStatement!)
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE id = ?"
);
ps.setInt(1, userId);  // Parameter setzen

Schritt 3: ResultSet verarbeiten

ResultSet rs = ps.executeQuery();

while (rs.next()) {
    int id = rs.getInt("id");
    String name = rs.getString("name");
    int alter = rs.getInt("alter");
    
    System.out.println(name + " ist " + alter + " Jahre alt");
}

Schritt 4: Ressourcen schließen

// ALT: Manuell schließen (fehleranfällig!)
rs.close();
ps.close();
conn.close();

// NEU: try-with-resources (automatisch!)
try (Connection conn = DriverManager.getConnection(url, user, pass);
     PreparedStatement ps = conn.prepareStatement(sql)) {
    // Wird AUTOMATISCH geschlossen, auch bei Exceptions!
}

🔴 SQL Injection – Die #1 Sicherheitslücke

Abbildung 3: SQL Injection – Was passiert wenn du String-Konkatenation verwendest


Das Problem

// Benutzer gibt ein: ' OR '1'='1' --
String userInput = "' OR '1'='1' --";

// ❌ FALSCH: String-Konkatenation
String sql = "SELECT * FROM users WHERE name='" + userInput + "'";

// Ergebnis:
// SELECT * FROM users WHERE name='' OR '1'='1' --'
// ↓
// Gibt ALLE Benutzer zurück!

Die Lösung

// ✅ RICHTIG: PreparedStatement
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, userInput);

// Ergebnis:
// Sucht nach User mit Name "' OR '1'='1' --"
// ↓
// Findet nichts (wie erwartet)

💀 SQL Injection ist seit 25 Jahren bekannt und IMMER NOCH der häufigste Angriff!
Verwende IMMER PreparedStatement!


🟡 PROFESSIONALS: CRUD komplett

CREATE – Daten einfügen

String sql = "INSERT INTO personen (name, alter, email) VALUES (?, ?, ?)";

try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
    ps.setString(1, "Max Mustermann");
    ps.setInt(2, 28);
    ps.setString(3, "max@example.com");
    
    int affected = ps.executeUpdate();  // Anzahl betroffener Zeilen
    
    // Generierte ID abrufen
    try (ResultSet keys = ps.getGeneratedKeys()) {
        if (keys.next()) {
            int newId = keys.getInt(1);
            System.out.println("Neue ID: " + newId);
        }
    }
}

READ – Daten lesen

// Alle lesen
String sql = "SELECT id, name, alter, email FROM personen";
try (Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery(sql)) {
    
    while (rs.next()) {
        // Verarbeiten...
    }
}

// Nach ID suchen
String sql = "SELECT id, name, alter, email FROM personen WHERE id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setInt(1, suchId);
    
    try (ResultSet rs = ps.executeQuery()) {
        if (rs.next()) {
            // Gefunden!
        }
    }
}

UPDATE – Daten ändern

String sql = "UPDATE personen SET name = ?, alter = ?, email = ? WHERE id = ?";

try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, "Neuer Name");
    ps.setInt(2, 30);
    ps.setString(3, "neu@example.com");
    ps.setInt(4, personId);
    
    int affected = ps.executeUpdate();
    System.out.println(affected + " Zeile(n) aktualisiert");
}

DELETE – Daten löschen

String sql = "DELETE FROM personen WHERE id = ?";

try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setInt(1, personId);
    
    int affected = ps.executeUpdate();
    System.out.println(affected + " Zeile(n) gelöscht");
}

💬 Real Talk: Der erste Datenbank-Bug

Java Fleet Serverraum, Freitagabend.


Nova: „Franz-Martin, die App ist abgestürzt! Irgendwas mit ‚Too many connections‘?“

Franz-Martin: (seufzt) „Lass mich raten – du hast die Connections nicht geschlossen?“

Nova: „Ich… ich dachte die schließen sich von selbst?“

Franz-Martin: „Nope. Eine Connection ist wie eine Telefonleitung. Wenn du nicht auflegst, bleibt sie belegt.“

Nova: „Aber ich hab try-catch benutzt!“

Franz-Martin: „Zeig mal… Ah, da ist der Bug:“

try {
    Connection conn = DriverManager.getConnection(url, user, pass);
    // ... Code ...
    conn.close();  // Wird bei Exception NICHT erreicht!
} catch (SQLException e) {
    e.printStackTrace();
}

Nova: „Oh…“

Franz-Martin: „Klassiker. Wenn eine Exception fliegt, erreicht er conn.close() nie. Nach ein paar hundert Requests sind alle Connections weg.“

Nova: „Und die Lösung?“

Franz-Martin: „try-with-resources. Java 7, also kein Hexenwerk:“

try (Connection conn = DriverManager.getConnection(url, user, pass)) {
    // ... Code ...
}  // conn.close() wird AUTOMATISCH aufgerufen, auch bei Exception!

Nova: „Das ist ja viel sauberer!“

Franz-Martin: „Und sicherer. Merk dir: Alles was AutoCloseable implementiert – Connection, Statement, ResultSet – gehört in try-with-resources.“


✅ Checkpoint

📝 Quiz

Frage 1: Was ist der Hauptgrund für PreparedStatement statt Statement?

A) PreparedStatement ist schneller
B) PreparedStatement verhindert SQL Injection
C) Statement ist veraltet
D) PreparedStatement kann mehr Datentypen


Frage 2: Was passiert wenn du eine Connection nicht schließt?

A) Nichts, Java schließt sie automatisch
B) Die Verbindung bleibt offen und blockiert Ressourcen
C) Die Datenbank stürzt ab
D) Die JVM beendet sich


Frage 3: Welche Methode ruft man für SELECT-Abfragen auf?

A) executeUpdate()
B) execute()
C) executeQuery()
D) executeSelect()


Frage 4: Was gibt executeUpdate() zurück?

A) Ein ResultSet
B) Die Anzahl der betroffenen Zeilen
C) true oder false
D) Die generierte ID


📝 Quiz-Lösungen

Frage 1:B – PreparedStatement verhindert SQL Injection
Das ist der wichtigste Grund. Performance-Vorteile gibt es auch, aber Sicherheit ist kritisch.

Frage 2:B – Die Verbindung bleibt offen
Jede Connection verbraucht Ressourcen. Nach vielen nicht-geschlossenen Connections: „Too many connections“.

Frage 3:CexecuteQuery()
executeQuery() für SELECT, executeUpdate() für INSERT/UPDATE/DELETE.

Frage 4:B – Die Anzahl der betroffenen Zeilen
Für die generierte ID: Statement.RETURN_GENERATED_KEYS + getGeneratedKeys().


❓ FAQ

Welche Datenbank soll ich zum Lernen verwenden?
H2 (In-Memory). Keine Installation, läuft im Speicher, perfekt zum Experimentieren. In Produktion: PostgreSQL oder MySQL/MariaDB.

Muss ich den JDBC-Treiber manuell laden?
Früher ja (Class.forName("com.mysql.jdbc.Driver")). Seit JDBC 4.0 (Java 6) nicht mehr – der Treiber registriert sich automatisch.

Wann Statement, wann PreparedStatement?
IMMER PreparedStatement wenn User-Input involviert ist. Statement nur für statische SQL-Befehle ohne Parameter.

Was ist der Unterschied zwischen execute(), executeQuery() und executeUpdate()?

  • executeQuery() → für SELECT, gibt ResultSet zurück
  • executeUpdate() → für INSERT/UPDATE/DELETE, gibt Zeilenanzahl zurück
  • execute() → für alles, gibt boolean zurück (true wenn ResultSet)

Warum nicht einfach JPA von Anfang an?
JPA baut auf JDBC auf. Wenn du JDBC verstehst, verstehst du auch was JPA im Hintergrund macht – und kannst Probleme debuggen.


📦 Downloads

ProjektInhaltDownload
java-anwendungsentwicklung-tag6.zipJDBC-Demos mit H2⬇️ Download

Quick Start:

mvn exec:java                # Hallo JDBC
mvn exec:java -Pcrud         # CRUD-Beispiel
mvn exec:java -Pinjection    # SQL Injection Demo

🔗 Weiterführende Links

RessourceBeschreibung
JDBC Tutorial (Oracle)Offizielle Dokumentation
H2 DatabaseDie In-Memory-Datenbank
SQL Injection (OWASP)Sicherheits-Referenz

🎉 Tag 6 geschafft!

Was du heute gelernt hast:

✅ JDBC-Architektur verstehen (Driver → Connection → Statement → ResultSet)
✅ try-with-resources für sauberes Ressourcen-Management
✅ CRUD-Operationen mit PreparedStatement
✅ SQL Injection verstehen und verhindern
✅ H2 als Lern-Datenbank verwenden

Morgen – Tag 7: JDBC Best Practices

Connection Pooling, Transaktionen, das DAO-Pattern. Von „funktioniert“ zu „produktionsreif“!


Fragen? franz-martin@java-developer.online

© 2025 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.