[Resolvido] Inner Join Criteria

Olá Camaradas,
Abro este tópico após algumas horas de pesquisa, tentativa e erro.
Estou num projeto utilizando JSF2 e Hibernate 3.
Tenho as Entities Pessoa e Ciente, cujo relacionamento é 1 : 1, cliente é composto por Pessoa.
O Hibernate gerou as tabelas pessoa e cliente (com FK em CLiente).

Pessoa
Integer id PK
Varchar(150) nome
//demais colunas

e

Cliente
Integer id PK
Integer pessoa_id FK
//demais colunas

O problema é que, de acordo com as especificações do projeto, a construção das tabelas está ok, porém, há uma regra negocial que diz que o sistema deve permitir consulta através de ID (atributo de cliente) e, também, pelo nome ou CPF da pessoa.
A partir daí começa o problema.
A única forma de fazer isto seria

SELECT c.id, c.renda, c.ocupacao, c.pessoa_id, p.id, p.nome, p.cpf
FROM
Pessoa p
INNER JOIN
Cliente c
ON
pessoa.id = cliente.pessoa_id
AND 
pessoa.nome like =?;

Certo?
O que está pegando é que estou tentando usar Criteria e até o momento não encontrei nada que me permita buscar o Cliente a partir do Nome da Pessoa.

Alguém pode me dar uma força?

Olá drsmachado,

Já tentou criar um ‘alias’ ? , algo mais ou menos assim:

crit.createAlias("Pessoa", "b"); crit.add(Restrictions.eq("b.cpf", valor)); T entity = (T) crit.uniqueResult();

[quote=jamirdeajr]Olá drsmachado,

Já tentou criar um ‘alias’ ? , algo mais ou menos assim:

crit.createAlias("Pessoa", "b"); crit.add(Restrictions.eq("b.cpf", valor)); T entity = (T) crit.uniqueResult();[/quote]
Então, a questão é que a pessoa eu consigo pegar.
Quando crio um alias, por exemplo, referenciando “Cliente”, “cli”, recebo o erro dizendo que nenhuma propriedade Cliente foi encontrada no entity pessoa.

Tem uma outra opção que é incluir teu inner join no Criteria, só não sei se é boa prática isso…
Em todo caso fica a sugestão, acho que fica mais ou menos assim:

// Criteria crit = session.createCriteria(Cliente.class, "cliente"); crit.createCriteria("pessoa","Pessoa",Criteria.INNER_JOIN); crit.add(Restrictions.like("pessoa.nome",likeString, MatchMode.ANYWHERE);

Como está o seu mapeamento de pessoa e Cliente?

vc tem OneToOne na Entidade Cliente da Entidade Pessoa?

Posta aí

[quote=Nocchi]Como está o seu mapeamento de pessoa e Cliente?

vc tem OneToOne na Entidade Cliente da Entidade Pessoa?

Posta aí[/quote]

package br.com.locadora.entity;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.IndexColumn;

@Entity
public class Pessoa implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue
    private Long              id;
    @Column(nullable = false)
    private String            name;
    @Column(nullable = false)
    private String            nmMae;
    @Column(nullable=true)
    private String            nmPai;
    @Column(nullable = false)
    @Temporal(TemporalType.DATE)
    private Date              dtNasc;
    @Column(nullable = false)
    private String            documento;
    @Column(nullable = false)
    private String            nacionalidade;
    @Column(nullable = false)
    private String            naturalidade;
    @ManyToMany(fetch = FetchType.EAGER)
    @IndexColumn(name="id")
    private List<Telefone>    listaTel;
    @ManyToMany(fetch = FetchType.EAGER)
    @IndexColumn(name="id")
    private List<Endereco>    listaEnd;

//getters e setters
}
package br.com.locadora.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

@Entity
public class Cliente implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue
    private Long              id;
    private Float             renda;
    private String            ocupacao;
    @OneToOne
    @Fetch(FetchMode.JOIN)
    private Pessoa            pessoa;

