Beispielarbeiten · Code & Fehler
Spring Boot: REST-Schnittstelle mit Datenbank, Validierung und Tests
Eine Aufgaben-API mit Spring Boot und Maven: Anlegen, Lesen, Ändern und Löschen, Eingabeprüfung mit klaren Fehlermeldungen. Geprüft mit JUnit und mit echten HTTP-Aufrufen, dazu eine Gegenprobe mit kaputten Fassungen.
Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.
Was gebaut wurde
- Controller, Repository und Fehlerbehandlung getrennt, Antworten mit passenden Statuscodes
- Eingaben werden geprüft, Fehler kommen als lesbare Meldung zurück
- JUnit-Tests und Aufrufe gegen den laufenden Server
- Gegenprobe: jede absichtlich kaputte Fassung wird von den Prüfungen erkannt
spring-boot-api-quellcode.zip herunterladen
Spring Boot 3 mit Java 21 und Maven; Spring Boot 2, ältere Java-Versionen und Gradle sind nicht geprüft. Die Datenbank ist H2 im Speicher, PostgreSQL oder MySQL liefen nicht. Ohne Anmeldung (Spring Security, JWT), ohne Microservices und ohne Docker. Geliefert wird Quellcode mit Testnachweis, gearbeitet wird nicht auf dem Server des Kunden.
Wie geprüft wurde
Automatischer Prüflauf vom 01.11.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):
- mvn test 5 JUnit-Tests, 0 rot
- HTTP 21/21 gruen
- Gegenprobe 3/3 kaputte Fassungen von JUnit UND HTTP-Pruefung erkannt
Grenzen
Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:
- Spring Boot 3.3 / Java 21 / Maven 3.9: andere Hauptversionen (Spring Boot 2.x mit javax, Java 8/11, Gradle) nicht geprueft
- Datenbank nur H2 im Speicher; PostgreSQL/MySQL/Oracle nicht gelaufen (Kundencode mit anderer DB wird nicht gegen sie getestet)
- keine Authentifizierung (Spring Security, JWT, OAuth), keine Microservices/Messaging, kein Docker/Kubernetes
- Maven-Abhaengigkeiten kommen aus Maven Central (Netz noetig beim ersten Lauf); Kundenprojekte mit privatem Repository brauchen Zugang
- keine Arbeit auf dem Server des Kunden ohne Zugang; Ergebnis ist Quellcode + Testnachweis
Der Controller
package de.gltools.aufgaben;
import jakarta.validation.Valid;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/aufgaben")
public class AufgabenController {
private final AufgabenRepo repo;
public AufgabenController(AufgabenRepo repo) { this.repo = repo; }
@GetMapping
public List<Aufgabe> liste() { return repo.findAll(); }
@GetMapping("/{id}")
public ResponseEntity<Aufgabe> eine(@PathVariable Long id) {
return repo.findById(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Aufgabe neu(@Valid @RequestBody Aufgabe a) {
a.setId(null);
return repo.save(a);
}
@PutMapping("/{id}")
public ResponseEntity<Aufgabe> aendern(@PathVariable Long id, @Valid @RequestBody Aufgabe a) {
if (!repo.existsById(id)) return ResponseEntity.notFound().build();
a.setId(id);
return ResponseEntity.ok(repo.save(a));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> loeschen(@PathVariable Long id) {
if (!repo.existsById(id)) return ResponseEntity.notFound().build();
repo.deleteById(id);
return ResponseEntity.noContent().build();
}
}
Die Tests
package de.gltools.aufgaben;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
class AufgabenTest {
@Autowired MockMvc mvc;
private String anlegen(String json) throws Exception {
return mvc.perform(post("/api/aufgaben").contentType(MediaType.APPLICATION_JSON).content(json))
.andExpect(status().isCreated()).andReturn().getResponse().getContentAsString();
}
@Test
void anlegenUndLesen() throws Exception {
anlegen("{\"titel\":\"Test lesen\",\"prioritaet\":2}");
mvc.perform(get("/api/aufgaben")).andExpect(status().isOk())
.andExpect(jsonPath("$[*].titel", hasItem("Test lesen")));
}
@Test
void leererTitelIst400() throws Exception {
mvc.perform(post("/api/aufgaben").contentType(MediaType.APPLICATION_JSON).content("{\"titel\":\" \"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.felder.titel", is("Titel fehlt")));
}
@Test
void prioritaetAusserhalbIst400() throws Exception {
for (int p : new int[] {0, 6}) {
mvc.perform(post("/api/aufgaben").contentType(MediaType.APPLICATION_JSON)
.content("{\"titel\":\"x\",\"prioritaet\":" + p + "}"))
.andExpect(status().isBadRequest()).andExpect(jsonPath("$.felder.prioritaet").exists());
}
}
@Test
void unbekannteIdIst404() throws Exception {
mvc.perform(get("/api/aufgaben/999999")).andExpect(status().isNotFound());
mvc.perform(delete("/api/aufgaben/999999")).andExpect(status().isNotFound());
mvc.perform(put("/api/aufgaben/999999").contentType(MediaType.APPLICATION_JSON).content("{\"titel\":\"gibt es nicht\"}"))
.andExpect(status().isNotFound());
mvc.perform(get("/api/aufgaben")).andExpect(jsonPath("$[*].titel", not(hasItem("gibt es nicht"))));
}
@Test
void aendernUndLoeschen() throws Exception {
String body = anlegen("{\"titel\":\"alt\"}");
String id = body.replaceAll(".*\"id\":(\\d+).*", "$1");
mvc.perform(put("/api/aufgaben/" + id).contentType(MediaType.APPLICATION_JSON)
.content("{\"titel\":\"neu\",\"erledigt\":true,\"prioritaet\":1}"))
.andExpect(status().isOk()).andExpect(jsonPath("$.titel", is("neu"))).andExpect(jsonPath("$.erledigt", is(true)));
mvc.perform(delete("/api/aufgaben/" + id)).andExpect(status().isNoContent());
mvc.perform(get("/api/aufgaben/" + id)).andExpect(status().isNotFound());
}
}
So etwas für deinen Betrieb?
Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.