Saltar al contenido

Cómo convertir una relación JPA OneToMany a DTO

Verificamos de forma exhaustivamente cada uno de los tutoriales en nuestro espacio con el objetivo de mostrarte siempre la información con la mayor veracidad y certera.

Solución:

Ahora, si realmente quieres arreglar las cosas por tu cuenta:

1) En la clase de mapeador, puede definir mapeadores de implementos que resuelvan este problema haciéndolos unidireccionales. Con métodos como
MapPlanWithActivities(), MapPlan(), MapActivitiesWithPlan() y MapActivities(). de esta forma podría saber qué datos necesita y según la función que utilice sabrá cuándo detener la recursividad.

2) La otra solución (mucho) más compleja sería resolver el problema por lógica y detectar el bucle. Por ejemplo, puede definir una anotación para ese caso como lo hace Jackson Library. para eso tendrás que usar alguna reflexión de Java. Vea la Reflexión de Java aquí

3) la forma más fácil sería usar Dozer como dije en mi comentario: Dozer

Relaciones de mesa

Supongamos que tenemos lo siguiente post y post_comment tablas, que forman una relación de uno a varios a través de la post_id Columna de clave externa en el post_comment mesa.

Las tablas post y post_comment utilizadas para la proyección JPA DTO

Obtener una proyección DTO de uno a varios con JPA e Hibernate

Teniendo en cuenta que tenemos un caso de uso que solo requiere obtener el id y title columnas de la post mesa, así como la id y review columnas de la post_comment tablas, podríamos usar la siguiente consulta JPQL para obtener la proyección requerida:

select p.id as p_id, 
       p.title as p_title,
       pc.id as pc_id, 
       pc.review as pc_review
from PostComment pc
join pc.post p
order by pc.id

Al ejecutar la consulta de proyección anterior, obtenemos los siguientes resultados:

| p.id | p.title                           | pc.id | pc.review                             |
|------|-----------------------------------|-------|---------------------------------------|
| 1    | High-Performance Java Persistence | 1     | Best book on JPA and Hibernate!       |
| 1    | High-Performance Java Persistence | 2     | A must-read for every Java developer! |
| 2    | Hypersistence Optimizer           | 3     | It's like pair programming with Vlad! |

Sin embargo, no queremos utilizar una tabla ResultSet o el predeterminado ListProyección de consultas JPA o Hibernate. Queremos transformar el conjunto de resultados de la consulta mencionado anteriormente en un List de PostDTO objetos, cada uno de estos objetos tiene un comments colección que contiene todos los asociados PostCommentDTO objetos:

PostDTO y PostCommentDTO utilizados para la proyección DTO

Podemos usar un Hibernate ResultTransformer, como se ilustra en el siguiente ejemplo:

List postDTOs = entityManager.createQuery("""
    select p.id as p_id, 
           p.title as p_title,
           pc.id as pc_id, 
           pc.review as pc_review
    from PostComment pc
    join pc.post p
    order by pc.id
    """)
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(new PostDTOResultTransformer())
.getResultList();

assertEquals(2, postDTOs.size());
assertEquals(2, postDTOs.get(0).getComments().size());
assertEquals(1, postDTOs.get(1).getComments().size());

El PostDTOResultTransformer va a definir el mapeo entre el Object[] proyección y la PostDTO objeto que contiene el PostCommentDTO objetos DTO secundarios:

public class PostDTOResultTransformer 
        implements ResultTransformer 

    private Map postDTOMap = new LinkedHashMap<>();

    @Override
    public Object transformTuple(
            Object[] tuple, 
            String[] aliases) 
            
        Map aliasToIndexMap = aliasToIndexMap(aliases);
        
        Long postId = longValue(tuple[aliasToIndexMap.get(PostDTO.ID_ALIAS)]);

        PostDTO postDTO = postDTOMap.computeIfAbsent(
            postId, 
            id -> new PostDTO(tuple, aliasToIndexMap)
        );
        
        postDTO.getComments().add(
            new PostCommentDTO(tuple, aliasToIndexMap)
        );

        return postDTO;
    

    @Override
    public List transformList(List collection) 
        return new ArrayList<>(postDTOMap.values());
    

El aliasToIndexMap es solo una pequeña utilidad que nos permite construir un Map estructura que asocia los alias de columna y el índice donde se encuentra el valor de la columna en el Object[]tuple array:

public  Map aliasToIndexMap(
        String[] aliases) 
    
    Map aliasToIndexMap = new LinkedHashMap<>();
    
    for (int i = 0; i < aliases.length; i++) 
        aliasToIndexMap.put(aliases[i], i);
    
    
    return aliasToIndexMap;

El postDTOMap es donde vamos a almacenar todo PostDTO entidades que, al final, serán devueltas por la ejecución de la consulta. La razón por la que estamos usando el postDTOMap es que las filas principales se duplican en el conjunto de resultados de la consulta SQL para cada registro secundario.

El computeIfAbsent El método nos permite crear un PostDTO Objeto solo si no existe PostDTO referencia ya almacenada en el postDTOMap.

El PostDTO la clase tiene un constructor que puede establecer el id y title propiedades usando los alias de columna dedicados:

public class PostDTO 

    public static final String ID_ALIAS = "p_id";
    
    public static final String TITLE_ALIAS = "p_title";

    private Long id;

    private String title;

    private List comments = new ArrayList<>();

    public PostDTO(
            Object[] tuples, 
            Map aliasToIndexMap) 
            
        this.id = longValue(tuples[aliasToIndexMap.get(ID_ALIAS)]);
        this.title = stringValue(tuples[aliasToIndexMap.get(TITLE_ALIAS)]);
    

    //Getters and setters omitted for brevity

El PostCommentDTO está construido de manera similar:

public class PostCommentDTO 

    public static final String ID_ALIAS = "pc_id";
    
    public static final String REVIEW_ALIAS = "pc_review";

    private Long id;

    private String review;

    public PostCommentDTO(
            Object[] tuples, 
            Map aliasToIndexMap) 
        this.id = longValue(tuples[aliasToIndexMap.get(ID_ALIAS)]);
        this.review = stringValue(tuples[aliasToIndexMap.get(REVIEW_ALIAS)]);
    

    //Getters and setters omitted for brevity

¡Eso es!

Utilizando el PostDTOResultTransformer, el conjunto de resultados de SQL se puede transformar en una proyección DTO jerárquica, con la que es muy conveniente trabajar, especialmente si necesita ser calculado como una respuesta JSON:

postDTOs = ArrayList, size = 2
  0 = PostDTO 
    id = 1L
    title = "High-Performance Java Persistence"
    comments = ArrayList, size = 2
      0 = PostCommentDTO 
        id = 1L
        review = "Best book on JPA and Hibernate!"
      1 = PostCommentDTO 
        id = 2L
        review = "A must read for every Java developer!"
  1 = PostDTO 
    id = 2L
    title = "Hypersistence Optimizer"
    comments = ArrayList, size = 1
      0 = PostCommentDTO 
       id = 3L
       review = "It's like pair programming with Vlad!"

Más adelante puedes encontrar las referencias de otros programadores, tú asimismo tienes el poder dejar el tuyo si te gusta.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *