Start / Cheat Sheets

Cheat Sheet

Java SE in der Praxis — Cheat Sheet

Stand: · Java SE in der Praxis

JavaMavenJDBCJAX-RS

Der Spickzettel zum Seminar Java SE in der Praxis — für den Moment, in dem eine POM nicht baut, ein Scope falsch gesetzt ist oder die Frage aufkommt, wie ein PreparedStatement noch mal aussah. Er zeigt die Formen, nicht den Weg dorthin: Wie man ein Projekt Schritt für Schritt aufzieht, ist die Leistung des Seminars. Maßgeblich bleiben die Dokumentationen von Maven, dem JDK und den eingesetzten Bibliotheken. Jeder Abschnitt verweist auf sein Modul.

Werkzeuge und Projektlayout

WerkzeugVersionAnmerkung
JDK25LTS, ältere Stände fallen an Sprachfeatures aus
Maven3.9.xGenaue Patch-Version egal
IntelliJ IDEAaktuellCommunity-Umfang genügt
SDKMAN!optionalMehrere Java- und Maven-Stände parallel
PfadInhalt
src/main/javaProduktionscode
src/main/resourcesKonfiguration und Dateien zur Laufzeit
src/test/javaTestcode
targetErzeugnisse des Builds, nie eingecheckt

maven.compiler.release ersetzt source und target — eine Angabe statt zweier.

Modul: Projekt aufsetzen

Abhängigkeiten: Scope und Ort

ScopeBedeutung
compileGegen diese API wird programmiert (Vorgabe)
runtimeWird zur Laufzeit gebraucht, nie im Code referenziert
testNur im Testbaum verfügbar
OrtInhalt
Eltern-POM, propertiesVersionsnummern der geteilten Bibliotheken
Eltern-POM, dependencyManagementVersion und Scope, auch für Mitgezogenes
Untermodul, dependenciesNur groupId und artifactId
Untermodul, EinzelfallVersion nur, wenn die Bibliothek nur dort vorkommt

dependencyManagement bindet nichts ein — es legt nur fest, wie eingebunden wird, falls ein Modul die Abhängigkeit deklariert. Die Angabe im Untermodul gewinnt immer.

Module: Projekt aufsetzen · Richtung Produktion

Logging mit SLF4J

ArtefaktScopeRolle
slf4j-apicompileGegen diese API wird programmiert
slf4j-simpleruntimeSchreibt zur Laufzeit, nie im Code referenziert
jul-to-slf4jruntimeBrücke für fremdes java.util.logging
private static final Logger LOG =
        LoggerFactory.getLogger(KursAbruf.class);
LOG.info("Rufe Kurse für '{}' ab", autorId);
LOG.error("Unerwarteter Fehler", e);

Platzhalter {} statt Verkettung — die Nachricht entsteht nur, wenn die Stufe aktiv ist. Die Ausnahme gehört als zweites Argument übergeben, nicht in den Text. Fehlt die Implementierung, meldet SLF4J beim Start „NOP logger implementation”.

Module: Projekt aufsetzen · Richtung Produktion

HTTP-Aufrufe mit dem JDK-Client

private static final HttpClient CLIENT =
    HttpClient.newBuilder()
        .followRedirects(Redirect.NORMAL)
        .build();

HttpRequest anfrage = HttpRequest.newBuilder()
    .uri(URI.create(URL.formatted(autorId)))
    .GET()
    .build();
CLIENT.send(anfrage, ofString());

Ein HttpClient ist threadsicher — einmal anlegen und wiederverwenden. send wirft IOException und InterruptedException; beide lassen sich in einem Catch-Block fangen. Den Statuscode als switch-Ausdruck auszuwerten hält die Fälle beieinander; der default-Zweig ist dort Pflicht.

Modul: Externe Web-API

JSON binden mit Jackson

Erzeugt ein RecordBedeutung
Kanonischer KonstruktorEin Parameter je Komponente, in Reihenfolge
Zugriffsmethodentitel() statt getTitel()
equals und hashCodeVergleich über alle Komponenten
toStringLesbare Ausgabe im Log ohne Zusatzcode
@JsonIgnoreProperties(ignoreUnknown = true)
record KatalogKurs(String id, String titel, String dauer) { }

var typ = MAPPER.getTypeFactory()
    .constructCollectionType(List.class, KatalogKurs.class);
List<KatalogKurs> kurse = MAPPER.readValue(json, typ);

Ohne ignoreUnknown bricht ein zusätzliches Feld in der Antwort den Lauf ab („Unrecognized field”). Für Listen genügt List.class nicht — sonst bekommen Sie eine Liste, nur nicht von KatalogKurs. Den ObjectMapper einmal anlegen, nicht je Aufruf.

Modul: Externe Web-API

