Wzorce Java i Spring Boot
Twój serwis Spring Boot się kompiluje, ale AI wstrzyknął zależności przez pola, zostawił nieszczelną granicę @Transactional i żadnego @ControllerAdvice — i wszystko się sypie przy pierwszym wyścigu dwóch żądań o to samo zamówienie. Wygenerowana Java wygląda idiomatycznie, dopóki produkcyjny ruch nie znajdzie szwów: zapytania N+1, połknięte wyjątki, wywołanie KafkaTemplate, które nie kompiluje się już na Spring Boot 3. Te przepisy produkują kod Spring Boot 3.x na Javie 21, który się broni, i — co równie ważne — pokazują, jak wyłapać tryby awarii, które model wciąż psuje.
Co wyniesiesz z tego poradnika
Dział zatytułowany „Co wyniesiesz z tego poradnika”- Gotowy do wklejenia prompt na zasób REST z walidacją, paginacją i jednym globalnym handlerem wyjątków
- Prompt JPA, który używa zarządzanego soft-delete z Hibernate 6.4 zamiast przestarzałego wzorca
@Where - Prompt na odporny klient downstream (circuit breaker Resilience4j + aktualne API Kafki na
CompletableFuture) - Prompt Spring Security 6 na bezstanowe uwierzytelnianie JWT, które przejdzie review
- Prompt na testy produkujący testy integracyjne Testcontainers, a nie mocki happy-path
- Specyficzne dla Springa pułapki do sprawdzenia przy każdej generacji oraz serwery MCP, które zacieśniają pętlę
Workflow
Dział zatytułowany „Workflow”Krok 1: Scaffold projektu (ta część różni się per narzędzie)
Dział zatytułowany „Krok 1: Scaffold projektu (ta część różni się per narzędzie)”Spring Initializr jest źródłem prawdy dla drzewa zależności, więc sensowne jest, by agent go wywołał, a potem nałożył na to twoje konwencje.
W trybie agenta wskaż Cursorowi świeży katalog:
Generate a Spring Boot 3.4 project on Java 21 using start.spring.io withdependencies: web, data-jpa, validation, security, actuator, postgresql,flyway. Maven build. Then add a CLAUDE.md-style conventions file documentingthe rules below and apply them to the generated code.Cursor robi scaffold w workspace, pokazuje diff, a ty akceptujesz per plik. Trzymaj konwencje w pliku .cursor/rules/, żeby każda późniejsza edycja je dziedziczyła.
claude "Create a Spring Boot 3.4 project on Java 21 via start.spring.io withweb, data-jpa, validation, security, actuator, postgresql, and flyway. Maven.Write a CLAUDE.md capturing our conventions, then make the generated code follow it."Claude Code uruchamia curl do start.spring.io, rozpakowuje i edytuje na miejscu. CLAUDE.md, który napisze, jest automatycznie wykorzystywany przy każdym kolejnym uruchomieniu w repo.
codex "Scaffold a Spring Boot 3.4 / Java 21 Maven project from start.spring.io(web, data-jpa, validation, security, actuator, postgresql, flyway). Add anAGENTS.md with our conventions and apply them."Przy większych refaktorach odpal to jako zadanie Codex Cloud na worktree, żeby scaffold i twoja istniejąca gałąź się nie zderzyły. Codex czyta AGENTS.md przy każdym zadaniu.
Niezależnie od narzędzia, podaj mu te konwencje z góry — zapobiegają najczęstszemu outputowi blokującemu review:
- Java 21: records for DTOs, pattern matching, virtual threads where I/O-bound- Constructor injection only (never @Autowired field injection)- One @ControllerAdvice for the whole app; never catch-and-swallow- @Transactional only on service methods, readOnly=true by default- No business logic in controllers; no entities returned from controllers (DTOs only)- Every new endpoint ships with a Testcontainers integration testKrok 2: Wygeneruj zasób REST
Dział zatytułowany „Krok 2: Wygeneruj zasób REST”Teraz część identyczna we wszystkich trzech narzędziach. Sztuczka polega na określeniu koperty odpowiedzi i zachowania przy awarii, a nie samego “CRUD” — inaczej dostaniesz kontroler happy-path, który wycieka encje i zwraca stack trace’y.
Następnie przejrzyj to, co wróciło, pod kątem konwencji. Najcenniejsza kontrola dotyczy handlera wyjątków: modele uwielbiają rozsiewać bloki try/catch, które logują i zwracają null. Chcesz dokładnie jednej klasy advice i zera połkniętych wyjątków. Poprawny kontroler pozostaje tak chudy:
@RestController@RequestMapping("/api/v1/products")class ProductController { private final ProductService products;
ProductController(ProductService products) { // constructor injection, no @Autowired this.products = products; }
@GetMapping Page<ProductResponse> list(@PageableDefault(size = 20) Pageable pageable, @RequestParam(required = false) String search) { return products.search(search, pageable); // validation + mapping live in the service }}Jeśli wygenerowany kontroler jest grubszy niż to — logika mapowania, try/catch, wstrzyknięty EntityManager — odeślij go z powrotem: “Move all mapping and error handling out of the controller; rely on the @RestControllerAdvice.”
Krok 3: Persystencja bez pułapki przestarzałego soft-delete
Dział zatytułowany „Krok 3: Persystencja bez pułapki przestarzałego soft-delete”Poproś o soft delete, a większość modeli wciąż wypluwa parę @SQLDelete + @Where z Hibernate 5. @Where jest przestarzałe w Hibernate 6 (zastąpione przez @SQLRestriction), a Spring Boot 3.2+ dostarcza Hibernate 6.4, który ma w pełni zarządzaną adnotację @SoftDelete. Nazwij wersję, żeby dostać nowoczesną ścieżkę.
Zarządzana adnotacja zwija dawny taniec z dwiema adnotacjami do jednej linijki, a Hibernate automatycznie filtruje wiersze usunięte miękko wszędzie:
@Entity@Table(name = "orders")@SoftDelete // Hibernate 6.4+: replaces @SQLDelete + @Where, applied to every query@EntityListeners(AuditingEntityListener.class)class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id", nullable = false) private Customer customer; // ... items, status, auditing fields}Krok 4: Odporne wywołania downstream
Dział zatytułowany „Krok 4: Odporne wywołania downstream”Mikroserwis, który synchronicznie wywołuje płatności lub magazyn, potrzebuje circuit breakera, a każdy kod dotykający Kafki musi używać aktualnego API. Na Spring Kafka 3.x (Spring Boot 3.x) KafkaTemplate.send() zwraca CompletableFuture — dawne ListenableFuture.addCallback(...) już nie istnieje i się nie skompiluje.
Publisher Kafki powinien wyglądać tak — whenComplete, nie addCallback:
@Componentclass OrderEventPublisher { private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
OrderEventPublisher(KafkaTemplate<String, OrderEvent> kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; }
@EventListener void onOrderCreated(OrderCreatedEvent event) { var kafkaEvent = OrderEvent.from(event); // Spring Kafka 3.x returns CompletableFuture; .addCallback was removed kafkaTemplate.send("order-events", kafkaEvent).whenComplete((result, ex) -> { if (ex == null) log.info("Event sent: {}", kafkaEvent.eventId()); else log.error("Failed to send event {}", kafkaEvent.eventId(), ex); }); }}Krok 5: Bezstanowe bezpieczeństwo JWT
Dział zatytułowany „Krok 5: Bezstanowe bezpieczeństwo JWT”Spring Security 6 porzucił WebSecurityConfigurerAdapter i dawny DSL. Przypnij wersję, żeby agent wyprodukował lambda DSL z beanem SecurityFilterChain, a nie wskrzeszał przestarzałą klasę bazową.
Krok 6: Testy, które dowodzą zachowania, a nie teatru pokrycia
Dział zatytułowany „Krok 6: Testy, które dowodzą zachowania, a nie teatru pokrycia”Powód, dla którego upieramy się przy Testcontainers w konwencjach, jest taki, że testy z zamockowanym repozytorium przechodzą, podczas gdy prawdziwe zapytanie jest zepsute. Generacja nie jest skończona, dopóki nie dostarczy testu integracyjnego, który ćwiczy prawdziwą bazę danych.
Kiedy to się psuje
Dział zatytułowany „Kiedy to się psuje”Co dalej
Dział zatytułowany „Co dalej”- Wzorce baz danych — głębsze JPA, strojenie zapytań i workflowy migracji
- Wzorce API — międzyjęzykowy REST i projektowanie koperty błędów
- Wzorce ORM — playbook na N+1 i lazy-loading w różnych ORM-ach
- Wzorce Kubernetes — wdrażanie tych serwisów na klaster