leandromoraesrj / sincad-backend

PROJETO BASE DE EXEMPLO USANDO AS MELHORES PRATICAS DE DESENVOLVIMENTO
0 stars 0 forks source link

Testar uso de DTO (Orika ou Mapstruct) #23

Open leandromoraesrj opened 2 years ago

leandromoraesrj commented 2 years ago
leandromoraesrj commented 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