Hibernate: Diferenca no tratamento de FetchType.EAGER e FetchType.LAZY?

Pessoal,

Estou com um problema esquisito aqui. Tenho dois relacionamentos 1-N numa classe. Se utilizo a estrategia de fetch FetchType.LAZY, a coisa funciona legal, se utilizo FetchType.EAGER parece que o Hibernate “se confunde”. Provavelmente eu que confundi alguma coisa - mas nao consigo entender esse comportamento.

Em poucas palavras:

  • Trazendo tudo com Query (FetchType.LAZY): OK
  • Trazendo tudo com Query (FetchType.EAGER): OK
  • Trazendo um objeto com Session.get (FetchType.LAZY): OK
  • Trazendo um objeto com Session.get (FetchType.EAGER): Problema

Abaixo segue codigo das classes, tabelas geradas e a saida do teste:

Um cliente pode ter N carros e N imoveis:

[code]package teste;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratorType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = “cliente”)
public class Cliente {

private int id;
private String nome;
private List<Carro> carroList = new ArrayList<Carro>();
private List<Imovel> imovelList = new ArrayList<Imovel>();

@Id(generate = GeneratorType.AUTO)
public int getId() { return this.id; }
public void setId(int id) { this.id = id; }

@Column(name = "nome", nullable = false)
public String getNome() { return this.nome; }
public void setNome(String nome) { this.nome = nome; }

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "id_cliente")
public List<Carro> getCarroList() { return this.carroList; }
public void setCarroList(List<Carro> carroList) { this.carroList = carroList; }

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "id_cliente")
public List<Imovel> getImovelList() { return this.imovelList; }
public void setImovelList(List<Imovel> imovelList) { this.imovelList = imovelList; }

public boolean equals(Object o) {
    return ((o instanceof Cliente) && (((Cliente)o).getId() == getId()));
}
public int hashCode() {
    return new Integer(this.getId()).hashCode();
}

}[/code]

Carro:

[code]package teste;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratorType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = “carro”)
public class Carro {

private int id;
private String modelo;

@Id(generate = GeneratorType.AUTO)
public int getId() { return this.id; }
public void setId(int id) { this.id = id; }

@Column(name = "modelo", nullable = false)
public String getModelo() { return this.modelo; }
public void setModelo(String modelo) { this.modelo = modelo; }

public boolean equals(Object o) {
    return ((o instanceof Carro) && (((Carro)o).getId() == getId()));
}
public int hashCode() {
    return new Integer(this.getId()).hashCode();
}

}[/code]

Imovel:

[code]package teste;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratorType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = “imovel”)
public class Imovel {

private int id;
private String endereco;

@Id(generate = GeneratorType.AUTO)
public int getId() { return this.id; }
public void setId(int id) { this.id = id; }

@Column(name = "endereco", nullable = false)
public String getEndereco() { return this.endereco; }
public void setEndereco(String endereco) { this.endereco = endereco; }

public boolean equals(Object o) {
    return ((o instanceof Imovel) && (((Imovel)o).getId() == getId()));
}
public int hashCode() {
    return new Integer(this.getId()).hashCode();
}

}[/code]

Programinha pra testar esse cenario:

[code]package teste;

import java.util.Arrays;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;

