Criteria trazendo resultados duplicados

21 respostas
mateusviccari

Olá, tenho uma classe NotaFiscal e uma NotaFiscalItem, os relacionamentos estão definidos da seguinte forma:
Na NotaFiscal:

@OneToMany(mappedBy = "notaFiscal",fetch= FetchType.EAGER)
    List<NotaFiscalItem> item=new ArrayList<>();

Na NotaFiscalItem:

@ManyToOne
    private NotaFiscal notaFiscal;

Quado tento fazer a pesquisa através desse método:

Session session = ConnectDB.getInstance();
        Criteria criteria = session.createCriteria(NotaFiscal.class);
        List nfs=criteria.list();
        for(int i=0;i<nfs.size();i++){
            System.out.println(((NotaFiscal)nfs.get(i)).getId());
        }

Ele me retorna o seguinte resultado:
11
11
9
9
9
O que é estranho, pois tenho somente 2 notas no banco, a 11 e a 9.
Porém, a nota 11 tem 2 itens e a 9 tem 3, por isso acredito que tem alguma coisa ha ver…
Detalhe: Se eu alterar o fetch type no NotaFiscalItem pra LAZY, funciona certinho… ele me retorna somente 2 objetos.

21 Respostas

lele_vader

Tente colocar isso aqui.
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)

mateusviccari

Funcionou!
Aproveitando o barco, como eu faço pra botar o javadoc do hibernate no netbeans, pra nao precisar vir aqui pedir sempre que tiver alguma duvida simples como esta rsrs

lele_vader

Uma vez eu achei um jar chamado hibernate-sources.jar e o inclui no classpath, daí isso fazia aparecer o fonte do hibernate.
Qualquer dúvida dá para você o javadoc aqui

http://docs.jboss.org/hibernate/orm/4.1/javadocs/

Baratao

Mas porque aconteceu isso??

mateusviccari

Gostaria de saber tambem por que ele fez essa maracutaia, essa opção DISTINCT_ROOT_ENTITY deveria vir por padrão no Criteria pela lógica… Aliás nao consigo ver logica nenhuma do jeito que ele fez anteriormente rsrs

lele_vader

Aconteceu isso comigo uma vez antigamente.
Daí sabia o porque de ter que colocar o distinct root entity.
Acredito que talvez seja por conta de cada nota fiscal possuir mais de uma notafiscal item e as notafiscal item são eager, o que faria carregar elas junto com a consulta de nota fiscal.

Talvez alguém mais experiente possa dar a resposta.

Tenta 2 testes, 1 é colocar a propriedade como lazy e o outro é mostrar o sql gerado pelo hibernate.

ffoliveira

Amigos bom dia!, estou ressuscitando este tópico depois de algum tempo.
Estou com o mesmo problema do mateusviccari , porém não achei a melhor solução no meu caso utilizar a sugestão
do “lele_vader”, antes de tudo gostaria de saber se alguém sabe qual é a origem dos dados duplicados?
Sera que meus mapeamentos estão errados (sou novo no hibernate, hehe)?

Muito Obrigado!!!

javaflex

ffoliveira:
Amigos bom dia!, estou ressuscitando este tópico depois de algum tempo.
Estou com o mesmo problema do mateusviccari , porém não achei a melhor solução no meu caso utilizar a sugestão
do “lele_vader”, antes de tudo gostaria de saber se alguém sabe qual é a origem dos dados duplicados?
Sera que meus mapeamentos estão errados (sou novo no hibernate, hehe)?

Muito Obrigado!!!


Por causa do fetch= FetchType.EAGER na lista de Itens da NF o Hibernate fez diretamente Join entre tabelas, é só pegar o SELECT gerado pelo Hibernate e rodar diretamente no banco para entender o que está acontecendo.

ffoliveira

Obrigado javaflex, vou analisar e ver se consigo decifrar.
Mas uma pergunta.
Quando uso a anotaçao manytoone de um lado tenho que usar a onetomany do outro lado do relacionamento?

javaflex

ffoliveira:
Obrigado javaflex, vou analisar e ver se consigo decifrar.
Mas uma pergunta.
Quando uso a anotaçao manytoone de um lado tenho que usar a onetomany do outro lado do relacionamento?

Você faz só se tiver necessidade, modele o básico necessário e depois incrementa com o que precisar. Sobre o caso acima na maioria das vezes o ideal é usar LAZY ou invés de EAGER para listas, e quando quiser fazer fetch join você resolve isso pelo Criteria, sem deixar o modelo pesado.

ffoliveira

Ótimo! Esses relacionamentos causam alguns problemas de inicio.
Valeu pela dica!

ffoliveira

Legal, consegui verificar o ponto onde a SQL esta replicando as informações (join com tabela desnecessária) .
como que eu faço para retirar esse join? (estou usando criteria)

javaflex

ffoliveira:
Legal, consegui verificar o ponto onde a SQL esta replicando as informações (join com tabela desnecessária) .
como que eu faço para retirar esse join? (estou usando criteria)

Mostre as entidades e mapeamentos envolvidos e sua Criteria. O SQL gerado também.

