Hibernate Relations N x N. Deu certo mas sem juizo

Olá Pessoal,
Primeiramente agradeço poder estar aqui, hoje, falando sobre algo tão interessante.
Pelo menos pra mim. Sou programador PHP no dia a dia mas não vejo a hora do fim de semana chegar para estudar JAVA.
Estou seguindo a apostila do curso FJ26 da Caelum. Nesta tem um pouco de relations do hibernate, mas não muito. Então andei caçando por ai alguns exemplos e discussões na web.
Tenho a seguinte situação, resolvida, mas resolução esta qual não entendi nada.

Supondo o relacionamento entre 2 entidades: Pessoa, Endereço.
Sendo que:

Uma ou mais pessoas podem possuir o mesmo endereço.

Uma pessoa pode ter mais de um endereço.

Assim, nosso relacionamento sendo de N x N, precisamos de uma tabela relacional para alocar as relações.

Seria esta a entidade: pessoa_endereco

Qual, por exemplo, poderia ter o seguinte registro:

pessoa_id | endereco_id
1 -------------- 1
1 -------------- 2
2 -------------- 1
2 -------------- 2

Ou seja, a pessoa 1 possui os mesmos dois endereços da pessoa 2.

Nota: Um exemplo de negócio seria ter na base um casal de advogados com dois escritórios.

Como estão minhas classes:

CLASSE Pessoa

@Entity
@Table(name="PESSOA")
public class Pessoa {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String nome;
	private String cpf;
	
	@ManyToMany(fetch = FetchType.LAZY)
	@JoinColumn(name="endereco_id")
	private List<Endereco> endereco;
	
	/** G AND S **/

}

CLASSE Pessoa

@Entity
@Table(name="ENDERECO")
public class Endereco {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String logradouro;
	private String numero;
	
	@ManyToMany(targetEntity=Pessoa.class, fetch = FetchType.LAZY)
	@JoinTable(
				name="pessoa_endereco",
				joinColumns=@JoinColumn(name="endereco_id"),
				inverseJoinColumns=@JoinColumn(name="pessoa_id")
				)
	private List<Pessoa> pessoa;
}

Main de inserção e listagem (debugando)

public class EnderecoPessoaInDAO {
	
	@SuppressWarnings("unchecked")
	public static void main(String[] args){
		
		Session session = HiberbateUtils.openSession();
		Transaction t = session.beginTransaction();

		EnderecoDAO enderecoDAO = new EnderecoDAO(session, Endereco.class);
		PessoaDAO pessoaDAO = new PessoaDAO(session, Pessoa.class);
		
		t.begin();
		try{
			
			Endereco e = new Endereco();
			e.setLogradouro("Rua cantagalo 1801");
			e.setNumero("1901");
			
			Endereco e2 = new Endereco();
			e2.setLogradouro("Rua cantagalo 1801");
			e2.setNumero("1901");
			
			List<Endereco> end = new ArrayList();
			end.add(e2);
			end.add(e);

			Pessoa p = new Pessoa();
			p.setNome("Davi de Féo");
			p.setCpf("012456789");
			p.setEndereco(end);
			
			Pessoa p2 = new Pessoa();
			p2.setNome("Sandra matias");
			p2.setCpf("0987654321");
			p2.setEndereco(end);

			enderecoDAO.saveOrUpdate(e);
			enderecoDAO.saveOrUpdate(e2);
			pessoaDAO.saveOrUpdate(p);
			pessoaDAO.saveOrUpdate(p2);
			
		}catch (Exception e) {
			t.rollback();
			System.out.println(e.getMessage());
		}
		t.commit();
		
		 Pessoa p = (Pessoa) pessoaDAO.load(1L);
			for(Endereco e: p.getEndereco()){
				System.out.println(e.getId());
				System.out.println("Pessoas no endereco:");
				for(Pessoa pIn: e.getPessoa()){
					System.out.println(pIn.getNome());
				}
			}			
	}
}

Resultado do main (apenas da listagem):

