JPA + @OneToMany + TopLink = Tabela M x N?

Pessoal,

Estou estudando pelo livro Enterprise JavaBeans 3.0 5th Edition de Bill Burke .

Lá diz que ao criar um relacionamento unidirecional um para muitos tem pelo menos 2 possibilidades:

  1. na tabela do lado “muitos” colocar uma FK para a tabela do lado 1.
  2. criar uma tabela de ligacao com 2 colunas, cada uma sendo FK de cada entidade, sendo que a coluna do lado do “muitos” deve ser AK (pois senao, a relacao seria NxM).

ex. usando linguagem natural pra representar tabelas:

1 Pessoa tem N Enderecos:

  1. Pessoa (id), Endereco (id, pessoa_id)
  2. Pessoa(id), Endereco(id), Pessoa_Has_Endereco(pessoa_id, endereco_id (unique))

A implementação do Hibernate usa a abordagem 1 e n tive problemas com a mesma… Só que precisei mudar o AS para GlassFish e, por padrao, ele usa TopLink… Resolvi usar o TopLink, já que vou implementar a aplicaçao independente de Provedor JPA.

Só que as classes anotadas que estavam funcionando ok com Hibernate, quando mudei para TopLink dava erro… TopLink usa a 2a. abordagem (uso de tabela intermediaria para ligacao). Porem, ele exige que tiremos a annotation @JoinColumn na entidade que mapea uma coluna com @OneToMany.

Classe Pessoa (tambem usando uma linguagem mais simples possivel):

class Pessoa {
@OneToMany
@JoinColumn(name=“pessoa_id”)
private Collection listaEndereco;
}

Com essa configuracao, o Hibernate gera as tabelas conforme previsto. Porém, usando TopLink, ele diz que não podemos colocar @JoinColumn para relacoes unidirecionais (onde pessoa tem atributo com os enderecos, mas endereco nao tem atributo pessoa). Tudo bem… como o projeto ta pequeno, retirei o @JoinColumn, e o TopLink criou, quase sem problemas, as tabelas conforma a abordagem 2… Porém, ele não colocou endereco_id como chave unica… com isso, a relacao que ele cria, na verdade, é NxM.

Alguém sabe se isso eh um bug conhecido do TopLink? Pesquisei um pouco em um forum da Oracle e o pessoal parece que acha isso comum (mapear relacionamento 1xN nas classes para tabelas NxM eh comum???)

O que acham?

Olá,

Não entendi o que queres dizer com o uso da aboradagem do toplink.

Uso JPA (a especificação), normalmente com o provedor toplink (tanto no glassfish quanto no oc4j)
e a implementação 1xN é identica a do hibernate, até por que a JPA é que dita as regras e a forma
de fazer. Os providers apenas executam (simialr ao jdbc). Por isto não gosto de fazer nada fora
da implementação.

Por exemplo, as entidades abaixo funcionam vinculadas a apenas duas tabelas (a chave de estado é composta por se tratar de
um sistema já existente). Este código esta operacional usando Toplink em um relacionamento 1xN com duas tabelas apenas.

Espero ter ajudado, ou me desculpa se não entendi direito a dúvida. :slight_smile:

@Entity
@Table(name = "PAIS")
public class Pais implements Serializable {
    @Id
    @Column(nullable = false)
    private String id;

    @Column(nullable = false)
    private String descricao;

    @OneToMany(mappedBy = "pais")
    private List<Estado> estadoList = new ArrayList<Estado>();
// ... setters and getters
    public void addEstado(Estado estado) {
        getEstadoList().add(estado);
        estado.setPais(this);
    }
    public void removeEstado(Estado){
    ...
    }
}

@Entity
@Table(name = "ESTADO")
@IdClass(EstadoPK.class)
public class Estado implements Serializable {
    @Id
    @Column(nullable = false)
    private String id;

    @Id
    @Column(nullable = false, insertable = false, updatable = false)
    private String idPais;

    @Column(nullable = false)
    private String descricao;

    @ManyToOne
    @JoinColumn(name = "IDPAIS", referencedColumnName = "ID")
    private Pais pais;

  // ... setters and getters
}

CREATE TABLE PAIS
(
ID CHAR(2) NOT NULL,
DESCRICAO VARCHAR2(50) NOT NULL,
)
;

CREATE TABLE ESTADO
(
IDPAIS CHAR(2) NOT NULL,
ID CHAR(2) NOT NULL,
DESCRICAO VARCHAR2(100) NOT NULL,
)
;

ALTER TABLE PAIS
ADD CONSTRAINT PAIS_PK PRIMARY KEY
(
ID
)
 ENABLE
;

ALTER TABLE ESTADO
ADD CONSTRAINT ESTADO_PK PRIMARY KEY
(
ID,
IDPAIS
)
 ENABLE
;

ALTER TABLE ESTADO
ADD CONSTRAINT PAIS_ESTADO_FK FOREIGN KEY
(
IDPAIS
)
REFERENCES PAIS
(
ID
) ENABLE
;

mertins, obrigado pela resposta…

Ve soh… o problema esta quando o relacionamento entre as classes eh 1xN, mas eh UNIDIRECIONAL, ou seja, no seu exemplo, pra o toplink gerar as tabelas do modo como falei, a classe Pais tinha que ter um atributo Collection (como ja tem), soh que Estado NAO PODE ter um atributo Pais. No seu caso, o relacionamento eh 1xN, mas eh BIDIRECIONAL.

Por favor, tenta fazer esse teste (apenas retirar o atributo Pais da classe Estado) e tenta gerar as tabelas pelo toplink. E vc verá que vao ser geradas 3 tabelas, uma para Pais, uma para Estado e uma Pais_Estado (por ex.) com as colunas pais_id e estado_id.

Hum, o teu problema é com o relacionamento unidirecional. :slight_smile:

Foi mal. Pois é. Eu não uso o Relacionamento 1xN unidirecional por este detalhe da terceira tabela (acho totalmente desnecessário)
e também por vícios do desenvolvimento relacional, onde tudo é bidirecional. A especificação do JPA
diz que este tipo de relacionamento deve implementar uma terceira tabela, bem como tu
descreveu (O item 2.1.8.5.1 da JSR 220 diz isto). A resalva é que deveria ser criada
uma unique key para garantir o 1xN). O que não aconteceu. Pode ser um bug.

Ah, também não crio as tabelas através do mecanismo do JPA, dai este problema nunca me ocorreu.Mas testei
com duas classes no jdeveloper e o toplink do oc4j também não criou a constraint de unique. Dai ficou permitindo NxN.
Não testei dentro do Glashfish.

Parece mesmo um bug, mas não achei nenhuma reclamação explicita sobre isto na net.

Só uma dúvida minha, o Hibernate construiu o relacionamento unidirecional de um para muitos sem usar a terceira tabela?
Apesar de achar isto o mais correto e sensato, vai contra a especificação do JPA. E dai tá errado também. :frowning:

Mais uma: é provável que JPA 2 suporte Relacionamento 1xN unidirecional com apenas chave estrangeira, sem
a necessidade de join table. Muito melhor.

t+

mertins, foi exatamente isso que aconteceu. Vejo, entao dois problemas aih:

  1. Hibernate está implementando contra a especificação, embora esteja fazendo da maneira mais sensata.
    2.1. Se a especificação JPA não mencionar que deve haver a restrição única, então a especificação JPA está errada (por transformar o relacionamento 1xN em NxM no banco) e a implementação do Toplink está “corretissima” por estar seguindo a especificação, mas estaria propagando o erro da mesma.
    2.2. Se a especificação JPA mencionar a restrição única, então o Toplink está implementando quase como especificado, pois está criando a terceira tabela, mas não está colocando a restrição unique, permitindo relacionamento NxM.

Esse seria um caso onde deveriamos notificar um dos responsáveis?

t+

Rafael,

Acho que a especificação tá certa, pois mesmo não ressaltando veemente a necessidade da unique key, no final do item ela afirma
que uma unique key será criada.

O Hibernate tá desrespeitando a especificação. Mas não é um grande crime. :frowning:

Acho que o correto seria lançar o bug nos canais corretos (sinceramente não sei onde, mas deve ser dentro do glassfish) :slight_smile:

Eu continuo criando as bases através de scripts.sql. E os projetos tem de possuir os modelos ER adequados. :slight_smile:

t+

mertins, eu considero esse erro do hibernate um grande crime pelo fato de que se vc migrar o provider, vai dar pau… tipo se eu tou usando o hibernate, eu vou ter uma FK na tabela do lado do N. Se eu trocar pra Toplink, ele vai assumir que eu tenho a terceira tabela, quando na verdade ela nao existe e aih ja viu a bronca…

Sem contar que os mapeamentos sao incompativeis… por ex… se for unidirecional, no hibernate basta colocar @JoinColumn(name=“pais_id”)… Já no Toplink, nao pode ter @JoinColun… ao inves disso, deve ter a @JoinTable com uma lista de joinColumns e inverseColumns. Ou seja, bagunça total.

Pois é Rafael. Pensando sobre o assunto cheguei a duas conclusões.

A primeira é que até onde eu ouvi falar o Hibernate responde a especificação JPA, além de permitir a utilização
de recursos extras (toplink também faz isto, e claro que os recursos extras podem ter até similaridades
entre funcionalidades de um e outro, mas a sintaxe é diferente). Assim, deveria o hibernate aceitar a chamada
@OneToMany(), por ser o formato correto da especificação (inclusive ele deveria criar as três tabelas, com pks, fks, e
a famosa unique key). Tu testou este formato no hibernate (não tenho aqui um hibernate para testes, não por hora).

A outra conclusão é que se tu deixa o mecanismo JPA criar as tabelas é por que tu não tá muito interessado
no formato das tabelas, pois o que interessa é o resultado nos objetos Entity. Isto abriria margem (apesar da
especificação não permitir) a escolha pelo mecanismo da forma de implatar o recurso. Tipo, vamos supor que no Oracle
o 1xN unidirecional possui melhor performance com 3 tabelas e no Postgresql a melhor performance é com duas
tabelas (lembrando que isto é pura ficção e delirio). Claro que não seria mais possível trocar o provider de JPA sem
reestruturar a base.

No mais é ver se o hibernate responde a especificação e que pelo menos nos nossos testes o toplink apresentou um
bug na geração das tabelas (mais um motivo pra continuar gerando a base através de scripts :smiley: )

t+

Bem lembrado.

Acho, então, que o fato de o Hibernate ter gerado feito o relacionamento com 2 tabelas + FK deve ser algo “a mais” que o hibernate oferece. Pois, como falei la no primeiro post, estou comecando a estudar EJB, JPA, etc… e na hora de fazer o mapeamento acabei me desligando da especificacao e fui desenvolvendo apenas na intuicao e nas dicas da IDE… Com isso, passou despercebido o fato de o mapeamento unidirecional ser especificado para ser feito atraves de @JoinTable, ao inves de @JoinColumn… talvez a possibilidade de colocar @JoinColumn seja especifica do hibernate e signifique que, caso vc coloque tal annotation, ele vai gerar o relacionamento apenas com duas tabelas…

Eu realmente nao tentei gerar a tabela com o hibernate sem @JoinColumn… aqui nao tenho como testar isso, mas em casa testarei.

Quanto ao fato de eu ter deixado o mecanismo JPA gerar as tabelas foi pq, como estou estudando essa especificacao, gostaria de ver como eh o comportamento da geracao. Claro que para desenvolver um sistema em producao, eu costumo modelar em uma ferramenta ER e gerar os scripts de criacao.

Depois posto o resultado do teste com hibernate.

Rafael, a puco fiz curso de EJB 3.0 na Qualit, e não conheço muita gente que está programando nesta tecnologia. Já fiz alguns programas aqui na empresa, se quizer podemos trocar idéias sobre esta plataforma.

Depois de apanhar tentando não criar esta terceira tabela, desisti.
vejo que isso é uma limitação do JPA e não tem pra onde fugir.
Quem resolver esse problema sem usar um relacionamento BIDIRECIONAL, por favor, responda a este tópico.

