Przejdź do głównej zawartości

Wzorce Java i Spring Boot

Opanuj enterprise rozwój Java z Cursor i Claude Code. Ten przewodnik obejmuje aplikacje Spring Boot, architektury mikroserwisów, integrację bazy danych z JPA/Hibernate, strategie testowania i wzorce enterprise wspomagane AI do budowania skalowalnych aplikacji Java.

  1. Inicjalizacja projektu Java

    Okno terminala
    # Spring Boot z Spring Initializr
    Ask: "Utwórz nowy projekt Spring Boot z zależnościami Web, JPA, Security i Actuator"
    # Lub użyj trybu Agent
    Agent: "Skonfiguruj mikroserwis Spring Boot z REST API, PostgreSQL i wsparciem Docker"
  2. Konfiguracja środowiska deweloperskiego

    application.yml
    spring:
    profiles:
    active: dev
    datasource:
    url: jdbc:postgresql://localhost:5432/myapp
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    jpa:
    hibernate:
    ddl-auto: validate
    show-sql: true
  3. Konfiguracja reguł AI

    Okno terminala
    # .cursorrules lub CLAUDE.md
    - Używaj funkcji Java 21 (records, pattern matching, virtual threads)
    - Przestrzegaj najlepszych praktyk Spring Boot
    - Implementuj właściwą obsługę wyjątków z @ControllerAdvice
    - Używaj constructor injection zamiast field injection
    - Pisz testy dla wszystkich metod publicznych
    - Przestrzegaj zasad clean architecture