//getters e setters
}

Cara, eu utilizo assim

Na Entidade Cliente:

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name=“ID_PESSOA”, insertable=false, updatable=false, referencedColumnName=“ID_PESSOA”)
private Pessoa pessoa;

acho que vc pode fazer assim no Criteria

Criteria crit = session.createCriteria(Cliente.class, “cliente”);
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
crit.setFetchMode(“pessoa”, FetchMode.JOIN);
crit.createAlias(“cliente.pessoa”, “pessoa”, Criteria.INNER_JOIN,Restrictions.like(“pessoa.codCPF”, param_documento));

[quote=Nocchi]Cara, eu utilizo assim

Na Entidade Cliente:

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name=“ID_PESSOA”, insertable=false, updatable=false, referencedColumnName=“ID_PESSOA”)
private Pessoa pessoa;

acho que vc pode fazer assim no Criteria

Criteria crit = session.createCriteria(Cliente.class, “cliente”);
crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
crit.setFetchMode(“pessoa”, FetchMode.JOIN);
crit.createAlias(“cliente.pessoa”, “pessoa”, Criteria.INNER_JOIN,Restrictions.like(“pessoa.codCPF”, param_documento));
[/quote]

Mas isso ainda me traria a pessoa, certo?

Deixa eu ser mais claro.

O meu sistema permitirá consultas a partir do nome e do CPF do cliente.
Como estes dados são inerentes à pessoa e pessoa é um atributo de cliente, eu preciso fazer com que, a partir da pessoa (que eu consigo encontrar), o sistema me traga os dados do cliente (como o código do mesmo).
Com SQL mesmo, eu posso fazer com inner join, da seguinte maneira

//nome
SELECT c.id, p.nome, p.cpf from PESSOA p INNER JOIN CLIENTE C ON p.id = c.pessoa_id WHERE p.nome LIKE '%Nome%';
//ou CPF
SELECT c.id, p.nome, p.cpf from PESSOA p INNER JOIN CLIENTE C ON p.id = c.pessoa_id WHERE p.cpf = 47464575767;

Já no Criteria, quando eu coloco um alias ou tento um Criteria.JOIN, ele me retorna o erro

