
📋 Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden
Die Big 3 Verbesserungen:
- Constructor Injection statt @Autowired (bessere Testbarkeit + Immutability)
- ResponseEntity für korrekte HTTP-Status-Codes
- Validation für sichere Eingaben
- Bonus: Lombok Dos & Don’ts für cleanen Code ohne Boilerplate
- Zeit-Investment: 15 Minuten Refactoring
- Sofort umsetzbar: Ja, vollständiger Code enthaltenofessionelle HTTP-Responses
- 🎯 GitHub Repository v1.1 mit allen Code-Beispielen
Zeitaufwand: 12 Minuten Lesezeit | Level: Junior bis Mid-Level | Frameworks: Spring Boot, Lombok
Hi zusammen! 👋
Erstmal zu mir: Ich bin Elyndra, 28 Jahre alt und seit kurzem Senior Entwicklerin bei Java Fleet – nach 4 Jahren intensiver Java-Entwicklung. Davor hatte ich 2 Jahre bei einem ERP-Anbieter gearbeitet, wo ich hauptsächlich Legacy-Systeme modernisiert habe. Das war eine harte aber sehr lehrreiche Schule!
Nach dem großartigen Feedback auf Nova’s ersten Spring Boot Post dachte ich mir: Zeit für einen konstruktiven Code Review!
Als frischgebackene Senior Developerin (ja, das Impostor Syndrome ist real! 😅) weiß ich noch genau, wie es sich anfühlt, den ersten funktionierenden Code zu schreiben. Nova hat fantastische Arbeit geleistet! Aber es gibt immer Raum für Verbesserungen.
Spoiler: Der Code ist richtig gut für den Anfang! Aber natürlich gibt es immer Verbesserungspotential. 😊
💚 Was Nova instinktiv RICHTIG gemacht hat
Bevor wir zu Verbesserungen kommen – let’s celebrate! Diese 3 Dinge zeigen, dass Nova gute Entwickler-Instinkte hat:
1. Saubere Package-Struktur gewählt
src/main/java/com/javafleet/taskmanager/ ├── entity/ ├── repository/ └── controller/
Why it’s great: Klare Trennung der Verantwortlichkeiten. Viele Anfänger werfen alles in ein Package!
2. H2 für Prototyping verwendet
Während andere Anfänger sich mit MySQL-Setup abmühen, ging Nova den pragmatischen Weg. Smart choice!
3. JpaRepository Interface korrekt erweitert
@Repository public interface TaskRepository extends JpaRepository<Task, Long> { // Kein Code nötig! Spring Data generiert automatisch: // - findAll() // - findById(Long id) // - save(Task task) // - deleteById(Long id) // - count() // und viele mehr! // Aber hier kommt der Clou - Nova entdeckt Custom Finder: List<Task> findByCompleted(boolean completed); }
Das ist bemerkenswert! Nova hat intuitiv verstanden, wie Spring Data JPA Naming Conventions funktionieren. Als jemand, die selbst Jahre gebraucht hat, um Spring Data richtig zu durchblicken, erkenne ich echtes Talent, wenn ich es sehe!
🎯 Warum Nova’s findByCompleted() so wertvoll ist:
Nova hat in ihrem Blog geschrieben:
„Das ist unglaublich: Ich schreibe eine Methodensignatur und Spring Data versteht automatisch, was ich will!“
Und sie hat vollkommen recht! Spring Data JPA analysiert den Methodennamen und generiert automatisch:
/ Ich schreibe: List<Task> findByCompleted(boolean completed); // Spring Data JPA generiert automatisch: // SELECT * FROM tasks WHERE completed = ?
/ Meine Entity hat: title, description, completed public interface TaskRepository extends JpaRepository<Task, Long> { // Nach einzelnen Properties suchen: List<Task> findByTitle(String title); // WHERE title = ? List<Task> findByCompleted(boolean completed); // WHERE completed = ? // Mit Operatoren: List<Task> findByTitleContaining(String text); // WHERE title LIKE %?% List<Task> findByTitleStartingWith(String text); // WHERE title LIKE ?% // Kombinationen: List<Task> findByCompletedAndTitleContaining(boolean completed, String title); // WHERE completed = ? AND title LIKE %?% // Sortierung: List<Task> findByCompletedOrderByTitleAsc(boolean completed); // WHERE completed = ? ORDER BY title ASC }
🎉 Nova’s Test – Es funktioniert wirklich!
# Test: Nur abgeschlossene Tasks finden curl -X POST http://localhost:8080/api/tasks \ -H "Content-Type: application/json" \ -d '{"title": "Erledigt", "completed": true}' curl -X POST http://localhost:8080/api/tasks \ -H "Content-Type: application/json" \ -d '{"title": "Offen", "completed": false}' # Jetzt den Custom Finder testen: curl http://localhost:8080/api/tasks/completed/true # Antwort: Nur der "Erledigt" Task! 🎯
🤯 Warum ist das so genial?
1. Kein SQL schreiben: Ich denke in Java-Properties, nicht in SQL-Spalten 2. Type-Safe: Compiler checkt meine Methodensignatur 3. Lesbar: findByCompleted(true)
ist selbsterklärend 4. Konsistent: Einmal gelernt, funktioniert überall gleich
Das versteht mein Gehirn noch nicht komplett: Wie kann Spring aus einem Methodennamen wissen, welche SQL-Query es bauen soll?!
📚 Dr. Cassian’s wissenschaftliche Erklärung:
„Nova, das nennt sich ‚Convention over Configuration‘ kombiniert mit ‚Reflection und Metaprogrammierung‘. Spring Data parst zur Laufzeit deine Methodennamen mit einem regulären Ausdruck-Parser, mappt die Wörter auf Entity-Properties und generiert dann die entsprechende SQL-Query über Hibernate’s Criteria API!“
Mein Hirn: „Okay Dr. Cassian, ich verstehe nur die Hälfte, aber es ist FANTASTISCH!“
🔧 Praktische Verbesserungen (die ich sofort umsetzen würde)
Verbesserung #1: Constructor Injection statt @Autowired – Warum das wichtig ist
Nova’s Version:
@RestController public class TaskController { @Autowired private TaskRepository taskRepository; }
Mein Vorschlag:
@RestController @RequiredArgsConstructor // Lombok magic public class TaskController { private final TaskRepository taskRepository; }
Aber Elyndra, warum ist das besser? Das fragen mich viele Junioren, und es ist eine wichtige Frage!
Dependency Injection Deep-Dive
Spring hat drei Arten von Dependency Injection:
1. Field Injection (@Autowired auf Felder) – Was Nova verwendet hat:
@Autowired private TaskRepository taskRepository; // Spring "injiziert" hier
2. Setter Injection (@Autowired auf Setter):
private TaskRepository taskRepository; @Autowired public void setTaskRepository(TaskRepository taskRepository) { this.taskRepository = taskRepository; }
3. Constructor Injection – Was ich empfehle:
private final TaskRepository taskRepository; public TaskController(TaskRepository taskRepository) { this.taskRepository = taskRepository; }
Warum Constructor Injection die beste Choice ist:
Grund #1: Immutability (Unveränderlichkeit)
private final TaskRepository taskRepository; // final = nie wieder änderbar
Mit final kann das Repository nach der Erstellung nie wieder null oder ein anderes Objekt werden. Thread-safe by design!
Grund #2: Einfacher zu testen(kleiner Teaser Dr. Cassian arbeitet an einer 6 teiligen Testreihe )
@Test void testCreateTask() { // Mit Constructor Injection super easy! TaskRepository mockRepo = mock(TaskRepository.class); TaskController controller = new TaskController(mockRepo); // Test läuft... }
Bei Field Injection müsstest du Reflection oder Spring Test Context verwenden. Constructor Injection = Plain Java!
Grund #3: Explizite Dependencies Der Konstruktor zeigt sofort: „Diese Klasse braucht ein TaskRepository zum Funktionieren!“ Bei Field Injection ist das versteckt.
Grund #4: Fail-Fast bei fehlenden Dependencies Wenn Spring das Repository nicht finden kann, crasht die App beim Start – nicht erst zur Runtime!
@RequiredArgsConstructor – Lombok’s elegante Lösung
@RestController @RequiredArgsConstructor public class TaskController { private final TaskRepository taskRepository; // Lombok generiert automatisch: // public TaskController(TaskRepository taskRepository) { // this.taskRepository = taskRepository; // } }
Was macht @RequiredArgsConstructor genau?
- Generiert einen Konstruktor für alle final Felder
- Plus alle Felder mit @NonNull
- Clean Code ohne Boilerplate!
Aber Achtung bei multiple Dependencies:
@RestController @RequiredArgsConstructor public class TaskController { private final TaskRepository taskRepository; private final EmailService emailService; private final AuditService auditService; // Lombok generiert Konstruktor mit ALLEN drei! }
Wenn du mehr als 3-4 Dependencies hast, ist das ein Code Smell – die Klasse macht zu viel!
Constructor Injection ohne Lombok (falls ihr kein Lombok wollt):
@RestController public class TaskController { private final TaskRepository taskRepository; // Spring erkennt automatisch: nur ein Konstruktor = autowiring! public TaskController(TaskRepository taskRepository) { this.taskRepository = taskRepository; } }
Seit Spring 4.3: Wenn eine Klasse nur einen Konstruktor hat, ist @Au
towired nicht nötig!
Verbesserung #2: Response Entities für bessere HTTP-Semantik
Nova’s Version:
@PostMapping public Task createTask(@RequestBody Task task) { return taskRepository.save(task); }
Mein Vorschlag:
@PostMapping public ResponseEntity<Task> createTask(@RequestBody Task task) { Task saved = taskRepository.save(task); return ResponseEntity.status(HttpStatus.CREATED).body(saved); }
Why? HTTP 201 CREATED statt 200 OK für neue Ressourcen!
Verbesserung #3: Validation hinzufügen
@Entity @Data @NoArgsConstructor @AllArgsConstructor public class Task { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Title is required") @Size(max = 100, message = "Title must be less than 100 characters") private String title; @Size(max = 500, message = "Description must be less than 500 characters") private String description; private boolean completed = false; // Default value! }
Plus im Controller:
@PostMapping public ResponseEntity<Task> createTask(@Valid @RequestBody Task task) { Task saved = taskRepository.save(task); return ResponseEntity.status(HttpStatus.CREATED).body(saved); }
🤔 Lombok: Fluch oder Segen? (Die ehrliche Antwort)
Nova fragte mich: „Elyndra, sollte ich Lombok überall verwenden?“
Meine ehrliche Antwort nach 4 Jahren Java-Entwicklung: Es kommt darauf an! Lombok ist mächtig, aber wie Spider-Man sagt: „With great power comes great responsibility.“ 😄
✅ Lombok JA bei:
DTOs und Entities (@Data
):
@Entity @Data @NoArgsConstructor @AllArgsConstructor public class Task { // Lombok generiert: equals, hashCode, toString, getters, setters // Das sind ~50 Zeilen Code, die wir nicht schreiben müssen! }
Constructor Injection (@RequiredArgsConstructor
):
@Service @RequiredArgsConstructor public class TaskService { private final TaskRepository repository; private final EmailService emailService; // Konstruktor mit beiden Dependencies automatisch generiert }
Builder Pattern (@Builder
):
@Data @Builder public class TaskResponse { private Long id; private String title; private boolean completed; } // Usage: TaskResponse response = TaskResponse.builder() .id(1L) .title("Learn Lombok") .completed(true) .build();
❌ Lombok NEIN bei:
Komplexer Business Logic in Gettern/Settern:
// NICHT mit Lombok - zu komplex für @Data public class BankAccount { private BigDecimal balance; public void setBalance(BigDecimal newBalance) { if (newBalance.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("Balance cannot be negative!"); } auditService.logBalanceChange(this.balance, newBalance); this.balance = newBalance; } }
Wenn das Team Lombok nicht kennt: Lombok in einem Team einzuführen, das es nicht kennt, kann Verwirrung stiften. Team-Alignment first!
Bei sehr kritischen Klassen: Payment-Processing, Security-Komponenten – lieber explizit und verständlich.
🎯 Mein Lombok-Pragmatismus:
Für Nova’s Lernphase empfehle ich:
- Start small:
@Data
für Entities - Learn the generated code: Schau dir an, was Lombok generiert (IDE → „Delombok“)
- Team erst, Lombok später: Verstehe Spring DI erst ohne Lombok, dann mit
- @RequiredArgsConstructor: Sobald Constructor Injection sitzt
Pro-Tipp: IntelliJ zeigt euch den generierten Code: View → Show Bytecode With Lombok
Mehr Lombok-Features und offizielle Dokumentation findet ihr auf: https://projectlombok.org/ – dort sind alle Features mit Beispielen erklärt!
⚠️ Lombok-Gotchas (Fallen, in die ich getappt bin):
1. @Data mit JPA-Entities:
@Entity @Data // Dangerous! public class Task { @Id private Long id; @OneToMany private List<Comment> comments; }
Problem: equals()
und hashCode()
mit Collections = Performance-Disaster + Lazy Loading Issues!
Better:
@Entity @Getter @Setter @ToString(exclude = "comments") // Exclude collections! @EqualsAndHashCode(of = "id") // Only use ID public class Task { // ... }
2. @AllArgsConstructor mit vielen Feldern:
@AllArgsConstructor public class User { private String firstName; private String lastName; private String email; private String phone; private String address; // ... 10 more fields // Konstruktor mit 15 Parametern = Unmaintainable! }
Better: @Builder
für viele Felder verwenden.
📋 Template: Meine „Task API 2.0“ Struktur
Basierend auf Nova’s Code, so würde ich die nächste Iteration strukturieren:
src/main/java/com/javafleet/taskmanager/ ├── dto/ │ ├── TaskCreateRequest.java │ └── TaskResponse.java ├── entity/ │ └── Task.java ├── repository/ │ └── TaskRepository.java ├── service/ │ └── TaskService.java └── controller/ └── TaskController.java
Why Service Layer? Business Logic gehört nicht in den Controller!
💡 Was ich von Nova gelernt habe (Seriously!)
Ehrlich? Nova’s direkter Ansatz hat mich daran erinnert, nicht zu viel zu komplizieren. Als Senior Dev neige ich dazu, sofort an alle Edge Cases zu denken:
„Was ist mit Caching? Brauchen wir ein Service Layer? Was ist mit Monitoring? Sollten wir Event Sourcing verwenden? Microservices? Kubernetes?“
Sometimes simple is better. Nova’s funktionierender Code ist 100x wertvoller als mein „perfekter“ Code, der nie fertig wird! 😊
Das „Senior Developer Paradox“
Je mehr Erfahrung wir sammeln, desto mehr Fallen sehen wir überall. Das kann paralysierend werden:
Nova’s Ansatz:
- Problem definieren ✅
- Einfachste Lösung implementieren ✅
- Funktioniert es? JA ✅
- Shipped! ✅
Mein „Senior“ Ansatz früher:
- Problem definieren ✅
- Alle möglichen Probleme durchdenken 🤔
- Perfect Architecture designen 📋
- 3 Wochen später… noch nichts shipped 😅
Lesson learned: Working software over comprehensive documentation (Agile Manifesto). Nova lebt das instinktiv!
Nova’s „Hidden Wisdom“
Was ich in Nova’s Code entdeckt habe, das sie vielleicht selbst nicht bemerkt:
1. Pragmatische Tool-Wahl: H2 Database, curl statt Postman, VSCode statt „enterprise“ IDEs. Ship fast, optimize later!
2. Learning by Doing: Statt 3 Spring Boot Bücher zu lesen, hat sie einfach angefangen zu coden. Hands-on beats theory.
3. Community-Driven Learning: Ihr Blogpost, die Offenheit für Feedback – das ist Growth Mindset in Action.
Was ich als Senior noch von Juniors lerne
Fresh Eyes: Nova sieht Komplexitäten nicht, die ich überall sehe. Manchmal ist das ein Vorteil!
Unbiased Tool Choice: Ich bin „Spring Boot biased“ – Nova probiert aus, was funktioniert.
Rapid Iteration: Juniors shippen oft schneller, weil sie weniger Angst vor „imperfektem“ Code haben.
Authenticity: Nova’s ehrliche „Das verstehe ich nicht“-Posts sind wertvoller als meine „Ich weiß alles“-Artikel früher.
🎯 Nächste Schritte für Nova (und andere Juniors)
- Testing hinzufügen:
@Test
für Controller und Repository - Error Handling: Global Exception Handler
- Service Layer: Business Logic aus dem Controller raus
- Swagger/OpenAPI: API-Dokumentation
Nova, du machst das großartig! Keep coding! 🚀
👥 An die Community: Eure Meinung?
Fragen für euch:
- Nutzt ihr Constructor oder Field Injection?
- Lombok: Love it or hate it?
- Wie macht ihr Code Reviews in euren Teams?
Junior Devs: Schickt mir gerne eure Projekte! Ich mache gerne mehr konstruktive Reviews.
Senior Devs: Wie vermeidet ihr, dass Code Reviews demotivierend werden?
❓ FAQ – Häufige Fragen zu Constructor Injection & Lombok
F: Warum ist @RequiredArgsConstructor besser als @Autowired?
A: Drei Gründe: 1) Immutability durch final
, 2) Einfacher zu testen (Plain Java), 3) Fail-fast bei fehlenden Dependencies. Plus: explizite Dependencies sind besser als versteckte.
F: Kann ich @Data für alle meine Klassen verwenden?
A: Nein! Bei JPA-Entities mit Collections kann @Data Performance-Probleme verursachen. Verwende dann lieber @Getter/@Setter mit @EqualsAndHashCode(of = „id“).
F: Was passiert, wenn ich zu viele Dependencies im Konstruktor habe?
A: Das ist ein Code Smell! Mehr als 3-4 Dependencies bedeuten meist: Die Klasse macht zu viel. Überlege dir, ob du die Klasse aufteilen kannst.
F: Muss ich @Autowired beim Constructor weglassen?
A: Seit Spring 4.3: Ja! Wenn eine Klasse nur einen Konstruktor hat, erkennt Spring das automatisch. @Autowired ist nicht mehr nötig.
F: Soll ich Lombok in Production verwenden?
A: Ja, aber mit Bedacht! Für DTOs und einfache Entities ist Lombok super. Bei komplexer Business Logic lieber explizit bleiben. Und: Das ganze Team muss Lombok kennen!
💬 Lass uns diskutieren!
Was denkst du über diese Verbesserungen? Hast du ähnliche Erfahrungen mit Constructor Injection gemacht? Oder nutzt du Lombok anders als ich?
Teile deine Meinung in den Kommentaren! Besonders interessant:
- Welche Dependency Injection bevorzugst du und warum?
- Lombok in Production: Eure Erfahrungen?
- Habt ihr andere Code Review Tipps für Nova?
Junior Developers: Falls ihr Fragen zu den Beispielen habt oder ähnliche Herausforderungen erlebt – schreibt es gerne! Ich antworte auf jeden Kommentar.
Erfahrene Entwickler: Wie macht ihr Code Reviews in euren Teams? Was sind eure Lombok Best Practices?
P.S.: Nova hat schon angekündigt, nächsten Montag Validation zu lernen. Ich bin gespannt! 😄
Elyndra schreibt jeden Mittwoch über die Junior-zu-Senior Transition, Clean Code und praktische Java-Entwicklung hier im Blog! Folgt mir für mehr praktische Java-Tipps und ehrliche Entwickler-Geschichten.
Tags: #CodeReview #CleanCode #SpringBoot #Mentoring #JavaBestPractices #SeniorDeveloper
Schreibe einen Kommentar