// Prompt AI
Agent: "Utwórz kompletne REST API dla zarządzania produktami z:
- Operacjami CRUD
- Paginacją i sortowaniem
- Funkcją wyszukiwania
- Walidacją wejścia
- Obsługą wyjątków
- Dokumentacją OpenAPI"
// Agent wygeneruje:
@RestController
@RequestMapping("/api/v1/products")
@RequiredArgsConstructor
@Tag(name = "Zarządzanie produktami")
public class ProductController {
private final ProductService productService;
@GetMapping
@Operation(summary = "Pobierz wszystkie produkty z paginacją")
public Page<ProductDTO> getAllProducts(
@PageableDefault(size = 20) Pageable pageable,
@RequestParam(required = false) String search) {
return productService.findAll(search, pageable);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Utwórz nowy produkt")
public ProductDTO createProduct(@Valid @RequestBody CreateProductRequest request) {
return productService.create(request);
}
}
// Prompt AI: "Zaimplementuj warstwę serwisu z zarządzaniem transakcjami i cache'owaniem"
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EventPublisher eventPublisher;
@Transactional
@CacheEvict(value = "users", key = "#result.id")
public UserDTO createUser(CreateUserRequest request) {
log.debug("Tworzenie użytkownika z emailem: {}", request.email());
if (userRepository.existsByEmail(request.email())) {
throw new DuplicateResourceException("Email już istnieje");
}
User user = User.builder()
.email(request.email())
.password(passwordEncoder.encode(request.password()))
.roles(Set.of(Role.USER))
.build();
user = userRepository.save(user);
eventPublisher.publish(new UserCreatedEvent(user.getId()));
return UserMapper.toDTO(user);
}
@Cacheable(value = "users", key = "#id")
public UserDTO findById(Long id) {
return userRepository.findById(id)
.map(UserMapper::toDTO)
.orElseThrow(() -> new ResourceNotFoundException("Użytkownik", id));
}
}
// Prompt AI
Ask: "Utwórz encje JPA dla systemu e-commerce z:
- Właściwymi relacjami (OneToMany, ManyToMany)
- Polami audytu
- Funkcją soft delete
- Niestandardowymi metodami repozytorium
- Specifications dla złożonych zapytań"
// Wygenerowana Encja
@Entity
@Table(name = "orders")
@EntityListeners(AuditingEntityListener.class)
@SQLDelete(sql = "UPDATE orders SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> items = new ArrayList<>();
@Enumerated(EnumType.STRING)
private OrderStatus status;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
private boolean deleted = false;
}
// Prompt AI: "Zaimplementuj odporną komunikację serwisów z Circuit Breaker"
@Component
@RequiredArgsConstructor
public class PaymentServiceClient {
private final WebClient webClient;
@CircuitBreaker(name = "payment-service", fallbackMethod = "fallbackPayment")
@Retry(name = "payment-service")
@TimeLimiter(name = "payment-service")
public Mono<PaymentResponse> processPayment(PaymentRequest request) {
return webClient.post()
.uri("/api/payments")
.bodyValue(request)
.retrieve()
.bodyToMono(PaymentResponse.class)
.doOnError(error -> log.error("Płatność nie powiodła się", error));
}
public Mono<PaymentResponse> fallbackPayment(PaymentRequest request, Exception ex) {
log.warn("Serwis płatności niedostępny, używam fallback", ex);
return Mono.just(PaymentResponse.pending(request.getOrderId()));
}
}
// Prompt AI
Agent: "Zaimplementuj architekturę opartą na zdarzeniach z:
- Integracją Kafka
- Wzorcem event sourcing
- Orkiestracją Saga
- Obsługą dead letter queue"
// Event Publisher
@Component
@RequiredArgsConstructor
public class OrderEventPublisher {
private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
@EventListener
@Async
public void handleOrderCreated(OrderCreatedEvent event) {
OrderEvent kafkaEvent = OrderEvent.builder()
.eventId(UUID.randomUUID().toString())
.eventType("ORDER_CREATED")
.aggregateId(event.getOrderId())
.payload(event)
.timestamp(Instant.now())
.build();
kafkaTemplate.send("order-events", kafkaEvent)
.addCallback(
result -> log.info("Zdarzenie wysłane: {}", kafkaEvent.getEventId()),
error -> log.error("Nie udało się wysłać zdarzenia", error)
);
}
}
// Prompt AI: "Wygeneruj kompleksowe testy jednostkowe z Mockito"
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
@Mock
private ProductRepository productRepository;
@Mock
private EventPublisher eventPublisher;
@InjectMocks
private ProductService productService;
@Test
@DisplayName("Powinien utworzyć produkt i opublikować zdarzenie")
void createProduct_Success() {
// Given
CreateProductRequest request = CreateProductRequest.builder()
.name("Produkt testowy")
.price(BigDecimal.valueOf(99.99))
.build();
Product savedProduct = Product.builder()
.id(1L)
.name(request.name())
.price(request.price())
.build();
when(productRepository.save(any(Product.class))).thenReturn(savedProduct);
// When
ProductDTO result = productService.create(request);
// Then
assertThat(result).isNotNull();
assertThat(result.name()).isEqualTo(request.name());
verify(eventPublisher).publish(any(ProductCreatedEvent.class));
}
}
// Prompt AI
Ask: "Utwórz testy integracyjne z:
- @SpringBootTest
- TestContainers dla PostgreSQL
- MockMvc dla testowania API
- Builderami danych testowych
- Czyszczeniem bazy danych między testami"
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
@ActiveProfiles("test")
class ProductIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Autowired
private MockMvc mockMvc;
@Test
@Sql("/test-data/products.sql")
void getProducts_ReturnsPagedResults() throws Exception {
mockMvc.perform(get("/api/v1/products")
.param("page", "0")
.param("size", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray())
.andExpect(jsonPath("$.content[0].name").value("Produkt testowy"));
}
}
// Prompt AI: "Zaimplementuj uwierzytelnianie oparte na JWT z Spring Security"
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new CustomAccessDeniedHandler())
)
.build();
}
}
// Prompt AI
Agent: "Zoptymalizuj aplikację dla wysokiej współbieżności używając:
- Virtual threads
- Poolingu połączeń
- Optymalizacji zapytań
- Cache'owania odpowiedzi
- Przetwarzania asynchronicznego"
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public ExecutorService virtualThreadExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
return new TaskExecutorAdapter(virtualThreadExecutor());
}
}
// Prompt AI: "Skonfiguruj kompleksowe monitorowanie z Micrometer"
@RestController
@RequestMapping("/api/orders")
@Timed
public class OrderController {
private final MeterRegistry meterRegistry;
private final Counter orderCounter;
public OrderController(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.orderCounter = Counter.builder("orders.created")
.description("Liczba utworzonych zamówień")
.register(meterRegistry);
}
@PostMapping
@Timed(value = "orders.create.time", description = "Czas tworzenia zamówienia")
public OrderDTO createOrder(@RequestBody CreateOrderRequest request) {
return Metrics.timer("order.creation.timer").record(() -> {
OrderDTO order = orderService.create(request);
orderCounter.increment();
return order;
});
}
}

Wytyczne Enterprise Java

  1. Clean Architecture - Oddziel logikę biznesową od frameworków
  2. Zasady SOLID - Projektuj dla łatwości utrzymania
  3. Domain-Driven Design - Modeluj złożone domeny biznesowe
  4. Piramida testów - Unit > Integration > E2E tests
  5. 12-Factor App - Buduj aplikacje cloud-native
  6. API-First Design - Zacznij od specyfikacji OpenAPI
// AI: "Wygeneruj wzorzec builder z walidacją"
@Builder(toBuilder = true)
@Value
public class Order {
@NotNull Long id;
@NotNull Long customerId;
@NotEmpty List<OrderItem> items;
@NotNull OrderStatus status;
@NotNull Instant createdAt;
public static class OrderBuilder {
public Order build() {
Order order = new Order(id, customerId, items, status, createdAt);
ValidationUtils.validate(order);
return order;
}
}
}