Spring Boot + Spring Data JPA + MySQL + Arhitectura N-Tier

Concepte esențiale, anotări principale, exemple practice și bune practici

Ce este Spring Boot?

Spring Boot este un strat peste Spring Framework care oferă: auto-configurare, starter-e pentru dependențe și un server web încorporat (Tomcat/Jetty/Undertow). Scopul este să lansezi aplicații rapid, cu configurație minimă.

Anotarea-cheie

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan. Activează auto-config și scanează componentele în pachetul curent și sub-pachete.

Containerul IoC și Beans

Containerul IoC (Inversion of Control), numit în Spring ApplicationContext, este motorul care creează, configurează și gestionează ciclul de viață al obiectelor tale numite beans. În loc să le creezi cu new în codul tău, îi spui containerului „dă-mi un bean de tipul X”, iar el ți-l oferă cu dependențele deja injectate.

Ce este un „bean”?

  • Un obiect gestionat de Spring (creat, configurat, distrus de container).
  • Este înregistrat prin @Component & co. sau prin metode @Bean.
  • Are un scope: singleton (implicit), prototype, request, session.

Injecția de dependențe (DI)

  • Constructor DI (recomandat): imutabilitate & testabilitate.
  • Field DI (@Autowired pe câmp) – rapid, dar mai greu de testat.
  • Setter DI – util pentru dependențe opționale.

Exemplu scurt DI prin constructor

@Service public class EmailService { private final MailClient client; public EmailService(MailClient client) { this.client = client; } } @Component class MailClient {}

Tip: Evită new în servicii. Cere dependențele prin constructor și lasă Spring să le ofere.

Ciclul de viață & hooks

@Component class WarmupCache { @PostConstruct void init(){ /* pornește cache-uri */ } @PreDestroy void shutdown(){ /* curăță resurse */ } }

Anotări principale — pe scurt

Bootstrapping

  • @SpringBootApplication — pornește auto-config & scanning.
  • @Configuration — clasă de config Java-based.
  • @Bean — înregistrează manual un bean în container.
  • @ComponentScan — definește pachetele pentru scanning.

Layering

  • @Component — componentă generică.
  • @Service — logică de business.
  • @Repository — acces la DB; traduce excepțiile (Spring Data).
  • @Controller / @RestController — MVC/REST.

Wiring & Mediu

  • @Autowired — injecție dependențe.
  • @Qualifier — selectează implementarea când sunt multiple.
  • @Primary — marcați implementarea preferată.
  • @Profile — activează bean-ul doar pentru un profil.
  • @Value — injectează valori din proprietăți/ENV.

Web & REST (Spring MVC)

Anotări frecvente pentru API-uri:

@RestController @RequestMapping("/api/products") class ProductController { private final ProductService service; public ProductController(ProductService service) { this.service = service; } @GetMapping List<ProductDto> all(@RequestParam(defaultValue="0") int page, @RequestParam(defaultValue="10") int size) { return service.getPage(page, size); } @GetMapping("/{id}") ProductDto byId(@PathVariable Long id){ return service.getById(id); } @PostMapping @ResponseStatus(HttpStatus.CREATED) ProductDto create(@Valid @RequestBody CreateProductDto in){ return service.create(in); } }

Global error handling

@RestControllerAdvice class ApiErrors { @ExceptionHandler(EntityNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) Map<String,String> notFound(RuntimeException ex) { return Map.of("error", ex.getMessage()); } }

Spring Data JPA & MySQL

Anotări uzuale în modelul de date:

Entități & relații

@Entity @Table(name="products") public class Product { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; @Column(nullable=false, length=120) private String name; private double price; @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(name="category_id") private Category category; } @Entity public class Category { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; @Column(nullable=false, unique=true) private String name; @OneToMany(mappedBy="category", cascade=CascadeType.ALL) private List<Product> products = new ArrayList<>(); }

Repository & interogări

