Java CheatSheet

Java CheatSheet

Kompakte Referenz zum Coden: Sprache, Collections, Streams, I/O, JDBC und Spring Boot. Code-Blöcke lassen sich per Klick kopieren.

Grundgerüst

Hello World

Jedes Java-Programm startet in der main-Methode. Der Dateiname muss zum Namen der public-Klasse passen.

public class Main {
    public static void main(String[] args) {
        System.out.println("Hallo Welt");
    }
}

Kompilieren & starten

javac übersetzt Quellcode in Bytecode (.class), java führt ihn auf der JVM aus. jshell eignet sich zum schnellen Ausprobieren.

javac Main.java      # kompilieren
java Main            # ausführen
java Main.java       # Single-File direkt starten (ab Java 11)
jshell               # interaktive Konsole

Datentypen

Primitive Typen

Primitive Typen speichern den Wert direkt und sind nie null. Ganzzahlen: byte bis long, Kommazahlen: float/double. Für Geldbeträge BigDecimal statt double nehmen.

TypGrößeWertebereichBeschreibungBeispiel
byte8 Bit-128 bis 127Kleinste Ganzzahl. Typisch für rohe Binärdaten, Dateiinhalte und Netzwerkpuffer (byte[]). Standardwert 0.byte b = 10;
short16 Bit-32.768 bis 32.767Kleine Ganzzahl, selten genutzt. Spart nur in großen Arrays Speicher. Standardwert 0.short s = 1000;
int32 Bitca. ±2,1 Mrd.
(-2.147.483.648 bis 2.147.483.647)
Standardtyp für Ganzzahlen: Zähler, Indizes, Alter, Mengen. Läuft ohne Warnung über (Integer.MAX_VALUE + 1 ergibt eine negative Zahl). Standardwert 0.int i = 42;
long64 Bitca. ±9,2 · 1018Für große Ganzzahlen wie Zeitstempel in Millisekunden, IDs oder Dateigrößen. Literal braucht ein L am Ende, Unterstriche gliedern Zahlen. Standardwert 0L.long l = 10_000L;
float32 Bitca. ±3,4 · 1038, etwa 7 Stellen genauKommazahl mit einfacher Genauigkeit. Literal braucht ein f. Nur sinnvoll bei viel Speicher- oder Grafikbedarf. Standardwert 0.0f.float f = 1.5f;
double64 Bitca. ±1,8 · 10308, etwa 15 Stellen genauStandardtyp für Kommazahlen. Rundungsfehler sind möglich (0.1 + 0.2 != 0.3), für Geld deshalb BigDecimal. Standardwert 0.0.double d = 3.14;
char16 Bit0 bis 65.535 (Unicode-Zeichen, UTF-16)Ein einzelnes Zeichen in einfachen Anführungszeichen. Intern eine Zahl, daher geht c + 1. Standardwert '\u0000'.char c = 'A';
booleanlogisch 1 Bittrue oder falseWahrheitswert für Bedingungen und Flags. Kein Zusammenhang mit Zahlen, if (1) ist in Java ungültig. Standardwert false.boolean ok = true;

Umwandlung & var

Ein Cast ((int)) schneidet Nachkommastellen ab. Strings wandelt man mit parseXxx in Zahlen um. var lässt den Compiler den Typ erkennen, gilt aber nur für lokale Variablen.

Wrapper-Klassen: Integer, Long, Double, Boolean, Character (Autoboxing).

var x = 10;                        // Typinferenz (ab Java 10)
int n = (int) 3.9;                 // Cast -> 3
int p = Integer.parseInt("42");
double d = Double.parseDouble("1.5");
String s = String.valueOf(42);
long big = Long.MAX_VALUE;

Operatoren

Übersicht

Bei int ist die Division ganzzahlig, % liefert den Rest. && und || werten den rechten Teil nur aus, wenn nötig. == vergleicht bei Objekten Referenzen, nicht Inhalte.

+ - * / %           // Arithmetik (7 / 2 == 3 bei int)
++ --  += -= *= /=
== != < > <= >=
&& || !             // logisch (Short-Circuit)
& | ^ ~ << >> >>>   // bitweise
cond ? a : b         // ternär
obj instanceof Foo f // Pattern Matching

Kontrollfluss

if / Schleifen

for eignet sich für Zählschleifen, for-each zum Durchlaufen von Arrays und Collections, while prüft vor dem Durchlauf, do-while danach. break beendet die Schleife, continue springt zum nächsten Durchlauf.

if (x > 0) { } else if (x == 0) { } else { }

for (int i = 0; i < 10; i++) { }
for (String s : list) { }      // for-each
while (cond) { }
do { } while (cond);
break; continue;
outer: for (...) { break outer; }

switch-Expression (ab Java 14)

Die neue Schreibweise mit -> liefert direkt einen Wert und braucht kein break. Mehrere Fälle lassen sich mit Komma zusammenfassen.

String name = switch (day) {
    case 1, 7 -> "Wochenende";
    case 2, 3, 4, 5, 6 -> "Werktag";
    default -> "?";
};

Strings

Wichtige Methoden

Strings sind unveränderlich: Methoden wie replace oder toUpperCase geben ein neues String-Objekt zurück. Die Zählung von Positionen beginnt bei 0.

String s = "Hallo";
s.length();  s.charAt(0);  s.substring(1, 3);
s.indexOf("l");  s.contains("al");  s.replace("a", "e");
s.toUpperCase();  s.toLowerCase();  s.trim();  s.strip();
s.split(",");  s.isEmpty();  s.isBlank();
s.equals("x");  s.equalsIgnoreCase("X");  s.compareTo("x");
String.join(", ", list);
"a".repeat(3);
String.format("%s ist %d Jahre, %.2f m", name, age, h);

Strings immer mit equals() vergleichen, nie mit ==.

Textblock & StringBuilder