Criteria sql = session.createCriteria(Pessoa.class);
sql.createAlias("cliente", "cli");
sql.createCriteria(Restrictions.like(nome, value);
//erro
no property 'cliente' in br.com.teste.entity.Pessoa

esse erro é pq vc não tem Cliente mapeado em Pessoa, vc só tem Pessoa na entidade Cliente

no exemplo q eu te passei do Criteria ele vai trazer cliente com o atributo Pessoa preenchido

testa essa query pra ver se o resultado é o esperado

SELECT c.id, p.nome, p.cpf from CLIENTE c INNER JOIN PESSOA p ON c.pessoa_id = p.id WHERE p.nome LIKE ‘%Nome%’;

se for o esperado, vamos criar um Criteria depois.

roda aí

[quote=Nocchi]esse erro é pq vc não tem Cliente mapeado em Pessoa, vc só tem Pessoa na entidade Cliente

no exemplo q eu te passei do Criteria ele vai trazer cliente com o atributo Pessoa preenchido

testa essa query pra ver se o resultado é o esperado

SELECT c.id, p.nome, p.cpf from CLIENTE c INNER JOIN PESSOA p ON c.pessoa_id = p.id WHERE p.nome LIKE ‘%Nome%’;

se for o esperado, vamos criar um Criteria depois.

roda aí[/quote]
Não é um erro, camarada.

Eu terei dois tipos de pessoa, uma, que é cliente e outra que é funcionário. Certo?
Qual a razão para ter, em pessoa, um atributo chamado Cliente?
Aí eu teria que ter mais um, chamado funcionário.
E outro chamado, sei lá, tio do cachorro quente.

Estou testando o @Inheritance(strategy=InheritanceType.JOINED), mas não está como quero, pois une a classe cliente em pessoa (os atributos de cliente se tornam colunas na tabela pessoa), não quero isso.
Vou tentar agora o ONE_PER_CLASS

cara, foi mal, posso ter entendido errado, o erro q falei foi que vc disse “no property ‘cliente’ in br.com.teste.entity.Pessoa”

Não sugeri pra vc adicionar Cliente em Pessoa, só estava descrevendo o erro.

Se a query q te passei retornar o valor que vc espera pode dar certo, de outro forma, vai precisar avançar no Criteria, e nesse caso não sou especialista.

qq coisa to aí pra ajudar.

[quote=Nocchi]cara, foi mal, posso ter entendido errado, o erro q falei foi que vc disse “no property ‘cliente’ in br.com.teste.entity.Pessoa”

Não sugeri pra vc adicionar Cliente em Pessoa, só estava descrevendo o erro.

Se a query q te passei retornar o valor que vc espera pode dar certo, de outro forma, vai precisar avançar no Criteria, e nesse caso não sou especialista.

qq coisa to aí pra ajudar.
[/quote]

Então, estamos no mesmo barco.
Eu alterei a forma de construção de @OneToOne para @Inheritance
Com a estratégia JOINED funcionou, mas ainda acho meio “gambiarra” ter um “DTYPE” como coluna e definir o tipo de “Pessoa” ali.
Quero uma tabela por classe, que é o que estou testando neste momento.

Bom, camaradas, encontrei uma solução, mas não é a mais adequada.
Como alterei a estrutura das classes, colocando Cliente e Funcionario como classes filhas de Pessoa, precisei modificar o relacionamento.
Uma vez que a herança permite que a classe filha herde todos os atributos da classe pai, métodos e afins (com algumas ressalvas), removi o atributo Pessoa, cuja relação era OneToOne das classes filhas.
Ao invés disto, acrescentei a keyword extends e indiquei a superclasse Pessoa (Cliente extends Pessoa).
Para tanto, precisei remover o Id de ambas as filhas e colocar a anotação
@Inheritance(strategy=InheritanceType.ONE_PER_CLASS)
E a estratégia do @GeneratedValue, do atributo Id, de Pessoa, do default para strategy=GenerationType.TABLE.

Após estas alterações, consigo fazer a busca através de ID, nome ou CPF do cliente (E do funcionário).

Porém, aí vão algumas ressalvas:
1 - O Hibernate considera que todos os atributos de uma classe Pai deverá ser colocado em uma classe Filho. Ou seja, minha tabela Cliente, que antes possuía 4 colunas (ID, ocupacao, renda e pessoa_id), agora conta com estes, mais as colunas de pessoa (nome, dtNasc, nmMae, nmPai, nacionalidade, naturalidade, etc).
2 - Se a estratégia for JOINED, então, o hibernate inverte as coisas e, ainda, ignora tabelas para as classes filhas. Todas as colunas de Cliente e Funcionario são associadas à Pessoa. Uma coluna adicional é criada, com o nome “DTYPE” e, recebe o “nome” da classe à qual cada inserção está associada ('Pessoa", quando insere Pessoa, 'Cliente" quando é um cliente e ‘Funcionario’, quando um funcionario). Além disto, se os atributos forem marcados com a anotação @Column(nullable=false), nas classes filhas, há que se prever um valor que os possa preencher, caso não sejam comuns à todas as classes filhas. Por exemplo, Funcionario possui um atributo Date chamado dtAdm, não nulo, pois indica quando ele foi contratado. Cliente não é admitido para a empresa, logo, não possui este campo.

Enfim, irei continuar a pesquisar o Criteria, sei que tudo o que se pode fazer com SQL (mesmo um outer left rigth inner join de uma só tabela) é possível com criteria.
Por hora, estou satisfeito com essa solução gambiarrística.

Cara, não me conformei , fiz uns testes:

Usei tuas classes Pessoa e Cliente com as anotações iniciais, somente omiti os campos tipo para simplificar o código.
Fiz o Hibernate criar as tabelas, ficou assim:

[code]create table Cliente (id numeric(19,0) identity not null, ocupacao varchar(255) null, renda float null, pessoa_id numeric(19,0) null, primary key (id))

create table Pessoa (id numeric(19,0) identity not null, documento varchar(255) not null, dtNasc datetime not null, nacionalidade varchar(255) not null, name varchar(255) not null, naturalidade varchar(255) not null, nmMae varchar(255) not null, nmPai varchar(255) null, primary key (id))

alter table Cliente add constraint FKD155D859AB2C381E foreign key (pessoa_id) references Pessoa
[/code]
Inseri dados nas tabelas,
E ai parti para os testes:

[code]//Usando INNER_JOIN - Esse deu erro
Criteria crit = session.createCriteria(Cliente.class);
crit.createCriteria(“pessoa”,“Pessoa”,Criteria.INNER_JOIN);
crit.add(Restrictions.like(“pessoa.name”,“Joao”, MatchMode.ANYWHERE));
Cliente c = (Cliente) crit.uniqueResult();

// org.hibernate.QueryException: could not resolve property: pessoa.name of: dominio.Cliente[/code]
Já este outro com Alias funcionou, gerando o sql da consulta e retornando o cliente a partir do campo Pessoa…

Criteria crit2 = session.createCriteria(Cliente.class); crit2.createAlias("pessoa","a"); crit2.add(Restrictions.like("a.name","Joao", MatchMode.ANYWHERE)); Cliente c2 = (Cliente) crit2.uniqueResult(); // 16:18:55,415 DEBUG SQL:111 - select this_.id as id47_1_, this_.ocupacao as ocupacao47_1_, this_.pessoa_id as pessoa4_47_1_, this_.renda as renda47_1_, a1_.id as id48_0_, a1_.documento as documento48_0_, a1_.dtNasc as dtNasc48_0_, a1_.nacionalidade as nacional4_48_0_, a1_.name as name48_0_, a1_.naturalidade as naturali6_48_0_, a1_.nmMae as nmMae48_0_, a1_.nmPai as nmPai48_0_ from Cliente this_ inner join Pessoa a1_ on this_.pessoa_id=a1_.id where a1_.name like ?
Desculpe a insistência, pode ser que eu tenha errado alguma coisa ai…
Boa sorte!

Beleza, camarada, irei pesquisar.
Na verdade, não tentei usar o criteria a partir de um Cliente, provavelmente este foi o erro.
Irei testar e te respondo em breve.

Tava com um problema parecido com este na utilização do criteria, estava dando problema na busca de uma classe que era filha de outra, eu adicionei o alias e resolveu o problema.

Obrigado pelas informações :smiley:

public ItemMatriz BuscarPorProduto(int matriz, int prod) throws Exception { Criteria criteria = getSession().createCriteria(ItemMatriz.class); criteria.createAlias("matriz","m"); criteria.add(Restrictions.eq("icd_produto", prod)); criteria.add(Restrictions.eq("m.icd_matriz", matriz)); return (ItemMatriz) criteria.uniqueResult(); }

Boa noite amigos,

Estou com um problema desse, mas pra agravar, as tabelas não tem relacionamento (aquelas tabelas que a gente recebe do gov. federal pra consulta, que não tem PK nem FK…aff). Em ambas tabelas eu tenho um CodFamiliarFam que eu uso pra fazer o inner join no banco… mas na hora da criteria, não sei como fazer pra que a pessoa.CodFamiliarFam = familia.CodFamiliarFam.

Se alguem puder ajudar, agradeço.

rapa me ajudou muito esse post.