1
resposta

Resultado como LAZY, mas deveria ser EAGER

Olá.

Estou percebendo um comportamento que, com base no que tenho estudado até agora, não deveria estar ocorrendo. Trata-se do N+1.

Algumas entidades se relacionam para compor um esquema muitos-pra-muitos, porém, não estou usando o @ManyToMany, pois a tabela associativa precisa ter campos adicionais. Por conta disso, estou criando a entidade referente a associação e fazendo dois relacionamentos @ManyToOne para as demais entidades.

Contexto:

Trata-se de um sistema de gerenciamento de cursos por vídeo (isso, tipo Alura - trata-se de um trabalho acadêmico :-). A ideia seria: um curso é organizado por unidades e os vídeos são associados às unidades. Os alunos são representados pela entidade Usuario. Para registrar os vídeos assistidos e a data e hora da interação criei a entidade VideoAssistido.

Segue abaixo os trechos das entidades que são relevantes para essa questão:

@Entity
@Table(name = “videos_assistidos”)
public class VideoAssistido {

    @EmbeddedId
    private VideoAssistidoId pk;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "assistido_em", nullable = false)
    private Calendar assistidoEm;

    // getters e setters
}

@Embeddable
public class VideoAssistidoId implements Serializable {

    @ManyToOne
    @JoinColumn(name = "usuario_id")
    private Usuario usuario;

    // pra não acontecer N+1 precisa-se anotar aki como LAZY e na query, fazer "join fetch". 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "video_id")
    private Video video;

    // construtor e getters, hashcode e equals
}

public class VideoAssistidoDAO {

    private EntityManager em;

    // construtor e outras coisas...

    public List<VideoAssistido> busca(Unidade unidade, Usuario usuairo) {
        try {
            return em.createQuery(
                    "select va"
                    + " from VideoAssistido va"
                    + " join fetch va.pk.video v"
                    + " where v.unidade = :unidade"
                    + " and va.pk.usuario = :usuario",
                    VideoAssistido.class)
                    .setParameter("unidade", unidade)
                    .setParameter("usuario", usuairo)
                    .getResultList();
        } catch (NoResultException e) {
            return null;
        }
    }
    // mais umas coisas
}

Observe o “join fetch” na jpql do método busca acima.

@Entity
@Table(name = “videos”)
public class Video {

    @Id
    // atributos...

    // bidirecional por conta de "VideoAssistido"
    @ManyToOne
    @JoinColumn(name = "unidade_id", nullable = false)
    private Unidade unidade;

    // getters e setters
}

@Entity
@Table(name = “unidades”)
public class Unidade {

    @Id
    // atributos ...

    @OneToMany(mappedBy = "unidade", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    private Set<Video> videos = new HashSet<>();

    // getters e setters…
}

A estranheza:

Só consigo fazer com que seja gerado apenas uma consulta com o relacionamento principal (entre VideoAssistido e Video) se configurar o relacionamento @ManyToOne do atributo video (da entidade VideoAssistidoId) como LAZY e defina o “join fetch” na jpql (método "busca" de VideoAssistidoDAO).

Se considerar a condição padrão do @ManyToOne (default EAGER), ou mesmo se explicitar o fetch como EAGER retorna N+1. Se além disso adicionar o “join fetch”, ainda assim N+1. Percebi que ele gera uma consulta para o relacionamento principal (envolvendo videos_assistidos e videos) e mais uma consulta para cada registro retornado, porém nesse caso envolvendo apenas as tabelas videos e unidades.

Obs: O relacionamento bidirecional entre Unidade e Video foi criado somente por conta dessa consulta, para que fosse possível associar os vídeos assistidos de uma unidade.

Como disse, consegui um jeito de funcionar como deveria, mas ta soando como gambiarra.

Alguém teria uma explicação para esse comportamento?

1 resposta

Eu acho que entendi, apenas acho. Meu chute é que como os @ManyToOne também são chaves primarias, ele não está indo buscar as informações de forma "eager".