Textblöcke (""") erlauben mehrzeilige Strings ohne Escapes. Wer in Schleifen viele Teile zusammenfügt, nimmt StringBuilder, das ist schneller als +.

String json = """
    { "a": 1 }
    """;                              // ab Java 15

StringBuilder sb = new StringBuilder();
sb.append("a").append(1).reverse().toString();

Arrays

Anlegen & Arrays-Helfer

Arrays haben eine feste Länge und beginnen bei Index 0. Wenn die Größe wechselt, ist eine ArrayList besser. Die Klasse Arrays bringt Sortieren, Kopieren und Ausgeben mit.

int[] a = new int[5];
int[] b = {1, 2, 3};
int[][] m = new int[3][3];
a.length;

Arrays.sort(b);
Arrays.toString(b);
Arrays.fill(a, 0);
Arrays.copyOf(b, 10);
Arrays.asList(1, 2, 3);
Arrays.stream(b).sum();

Klassen & OOP

Klasse mit equals / hashCode

Eine Klasse bündelt Daten (Felder) und Verhalten (Methoden). Der Konstruktor initialisiert das Objekt, this meint das aktuelle Objekt. equals und hashCode definieren, wann zwei Objekte gleich sind (wichtig für HashMap und HashSet).

public class Person {
    private String name;
    private int age;
    private static int count = 0;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        count++;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @Override
    public String toString() { return name + " (" + age + ")"; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person p)) return false;
        return age == p.age && Objects.equals(name, p.name);
    }

    @Override
    public int hashCode() { return Objects.hash(name, age); }
}

Vererbung, Interface, abstract

extends erbt von genau einer Klasse, implements erfüllt beliebig viele Interfaces. Ein Interface beschreibt nur, was eine Klasse kann. Eine abstrakte Klasse kann nicht instanziiert werden und gibt Teile der Umsetzung vor.

public class Student extends Person implements Lernbar {
    public Student(String n, int a) { super(n, a); }
    @Override public void lernen() { }
}

interface Lernbar {
    void lernen();
    default void pause() { }     // default-Methode
    static void hilfe() { }
}

abstract class Tier {
    abstract void laut();
}

Zugriffsmodifizierer

Sie legen fest, wer auf eine Klasse, ein Feld oder eine Methode zugreifen darf. Faustregel: so restriktiv wie möglich, Felder meist private.

ModifierKlassePackageSubklasseÜberall
private✔
default✔✔
protected✔✔✔
public✔✔✔✔

final: nicht änderbar, nicht vererbbar, nicht überschreibbar. static: gehört zur Klasse.

Enum, Record, sealed

enum ist eine feste Menge an Konstanten. record ist eine kompakte, unveränderliche Datenklasse mit automatischem Konstruktor, Gettern, equals und toString. sealed begrenzt, welche Klassen ein Interface implementieren dürfen.

enum Farbe { ROT, GRUEN, BLAU }
Farbe.valueOf("ROT");  Farbe.values();

record Punkt(int x, int y) { }   // ab Java 16, immutable
Punkt p = new Punkt(1, 2);  p.x();

sealed interface Form permits Kreis, Rechteck { }

Collections

List & Set

List<String> list = new ArrayList<>();
list.add("a"); list.get(0); list.set(0, "b");
list.remove(0); list.size(); list.contains("a");
List<Integer> imm = List.of(1, 2, 3);   // unveränderlich

Set<String> set = new HashSet<>();
// LinkedHashSet: Reihenfolge, TreeSet: sortiert

Map

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.get("a");
map.getOrDefault("x", 0);
map.putIfAbsent("b", 2);
map.merge("a", 1, Integer::sum);
map.computeIfAbsent("k", k -> new ArrayList<>());
map.containsKey("a");
for (var e : map.entrySet()) {
    e.getKey(); e.getValue();
}

Queue, Deque, Sortieren

Queue<Integer> q = new LinkedList<>();
q.offer(1); q.poll(); q.peek();
Deque<Integer> st = new ArrayDeque<>();
st.push(1); st.pop(); st.peek();
PriorityQueue<Integer> pq = new PriorityQueue<>();

Collections.sort(list);
Collections.reverse(list);
Collections.max(list);
list.sort(Comparator.comparing(Person::getName)
                    .thenComparing(Person::getAge));

Welche Struktur wann?

StrukturZugriffEinfügenHinweis
ArrayListO(1)O(n)Standard-Liste
LinkedListO(n)O(1)Queue/Deque
HashMap/HashSetO(1)O(1)unsortiert
TreeMap/TreeSetO(log n)O(log n)sortiert

Generics

Klassen, Methoden, Wildcards

class Box<T> {
    private T inhalt;
    public T get() { return inhalt; }
}

static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

List<? extends Number> l1;   // lesen
List<? super Integer> l2;    // schreiben

Lambdas & Streams

Funktionale Interfaces

Runnable r = () -> System.out.println("hi");
Function<Integer, Integer> sq = x -> x * x;
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Predicate<String> leer = String::isEmpty;
Consumer<String> out = System.out::println;
Supplier<Double> rnd = Math::random;

Stream-Pipeline

List<String> result = list.stream()
    .filter(s -> s.length() > 2)
    .map(String::toUpperCase)
    .sorted()
    .distinct()
    .limit(10)
    .toList();                  // ab Java 16

int sum = nums.stream().mapToInt(Integer::intValue).sum();
Optional<Integer> max = nums.stream().max(Integer::compare);
boolean any = list.stream().anyMatch(s -> s.startsWith("a"));
IntStream.range(0, 5).forEach(System.out::println);

Collectors

Map<Integer, List<String>> gr = list.stream()
    .collect(Collectors.groupingBy(String::length));
String joined = list.stream()
    .collect(Collectors.joining(", "));
Map<String, Long> cnt = list.stream()
    .collect(Collectors.groupingBy(s -> s, Collectors.counting()));
Map<Boolean, List<Integer>> parts = nums.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));

Optional

Verwenden statt null

Optional<String> o = Optional.of("x");   // ofNullable(...) / empty()
o.isPresent();  o.isEmpty();
o.orElse("default");
o.orElseGet(() -> "x");
o.orElseThrow();
o.map(String::length);
o.ifPresent(System.out::println);

Exceptions

try / catch / finally

try {
    int x = Integer.parseInt("abc");
} catch (NumberFormatException | NullPointerException e) {
    e.printStackTrace();
} finally {
    // läuft immer
}

// try-with-resources schließt automatisch
try (var br = new BufferedReader(new FileReader("f.txt"))) {
    br.readLine();
} catch (IOException e) { }

Eigene Exception

throw new IllegalArgumentException("Ungültig");

class MeineException extends Exception {
    MeineException(String msg) { super(msg); }
}
void foo() throws MeineException { }

Checked: IOException, SQLException (müssen behandelt werden). Unchecked: RuntimeException, NullPointerException, IllegalArgumentException.

Dateien & I/O

NIO Files

Path p = Path.of("datei.txt");
String text = Files.readString(p);
List<String> lines = Files.readAllLines(p);
Files.writeString(p, "Inhalt");
Files.write(p, lines, StandardOpenOption.APPEND);
Files.exists(p);  Files.delete(p);
try (Stream<String> s = Files.lines(p)) { }

Konsole lesen

Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
int n = sc.nextInt();

Nebenläufigkeit

Threads & Executor

Thread t = new Thread(() -> System.out.println("Thread"));
t.start();  t.join();

ExecutorService ex = Executors.newFixedThreadPool(4);
Future<Integer> f = ex.submit(() -> 42);
f.get();
ex.shutdown();

Thread.startVirtualThread(() -> { });   // ab Java 21

CompletableFuture & Sync

CompletableFuture.supplyAsync(() -> "a")
    .thenApply(String::toUpperCase)
    .thenAccept(System.out::println);

synchronized (lock) { }
AtomicInteger ai = new AtomicInteger();
ai.incrementAndGet();
var chm = new ConcurrentHashMap<String, Integer>();

Datum & Zeit

java.time

LocalDate d = LocalDate.now();
LocalDateTime dt = LocalDateTime.of(2025, 1, 31, 12, 0);
d.plusDays(5);  d.getDayOfWeek();
ChronoUnit.DAYS.between(d1, d2);
Duration.ofMinutes(90);
d.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
LocalDate.parse("2025-01-31");

Math & Utilities

Math.max(a, b); Math.min(a, b); Math.abs(x);
Math.pow(2, 3); Math.sqrt(9); Math.round(2.5);
Math.floor(2.7); Math.ceil(2.1);
new Random().nextInt(100);
ThreadLocalRandom.current().nextInt(1, 7);
Objects.equals(a, b); Objects.requireNonNull(x);
System.currentTimeMillis(); System.nanoTime();

Annotationen & Patterns

Eingebaute Annotationen

@Override @Deprecated @FunctionalInterface @SuppressWarnings("unchecked") @SafeVarargs

Singleton, Builder, Varargs

public enum Singleton { INSTANCE; }

Person p = new Person.Builder().name("A").age(3).build();

static int sum(int... n) { return Arrays.stream(n).sum(); }

Pattern Matching für switch (ab Java 21)

String s = switch (obj) {
    case Integer i when i > 10 -> "große Zahl";
    case Integer i -> "Zahl";
    case String str -> "Text " + str;
    case null -> "null";
    default -> "anderes";
};

JDBC Datenbank

Verbindung & Abfrage

String url = "jdbc:postgresql://localhost:5432/shop";

try (Connection con = DriverManager.getConnection(url, "user", "pw");
     PreparedStatement ps = con.prepareStatement(
         "SELECT id, name, preis FROM produkt WHERE preis > ?")) {

    ps.setBigDecimal(1, new BigDecimal("9.99"));

    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            long id = rs.getLong("id");
            String name = rs.getString("name");
            BigDecimal preis = rs.getBigDecimal("preis");
        }
    }
} catch (SQLException e) {
    throw new RuntimeException(e);
}

Immer PreparedStatement mit ?-Platzhaltern nutzen, nie Werte per String-Konkatenation in SQL einbauen (SQL-Injection).

INSERT, UPDATE, DELETE

String sql = "INSERT INTO produkt (name, preis) VALUES (?, ?)";
try (PreparedStatement ps = con.prepareStatement(
        sql, Statement.RETURN_GENERATED_KEYS)) {
    ps.setString(1, "Tasse");
    ps.setBigDecimal(2, new BigDecimal("7.50"));
    int rows = ps.executeUpdate();          // Anzahl betroffener Zeilen

    try (ResultSet keys = ps.getGeneratedKeys()) {
        if (keys.next()) { long id = keys.getLong(1); }
    }
}

Transaktionen

con.setAutoCommit(false);
try {
    // mehrere Statements ausführen
    con.commit();
} catch (SQLException e) {
    con.rollback();
    throw e;
} finally {
    con.setAutoCommit(true);
}

Batch

try (PreparedStatement ps = con.prepareStatement(
        "INSERT INTO log (msg) VALUES (?)")) {
    for (String m : messages) {
        ps.setString(1, m);
        ps.addBatch();
    }
    int[] counts = ps.executeBatch();
}

Typ-Mapping

SQLJavaLesen
VARCHAR / TEXTStringgetString
INTEGERintgetInt
BIGINTlonggetLong
DECIMAL / NUMERICBigDecimalgetBigDecimal
BOOLEANbooleangetBoolean
DATELocalDategetObject(c, LocalDate.class)
TIMESTAMPLocalDateTimegetObject(c, LocalDateTime.class)

NULL prüfen: rs.wasNull() nach getInt, oder getObject(c, Integer.class).

Gängige JDBC-URLs

jdbc:postgresql://host:5432/db
jdbc:mysql://host:3306/db
jdbc:mariadb://host:3306/db
jdbc:sqlite:datei.db
jdbc:h2:mem:testdb          # In-Memory
jdbc:sqlserver://host:1433;databaseName=db

Connection-Pool (HikariCP)

HikariConfig cfg = new HikariConfig();
cfg.setJdbcUrl("jdbc:postgresql://localhost/shop");
cfg.setUsername("user");
cfg.setPassword("pw");
cfg.setMaximumPoolSize(10);

try (HikariDataSource ds = new HikariDataSource(cfg);
     Connection con = ds.getConnection()) {
    // Verbindung kommt aus dem Pool, close() gibt sie zurück
}

Einfaches DAO

public class ProduktDao {
    private final DataSource ds;
    public ProduktDao(DataSource ds) { this.ds = ds; }

    public Optional<Produkt> findById(long id) throws SQLException {
        String sql = "SELECT id, name, preis FROM produkt WHERE id = ?";
        try (Connection con = ds.getConnection();
             PreparedStatement ps = con.prepareStatement(sql)) {
            ps.setLong(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                return rs.next()
                    ? Optional.of(new Produkt(rs.getLong("id"),
                                              rs.getString("name"),
                                              rs.getBigDecimal("preis")))
                    : Optional.empty();
            }
        }
    }
}

Spring Boot Grundlagen

Projektstart

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}
mvn spring-boot:run
./gradlew bootRun
java -jar target/app.jar --server.port=9090

Projekte erzeugen: start.spring.io

Wichtige Annotationen

AnnotationBedeutung
@SpringBootApplicationKonfiguration + Auto-Config + Component-Scan
@ComponentAllgemeine Bean
@ServiceGeschäftslogik
@RepositoryDatenzugriff (übersetzt DB-Exceptions)
@RestControllerREST-Controller, Rückgabe als JSON
@ControllerMVC-Controller (Views)
@ConfigurationKlasse mit @Bean-Methoden
@BeanMethode liefert eine Bean
@AutowiredInjektion (besser: Konstruktor)
@Value("${key}")Wert aus Konfiguration
@Profile("dev")Bean nur in diesem Profil
@TransactionalMethode läuft in Transaktion

Dependency Injection

@Service
public class BestellService {
    private final ProduktRepository repo;

    // Konstruktor-Injektion, @Autowired ist bei einem Konstruktor optional
    public BestellService(ProduktRepository repo) {
        this.repo = repo;
    }
}

@Configuration
public class AppConfig {
    @Bean
    public Clock clock() { return Clock.systemUTC(); }
}

Konfiguration (application.yml)

server:
  port: 8080

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/shop
    username: user
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate       # none | validate | update | create
    show-sql: false
  profiles:
    active: dev

app:
  mail-from: shop@example.com
@Value("${app.mail-from}") private String from;

@ConfigurationProperties(prefix = "app")
public record AppProps(String mailFrom) { }

Spring Web REST

REST-Controller

@RestController
@RequestMapping("/api/produkte")
public class ProduktController {
    private final ProduktService service;
    public ProduktController(ProduktService service) { this.service = service; }

    @GetMapping
    public List<Produkt> alle() { return service.alle(); }

    @GetMapping("/{id}")
    public Produkt eins(@PathVariable Long id) { return service.finde(id); }

    @GetMapping("/suche")
    public List<Produkt> suche(@RequestParam String q,
                                @RequestParam(defaultValue = "0") int seite) { ... }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Produkt neu(@Valid @RequestBody ProduktDto dto) { return service.anlegen(dto); }

    @PutMapping("/{id}")
    public Produkt aendern(@PathVariable Long id, @Valid @RequestBody ProduktDto dto) { ... }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void loeschen(@PathVariable Long id) { service.loesche(id); }
}

ResponseEntity

@GetMapping("/{id}")
public ResponseEntity<Produkt> eins(@PathVariable Long id) {
    return service.finde(id)
        .map(ResponseEntity::ok)
        .orElse(ResponseEntity.notFound().build());
}

@PostMapping
public ResponseEntity<Produkt> neu(@RequestBody Produkt p) {
    Produkt saved = service.speichern(p);
    URI uri = URI.create("/api/produkte/" + saved.getId());
    return ResponseEntity.created(uri).body(saved);
}

Validierung

public record ProduktDto(
    @NotBlank String name,
    @NotNull @Positive BigDecimal preis,
    @Size(max = 200) String beschreibung,
    @Email String kontakt
) { }

Weitere: @Min @Max @Pattern @NotEmpty @Past @Future

Globale Fehlerbehandlung

@RestControllerAdvice
public class FehlerHandler {

    @ExceptionHandler(NichtGefundenException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Map<String, String> nichtGefunden(NichtGefundenException e) {
        return Map.of("fehler", e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> ungueltig(MethodArgumentNotValidException e) {
        return e.getBindingResult().getFieldErrors().stream()
            .collect(Collectors.toMap(FieldError::getField,
                                      FieldError::getDefaultMessage));
    }
}

HTTP-Status

CodeBedeutungTypisch für
200OKGET, PUT
201CreatedPOST
204No ContentDELETE
400Bad RequestValidierung
401 / 403Nicht angemeldet / verbotenSecurity
404Not FoundID unbekannt
409ConflictDuplikat
500Server ErrorUnerwarteter Fehler

Spring Data JPA Persistenz

Entity

@Entity
@Table(name = "produkt")
public class Produkt {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String name;

    private BigDecimal preis;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "kategorie_id")
    private Kategorie kategorie;

    @OneToMany(mappedBy = "produkt", cascade = CascadeType.ALL)
    private List<Bewertung> bewertungen = new ArrayList<>();

    protected Produkt() { }          // JPA braucht einen Default-Konstruktor
}

Repository

public interface ProduktRepository extends JpaRepository<Produkt, Long> {

    List<Produkt> findByName(String name);
    List<Produkt> findByPreisLessThanOrderByPreisAsc(BigDecimal max);
    Optional<Produkt> findFirstByNameContainingIgnoreCase(String teil);
    boolean existsByName(String name);
    long countByKategorie(Kategorie k);

    @Query("SELECT p FROM Produkt p WHERE p.preis BETWEEN :von AND :bis")
    List<Produkt> imPreisbereich(@Param("von") BigDecimal von,
                                  @Param("bis") BigDecimal bis);

    @Query(value = "SELECT * FROM produkt WHERE name ILIKE ?1",
           nativeQuery = true)
    List<Produkt> sucheNativ(String muster);

    Page<Produkt> findAll(Pageable pageable);
}

Standardmethoden

repo.save(p);                 // INSERT oder UPDATE
repo.findById(1L);            // Optional
repo.findAll();
repo.existsById(1L);
repo.deleteById(1L);
repo.count();

Pageable pg = PageRequest.of(0, 20, Sort.by("name").ascending());
Page<Produkt> seite = repo.findAll(pg);
seite.getContent();  seite.getTotalElements();

Transaktionen im Service

@Service
public class BestellService {

    @Transactional
    public void bestellen(Long produktId, int menge) {
        // alles oder nichts; RuntimeException löst Rollback aus
    }

    @Transactional(readOnly = true)
    public List<Produkt> alle() { return repo.findAll(); }
}

JdbcTemplate (ohne JPA)

@Repository
public class ProduktJdbcRepo {
    private final JdbcTemplate jdbc;
    public ProduktJdbcRepo(JdbcTemplate jdbc) { this.jdbc = jdbc; }

    public List<Produkt> alle() {
        return jdbc.query("SELECT id, name, preis FROM produkt",
            (rs, i) -> new Produkt(rs.getLong("id"),
                                   rs.getString("name"),
                                   rs.getBigDecimal("preis")));
    }

    public int neu(String name, BigDecimal preis) {
        return jdbc.update(
            "INSERT INTO produkt (name, preis) VALUES (?, ?)", name, preis);
    }
}

Spring Testen JUnit 5

Unit-Test mit Mockito

@ExtendWith(MockitoExtension.class)
class BestellServiceTest {
    @Mock ProduktRepository repo;
    @InjectMocks BestellService service;

    @Test
    void findetProdukt() {
        when(repo.findById(1L)).thenReturn(Optional.of(new Produkt()));

        assertTrue(service.finde(1L).isPresent());
        verify(repo).findById(1L);
    }
}

Controller-Test (MockMvc)

@WebMvcTest(ProduktController.class)
class ProduktControllerTest {
    @Autowired MockMvc mvc;
    @MockBean ProduktService service;

    @Test
    void liefertListe() throws Exception {
        when(service.alle()).thenReturn(List.of());

        mvc.perform(get("/api/produkte"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$").isArray());
    }
}

Integrationstest & Assertions

@SpringBootTest
@AutoConfigureMockMvc
class AppIT { }

@DataJpaTest                 // nur JPA-Schicht, In-Memory-DB

assertEquals(expected, actual);
assertTrue(cond);  assertNotNull(x);
assertThrows(IllegalArgumentException.class, () -> foo());
@ParameterizedTest @ValueSource(ints = {1, 2, 3})

Spring Weiteres Praxis

Geplante Tasks & Async

@EnableScheduling   // auf einer @Configuration-Klasse
@Component
public class Job {
    @Scheduled(cron = "0 0 3 * * *")        // täglich 03:00
    public void nachts() { }

    @Scheduled(fixedRate = 60_000)
    public void jedeMinute() { }
}

@EnableAsync
@Async public CompletableFuture<String> langsam() { ... }

Security (Kurzform)

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain chain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())     // nur bei reinen APIs mit Token
            .authorizeHttpRequests(a -> a
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults())
            .build();
    }

    @Bean PasswordEncoder encoder() { return new BCryptPasswordEncoder(); }
}

HTTP-Client (RestClient)

RestClient client = RestClient.create("https://api.example.com");

Produkt p = client.get()
    .uri("/produkte/{id}", 1)
    .retrieve()
    .body(Produkt.class);

client.post().uri("/produkte")
    .contentType(MediaType.APPLICATION_JSON)
    .body(neu)
    .retrieve()
    .toBodilessEntity();

Actuator & Logging

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

logging:
  level:
    root: INFO
    com.example: DEBUG
private static final Logger log = LoggerFactory.getLogger(Foo.class);
log.info("Bestellung {} angelegt", id);
log.error("Fehler beim Speichern", ex);

Maven & Gradle

Kommandos

mvn clean install     mvn test      mvn package
mvn dependency:tree
mvn spring-boot:run

gradle build          gradle test   gradle run
./gradlew bootRun

Dependency hinzufügen

<!-- Maven pom.xml -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

// Gradle build.gradle
implementation 'org.springframework.boot:spring-boot-starter-web'

Häufige Starter: web data-jpa jdbc validation security actuator test

Best Practices

Java

• Felder private, Zugriff über Getter oder record
• Immutability bevorzugen (final, List.of, record)
• equals() und hashCode() immer zusammen überschreiben
• Gegen Interfaces programmieren: List<String> l = new ArrayList<>()
• try-with-resources für alles AutoCloseable
• Keine leeren catch-Blöcke
• Optional als Rückgabetyp statt null
• Namen: Klassen PascalCase, Methoden camelCase, Konstanten UPPER_SNAKE_CASE

JDBC & Spring

• SQL nur mit PreparedStatement, nie mit String-Konkatenation
• Verbindungen über einen Pool, immer schließen
• Konstruktor-Injektion statt Feld-@Autowired
• Entities nicht direkt als API-Antwort, lieber DTOs oder Records
• Transaktionen im Service, nicht im Controller
• Passwörter und Schlüssel über Umgebungsvariablen, nicht in Git
• ddl-auto: validate plus Flyway/Liquibase für Schema-Änderungen
• LAZY-Beziehungen beachten (N+1-Problem, JOIN FETCH)

Keine Treffer. Anderen Suchbegriff versuchen.