ffoliveira

Entidade Pessoa Juridica

@Entity
@PrimaryKeyJoinColumn(name = "CD_PESSOA")
@Table(name="JURIDICA")
public class PessoaJuridica extends Pessoa implements Serializable{
        @Column(name="CNPJ",insertable=true,length=20,nullable=false,unique=true)
        private String cnpj;
        @Column(name="RAZAOSOCIAL",insertable=true,length=90,nullable=false)
        private String razaosocial;
        @Column(name ="DT_FUNDACAO",insertable=true)
        @Temporal(javax.persistence.TemporalType.DATE)
        private Date datadafundacao;
        @Column(name="IE",insertable=true,length=90,nullable=false)
        private String ie;
        @ManyToMany(fetch=FetchType.EAGER)
        @JoinTable(name="SOCIOS", joinColumns={@JoinColumn(name="CD_PESSOA_FISICA")},inverseJoinColumns={@JoinColumn(name="CD_PESSOA_JURIDICA")})
        private Set<PessoaFisica> fisica = new HashSet<>();

Entidade Telefones

@Entity
@Table(name="TELEFONE")
public class Telefone implements Serializable{
        @Id  
        @GeneratedValue (strategy = GenerationType.IDENTITY)  
        @Column(name="CD_TELEFONE",insertable=true,nullable=false,updatable=false)
        private Integer codigo;
        @ManyToOne
        @JoinColumn (name="CD_PESSOA")
        private Pessoa pessoa;
        @Column(name = "NUMERO",insertable=true,length=16,nullable=false)
        private String numero;
        @Column(name="TIPO",insertable=true,nullable=false)
        private int tipo;

Método Telefone que lista todos os telefones de uma determinada pessoa…

@Override
    public List<Telefone> listar(Integer codigoPessoa) {
       try{
         this.session = HibernateUtil.getSessionFactory().getCurrentSession();
         ts = session.beginTransaction();
         Criteria c =  this.session.createCriteria(Telefone.class).createAlias("pessoa", "p");
         
         c.add(Restrictions.eq("p.codigo", codigoPessoa));
         
         return c.list();
         
       }catch (HibernateException erro){
           System.out.println(erro.getMessage());
             ts.rollback();
             return null;
        }finally{
            try{
                if(this.session.isOpen()){
                    this.session.close();
                }
            }catch(Throwable e){
             
            }
            
        }
       
        
    }

SQL
select
this_.CD_TELEFONE as CD1_7_2_,
this_.NUMERO as NUMERO7_2_,
this_.CD_PESSOA as CD4_7_2_,
this_.TIPO as TIPO7_2_,
p1_.CD_PESSOA as CD1_5_0_,
p1_.CEP as CEP5_0_,
p1_.AGENCIA as AGENCIA5_0_,
p1_.BAIRRO as BAIRRO5_0_,
p1_.BANCO as BANCO5_0_,
p1_.CIDADE as CIDADE5_0_,
p1_.COMPLEMENTO as COMPLEME7_5_0_,
p1_.CONTA as CONTA5_0_,
p1_.ENDERECO as ENDERECO5_0_,
p1_.ESTADO as ESTADO5_0_,
p1_.NUMERO as NUMERO5_0_,
p1_.TP as TP5_0_,
p1_.UF as UF5_0_,
p1_1_.CPF as CPF8_0_,
p1_1_.DH_CADASTRO as DH2_8_0_,
p1_1_.ESTADO_CIVIL as ESTADO3_8_0_,
p1_1_.NACIONALIDADE as NACIONAL4_8_0_,
p1_1_.NOME as NOME8_0_,
p1_1_.PROFISSAO as PROFISSAO8_0_,
p1_1_.RG as RG8_0_,
p1_1_.TRATAMENTO as TRATAMENTO8_0_,
p1_2_.CNPJ as CNPJ9_0_,
p1_2_.DT_FUNDACAO as DT2_9_0_,
p1_2_.IE as IE9_0_,
p1_2_.RAZAOSOCIAL as RAZAOSOC4_9_0_,
case
when p1_1_.CD_PESSOA is not null then 1
when p1_2_.CD_PESSOA is not null then 2
when p1_.CD_PESSOA is not null then 0
end as clazz_0_,
fisica4_.CD_PESSOA_FISICA as CD1_5_4_,
pessoafisi5_.CD_PESSOA as CD2_4_,
pessoafisi5_.CD_PESSOA as CD1_5_1_,
pessoafisi5_1_.CEP as CEP5_1_,
pessoafisi5_1_.AGENCIA as AGENCIA5_1_,
pessoafisi5_1_.BAIRRO as BAIRRO5_1_,
pessoafisi5_1_.BANCO as BANCO5_1_,
pessoafisi5_1_.CIDADE as CIDADE5_1_,
pessoafisi5_1_.COMPLEMENTO as COMPLEME7_5_1_,
pessoafisi5_1_.CONTA as CONTA5_1_,
pessoafisi5_1_.ENDERECO as ENDERECO5_1_,
pessoafisi5_1_.ESTADO as ESTADO5_1_,
pessoafisi5_1_.NUMERO as NUMERO5_1_,
pessoafisi5_1_.TP as TP5_1_,
pessoafisi5_1_.UF as UF5_1_,
pessoafisi5_.CPF as CPF8_1_,
pessoafisi5_.DH_CADASTRO as DH2_8_1_,
pessoafisi5_.ESTADO_CIVIL as ESTADO3_8_1_,
pessoafisi5_.NACIONALIDADE as NACIONAL4_8_1_,
pessoafisi5_.NOME as NOME8_1_,
pessoafisi5_.PROFISSAO as PROFISSAO8_1_,
pessoafisi5_.RG as RG8_1_,
pessoafisi5_.TRATAMENTO as TRATAMENTO8_1_
from
TELEFONE this_
inner join
PESSOA p1_
on this_.CD_PESSOA=p1_.CD_PESSOA
left outer join
FISICA p1_1_
on p1_.CD_PESSOA=p1_1_.CD_PESSOA
left outer join
JURIDICA p1_2_
on p1_.CD_PESSOA=p1_2_.CD_PESSOA
left outer join
SOCIOS fisica4_
on p1_.CD_PESSOA=fisica4_.CD_PESSOA_FISICA
left outer join
FISICA pessoafisi5_
on fisica4_.CD_PESSOA_JURIDICA=pessoafisi5_.CD_PESSOA
left outer join
PESSOA pessoafisi5_1_
on pessoafisi5_.CD_PESSOA=pessoafisi5_1_.CD_PESSOA
where
p1_.CD_PESSOA=1

javaflex

Você viu como necessário FetchType EAGER? Senão muda de EAGER para LAZY principalmente onde for lista.

ffoliveira

javaflex, eu analisei e preciso do FetchType EAGER, pois faço uso de mais objetos do mapeamento

javaflex

Ideal é deixar as listas no mapeamento como Lazy e em consultas você usa setFetchMode para setar o tipo de fetch que quiser dinamicamente de acordo com a necessidade para montar o SQL único. Neste seu caso então será igual ao do outro colega, em que terá que usar o DISTINCT_ROOT_ENTITY para o Hibernate popular a lista e não deixar duplicar a objeto raiz.

ffoliveira

javaflex, muito obrigado pela força!
Vou rever os mapeamentos, e deixar da forma como vc disse.

ffoliveira

Novamente no tópico…
Ajustei as classes e os mapeamentos para usar o fetch = FetchType.LAZY
porem o hibernate esta retornando uma exception,

failed to lazily initialize a collection of role

Do que se trata esta erro, e como faço para resolve-lo?

javaflex

ffoliveira:
Novamente no tópico…
Ajustei as classes e os mapeamentos para usar o fetch = FetchType.LAZY
porem o hibernate esta retornando uma exception,

failed to lazily initialize a collection of role

Do que se trata esta erro, e como faço para resolve-lo?

Mas você não usou o setFetchMode desejado + DISTINCT_ROOT_ENTITY na sua consulta Criteria?

Exemplo:

c.setFetchMode("pessoa", FetchMode.JOIN);
        c.setFetchMode("pessoa.fisica", FetchMode.JOIN);
        c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

Assim você controla quando quer fazer o join para gerar um único SQL sem precisar cravar isso no mapeamento (o que faria degradar perfomance quando precisar acessar só atributos simples).

Mas mesmo assim terá outros casos em que precisará acessar o atributo sob demanda e vai dar mesmo esse erro que mostrou, para não ocorrer uma das soluções é deixar a conexão (session) aberta durante todo o request, que é a solução que uso. Aqui você fica sabendo mais sobre isso: http://uaihebert.com/?p=1367&page=5

ffoliveira

Opa valeu javaflex, seguinte consegui resolver a parada do fetch.LAZY, isto estava me tirando o sono.
A solução do tópico me levou a pensar no que fazer no meu caso, (que é uma aplicação desktop).

try{
        this.setSession(HibernateUtil.getSessionFactory().getCurrentSession());
        ts = getSession().beginTransaction();
        String hql = "from PessoaJuridica r where r.cnpj = :CNPJ";
        Query consulta = this.getSession().createQuery(hql);
	consulta.setString("CNPJ",pessoa.getCnpj());
        pessoa.setTelefones(new ArrayList<Telefone>());
        pessoa.setEmail(new ArrayList<Email>());
        
        pessoa =(PessoaJuridica) consulta.uniqueResult();
         //Inicializo as coleçoes(E partindo deste ponto posso até fecha-la)
        Hibernate.initialize(pessoa.getTelefones());
        Hibernate.initialize(pessoa.getEmail());
	return pessoa;
    }catch(HibernateException e){
        JOptionPane.showMessageDialog(null, e.getMessage());
        return null;
    }finally{
        try{
         if(    
          this.getSession().isOpen()){
             this.session.close();
         }   
        }catch(Throwable e){
            
        }
Criado 21 de maio de 2012
Ultima resposta 13 de fev. de 2013
Respostas 21
Participantes 5