Hibernate: 
    select
        pessoa0_.id as id1_0_,
        pessoa0_.cpf as cpf1_0_,
        pessoa0_.nome as nome1_0_ 
    from
        PESSOA pessoa0_ 
    where
        pessoa0_.id=?
Hibernate: 
    select
        endereco0_.PESSOA_id as PESSOA1_1_1_,
        endereco0_.endereco_id as endereco2_1_,
        endereco1_.id as id3_0_,
        endereco1_.logradouro as logradouro3_0_,
        endereco1_.numero as numero3_0_ 
    from
        PESSOA_ENDERECO endereco0_ 
    inner join
        ENDERECO endereco1_ 
            on endereco0_.endereco_id=endereco1_.id 
    where
        endereco0_.PESSOA_id=?
2
Pessoas no endereco:
Hibernate: 
    select
        pessoa0_.endereco_id as endereco1_3_1_,
        pessoa0_.pessoa_id as pessoa2_1_,
        pessoa1_.id as id1_0_,
        pessoa1_.cpf as cpf1_0_,
        pessoa1_.nome as nome1_0_ 
    from
        pessoa_endereco pessoa0_ 
    inner join
        PESSOA pessoa1_ 
            on pessoa0_.pessoa_id=pessoa1_.id 
    where
        pessoa0_.endereco_id=?
Davi de Féo
Sandra matias
1
Pessoas no endereco:
Hibernate: 
    select
        pessoa0_.endereco_id as endereco1_3_1_,
        pessoa0_.pessoa_id as pessoa2_1_,
        pessoa1_.id as id1_0_,
        pessoa1_.cpf as cpf1_0_,
        pessoa1_.nome as nome1_0_ 
    from
        pessoa_endereco pessoa0_ 
    inner join
        PESSOA pessoa1_ 
            on pessoa0_.pessoa_id=pessoa1_.id 
    where
        pessoa0_.endereco_id=?
Davi de Féo
Sandra matias

Então ficam as questões:

O resultado me atendeu no que queria mas está correta a tratativa, é assim mesmo o relacionamento para este caso?
Na entidade Endereco coloquei o mesmo @JoinColumn(name=“endereco_id”) da entidade Pessoa.
Seria a propriedade name da annotation @JoinColumn a chave de ligação entre as entidades ?

Outra coisa é:
Perceberam que salvei os registros individualmente, ou seja, sem utilizar o cascade. O cascade é usual e seguro em que casos?

Desde já agradecido,
Jsgin

Olá jsign,
Muito boa sua colocação sobre o relacionamento MXN (pessoa_has_endereco). Aqui na empresa nós fizemos dessa forma tb:

Uma pessoa tem N endereços ex: residencial, comercial etc…
Um endereço contém M pessoas!

Blz!!!

Agora vc já tentou manipular apenas uma entidade? Por exemplo a entidade Endereco tem uma lista de pessoas, certo?

Vi duas referências de objetos do seu controller, enderecoDao e PessoaDao.

Tente usar apenas o método enderecoDao.saveOrUpdate(endereco). Como tem duas referências de objetos “pessoa”, e estas por sua vez estão adicionadas na “listadepessoas” de endereco, então automaticamente criará as pessoas no banco e consequentemente vincula-as ao endereco(criando o registro na tabela HAS - pessoa_has_endereco).

Tente fazer isto para ver oq acontece.

Abraços.

Outra! Tente fazer o mapeamento das entidades do seu bd pelo Netbeans. Ele faz pra vc e te poupa de escrever código…daí vc analisa o mapeamento que ele gerou, é claro!

Vlw.

Olá BobFroes,
Muito obrigado pela sua ajuda e participação.
Fiz como disse, mudando apenas a ordem de inserção, ou seja, criar endereços a partir de pessoas.
Provável que a partir de interfaces ricas existam similares situações, portanto venho me precavendo.
Bom, não sei qual melhor regra, estou estudando então pensei assim:

No cadastro/interface de endereços posso ter pessoas, já cadastradas, à vincular a este(s) endereços (não obrigatório).
Assim, quando criar o endereço devo apenas inserir o código da pessoa na tabela relacional “pessoa_endereco”.

