Hibernate - Trazer pai e filhos em uma query

6 respostas
rbonjour

Pessoal, estou apanhando aqui:

Tenho, digamos, 2 Beans:

Pai e Filho. O Pai possui uma lista de Filhos . Sei que posso buscar o Pai e depois fazer um pai.getFilhos() e os filhos serão carregados, porém dois SQLs são executados, e eu preciso executar tudo em um SQL só por questões de performance.

Então eu tenho, digamos, um HQL:

"from Pai pai LEFT JOIN pai.filhos "

A Query é executada perfeitamente, porém o Hibernate não consegue criar um Pai e alimentar a lista de filhos. Já tentei de tudo que é coisa mas não consigo, não rola. Sempre diz que não tem seter para filhos, mas tem. Sempre me enrolo com estas queries, resultTransform, etc.

Alguma luz ?

6 Respostas

Lavieri

session.createCriteria(Pai.class) .createCriteria("Filho") .lis();

Obs.: não testei

C

rbonjour,

Segue abaixo um exemplo que deve funcionar como você precisa.

@Entity
@Table(name = "pai")
@javax.persistence.SequenceGenerator(name = "seqpai",
                                     sequenceName = "seqpai",
                                     allocationSize = 1, initialValue = 1)
public class Pai {
    @Id
    @Column(name = "idpai", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqpai")
    private Long idPai;

    @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "pai")
    @Cascade( value = { org.hibernate.annotations.CascadeType.DELETE_ORPHAN } )  
    private Set filhos;

    public Pai() {
        this.filhos = new HashSet();
    }

    public Long getIdPai() {
        return this.idPai;
    }

    public void setIdPai(Long idPai) {
        this.idPai = idPai;
    }

    public Set getFilhos() {
        return this.filhos;
    }

    public void setFilhos(Set filhos) {
        this.filhos = filhos;
    }
}

@Entity
@Table(name = "filho")
@javax.persistence.SequenceGenerator(name = "seqfilho",
                                     sequenceName = "seqfilho",
                                     allocationSize = 1, initialValue = 1)
public class Filho {
    @Id
    @Column(name = "idfilho", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqfilho")
    private Long idFilho;

    @ManyToOne(fetch = FetchType.EAGER )
    @JoinColumn(name = "idpai", referencedColumnName = "idpai", nullable = false)
    private Pai pai;

    public Long getIdFilho() {
        return this.idFilho;
    }

    public void setIdFilho(Long idFilho) {
        this.idFilho = idFilho;
    }

    public Pai getPai() {
        return this.pai;
    }

    public void setPai(Pai pai) {
        this.pai = pai;
    }
}

@Stateless
public class PaiEAO {

    @PersistenceContext(unitName = "DATABASESOURCE", type = PersistenceContextType.TRANSACTION)
    protected EntityManager em;

    public Pai carregarPai(Long idPai) throws Exception {
        Pai pai = null;
        try {
            StringBuffer sb = new StringBuffer();
            sb.append(" FROM Pai p ");
            sb.append(" LEFT JOIN FETCH p.filhos f ");
            sb.append(" WHERE p.idPai = :idPai  ");

            Query q = em.createQuery(sb.toString());
            q.setParameter("idPai", idPai);
            pai = (Pai)q.getSingleResult();
        } catch (Exception ex) {
            throw new Exception(ex.getMessage(), ex);
        }
        return pai;
    }

}

@braços

rbonjour

Para ilustar melhor o que eu estou fazendo:

O exemplo abaixo estou fazendo com Criteria, mas com HQL tb não funcionou.

Session session = getSessionFactory().getCurrentSession();
			Criteria criteria = session.createCriteria(GamJogoPossibilidade.class, "gamJogoPossibilidade");
			