Olá Pessoal… Estou com um problemão no relacionamento 1xN. O sistema que eu estou fazendo tem o entity principal Agent e o entity ContactAgent. O Agent tem uma lista de ContactAgent, no entity do ContactAgent eu quero que apareça o campo AGENTUID(chave primária do entity Agent). Então no sistema quando eu vou salvar o Agent com seus ContactAgent`s ele salva quase tudo certo. No banco os registros são salvos só que o campo AGENTUID na tabela ContactAgent fica nulo. E eu não acho a solução, encontrei bem poucas pessoas com esse problema. Eu uso como banco de dados, o MySql. Vejam os códigos abaixo:

Agent, parte do código onde tem os relacionamentos…

	@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	private List<ContactAgent> listContact = new ArrayList<ContactAgent>();

ContactAgent, parte do código onde tem os relacionamentos…

@ManyToOne
	@JoinColumn(name="AGENTUID")
	private Agent agent;

Classe que persist os registros…

Agent entity = new Agent();
entity.setAgentName(data.getAgentName());
entity.setUsuarioUid(usuarioEntity);
entity.setListContact(contactAgentList);
getEm.persist(entity);

Alguém aí já passou por isso?

O_SANTO_, não tem jeito, com o relacionamento 1xN(OneToMany) unidirecional a especificação JPA 1.0 (atual) exige a criação da terceira tabela (ruim, mas fazer o que?), ver item 2.1.8.5.1 (pg 32) da especificação JSR-220. Mas o relacionamento Nx1(ManyToOne) permite o uso de duas tabelas, mesmo quando unidirecional (item 2.1.8.3.2).

thiago_santos, tu adicionou itens a List referenciada pela variavel contactAgentList? O comportamento apresentado é a de uma lista vazia. Hum, e como o relacionamento é bidirecional, tem de associar ao outro lado também. No exemplo que coloquei bem no inicio do tópico, eu faço isto pois quando adiciono um Estado a list do Pais, registro o Pais no Estado em questão.

// método da classe Pais.
 public void addEstado(Estado estado) {  
         getEstadoList().add(estado);  
         estado.setPais(this);  
     }  

Aumenta o log do TopLink para ver os SQL’s gerados. No arquivo persistence.xml, dentro da tag coloca o seguinte:

<properties> <property name="toplink.logging.level" value="ALL"/> <property name="toplink.logging.timestamp" value="true"/> <property name="toplink.logging.session" value="true"/> <property name="toplink.logging.exceptions" value="true"/> </properties>

Talvez ajude,
t+

thiago_santos, estou com este mesmo problema seu…

Tenho duas tabelas, CD e faixa, onde 1 CD possui 1 ou mais faixas. Caso eu persista uma faixa, automaticamente está sendo persistido o CD, porém, se for o oposto, ao persistir o CD não está sendo persistido as faixas.

Mas estive analisando os códigos do mertins e acho que aí está a solução, ao chegar em casa a noite farei os testes.

Você já fez os testes thiago_santos?

[]'s.

Caramba, não creio que terei que criar uma terceira tabela. Que coisa chata hein!

[]'s.

Minha opinião pessoal sobre a terceira tabela:
Não vejo problema, pois só me interessam os objetos. Como as tabelas estão organizadas não me importa.

E agora minha contribuição:
Eu tenho um relacionamento ManyToOne
-> Todo pedido tem um responsável.
-> E um responsável pode sê-lo por vários pedidos.

Na minha regra de negócio, não importa saber quais pedidos o cara é responsável. Ou seja, não preciso colocar uma Collection no meu Responsável. Essa é a Regra de Negócio, e é assim por que o sistema assim o exige.
Mas todo Pedido deve ter um Responsável como atributo, para que eu possa saber quem deu entrada no Pedido.

Dessa forma, temos uma relação unidirecional onde o Pedido “enxerga” o responsável, mas o responsável não “enxerga” o pedido. Acho que existe um termo mais correto, qual seja, navegável em vez de enxerga.

Exemplo de classe Pedido

[code]@Entity
public class Pedido implements Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String descricao;
@ManyToOne(fetch=FetchType.EAGER)
private Responsavel responsavel;

[…]
[/code]

Exemplo de classe Responsavel

[code]@Entity
public class Responsavel implements Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String nome;

[…]
[/code]

Espero ter ajudado,