Mas quando no cadastro/interface de pessoas devo (obrigatoriedade), alem de pessoas, inserir seus respectivos endereços e caso estes endereços já existam no sistema apenas vincula-los a pessoa.

As interfaces seriam mais ou menos assim:

------------------ Pessoa ------------------
Nome * : [________________]
Cpf * : [
]

Endereço * : ----------------------------- [+] (Novo endereço para pessoa)
Logradouro * : [________________]
Número * : [
]
( (on change número ) Ajax - “Endereço Existente” Guarda id endereço )

------------------ Endereço ------------------
Logradouro * : [________________]
Número * : [
]

Escolha uma ou mais pessoas para o endereço (opcional):
| Fulano de tal ^ | ( list - mult select box )
| Outro Fulano ^ |
| Fulano de lá ^ |
| Fulano de cá ^ |
| Outro de lá ^ |

Obviamente são exemplos, não se aplicam, acho até inviável ter pessoas no cadastro de endereços, ou sequer ter um cadastro de endereços isentos de pessoa. Acho que corre-se o risco de popular a base com dados que não virão a ser utilizados.
Mas é bem possível que em um mesmo cadastro tenhamos vários endereços a serem criados por pessoa.

Bom, já enchi demais com esse blabla nehh, rs.

Meu relacionamento até aqui ficou assim:

Classe Pessoa

public class Pessoa {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String nome;
	private String cpf;
	
	@ManyToMany(fetch = FetchType.LAZY)
	@Cascade(CascadeType.SAVE_UPDATE)
	@JoinColumn(name="endereco_id")
	private List<Endereco> endereco;

...

Classe Endereco

public class Endereco {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String logradouro;
	private String numero;
	
	@ManyToMany(targetEntity=Pessoa.class, fetch = FetchType.LAZY)
	@Cascade(CascadeType.MERGE)
	@JoinTable(
				name="pessoa_endereco",
				joinColumns=@JoinColumn(name="endereco_id"),
				inverseJoinColumns=@JoinColumn(name="pessoa_id")
				)
	private List<Pessoa> pessoa;
...	

Tratativa na classe Pessoa
Acredito eu que usando a anotação @Cascade(CascadeType.SAVE_UPDATE)
sobre a propriedade list de enderecos, na classe Pessoa, forço a engine do hibernate a salvar ou atualizar,
caso existente, os registros de endereços presentes no list.

Tratativa na classe Endereço
Na classe endereço, não permito que sejam atualizados registros de pessoas utilizando a anotação
@Cascade(CascadeType.MERGE)

Fiz as duas controllers, como se submitados itens fossem, a partir das interfaces (acima) descritas.

Controller EnderecoCON


...

Endereco e = new Endereco();
e.setLogradouro("Rua cantagalo");
e.setNumero("123");

Endereco e2 = new Endereco();
e2.setLogradouro("Rua monte serrat");
e2.setNumero("456");

List<Pessoa> pessoaAll = pessoaDAO.hqlPessoas(session); /** static method (reusabilidade) **/

e.setPessoa(pessoaAll);
e2.setPessoa(pessoaAll);

enderecoDAO.saveOrUpdate(e);
enderecoDAO.saveOrUpdate(e2);

...

Neste caso, peguei todas as pessoas da base e adicionei a estas os dois, recém criados, endereços.

Log:

Hibernate: /** busca por pessoas, pessoaDAO.hqlPessoas(session) **/
    select
        pessoa0_.id as id1_,
        pessoa0_.cpf as cpf1_,
        pessoa0_.nome as nome1_ 
    from
        PESSOA pessoa0_

Hibernate: /** insere o primeiro endereço **/
    insert 
    into
        ENDERECO
        (id, logradouro, numero) 
    values
        (default, ?, ?)
Hibernate: 
values
    identity_val_local()

Hibernate:  /** insere o segundo endereço **/
    insert 
    into
        ENDERECO
        (id, logradouro, numero) 
    values
        (default, ?, ?)
Hibernate: 
values
    identity_val_local()


