Vai al contenuto

Step 04 — Service layer + transazioni

Obiettivo: spostare la logica fuori dal controller in un service transazionale.

Cosa costruiamo

ProjectService e TaskService con annotazioni transazionali e mapper dedicati.

ProjectService.java
@Service
@Transactional
public class ProjectService {

    private final ProjectRepository repository;
    private final ProjectMapper mapper;

    public ProjectService(ProjectRepository repository, ProjectMapper mapper) {
        this.repository = repository;
        this.mapper = mapper;
    }

    @Transactional(readOnly = true)
    public Page<ProjectResponse> list(ProjectStatus status, Pageable pageable) {
        Page<Project> page = (status == null)
                ? repository.findAll(pageable)
                : repository.findByStatus(status, pageable);
        return page.map(mapper::toResponse);
    }

    public ProjectResponse archive(UUID id) {
        Project project = loadOrThrow(id);
        project.archive();
        return mapper.toResponse(project);   // (1)
    }
}
  1. Niente repository.save(project): dentro la transazione, Hibernate fa il dirty checking e fa flush in automatico al commit.

Concetti chiave

  • @Transactional a livello classe (write) + @Transactional(readOnly = true) sulle letture.
  • Il service non sa nulla di HTTP: lavora con DTO e domain entity, non con ResponseEntity.
  • Mapper manuale (ProjectMapper) invece di MapStruct — sufficiente per progetti piccoli.

Pitfall

@Transactional su metodi private

Non funziona: il proxy Spring intercetta solo metodi public. Errore comune.

Modificare l'entità fuori dalla transazione

Hibernate non vede modifiche. Sempre dentro un metodo @Transactional.

Esercizi

  1. Aggiungi un metodo bulkArchive(List<UUID>) con transazione singola.
  2. Spiega cosa cambia con propagation = REQUIRES_NEW.
  3. Sostituisci ProjectMapper con un'implementazione MapStruct e confronta i tempi di build.

Riferimenti nel codice