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 KonsoleDatentypen
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.
| Typ | Größe | Wertebereich | Beschreibung | Beispiel |
|---|---|---|---|---|
byte | 8 Bit | -128 bis 127 | Kleinste Ganzzahl. Typisch für rohe Binärdaten, Dateiinhalte und Netzwerkpuffer (byte[]). Standardwert 0. | byte b = 10; |
short | 16 Bit | -32.768 bis 32.767 | Kleine Ganzzahl, selten genutzt. Spart nur in großen Arrays Speicher. Standardwert 0. | short s = 1000; |
int | 32 Bit | ca. ±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; |
long | 64 Bit | ca. ±9,2 · 1018 | Fü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; |
float | 32 Bit | ca. ±3,4 · 1038, etwa 7 Stellen genau | Kommazahl mit einfacher Genauigkeit. Literal braucht ein f. Nur sinnvoll bei viel Speicher- oder Grafikbedarf. Standardwert 0.0f. | float f = 1.5f; |
double | 64 Bit | ca. ±1,8 · 10308, etwa 15 Stellen genau | Standardtyp 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; |
char | 16 Bit | 0 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'; |
boolean | logisch 1 Bit | true oder false | Wahrheitswert 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 MatchingKontrollfluss
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.
| Modifier | Klasse | Package | Subklasse | Ü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: sortiertMap
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?
| Struktur | Zugriff | Einfügen | Hinweis |
|---|---|---|---|
ArrayList | O(1) | O(n) | Standard-Liste |
LinkedList | O(n) | O(1) | Queue/Deque |
HashMap/HashSet | O(1) | O(1) | unsortiert |
TreeMap/TreeSet | O(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; // schreibenLambdas & 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 21CompletableFuture & 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
| SQL | Java | Lesen |
|---|---|---|
VARCHAR / TEXT | String | getString |
INTEGER | int | getInt |
BIGINT | long | getLong |
DECIMAL / NUMERIC | BigDecimal | getBigDecimal |
BOOLEAN | boolean | getBoolean |
DATE | LocalDate | getObject(c, LocalDate.class) |
TIMESTAMP | LocalDateTime | getObject(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=dbConnection-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
| Annotation | Bedeutung |
|---|---|
@SpringBootApplication | Konfiguration + Auto-Config + Component-Scan |
@Component | Allgemeine Bean |
@Service | Geschäftslogik |
@Repository | Datenzugriff (übersetzt DB-Exceptions) |
@RestController | REST-Controller, Rückgabe als JSON |
@Controller | MVC-Controller (Views) |
@Configuration | Klasse mit @Bean-Methoden |
@Bean | Methode liefert eine Bean |
@Autowired | Injektion (besser: Konstruktor) |
@Value("${key}") | Wert aus Konfiguration |
@Profile("dev") | Bean nur in diesem Profil |
@Transactional | Methode 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
| Code | Bedeutung | Typisch für |
|---|---|---|
200 | OK | GET, PUT |
201 | Created | POST |
204 | No Content | DELETE |
400 | Bad Request | Validierung |
401 / 403 | Nicht angemeldet / verboten | Security |
404 | Not Found | ID unbekannt |
409 | Conflict | Duplikat |
500 | Server Error | Unerwarteter 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 bootRunDependency 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)