 /** gera/insere os relacionamentos (2 pessoas presentes na base * 2 endereços = 4 HSQL ) **/

Hibernate: /** 1 **/
    insert 
    into
        pessoa_endereco
        (endereco_id, pessoa_id) 
    values
        (?, ?)

Hibernate: /** 2 **/
    insert 
    into
        pessoa_endereco
        (endereco_id, pessoa_id) 
    values
        (?, ?)

Hibernate: /** 3 **/
    insert 
    into
        pessoa_endereco
        (endereco_id, pessoa_id) 
    values
        (?, ?)

Hibernate: /** 4 **/
    insert 
    into
        pessoa_endereco
        (endereco_id, pessoa_id) 
    values
        (?, ?)

Controller PessoasCON

Endereco e = new Endereco();
e.setLogradouro("Rua miraflores");
e.setNumero("345");

Endereco e2 = new Endereco();
e2.setLogradouro("Rua 7 de abril");
e2.setNumero("123");

List<Endereco> end = new ArrayList();
end.add(e2);
end.add(e);

Pessoa p = new Pessoa();
p.setNome("Davi de Féo");
p.setCpf("012456789");
p.setEndereco(end);

Pessoa p2 = new Pessoa();
p2.setNome("Sandra matias");
p2.setCpf("0987654321");
p2.setEndereco(end);

pessoaDAO.saveOrUpdate(p);
pessoaDAO.saveOrUpdate(p2);

Neste caso criei duas novas pessoas e dois novos endereços a serem vinculados as pessoas.
Log

Hibernate: /** pessoa 1 **/
    insert 
    into
        PESSOA
        (id, cpf, nome) 
    values
        (default, ?, ?)
Hibernate: 
    
values
    identity_val_local()
Hibernate:  /** endereco 1 **/
    insert 
    into
        ENDERECO
        (id, logradouro, numero) 
    values
        (default, ?, ?)
Hibernate: 
    
values
    identity_val_local()
Hibernate:  /** endereco 2 **/
    insert 
    into
        ENDERECO
        (id, logradouro, numero) 
    values
        (default, ?, ?)
Hibernate: 
    
values
    identity_val_local()
Hibernate:  /** pessoa 2 **/
    insert 
    into
        PESSOA
        (id, cpf, nome) 
    values
        (default, ?, ?)
Hibernate: 
    
values
    identity_val_local()
Hibernate:  /** pessoa 1 x endereco 1 **/
    insert 
    into
        PESSOA_ENDERECO
        (PESSOA_id, endereco_id) 
    values
        (?, ?)
Hibernate: /** pessoa 1 x endereco 2 **/
    insert 
    into
        PESSOA_ENDERECO
        (PESSOA_id, endereco_id) 
    values
        (?, ?)
Hibernate: /** pessoa 2 x endereco 1 **/
    insert 
    into
        PESSOA_ENDERECO
        (PESSOA_id, endereco_id) 
    values
        (?, ?)
Hibernate: /** pessoa 2 x endereco 2 **/
    insert 
    into
        PESSOA_ENDERECO
        (PESSOA_id, endereco_id) 
    values
        (?, ?)

Eu acho que está ok, mas gostaria da sua ótica BobFroes sobre, principalmente, se esta é melhor maneira de tratar tal situação.
Mais uma vez, valeu pela ajuda.

BobFroes, eu ainda não usei o netbeans para JAVA, que vergonha nehh. Todo mundo diz que ele cria muita coisa mas que é um pouco pesado e talvez justamente por promover extraordinárias funcionalidades. Mais hora, menos hora, eu vou baixa-lo, vai depender se eu vou começar a apanhar demais do Hibernate, rs.

Abraços,
Jesign

Eu ainda prefiro simplifiar todas as minhas relações para (1,n)…

Olá Carlos,
Muito obrigado pela sua opinião, levantei este tópico para, além de recolher técnicas sobre relacionamentos com hibernate, levantar boas práticas para soluções lógicas sobre um negócio.

Eu acredito, assim como você disse, que relacionamentos 1 x N é uma boa forma de reter a informação com maior segurança.
É complicado mesmo listar de um lado e/ou do outro.

Porem, levanto uma questão e gostaria da sua opinião sobre esta.
Supondo, como no exemplo dado, que tenhamos um cadastro de médicos à uma operadora de saúde.
Supondo que os médicos sejam um casal e, que também o filho venha a ser um novo doutor desta operadora.

Nós analistas/programadores, do lado da operadora tratamos a seguinte solicitação:
Lita de todos os médicos e seus respectivos endereços para envio de Mala direta.
Ai, o bonitinho(a) do mkt, sem preconceito, querendo uma promoção diz assim: Tive uma ideia! Que tal economizar para a empresa enviando apenas uma mala direta para todos os médicos do mesmo endereço, tipo casal de médicos ou pai e filho sabe!
Eu matava ele ou mandava concatenar logradouro+numero e depois agrupar os idênticos no excel, asuhuhh.

Enfim, quantas malas direita enviaria a esta residência? Será que alguém vai pensar nisso lá no mkt um dia ?

Até mais Carlos.
Abraços,
Jsign

Boa Jsign!