			ProjectionList projectionList = Projections.projectionList().create();
			projectionList.add(Projections.property("jogpossiCod"), "jogpossiCod");
			projectionList.add(Projections.property("jogpossiCotacao"), "jogpossiCotacao");
			projectionList.add(Projections.property("jogpossiResultado"), "jogpossiResultado");
			projectionList.add(Projections.property("jogpossiProcessado"), "jogpossiProcessado");
			projectionList.add(Projections.property("gamPossibilidade.possiDescricao"), "gamPossibilidade.possiDescricao");
			projectionList.add(Projections.property("gamClaPossibilidade.claposNome"), "gamPossibilidade.gamClaPossibilidade.claposNome");
			projectionList.add(Projections.property("gamClaPossibilidade.claposOrdem"), "gamPossibilidade.gamClaPossibilidade.claposOrdem");
			projectionList.add(Projections.property("gamParticipantePalpites.parpalPontos"), "gamParticipantePalpites.parpalPontos");
			projectionList.add(Projections.property("gamParticipantePalpites.parpalResultado"), "gamParticipantePalpites.parpalResultado");

			criteria.setProjection(projectionList);
		
			//Alias
			
			criteria.createAlias("gamPossibilidade", "gamPossibilidade");
			criteria.createAlias("gamPossibilidade.gamClaPossibilidade", "gamClaPossibilidade");
			criteria.createCriteria("gamJogoPossibilidade.gamParticipantePalpites", "gamParticipantePalpites", criteria.LEFT_JOIN);
			
			criteria.add(Restrictions.eq("gamJogo.gamjogCod", gamJogo.getGamjogCod()));
			
			criteria.addOrder(Order.asc("gamClaPossibilidade.claposOrdem"));
			criteria.addOrder(Order.asc("gamClaPossibilidade.claposNome"));
			criteria.addOrder(Order.asc("gamPossibilidade.possiDescricao"));
			
			criteria.setResultTransformer(new IgnoreCaseAliasToBeanResultTransformer(GamJogoPossibilidade.class));
			//criteria.setResultTransformer(Transformers.aliasToBean(GamJogoPossibilidade.class));
			//criteria.setResultTransformer(criteria.DISTINCT_ROOT_ENTITY);
			return (List<GamJogoPossibilidade>)criteria.list();

Isso dá o erro que falei. É assim mesmo que se faz ? Como posso fazer ?

O mapeamento dos meus beans são:

GamJogoPossibilidade

private List<GamParticipantePalpite> gamParticipantePalpites = new ArrayList<GamParticipantePalpite>(0);

	@OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, mappedBy = "gamJogoPossibilidade")
	public List<GamParticipantePalpite> getGamParticipantePalpites() {
		return gamParticipantePalpites;
	}

	public void setGamParticipantePalpites(
			ArrayList<GamParticipantePalpite> gamParticipantePalpites) {
		this.gamParticipantePalpites = gamParticipantePalpites;
	}

GamParticipantePalpite

private GamJogoPossibilidade gamJogoPossibilidade;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "jogpossi_cod", nullable = false)
	public GamJogoPossibilidade getGamJogoPossibilidade() {
		return this.gamJogoPossibilidade;
	}

	public void setGamJogoPossibilidade(GamJogoPossibilidade gamJogoPossibilidade) {
		this.gamJogoPossibilidade = gamJogoPossibilidade;
	}
C

rbonjour,

Veja que vc definiu que sua estratégia de recuperação dos dados como “fetch = FetchType.LAZY” em ambos os lados.

GamJogoPossibilidade

@OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, mappedBy = "gamJogoPossibilidade")   
    public List<GamParticipantePalpite> getGamParticipantePalpites() {   
        return gamParticipantePalpites;   
    }

GamParticipantePalpite

@ManyToOne(fetch = FetchType.LAZY)   
    @JoinColumn(name = "jogpossi_cod", nullable = false)   
    public GamJogoPossibilidade getGamJogoPossibilidade() {   
        return this.gamJogoPossibilidade;   
    }

Deste modo, a lista de GamParticipantePalpite só será buscada qdo vc acessar o método getGamParticipantePalpite(), e para isso ele precisaria estar com sua sessão na base de dados aberta, mas não vai estar e vai te retornar alguma exceção. E isso irá acontecer da mesma maneira para referência da entidade GamJogoPossibilidade .

Para que seus dados sejam recuperados no momento da excecução da HQL ou Criteria defina em seus mapeamentos FetchType.EAGER. Só tome cuidado com isso, não defina isso para todas suas coleções senão, dependendo do caso vc vai carregar sua base inteira na memória … :wink:

Normalmente eu uso a seguinte estratégia.

1 - Por padrão usar FetchType.LAZY;

2 - Usar FetchType.EAGER para mapeamentos tipo ManyToOne, porque somente traz a entidade relacionada;

3 - Usar FetchType.EAGER para mapeamentos tipo OneToMany, que trazem uma lista de objetos, somente para associações que representam composições, e mesmo assim avaliando se realmente é necessário.

E para fugir do problema que vc está enfrentando mudar a estratégia de recuperação conforme minha necessidade, como te mostrei na classe PaiEAO.

Veja que lá eu tenho o seguinte método:

public Pai carregarPai(Long idPai) throws Exception {   
        Pai pai = null;   
        try {   
            StringBuffer sb = new StringBuffer();   
            sb.append(" FROM Pai p ");   
            sb.append(" LEFT JOIN FETCH p.filhos f ");   
            sb.append(" WHERE p.idPai = :idPai  ");   
  
            Query q = em.createQuery(sb.toString());   
            q.setParameter("idPai", idPai);   
            pai = (Pai)q.getSingleResult();   
        } catch (Exception ex) {   
            throw new Exception(ex.getMessage(), ex);   
        }   
        return pai;   
    }

Veja que tem no HQL "LEFT JOIN FETCH p.filhos f ", isso independente de estiver mapeado para FetchType.LAZY na entity ele de qualquer forma vai carregar a lista de filhos.

Na API Criteria tem uma instrução, como abaixo, que permite vc mudar a estratégia de recuperação.

criteria.setFetchMode("filhos", FetchMode.JOIN);

@braços

rbonjour

Assim…

Passar para EAGER eu não posso, pois se fizer isso, sempre que tiver um select no objeto GamJogoPossibilidade ele vai trazer a coleção, e isso não pode ocorrer… :frowning:

Essa maneira que eu fiz, ele funciona 100% o SELECT, ou seja, ele imprimi e executa o Select corretamente, com os campos que pedi, com os Joins que solicitei também, tudo certo. O problema ocorre quando eu chamo o return (List)criteria.list(); , neste momento ele tenta jogar toda a lista de objetos para dentro de listas de Beans, e tenta colocar cada propriedade em seus devidos getters e setters, e é aí que dá problema, ele não acha o set da minha lista saca ?

Este teu exemplo, se tu mudar de getSingleResult para list(), funciona igual ? Eu acho que o problema está aqui:

Mas já tentei os transformers do Hibernate e não rola… não sei a maneira correta de fazer isso…

C

rbonjour,

Seguinte eu usei getSingleResult() porque minha HQL não retornará uma lista de objetos Pai. Observe que o método é carregarPai e o retorno é um objeto Pai. Se fosse uma lista eu usaria getResultList(), mas não seria o caso.

Enfim… Eu não uso o criteria assim como vc mostrou. Experimente fazer assim e me diga o resultado.

@Stateless   
public class GamJogoPossibilidadeEAO {

    public List<GamJogoPossibilidade> buscarGamJogoPossilidadesByGamJogo(GamJogo gamJogo) throws Exception {   
        List<GamJogoPossibilidade> listaGamJogoPossibilidade = new ArrayList<GamJogoPossibilidade>();
        try {
	    Session session = getSessionFactory().getCurrentSession();
	    criteria = session.createCriteria(GamJogoPossibilidade.class);
	    
	    criteria.setFetchMode("gamParticipantePalpites", FetchMode.JOIN);
	    criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            
	    // Acrescente aqui seu filtro, estilo o que vc usou, só não entendi de onde é esse gamJogo, pq não tem nenhum alias pra ele.
	    // criteria.add(Restrictions.eq("gamJogo.gamjogCod", gamJogo.getGamjogCod())); 

	    listaGamJogoPossibilidade = criteria.list();
        } catch (Exception ex) {   
            throw new Exception(ex.getMessage(), ex);   
        }   
        return listaGamJogoPossibilidade;  
    }   
  
}

@braços

Criado 7 de abril de 2010
Ultima resposta 8 de abr. de 2010
Respostas 6
Participantes 3