Open leandromoraesrj opened 2 years ago
Configuração de Fetch: Eager: a inicialização de dados é executada. Se carregarmos os dados de uma entity, ele também carregará todos os dados de entitys associadas e os armazenará em memória. Lazy: a inicialização de dados é adiada até que seja feita uma chamada a ele.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "SQ_LOCALIDADE")
private Localidade localidade;
Quando mapeado mais de uma entity com Eager, uma exception é lançada:
Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags
Para resolver é possível usar as notações abaixo:
remover o fetchType atributo e incluir
@LazyCollection(LazyCollectionOption.FALSE)
ou apenas incluir
@Fetch(value = FetchMode.SUBSELECT)
Quando spring.jpa.open-in-view=true: Usar @JsonIgnore em todos os mapeamentos para não ter problema de referência circular aumentando a produtividade do desenvolvedor. Por outro lado, causa problemas de desempenho do banco de dados;
Quando spring.jpa.open-in-view=false: Uma exceção LazyInitializationException será lançada ao popular um DTO (na camada de resource), pois deve ser populada/inicializada, manualmente (na camada de service), todas as associações da entity e a maneira mais rudimentar (e geralmente errada) é :
@Transactional(readOnly = true)
public List<UnidadeEmpresarial> listarTodos() {
return repo.findAll().stream().map(u -> {
u.getEstabelecimentos().size();
return u;
}).collect(Collectors.toList());
}
ou
@Transactional(readOnly = true)
public List<UnidadeEmpresarial> listarTodos() {
return repo.findAll().stream().map(u -> {
Hibernate.initialize(u.getEstabelecimentos());
return u;
}).collect(Collectors.toList());
}
_Ambas as abordagens não são recomendadas, pois incorrem (pelo menos) em uma consulta extra, além da original,_
Ao colocar o @Transactional(readOnly = true) a lista foi carregada automaticamente. É uma boa prática definir a @Transactional(readOnly = true) no nível de classe e substituí-la apenas por métodos de leitura e gravação. Dessa forma, podemos garantir que os métodos somente leitura sejam executados por padrão.
@Service
@Transactional(readOnly = true)
public class ForumServiceImpl
implements ForumService {
@PersistenceContext
private EntityManager entityManager;
@Override
@Transactional
public Post newPost(String title, String... tags) {
Post post = new Post();
}
}
@Transactional(readOnly = true): Suponha que tenhamos que estender nosso serviço de usuário simples para chamar outro serviço remoto depois de buscar o usuário no banco de dados:
@Transactional(readOnly = true)
public Optional<User> findOne(String username) {
Removendo a anotação @Transactional, a sessão não será mantida conectada enquanto aguardamos o serviço remoto. Mais detalhes em: https://www.baeldung.com/spring-open-session-in-view
Referência:https://www.devmedia.com.br/conheca-o-spring-transactional-annotations/32472 Referência:https://vladmihalcea.com/read-write-read-only-transaction-routing-spring/ Referência:https://javatute.com/spring/transactional-readonly-true-example-in-spring-boot/
Foi necessário, também, colocar @JsonManagedReference e @JsonBackReference nos mapeamentos para não ter problema de referência circular:
@JsonManagedReference
@OneToMany(mappedBy = "unidadeEmpresarial")
private List<Estabelecimento> estabelecimentos = new ArrayList<>();
@JsonBackReference
@ManyToOne(optional = false)
@JoinColumn(name = "sq_unidade_empresarial")
private UnidadeEmpresarial unidadeEmpresarial;
Mais detalhes sobre o spring.jpa.open-in-view=false na issue de Criar Profile de Test: https://github.com/leandromoraesrj/sincad-backend/issues/7
@EntityGraph Ao definir métodos de consulta no Spring Data JPA, podemos anotar um método de consulta com @EntityGraph para buscar ansiosamente alguma parte da entidade Se precisarmos retornar várias projeções da mesma consulta, devemos definir várias consultas com diferentes configurações de gráfico de entidade:
Mais detalhes : https://www.baeldung.com/spring-data-jpa-named-entity-graphs
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = "permissions")
Optional<User> findDetailedByUsername(String username);
Optional<User> findSummaryByUsername(String username);
}
Melhor maneira e converter para DTO na camada de service:
default R findAllById(ID id) throws EntityNotFoundException {
return getReadOnlyRepository().findById(id).map(getMapper()::toDTO)
.orElseThrow(() -> new EntityNotFoundException());
}
Os campos do DTO podem ter quantidade menor do que da entity e serão serializados, mas o sql executado ainda terá todos os campos e joins, conforme mapeado na entity.
Na camada Service deve ser retornada a "ExceptionRuntime" e na camada Resource deve ser convertida para a ResponseException com o status de retrorno desejado:
@GetMapping("/{id}")
public ResponseEntity<?> obterPorId(Long id) {
try {
return ResponseEntity.ok(service.findAllById(id));
} catch (EntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, id.toString());
}
}
Referência:https://www.baeldung.com/exception-handling-for-rest-with-spring
Mapstruc: Referência:https://medium.com/mobicareofficial/mapstruct-simplificando-mapeamento-de-dtos-em-java-c29135835c68
Referência:https://cezarcruz.com.br/post/incluindo-map-struct-em-sua-aplicacao-java/
Referência:https://www.baeldung.com/java-performance-mapping-frameworks