   Afinal a lógica sempre prevalecerá! Concordo tb com a simplicidade, mas que tenha sentido, é claro!

   Com relação ao que vc colocou sobre o mapeamento, o mais lógico e correto para mim, seria inserir endereços em pessoa, concordo plenamente com vc. Foi assim mesmo que fizemos aqui na interface do nosso sistema.
   Corrigindo, eu queira mostrar a vc que apenas com um controller (que deveria ser com pessoaDAO no caso), vc poderia fazer a manipulação da entidade (pessoa) e inserir enderecosList.add(endereco) a ela.

   Com relação ao netbeans (não sou xiita...rsrs...gosto muito dele para desktop e do eclipse para web), ele realmente cria muitas coisas que vc pode retirar oq vc não for usar, conforme eu tinha dito anteriormente que deve-se fazer uma análise com a geração de código do netbeans.
   
   Vc está indo muito bem em sua análise! Parabéns e let's code!

Obrigado BobFroes mas o mérito não é só meu.
O bom de tópicos é isso nehh, todo mundo participa e a sua idéia quanto à apenas uma DAO é perfeita.
Tanto que percebi o mal uso que estava fazendo e fiz como apontou no segundo modelo.

As vezes uso métodos estáticos nas DAOs quando estimo que pode haver aproveitamento de código em algumas situações.
Como em: pessoaDAO.hqlPessoas(session);

Obviamente que é bem difícil adivinhar a existência destes estáticos métodos no dia a dia mas quando não impacta negativamente na DAO vigente, ou seja, a qual o método é parte, eu os torno estáticos, e já que em java temos o extraordinário advento do polimorfismo fico meu perdido, estou ainda me acostumando com tantas opções.

Exemplo:

        public static boolean cpfExistente(int cpf){
		rotina cpf
	}
        public boolean cpfExistente(){
               rotina this.bean.cfp
       }

Assim posso opcionalmente validar um cpf a partir de uma bean, previamente setada ou enviando um inteiro para um método estático.
Eu acho isso legal o problema é encontrar todos estes métodos estáticos na aplicação. Fica bem difícil.
Na verdade, pensando nisso, acho que deve haver algum design pattern para trabalhar este conceito de uma forma mais organizada, não!

Tipo, viajando um pouco, este método poderia estar em uma classe de nome bem sugestivo como EstaticClass, em muitos projetos vê-se isto não é mesmo! Porem, já viu o tamanho que esta classe vai alcançar nehh.

Talvez deva haver uma lógica ou prática de boa aplicação de métodos estáticos, se conhecer algum conceito bacana me avisa ok.

No mais, quanto ao Netbeans, tenho vergonha mesmo, rs, brincadeira.
Estou esperando terminar este material da caelum (FJ-26) para pegar um material legal da GlobalCode que trata de JSF, daí então são incontáveis, segundo um amigo meu as possibilidades de rápidas soluções com esta IDE.

Gostaria de ter respondido antes, mas não teve como.
Muito obrigado mesmo pela sua participação

Abraços, Jsign