Datenbankzugriff mit JDBC

BausteinBedeutung
H2Eingebettete SQL-Datenbank, keine Installation nötig
JDBCStandard-API im JDK für den Zugriff auf SQL-Datenbanken
DataSourceBeschreibt die Datenbank, liefert Verbindungen
AUTO_SERVERErlaubt weiteren Prozessen den Zugriff auf dieselbe Datei
try (Connection c = quelle.getConnection();
     var s = c.prepareStatement(MERGE)) {
  s.setString(1, kurs.id());
  s.setLong(3, kurs.minuten());
  s.execute();
} catch (SQLException e) {
  throw new RepositoryException("Speichern", e);
}

Platzhalter statt Verkettung: Werte werden als Werte übergeben und können die Struktur der Anfrage nicht mehr verändern. try-with-resources schließt Verbindung und Statement auch im Fehlerfall. MERGE statt INSERT legt denselben Datensatz beim zweiten Lauf nicht doppelt an.

Die Repository-Schnittstelle bleibt frei von Technikbegriffen — keine SQLException in der Signatur, sonst steht die Technik in der Abstraktion.

Modul: Datenbankzugriff

REST mit JAX-RS und Jersey

AnnotationWirkung
@PathPfad an Klasse oder Methode, relativ zur Klasse
@GET @POSTHTTP-Verfahren der Methode
@ProducesMedientyp der Antwort
@ConsumesErwarteter Medientyp der Anfrage
@PathParamBindet einen Pfadabschnitt an einen Parameter
ArtefaktScopeRolle
jakarta.ws.rs-apicompileDie Annotationen der Spezifikation
jersey-servercompileKern der Umsetzung
jersey-container-grizzly2-httpcompileDer HTTP-Server
jersey-hk2runtimeWird zur Laufzeit vorausgesetzt
jersey-media-json-jacksonruntimeBrücke von Jersey zu Jackson
@Path("/kurse")
public class KursRessource {
  @GET @Produces(MediaType.APPLICATION_JSON)
  public List<Kurs> kurse() { … }

  @POST @Path("/{id}/notizen")
  @Consumes(MediaType.TEXT_PLAIN)
  public void notiz(@PathParam("id") String id, String notiz) { … }
}

Annotationen allein führen nichts aus — die Ressource muss registriert werden, sonst antwortet der Server mit 404. Eigene Ausnahmen gehören in Statuscodes übersetzt, sonst wird jede Störung zu einem 500.

Modul: REST-API

Ausliefern

mvn clean verify
java -jar kursradar-server/target/kursradar-server-1.0-SNAPSHOT.jar

Das Shade-Plugin erzeugt ein JAR samt Bibliotheken; daneben liegt original-…jar, das schlanke ohne. Zwei Transformer sind dabei wesentlich: einer trägt die Hauptklasse ins Manifest ein, der andere führt gleichnamige Dienst-Dateien aus mehreren JARs zusammen, statt sie einander überschreiben zu lassen. Konfiguration gehört in eine Eigenschaftsdatei unter src/main/resources; getProperty liefert bei fehlendem Schlüssel still null.

Modul: Richtung Produktion

Typische Fallen

  • Zwei JDKs installiert und der Pfad zeigt auf das ältere. Vor dem ersten Build java -version und mvn -version gegeneinander halten — das JDK der IDE ist nicht zwingend das der Kommandozeile.
  • target/ oder die Datenbankdatei eingecheckt. Beides ist Ergebnis des Builds, nicht Quelle.
  • Nur in der IDE gebaut. Der erste Lauf auf der Kommandozeile oder im CI fördert dann Überraschungen zutage.
  • Implementierung mit compile statt runtime eingebunden — und anschließend gegen sie programmiert. Damit ist die Austauschbarkeit weg.
  • Für jeden Aufruf einen neuen HttpClient oder ObjectMapper erzeugt, statt einen zu teilen.
  • Geprüfte Ausnahmen geschluckt, statt sie mit Kontext weiterzureichen. e.printStackTrace() als Dauerlösung verdeckt den Fehler doppelt.
  • SQL aus Zeichenketten zusammengesetzt und Werte hineingeklebt — genau das verhindern Platzhalter.
  • Verbindungen geöffnet und nicht geschlossen. Die Ressourcen gehen still aus; auffallen wird es unter Last.
  • Die veränderliche Liste zurückgegeben, statt eine Kopie zu liefern.
  • Unsortiert ausgeliefert und die Reihenfolge für stabil gehalten.
  • Eine schreibende Aktion über GET angeboten, weil es im Browser bequemer ist.
  • Zugangsdaten in die Eigenschaftsdatei gelegt, die mit ins JAR und ins Repository wandert.

Zum Seminar Java SE in der Praxis