In der Klausur müsst ihr JDBC-Code nicht nur lesen und kommentieren, sondern auch selbst SCHREIBEN und AENDERN. Bonusaufgaben: Syntaxfehler korrigieren, Java-Programme umschreiben. Über die Muster unten sollen tippfertig sitzen.
Zusammenfassung
⭐ Laut Dozent: Code-Review-Aufgabe (JDBC-Quelltext kommentieren & erklären) plus Bonus: Java selbst programmieren & abändern, Syntaxfehler finden. JDBC aus dem Skript!
JDBC (Java Database Connectivity) ist die Java-API für den standardisierten Zugriff auf relationale Datenbanken via DBMS-spezifische Treiber (typisch Typ 4, pure Java). Kernelemente sind DriverManager, Connection, PreparedStatement und ResultSet - kombiniert mit expliziter Transaktionssteuerung über setAutoCommit/commit/rollback und automatischer Ressourcenfreigabe per try-with-resources. Der wichtigste Praxis-Reflex: immer PreparedStatement mit ?-Platzhaltern statt Statement mit String-Konkatenation verwenden, um SQL Injection zu verhindern.
Kernkonzepte
Class.forName und DriverManager
Class.forName(org.postgresql.Driver) lädt die Treiberklasse und registriert sie beim DriverManager. Danach liefert DriverManager.getConnection(url, user, pw) eine Connection. Die URL hat die Form jdbc:<dbms>://host:port/db. Seit JDBC 4 werden Treiber via ServiceLoader oft automatisch geladen - Class.forName wird aber in Lehrbeispielen weiter gezeigt.
Treiber laden, dann Verbindung holen - erst Class.forName, dann DriverManager.getConnection.
Connection
Repräsentiert die aktive Verbindung zur Datenbank. Über die Connection werden Statements erstellt und Transaktionen gesteuert (setAutoCommit, commit, rollback). Nach Gebrauch muss sie geschlossen werden - am besten via try-with-resources. Eine Connection kapselt einen Netzwerk-Socket zum DBMS.
Eine Verbindung, viele Statements - Connection ist teuer, Statements sind billig.
Statement vs PreparedStatement vs CallableStatement
Statement für statische SQL-Strings ohne Parameter (SQL Injection Gefahr bei Konkatenation, keine Wiederverwendung). PreparedStatement für parametrisierte Abfragen mit Platzhaltern ?, vorkompiliert, sicher gegen SQL Injection, ideal bei wiederholten Aufrufen. CallableStatement für Stored Procedure Calls mit Syntax {CALL name(?)}.
PreparedStatement fast immer, Statement fast nie - Injection-sicher und performant.
ResultSet
Datenkontainer / Cursor für SELECT-Ergebnisse. Startet VOR der ersten Zeile. rs.next() bewegt Cursor eine Zeile weiter und liefert boolean. Zugriff auf Werte via getInt(spalte), getString(spalte) - entweder per Spaltenname oder 1-basiertem Index. Standardmäßig Forward-Only und Read-Only.
Erst next, dann get - der Cursor steht anfangs vor der ersten Zeile.
Transaktionen mit setAutoCommit, commit, rollback
Standard: autoCommit=true (jedes Statement eigene Transaktion). Für atomare Multi-Statement-Operationen setAutoCommit(false) aufrufen. Bei Erfolg conn.commit(), bei Fehler conn.rollback() im catch-Block. Am Ende autoCommit wieder auf true zurücksetzen. Ohne diese Steuerung sind zusammengehörige Änderungen NICHT atomar.
autoCommit aus, dann commit oder rollback - alles oder nichts.
Exception Handling mit SQLException
Fast alle JDBC-Methoden werfen SQLException (checked exception). Muss per try-catch behandelt oder deklariert werden. SQLException bietet getMessage, getSQLState (SQL-Standard-Codes) und getErrorCode (DBMS-spezifisch). Bei Transaktionen im catch-Block immer rollback aufrufen.
Jede JDBC-Zeile kann werfen - im catch immer an rollback denken.
Ressourcen schliessen mit try-with-resources
Connection, Statement, PreparedStatement und ResultSet implementieren AutoCloseable. try (Resource r = ...) ruft close automatisch auf - auch bei Exception, in umgekehrter Deklarationsreihenfolge. Löst das häufige Problem vergessener close-Aufrufe (Ressourcen-Leak, Sperren, Deadlocks).
try-with-resources statt manuellem close in finally - Java 7 aufwärts Standard.
PreparedStatement Parameter binden
Nach conn.prepareStatement(sql mit ?) werden Parameter mit setInt(index, wert), setString(index, wert), setDate, setBigDecimal etc. gebunden. Index ist 1-basiert (nicht 0-basiert). Erst danach executeQuery oder executeUpdate aufrufen. Reihenfolge der ? bestimmt den Index.
Index 1-basiert, Typ passend - setInt für INTEGER, setString für VARCHAR.
Wichtige Details
SQL Injection Schutz: NIE Benutzereingaben per String-Konkatenation in SQL einbauen. Immer PreparedStatement mit Platzhaltern ? und setXxx-Methoden verwenden. Beispiel Angriff: password = xxx' OR 1 = 1 umgeht Authentifizierung wenn per String-Konkatenation zusammengesetzt.
Statement vs PreparedStatement Performance: PreparedStatement wird einmal vom DBMS geparst und der Ausführungsplan gecached. Bei wiederholter Ausführung (nur setXxx neu, executeQuery neu) ist es deutlich schneller. Statement wird jedes Mal neu geparst.
Batch-Update mit addBatch() sammelt Statements und executeBatch() sendet sie in einem Netzwerk-Roundtrip an das DBMS. Reduziert Roundtrip-Overhead massiv bei vielen INSERTs/UPDATEs. Rückgabe ist int[] mit betroffenen Zeilen pro Statement.
Ressourcen schliessen in korrekter Reihenfolge: ResultSet vor Statement vor Connection. try-with-resources macht das automatisch in umgekehrter Deklarationsreihenfolge - deshalb Connection zuerst deklarieren, dann Statement, dann ResultSet.
ResultSet Cursor-Typen: TYPE_FORWARD_ONLY (Standard, nur next()), TYPE_SCROLL_INSENSITIVE (auch previous, absolute, ohne Live-Updates), TYPE_SCROLL_SENSITIVE (mit Live-Updates). Concurrency: CONCUR_READ_ONLY oder CONCUR_UPDATABLE.
PreparedStatement Parameter sind 1-basiert! ps.setInt(1, wert) für das erste ?, ps.setString(2, wert) für das zweite ?. ps.setInt(0, wert) wirft SQLException.
JDBC verwendet nur indizierte Parameter (?), NICHT named parameters (:name). Named Parameters gibt es nur bei Erweiterungen wie Spring JdbcTemplate oder JPA.
Metadata via ResultSetMetaData: rsmd = rs.getMetaData(); rsmd.getColumnCount(); rsmd.getColumnName(i); - für generische Anzeige unbekannter Ergebnisstrukturen. DatabaseMetaData über conn.getMetaData() gibt Info über DBMS.
SQLException hat getMessage (lesbar), getSQLState (SQLSTATE-Code nach Standard) und getErrorCode (DBMS-spezifisch). Beim Debugging alle drei loggen. Chained Exceptions über getNextException.
Connection Pooling: In Produktion nie DriverManager.getConnection direkt - Verbindungsaufbau ist teuer. Stattdessen DataSource mit Connection Pool (HikariCP, c3p0) verwenden. Für Klausur meistens DriverManager gezeigt.
Der JDBC-Treiber muss zur Laufzeit im Classpath sein. Bei Maven: postgresql als dependency in pom.xml. Bei Kommandozeile: java -cp postgresql.jar:. MyProgram.
Boilerplate von JDBC ist der Hauptnachteil - viel wiederholter Code für Verbindung, Statement, Iteration, Fehlerbehandlung. Deshalb in Praxis oft ORM (Hibernate/JPA) oder Query-Builder (jOOQ). Für die Klausur muss man das Boilerplate aber können.
Code-Muster - Diese SOLLEN sitzen
Jedes Muster einmal mitschreiben. Der Kopieren-Button hilft beim Üben in einer IDE.
1. Verbindung aufbauen und schliessen mit try-with-resources
Der absolute Standard-Einstieg jeder JDBC-Anwendung. Treiber wird geladen, Connection wird geöffnet und garantiert wieder geschlossen - auch bei Exception. Das try-with-resources ruft close() automatisch auf, deshalb kein finally nötig.
2. SELECT mit PreparedStatement und ResultSet-Schleife
Ausführen einer Anfrage mit Benutzereingabe. Der Fragezeichen-Platzhalter und setString/setInt verhindern SQL Injection. Das ResultSet wird mit rs.next() Zeile für Zeile abgearbeitet.
String sql = "SELECT matrnr, name, semester FROM studenten WHERE semester >= ?";
try (Connection conn = DriverManager.getConnection(url, user, pw);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, 3);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
int matrnr = rs.getInt("matrnr");
String name = rs.getString("name");
int sem = rs.getInt("semester");
System.out.println(matrnr + " " + name + " " + sem);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
Kern-Zeilen zum Auswendiglernen:
conn.prepareStatement(sql) mit Platzhaltern
ps.setInt(1, wert) - Parameter binden, 1-basiert
ResultSet rs = ps.executeQuery()
while (rs.next()) { rs.getString(spalte); }
3. INSERT / UPDATE / DELETE mit PreparedStatement
Datenänderungen werden mit executeUpdate ausgeführt (nicht executeQuery). Der Rückgabewert ist die Anzahl betroffener Zeilen als int. Auch hier PreparedStatement mit setXxx-Methoden verwenden.
executeUpdate() statt executeQuery() bei INSERT/UPDATE/DELETE
Rueckgabewert int = Anzahl betroffener Zeilen
ps.setInt / ps.setString mit 1-basiertem Index
PreparedStatement wiederverwendbar durch setXxx neu setzen
4. Transaktion mit setAutoCommit, commit und rollback
Zwei zusammengehörige Änderungen (klassisch: Überweisung zwischen zwei Konten) müssen atomar erfolgen. Ohne setAutoCommit(false) wird jedes Statement einzeln committet - das darf hier nicht passieren.
conn.commit() - am Ende des try-Blocks nach allen Statements
conn.rollback() - im catch bei jedem Fehler
finally: setAutoCommit(true) und close()
5. Batch-Update für viele Datensätze
1000 einzelne INSERTs = 1000 Netzwerk-Roundtrips = langsam. Batch fasst mehrere Statements zusammen und sendet sie als Block. Idealerweise kombiniert mit einer Transaktion.
String sql = "INSERT INTO logeintraege (zeitstempel, nachricht) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, pw)) {
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement(sql)) {
for (int i = 0; i < 1000; i++) {
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
ps.setString(2, "Nachricht Nr " + i);
ps.addBatch();
if (i % 100 == 0) {
ps.executeBatch();
}
}
int[] ergebnisse = ps.executeBatch();
conn.commit();
System.out.println("Insgesamt " + ergebnisse.length + " Statements");
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
Kern-Zeilen zum Auswendiglernen:
ps.addBatch() - Statement zur Batch hinzufuegen
ps.executeBatch() - alle gesammelten Statements ausfuehren
conn.setAutoCommit(false) - Batch in einer Transaktion
Rueckgabe int[] = Anzahl betroffener Zeilen pro Statement
6. Fehlerhaftes Snippet erkennen und korrigieren
Klassische Klausur-Aufgabe. Das erste Snippet enthält mehrere Anti-Patterns: Statement (kein PreparedStatement) mit String-Konkatenation (SQL Injection), kein close (Ressourcen-Leak), autoCommit nicht gesetzt bei zwei zusammengehörigen Statements, kein Rollback im Fehlerfall.
// FEHLERHAFT:
Connection conn = DriverManager.getConnection(url, user, pw);
Statement stmt = conn.createStatement();
String userInput = request.getParameter("name");
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE name = '" + userInput + "'");
stmt.executeUpdate("UPDATE konto SET saldo = saldo - 100 WHERE id = 1");
stmt.executeUpdate("UPDATE konto SET saldo = saldo + 100 WHERE id = 2");
// KORRIGIERT:
String sel = "SELECT * FROM users WHERE name = ?";
String upd1 = "UPDATE konto SET saldo = saldo - ? WHERE id = ?";
String upd2 = "UPDATE konto SET saldo = saldo + ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, pw)) {
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement(sel)) {
ps.setString(1, userInput);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) { /* ... */ }
}
}
try (PreparedStatement p1 = conn.prepareStatement(upd1);
PreparedStatement p2 = conn.prepareStatement(upd2)) {
p1.setInt(1, 100); p1.setInt(2, 1); p1.executeUpdate();
p2.setInt(1, 100); p2.setInt(2, 2); p2.executeUpdate();
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
Kern-Zeilen zum Auswendiglernen:
Statement + String-Konkatenation ist SQL Injection anfaellig
PreparedStatement mit ? und setXxx ist sicher
Fehlendes close() = Ressourcen-Leak - try-with-resources loest das
Zwei zusammengehoerige Updates brauchen setAutoCommit(false) + commit/rollback
7. Stored Procedure per CallableStatement aufrufen
Zum Aufruf einer gespeicherten Prozedur in der Datenbank nutzt man CallableStatement mit prepareCall und der Syntax {CALL name(?, ?)}. Parameter werden wie bei PreparedStatement gebunden.
conn.prepareCall({CALL name(?)}) mit geschweiften Klammern
setInt / setString wie sonst auch
executeQuery() bzw executeUpdate() je nach Prozedur
Beispiele
Vollständiger JDBC-Ablauf für SELECT
Klassisches 5-Schritt-Muster: Treiber laden, Verbindung aufbauen, PreparedStatement erstellen mit Platzhaltern, Parameter binden, executeQuery aufrufen, ResultSet in while-Schleife durchlaufen und mit rs.next() Zeile für Zeile abarbeiten, Ressourcen im richtigen umgekehrten Reihenfolge schliessen (rs, ps, conn) - am besten via try-with-resources damit dies automatisch geschieht.
1. Class.forName(org.postgresql.Driver) - Treiber registrieren
2. DriverManager.getConnection(url, user, pw) - Verbindung
3. conn.prepareStatement(SELECT ... WHERE x = ?) - PreparedStatement
4. ps.setInt(1, wert) - Parameter binden
5. ResultSet rs = ps.executeQuery() - Ausfuehren
6. while (rs.next()) { rs.getString(name); rs.getInt(alter); } - Iterieren
7. Ressourcen schliessen (automatisch mit try-with-resources)
Banktransaktion mit Rollback
Klassische Uni-Aufgabe: Betrag von Konto A abbuchen und auf Konto B gutschreiben. Beide UPDATEs müssen zusammen erfolgreich sein oder gar nicht. autoCommit muss auf false gesetzt werden, damit die zwei Statements in einer Transaktion gebündelt werden. Bei Exception: rollback im catch-Block. Bei Erfolg: commit am Ende des try-Blocks. Im finally: autoCommit wieder auf true setzen und Connection schliessen.
conn.setAutoCommit(false)
UPDATE konto SET saldo = saldo - 100 WHERE id = A
UPDATE konto SET saldo = saldo + 100 WHERE id = B
conn.commit()
catch (SQLException e) { conn.rollback(); }
Fehleranalyse eines schlechten Snippets
Ein typisches Klausur-Beispiel: Statement (kein PreparedStatement) mit String-Konkatenation, kein close, kein try-catch, autoCommit nicht gesetzt. Studenten müssen die drei bis vier Fehler benennen (SQL Injection Risiko, Ressourcen-Leak, keine Transaktion, kein Exception-Handling) und den Code korrigieren.
Fehlerhafter Code: stmt.executeQuery(SELECT * FROM users WHERE name = + userInput)
Korrekt: PreparedStatement mit ? und ps.setString(1, userInput) in try-with-resources
Batch-Update für 1000 Datensätze
Grosse Datenmengen mit einzelnen executeUpdate-Aufrufen verursachen viele Roundtrips. Lösung: PreparedStatement einmal vorbereiten, in Schleife addBatch aufrufen und am Ende executeBatch. Zusätzlich autoCommit=false verwenden damit alle 1000 Inserts in einer Transaktion laufen. Am Ende commit.
JDBC-Ablauf im Überblick
Häufige Fragen
Warum PreparedStatement statt Statement?
Drei Gründe: 1) Schutz vor SQL Injection durch getrennte Übergabe von SQL-Struktur und Parametern, 2) Performance durch Vorkompilierung und Wiederverwendung des Query-Plans bei wiederholtem Aufruf, 3) Typsicherheit durch setInt, setString, setDate statt String-Konkatenation.
Was macht Class.forName und ist es noch nötig?
Class.forName lädt die Treiber-Klasse und registriert sie beim DriverManager. Seit JDBC 4 (Java 6) ist der Aufruf oft nicht mehr nötig, da moderne Treiber via ServiceLoader automatisch geladen werden. In Klausuraufgaben wird die explizite Registrierung aber meist noch gezeigt und gefordert.
Wie unterbindet man SQL Injection zuverlässig?
Immer PreparedStatement mit Platzhaltern ? verwenden und Parameter via setInt, setString etc. binden. NIE Benutzereingaben per String-Konkatenation in den SQL-String einfügen. Alternativ Stored Procedures oder ein Persistenz-Framework (JPA/Hibernate) verwenden.
Was passiert wenn man autoCommit=true lässt und mitten in einer Transaktion einen Fehler bekommt?
Bei autoCommit=true wird jedes einzelne Statement sofort committet. Das erste UPDATE (Abbuchung) wäre also schon persistiert wenn das zweite (Gutschrift) fehlschlägt - die Datenbank ist inkonsistent. Deshalb setAutoCommit(false) verwenden und explizit commit oder rollback aufrufen.
Warum try-with-resources statt manuellem close in finally?
try-with-resources ruft close() automatisch in umgekehrter Reihenfolge der Deklaration auf - auch bei Exceptions. Das eliminiert Boilerplate-Code, verhindert vergessene close-Aufrufe (Ressourcen-Leak) und ist die empfohlene Java-Praxis seit Java 7. Alle JDBC-Objekte (Connection, Statement, ResultSet) implementieren AutoCloseable.
Was ist der Unterschied zwischen executeQuery, executeUpdate und execute?
executeQuery: für SELECT, gibt ResultSet zurück. executeUpdate: für INSERT/UPDATE/DELETE/DDL, gibt int (Anzahl betroffener Zeilen) zurück. execute: universell, gibt boolean zurück (true wenn ResultSet vorhanden), danach getResultSet oder getUpdateCount aufrufen. Für Klausur: executeQuery nur bei SELECT.
Was macht ResultSet.next() genau?
next() bewegt den Cursor eine Zeile weiter und gibt true zurück wenn eine weitere Zeile existiert, false sonst. Vor dem ersten Aufruf steht der Cursor VOR der ersten Zeile - man muss also immer erst next() aufrufen bevor man mit getInt/getString zugreift. Deshalb das Muster while (rs.next()) { ... }.
Was ist der Unterschied zwischen JDBC Typ 2 und Typ 4 Treiber?
Typ 2 (Native API): JDBC ruft eine DBMS-spezifische native C-Library auf, die clientseitig installiert sein muss. Typ 4 (Java-nativ, pure Java): Treiber ist komplett in Java geschrieben und kommuniziert direkt über das Netzwerkprotokoll der Datenbank. Typ 4 ist heute Standard (portabel, keine native Installation nötig).
Prüfungsfragen
Fortschritt
0 / 14
Frage 1kurzantwort
Welcher JDBC-Aufruf lädt die Treiberklasse für PostgreSQL?
Antwort:
Class.forName("org.postgresql.Driver");
Der voll qualifizierte Klassenname des JDBC-Treibers wird an Class.forName übergeben. Dies lädt und registriert den Treiber beim DriverManager.
Frage 2multiple-choice
Welches Interface schützt vor SQL Injection?
Frage 3wahr-falsch
Wahr oder falsch: rs.next() muss vor dem ersten Zugriff auf ResultSet-Werte aufgerufen werden.
Frage 4code-schreiben
Ergänze das PreparedStatement: Schreibe Java-Code der alle Studenten mit Semester größer als einem Parameter selectiert und ausgibt.
Wichtige Elemente: try-with-resources für Connection und PreparedStatement, ? Platzhalter, setInt mit 1-basiertem Index, ResultSet ebenfalls in try-with-resources, while(rs.next()) für Iteration.
Frage 5kurzantwort
Was ist der Unterschied zwischen executeQuery und executeUpdate?
Antwort:
executeQuery für SELECT (gibt ResultSet zurück), executeUpdate für INSERT/UPDATE/DELETE/DDL (gibt int = Anzahl betroffener Zeilen zurück).
Beide sind Methoden auf Statement/PreparedStatement. Die Wahl hängt davon ab ob ein Ergebnis (Menge von Zeilen) erwartet wird oder nur die Anzahl geänderter Zeilen.
Frage 6code-schreiben
Schreibe eine Banktransaktion die 50 Euro von Konto 1 auf Konto 2 überweist mit korrektem Rollback im Fehlerfall.
Musterlösung:
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, pw);
conn.setAutoCommit(false);
try (PreparedStatement ps1 = conn.prepareStatement("UPDATE konto SET saldo = saldo - ? WHERE id = ?");
PreparedStatement ps2 = conn.prepareStatement("UPDATE konto SET saldo = saldo + ? WHERE id = ?")) {
ps1.setBigDecimal(1, new BigDecimal("50")); ps1.setInt(2, 1); ps1.executeUpdate();
ps2.setBigDecimal(1, new BigDecimal("50")); ps2.setInt(2, 2); ps2.executeUpdate();
conn.commit();
}
} catch (SQLException e) {
if (conn != null) try { conn.rollback(); } catch (SQLException ex) {}
throw new RuntimeException(e);
} finally {
if (conn != null) try { conn.close(); } catch (SQLException e) {}
}
Kernpunkte: setAutoCommit(false), beide Updates im try-Block, commit am Ende, rollback im catch, close im finally.
Frage 7multiple-choice
Welche der folgenden Aussagen zu try-with-resources sind korrekt?
Frage 8erkennen
Erkenne die Fehler im Code: Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE name = '" + name + "'");
Antwort:
1) Statement statt PreparedStatement - SQL Injection Gefahr durch String-Konkatenation. 2) Fehlendes close - Ressourcen-Leak. 3) Kein try-catch für SQLException.
Korrekt wäre: PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?"); ps.setString(1, name); - alles in try-with-resources.
Frage 9wahr-falsch
Wahr oder falsch: setAutoCommit(false) ist per Default gesetzt.
Frage 10multiple-choice
Wozu dient conn.rollback()?
Frage 11code-schreiben
Schreibe einen Batch-Update der 5 Log-Einträge einfügt.
addBatch() sammelt die Statements, executeBatch() sendet sie zusammen. Kombination mit setAutoCommit(false) und commit fasst alles in eine Transaktion.
Frage 12kurzantwort
Warum ist der 1-basierte Index bei setInt/setString eine häufige Fehlerquelle?
Antwort:
Weil Java Arrays und Collections 0-basiert sind, PreparedStatement-Parameter aber ab 1 gezählt werden. Vertauschung führt zu SQLException oder falschen Wertzuordnungen.
ps.setInt(0, x) wirft SQLException. Immer beim ersten ? mit 1 anfangen.
Frage 13wahr-falsch
Wahr oder falsch: Ein ResultSet kann nach dem Schliessen der Connection noch verwendet werden.
Frage 14kurzantwort
Nenne drei Vorteile von PreparedStatement gegenüber Statement.
Antwort:
1) Schutz vor SQL Injection durch getrennte Übergabe von Struktur und Parametern. 2) Performance durch Vorkompilierung und Wiederverwendung des Query-Plans. 3) Typsicherheit durch setInt, setString etc. statt fehleranfälliger String-Konkatenation.
Zusätzlich: bessere Lesbarkeit, einfacheres Escaping von Sonderzeichen, direkte Unterstützung für Datentypen wie Date und BigDecimal.