Cannot fetch multiple bags - Which type is the best for each case


#1

Hello.

Maybe, this is a silly question, but I’m really in doubt.

I’m working in a company where the older developer which starts the project, use the enable_lazy_load_no_trans property.
I always notice some performance issues in the whole software, but now I tried to process a lot of data and I have a lot of issues.

So after read: this, I understand the issues.

So the problem is, I removed this property and now I’m changing the @NamedQueries and creating DTO when it’s necessary.

But I’m getting the Fetch Multiples Bags exception, I read this and a lot of others topics in StackOverflow and Vlad’s blog, and I know that if I change one of them to Set it’ll work.

My question is: When should I use Set or List or what else? Is there any “pattern” or best performance choice?

Is there any recommendation for each of them?

For example:

@NamedQuery(name = "Usuario.fetchByLogin", query = "SELECT u FROM Usuario u WHERE UPPER(u.apelido) = UPPER(:apelido) AND u.senha = :senha AND u.ativo = true")

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "permissoes_usuarios_grupos", joinColumns = {
            @JoinColumn(name = "usuario_id", nullable = false, insertable = false, updatable = false)
    }, inverseJoinColumns = {
            @JoinColumn(name = "grupo_id", nullable = false, insertable = false, updatable = false)
    })
    private List<PermissaoGrupo> grupos;

    @OneToMany(mappedBy = "usuario", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<UsuarioPessoa> pessoas;

Thanks in advance and I’m sorry for the long text.


#2

Changing List to Set to deal with the multiple bag issue is going to lead to Cartesian Products, which will affect performance.

As a rule if thumb:

  • use List for bidirectional OneToMany
  • use Set for ManyToMany

And always log the queries executed by Hibernate.


#3

Thanks for your fast reply.

But If am I have two bidirectional OneToMany as List, will I still getting the same error?

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "permissoes_usuarios_grupos", joinColumns = {
            @JoinColumn(name = "usuario_id", nullable = false, insertable = false, updatable = false)
    }, inverseJoinColumns = {
            @JoinColumn(name = "grupo_id", nullable = false, insertable = false, updatable = false)
    })
    private List<PermissaoGrupo> grupos;

    @OneToMany(mappedBy = "usuario", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<TelaPermissao> permissoes;

    @OneToMany(mappedBy = "usuario", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<UsuarioPessoa> pessoas;

Thanks in advance


#4

You have this error because you try to JOIN FETCH them at once. You have to JOIN FETCH at most one collection, and the rest should be initialized using secondary queries. You can do that with the Hibernate.initialize(collection) utility.


#5

When I call Hibernate.initialize(collection) I’m getting this error:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.lorensid.model.Usuario.grupos, could not initialize proxy - no Session

I understand that my Session is closed, but this means that I always should to open my Session, before to use initialize?

Another doubt, doing this:

Session session = getEntityManager().unwrap(Session.class).getSessionFactory().openSession();
u2 = (Usuario) session.get(Usuario.class, u2.getId());
Hibernate.initialize(u2.getGrupos());

All my Collections are filled, for example: I just use the JOIN FETCH in pessoas and I initialized the grupos, but permissoes were load too. As you can see in the prints, is it the correct behavior?

Before and after:

Thanks in advance


#6

My bad, isn’t when I call iniatialize is when I use:

u2 = (Usuario) session.get(Usuario.class, u2.getId());

After that all the Collections were loaded.