Contrato e unidade de trabalho
Spring Boot 4.1.1 gere Spring Data JPA e Commons 4.1.1 e a API Jakarta Persistence 3.2. Os exemplos usam essa combinação. Os métodos CRUD herdados têm configuração transacional própria; uma consulta declarada como findByStatus não recebe automaticamente a mesma configuração por existir na interface.
No exemplo, a fronteira do serviço envolve a consulta e a transformação para DTO. Isso torna explícita a unidade de trabalho em que os dados são lidos. Uma assinatura de repositório não substitui a decisão sobre consistência, e readOnly não deve ser interpretado como um controlo universal de escrita.
Valores ligados e estrutura controlada
A query usa :status e o argumento declara Param("status"). O nome associa o valor à posição lógica sem depender da ordem dos argumentos Java. No JDBC, os placeholders e os argumentos separados cumprem o mesmo objetivo de manter valores fora da construção do texto SQL.
Isso não significa que qualquer fragmento de estrutura possa ser um valor ligado. Ordenação e campos permitidos precisam de um contrato controlado; não concatentes livremente texto recebido do cliente. No exemplo, a aplicação escolhe id como ordenação. A avaliação distingue o valor status da estrutura da consulta e das decisões de acesso.
Total, continuação e volume
Page inclui metadata sobre o total, o que pode exigir uma consulta de contagem adicional. Slice informa se há continuação sem pedir o mesmo total global. Se o consumidor apenas avança na lista, a exigência de Page pode acrescentar trabalho que não ajuda essa tarefa. A mudança deve preservar as necessidades reais do consumidor.
Pageable já contém ordenação e limite; não acrescentes um segundo parâmetro Sort ao mesmo método. Usa um desempate único quando o campo principal admite empates e limita o volume aceite. A ordem fica definida, mas páginas pedidas em instantes diferentes continuam sujeitas a inserções, remoções e mudanças de estado.
Investigar o custo observado
Um relatório pode carregar encomendas e depois pedir customer para cada uma, produzindo várias consultas. Define quais associações o relatório realmente usa e avalia um fetch plan específico, por exemplo EntityGraph. Mede as consultas e a cardinalidade resultantes; uma anotação não garante um único SQL nem torna segura qualquer combinação com paginação de coleções.
Se a consulta de dados demora 18 ms e o count 940 ms, a investigação aponta para outra necessidade: o total global. Retirar esse requisito quando o consumidor não o usa pode ser mais relevante do que aumentar ligações. O diagnóstico deve seguir os custos observados, sem prometer melhoria global só por mudar o tipo de devolução.
Versão e nova decisão
Version permite ao provider detetar que uma entidade foi alterada desde a revisão lida. O conflito otimista impede substituir silenciosamente a alteração entretanto confirmada e marca a transação para rollback. A verificação pode ocorrer no flush ou commit; chamar save ou modificar um campo não prova confirmação final.
Imagina uma reserva que viu capacidade 8 enquanto outra transação a reduziu para 2. Repetir a decisão antiga de reservar 5, copiando a versão nova, contorna a proteção. Termina a tentativa falhada, relê numa nova unidade de trabalho e reaplica as regras. A política pode rejeitar ou permitir uma nova tentativa limitada, mas precisa de considerar os dados atuais.
Base e contexto podem divergir
Uma Query de alteração em lote usa Modifying, mas não atualiza automaticamente todas as entidades já carregadas. Se Shipment continua a mostrar o status antigo depois de o lote mudar a base, isso não prova que a query falhou. Compara a contagem de linhas e o estado do contexto antes de concluir.
clearAutomatically pode limpar o contexto, mas também descartar mudanças ainda não sincronizadas. No caso ilustrativo, Shipment não tem alterações pendentes e uma Note distinta tem texto por guardar. Sincronizar a nota antes de limpar/reler preserva essa intenção. A ordem tem de ser revista com as entidades realmente alteradas; não é uma receita para fazer flush indiscriminado de estado antigo.
Ler e alterar sem inventar garantias
No exemplo, ready devolve um Slice de DTOs com id, versão e status. O limite é 20 e a ordenação usa id; não há promessa de um snapshot entre pedidos. pack encontra uma entidade gerida e só muda READY para PACKED. A alteração será sincronizada no âmbito transacional e uma versão concorrente pode impedir confirmação.
Compara com o UPDATE condicional da aula anterior: zero linhas não significa reserva aceite, nem prova SKU existente com capacidade zero. Pode haver ausência ou capacidade insuficiente. Em ambos os modelos, um resultado técnico precisa de interpretação funcional antes de anunciar sucesso, e uma tentativa repetida precisa de política explícita.
// Separate files in package com.acme.shipping, included in JPA and component scanning.
// Boot 4.1.1 managed Spring Data JPA 4.1.1 and Jakarta Persistence 3.2.
// Requires a configured JPA transaction manager, schema and database.
// Shipment.java
package com.acme.shipping;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
@Entity
@Table(name = "shipment")
public class Shipment {
@Id private Long id;
@Version private long version;
private String status;
protected Shipment() {}
public Long getId() { return id; }
public long getVersion() { return version; }
public String getStatus() { return status; }
public void markPacked() {
if (!"READY".equals(status)) throw new IllegalStateException("Shipment is not ready");
status = "PACKED";
}
}
// ShipmentRepository.java
package com.acme.shipping;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ShipmentRepository extends JpaRepository<Shipment, Long> {
@Query("select s from Shipment s where s.status = :status")
Slice<Shipment> findStatus(@Param("status") String status, Pageable page);
}
// ShipmentService.java
package com.acme.shipping;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ShipmentService {
public record ShipmentView(Long id, long version, String status) {}
private final ShipmentRepository repository;
public ShipmentService(ShipmentRepository repository) { this.repository = repository; }
@Transactional(readOnly = true)
public Slice<ShipmentView> ready(int page) {
if (page < 0) throw new IllegalArgumentException("Negative page");
return repository.findStatus("READY", PageRequest.of(page, 20, Sort.by("id")))
.map(s -> new ShipmentView(s.getId(), s.getVersion(), s.getStatus()));
}
@Transactional
public void pack(Long id) {
Shipment shipment = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Unknown shipment"));
shipment.markPacked();
}
}
ready pede até 20 resultados por página e continuação, com ordenação por id. pack altera uma entidade gerida dentro da transação; a sincronização e a verificação de versão podem ocorrer no flush/commit. Não há setter de version nem promessa de snapshot entre páginas.
Neste exemplo separado existe um único gestor JPA elegível para as anotações sem nome.
Armadilhas comuns
Concatenar valores no JPQL; pedir Page quando só se precisa de continuação; presumir snapshot por ordenar; gravar versões manualmente; repetir numa transação falhada; limpar o contexto sem preservar alterações pendentes.
Tópicos relacionados: Persistência e fronteiras de transação · Rotas HTTP e contratos de pedidos · Saúde, observabilidade e diagnóstico
O resultado de acesso a dados inclui cardinalidade, custo, versão e estado da unidade de trabalho. Reconciliar esses elementos é necessário antes de declarar sucesso ou repetir uma escrita.
Referência: Boot managed persistence dependencies · Spring Boot 4.1.1 / Spring Framework 7.0.9 / Java 25