public interface ProductRepository extends JpaRepository<Product,Long> { // Query derivation List<Product> findByNameContainingIgnoreCase(String name); // JPQL @Query("select p from Product p where p.price > :min") List<Product> expensive(@Param("min") double min); // Paginare Page<Product> findAll(Pageable pageable); }

application.yml

spring: datasource: url: jdbc:mysql://localhost:3306/demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC username: root password: parola jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate.format_sql: true
Atenție: Folosește ddl-auto: validate sau migrații (Flyway/Liquibase) în producție; update/create e doar pentru dev/POC.

Cross-cutting: Validare, Tranzacții, Scheduling, Caching

Validare (Jakarta Validation)

public record CreateProductDto( @NotBlank @Size(max=120) String name, @Positive double price, @NotNull Long categoryId ) {}

Folosește @Valid în controller pentru a valida inputul.

Tranzacții

@Service public class ProductService { private final ProductRepository repo; private final CategoryRepository catRepo; public ProductService(ProductRepository r, CategoryRepository c){ this.repo=r; this.catRepo=c; } @Transactional public ProductDto create(CreateProductDto in){ var category = catRepo.findById(in.categoryId()) .orElseThrow(() -> new EntityNotFoundException("Category not found")); var p = new Product(); p.setName(in.name()); p.setPrice(in.price()); p.setCategory(category); return toDto(repo.save(p)); } }

@Transactional asigură commit/rollback automat. Evită apelurile interne către metode tranzacționale (proxy-bypass).

Scheduling

@EnableScheduling @SpringBootApplication class App {} @Component class Housekeeping { @Scheduled(cron = "0 0 * * * *") // la fiecare oră void clean(){ /* cleanup */ } }

Caching

@EnableCaching @SpringBootApplication class App {} @Service class CategoryService { private final CategoryRepository repo; public CategoryService(CategoryRepository repo){ this.repo=repo; } @Cacheable("categoryById") Category get(Long id){ return repo.findById(id).orElseThrow(); } @CacheEvict(value="categoryById", key="#id") void evict(Long id){} }

Config, proprietăți & profiluri

@ConfigurationProperties (binding tip-safe)

@ConfigurationProperties(prefix="storage") public record StorageProps(Path root, int maxFiles, long maxSizeBytes) {}
@EnableConfigurationProperties(StorageProps.class) @SpringBootApplication class App {}
storage: root: "/var/app/data" max-files: 1000 max-size-bytes: 10485760

Injectezi StorageProps în servicii pentru a citi setări strongly-typed.

Profiluri

@Profile("dev") @Configuration class DevOnlyConfig { @Bean DataSource h2(){ /* dataSource H2 pentru dev */ return null; } }

Activezi profilul cu --spring.profiles.active=dev sau în application.yml.

Exemple „end-to-end” scurte

DTO + Mapare + Paginare

record ProductDto(Long id, String name, double price, String category) {} @Service class ProductService { private final ProductRepository repo; ProductService(ProductRepository repo){ this.repo=repo; } List<ProductDto> getPage(int page, int size){ var p = repo.findAll(PageRequest.of(page, size, Sort.by("id").descending())); return p.stream().map(this::toDto).toList(); } ProductDto toDto(Product e){ return new ProductDto(e.getId(), e.getName(), e.getPrice(), e.getCategory()!=null ? e.getCategory().getName() : null); } }

@Bean vs @Component

@Configuration class HttpConfig { @Bean RestTemplate restTemplate(){ return new RestTemplate(); } }

@Bean — când creezi bean-uri din librării third-party sau ai nevoie de control fin.

@Component class UuidGenerator { String newId(){ return UUID.randomUUID().toString(); } }

@Component — pentru clasele tale, descoperite prin scanning.

Scope-uri & @Lazy

@Component @Scope("prototype") class Task { /* instanță nouă la fiecare injectare */ } @Component class HeavyService { private final ReportEngine engine; HeavyService(@Lazy ReportEngine engine){ this.engine = engine; } // creat la primul apel }

Filtrare CORS simplă

@Configuration class CorsConfig { @Bean WebMvcConfigurer cors(){ return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry r){ r.addMapping("/api/**").allowedOrigins("https://app.example.com") .allowedMethods("GET","POST","PUT","DELETE"); } }; } }

Arhitectura N-Tier în practică

  1. Presentation: Controllers + DTOs
  2. Business: Services (reguli, tranzacții, orchestrare)
  3. Data Access: Repositories + Entities (JPA) + MySQL

Structură recomandată de pachete

com.example.app ├─ App.java // @SpringBootApplication ├─ config/ // @Configuration, @Bean, security, cors ├─ domain/ // @Entity, agregate, value objects ├─ repository/ // Spring Data repositories ├─ service/ // business services (@Service) ├─ web/ // controllers, DTOs, advice └─ util/ // helpers, mappers

Tip: Pune clasa cu @SpringBootApplication în rădăcina pachetelor pentru component scanning corect.