public class Programa {

private static final SessionFactory sessionFactory = 
    new AnnotationConfiguration().configure("/resource/hibernate.cfg.xml")
    .buildSessionFactory();

private void insert() throws Exception {

    Cliente cliente1 = new Cliente(); cliente1.setNome("Cliente 1");
    Cliente cliente2 = new Cliente(); cliente2.setNome("Cliente 2");

    Carro c1 = new Carro(); c1.setModelo("Carro 1");
    Carro c2 = new Carro(); c2.setModelo("Carro 2");
    Carro c3 = new Carro(); c3.setModelo("Carro 3");
    Carro c4 = new Carro(); c4.setModelo("Carro 4");

    Imovel i1 = new Imovel(); i1.setEndereco("Imovel 1");
    Imovel i2 = new Imovel(); i2.setEndereco("Imovel 2");
    Imovel i3 = new Imovel(); i3.setEndereco("Imovel 3");
    Imovel i4 = new Imovel(); i4.setEndereco("Imovel 4");

    cliente1.setCarroList(Arrays.asList(new Carro[]{ c1, c2 }));
    cliente1.setImovelList(Arrays.asList(new Imovel[]{ i1, i2 }));
    cliente2.setCarroList(Arrays.asList(new Carro[]{ c3, c4 }));
    cliente2.setImovelList(Arrays.asList(new Imovel[]{ i3, i4 }));

    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    session.saveOrUpdate(cliente1);
    session.saveOrUpdate(cliente2);
    tx.commit();
    session.close();

}

private void search() throws Exception {

    Session session = null;
    
    session = sessionFactory.openSession();
    Query query = session.createQuery("from Cliente c order by c.nome");
    List list = query.list();
    //session.close();

    for (Object object : list) {
        Cliente cliente = (Cliente)object;
        System.err.println("-------------");
        System.err.printf("%s (Carros: %d, Imoveis: %d)\n", cliente.getNome(), 
                cliente.getCarroList().size(), cliente.getImovelList().size());
        for (Carro c : cliente.getCarroList()) {
            System.err.printf("  Carro %d - %s\n", c.getId(), c.getModelo());
        }
        for (Imovel i : cliente.getImovelList()) {
            System.err.printf("  Imovel %d - %s\n", i.getId(), i.getEndereco());
        }
        System.err.println("-------------");
    }

    session.close();

    session = sessionFactory.openSession();
    Cliente cliente = (Cliente)session.get(Cliente.class, new Integer(1));
    //session.close();

    System.err.println("-------------");
    System.err.printf("%s (Carros: %d, Imoveis: %d)\n", cliente.getNome(), 
            cliente.getCarroList().size(), cliente.getImovelList().size());
    for (Carro c : cliente.getCarroList()) {
        System.err.printf("  Carro %d - %s\n", c.getId(), c.getModelo());
    }
    for (Imovel i : cliente.getImovelList()) {
        System.err.printf("  Imovel %d - %s\n", i.getId(), i.getEndereco());
    }
    System.err.println("-------------");

    session.close();

}

public static void main(String[] args) throws Exception {

    Programa programa = new Programa();
    programa.insert();
    programa.search();

}

}[/code]

Tabelas e dados apos rodar o teste (tudo OK):

[code]mysql> select * from cliente; select * from carro; select * from imovel;
±—±----------+
| id | nome |
±—±----------+
| 1 | Cliente 1 |
| 2 | Cliente 2 |
±—±----------+
2 rows in set (0.00 sec)

±—±--------±-----------+
| id | modelo | id_cliente |
±—±--------±-----------+
| 1 | Carro 1 | 1 |
| 2 | Carro 2 | 1 |
| 3 | Carro 3 | 2 |
| 4 | Carro 4 | 2 |
±—±--------±-----------+
4 rows in set (0.00 sec)

±—±---------±-----------+
| id | endereco | id_cliente |
±—±---------±-----------+
| 1 | Imovel 1 | 1 |
| 2 | Imovel 2 | 1 |
| 3 | Imovel 3 | 2 |
| 4 | Imovel 4 | 2 |
±—±---------±-----------+
4 rows in set (0.00 sec)[/code]

Saida do programa utilizando a estrategia de fetch FetchType.LAZY (OK):

[code]-------------
Cliente 1 (Carros: 2, Imoveis: 2)
Carro 1 - Carro 1
Carro 2 - Carro 2
Imovel 1 - Imovel 1
Imovel 2 - Imovel 2


Cliente 2 (Carros: 2, Imoveis: 2)
Carro 3 - Carro 3
Carro 4 - Carro 4
Imovel 3 - Imovel 3
Imovel 4 - Imovel 4


Cliente 1 (Carros: 2, Imoveis: 2)
Carro 1 - Carro 1
Carro 2 - Carro 2
Imovel 1 - Imovel 1
Imovel 2 - Imovel 2
-------------[/code]

Saida do programa (apagando as tabelas), mas mudando o fetch para FetchType.EAGER (problema):

[code]-------------
Cliente 1 (Carros: 2, Imoveis: 2)
Carro 1 - Carro 1
Carro 2 - Carro 2
Imovel 1 - Imovel 1
Imovel 2 - Imovel 2


Cliente 2 (Carros: 2, Imoveis: 2)
Carro 3 - Carro 3
Carro 4 - Carro 4
Imovel 3 - Imovel 3
Imovel 4 - Imovel 4


Cliente 1 (Carros: 4, Imoveis: 4)
Carro 1 - Carro 1
Carro 1 - Carro 1
Carro 2 - Carro 2
Carro 2 - Carro 2
Imovel 1 - Imovel 1
Imovel 2 - Imovel 2
Imovel 1 - Imovel 1
Imovel 2 - Imovel 2
-------------[/code]

Se leu ate aqui, obrigado pela paciencia. :slight_smile:

Onde esta o erro? Algum problema conceitual? O manual do Hibernate Annotations diz que eh incomum e nao recomenda mapear 1-N colocando a FK na mesma tabela do lado N, mas nao consegui entender o porque - parece algo tao normal :).

Marcio Kuchma