JPA - FK tambem é parte da PK (composta)

Boa tarde!

No meu programa tenho 2 tabelas com chave-composta, segue exemplo:

Logo, as classes ficariam parecidas com isso:

public class Pessoa{
    @EmbeddedId
    private PessoaPK pk; // classe mapeia 'id' e 'cidade'

    @ManyToOne
    @JoinColumns(
          @JoinColumn(name="empresa_id"), @JoinColumn(name="cidade")
    )
    private Empresa empresa; //o mapeamento para essa classe usa o mesmo campo que o mapeamento para a PK, ou seja, cidade é PK e FK

    // ...
}

public class Empresa{
    @EmbeddedId
    private EmpresaPK pk; // Encapsula 'id' e 'cidade'
    
    // ...
}

Quando executo o JPA (hibernate), ele reclama que a cidade está sendo usada em dois lugares.
Nao posso colocar insertable=“false” updatable=“false” na PK pq, bem… ela é a PK.
Nao posso colocar insertable=“false” updatable=“false” na FK, pois preciso inserir o ID da empresa, e nao posso mapear apenas um dos dois campos de FK com insertable=“false” updatable=“false”…

Alguem faz alguma ideia de como posso fazer essa implementacao sem usar JDBC puro? Nao precisa ser JPA + Hibernate, mas seria interessante se fosse ^^

Esse cara teve um problema semelhante ao meu:

Obrigado!

Ola Guilherme Gomes, por que vc usa o @JoinColumns?

@ManyToOne
    @JoinColumns(
          @JoinColumn(name="empresa_id"), @JoinColumn(name="cidade")
    )
    private Empresa empresa; //o mapeamento para essa classe usa o mesmo campo que o mapeamento para a PK, ou seja, cidade é PK e FK

    // ...
}

Acho que assim já funciona.

@ManyToOne    
    private Empresa empresa; //O hibernate "da os pulos dele" para encontrar a PK de empresa :)

    // ...
}

Não precisa mapear tudo de novo só porque vc tem empresa em pessoa.

Entao, eu ja tentei isso.
Ja fiz de diversas maneiras. Inclusive, ja tentei gerar o mapeamento JPA por engenharia reversa do banco de dados, com mais de uma ferramenta e mesmo assim nao funcionou.

Outra coisa é que o sisteminha que passei é bem mais simples que o que uso aqui. O meu sistema na verdade é muito mais complicado, com heranças, usando Inheritance Joined em diversos casos.

Olhando na internet, parece que isso que quero não é possível implementar. Não sei se tem uma razão por trás ou se é simplesmente um bug…

Caso alguém possa ajudar, agradeço.

Bom, quanto a usar diversas ferramentas isso não faz diferença, pois se estiver utilizando o hibernate - seja no eclipse ou no netbeans … - ele vai usar a mesma ferramenta de mapeamento nativa do hibernate, ou seja, diferentes ide’s mesma ferramenta :wink: .

Se não estou errado, corriga-me se estiver, vc tem um classe pessoa que “trabalha” numa empresa. A mutiplicidade seria:
Pessoa tem 1 Empresa; Empresa Tem 1 ou Mais Pessoa. Se esse é o caso já tentou mapear também pessoa em empresa?

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "empresa")
    private List<Pessoa> pessoaList; 

    // ...
}

Poste o código completo das duas classes em questão e poste se resolveu seu problema.

Eu entendi o que voce faloou, mas simplesmente nao tem a ver com o meu problema.
O problema está ao tentar mapear uma FK que tem como parte integrante um campo usado na PK. Isso acontece diversas vezes no meu sistema.

Acho que voce ainda nao entendeu o que acontece aqui, seria legal se voce pudesse dar uma testada aí, com o esquema de tabelas que passei. Assim, teria uma noção melhor do problema.

Obrigado plea ajuda ^^

Pelo que eu entendi vc tem o id_cidade como PK de duas tabelas (pessoa e empresa) e usa uma delas como FK na tabela pessoa. O que nos leva a ter na base uma tabela impossível, pois seria algo como:

EMPRESA 
ID_EMPRESA PK // id da própria empresa
ID_CIDADE FK PK // id de uma tabela cidade, ou seja, ela é pk e fk ao mesmo tempo

PESSOA
ID_PESSOA PK // id da própria pessoa
ID_CIDADE FK PK // id de uma tabela cidade, ou seja, ela é pk e fk ao mesmo tempo
// já chego lá...

Até aqui tudo bem, o hiberante e a base de dados falam a mesma llingua. O problema esta ao declarar no hibernate que empresa é uma fk de pessoa - tudo bem o hibernate vai aceitar, contudo tente imaginar a tabela pessoa depois disso:

PESSOA
// PK composta
ID_PESSOA PK 
ID_CIDADE FK PK 

// Agora o problema. A base como boa menina que é coloca a PK da tabela relacionada como deve ser. Contudo não existe como ela diferenciar os ID_CIDADE
ID_EMPRESA PK // id da própria empresa
ID_CIDADE FK //  como ele vai saber qual é PK e qual é a FK. Isso é demais para uma base relacional.

Assim vc acaba tendo duas colunas referenciando a mesma tabela, mas como a base vai saber qual usar como PK? Não sei se a base que vc utiliza é algum legado maldito ou se ela é nova. Se for nova recomendo reavaliar a lógica utilizada, afinal porque pessoa precisa ter cidade como PK? ou até mesmo empresa? Existe alguma chance ter duas empresas como o mesmo id em diferentes cidades? Acho que o problema é a lógica utilizada. Ao meu ver cidade é apenas FK em ambas as tabelas.

PS. Vc consegue gerar id_empresa como auto incrementavel mesmo usando id_cidade como FK PK? Como?

Como eu disse, esse esquema que eu passei de Empresa/Pessoa que passei é só pra exemplificar, na realidade, no meu sistema, as tabelas são outras.
No lugar da ‘cidade’ temos um código utilizado pelo sistema que só faz sentido para quem conhece o sistema.

Esse código serve para diferenciar os departamentos, então a chave primária de cada tabela faz uma relação entre o código do departamento e um int incrementado pela aplicação, pois esse inteiro DEVE ser condicionado aos departamentos.

Por exemplo, o departamento com codigo ‘financeiro’ e o ‘vendas’ na tabela abaixo:

Por esse motivo, a tabela de pessoas deve ter uma chave composta entre o ID e o CODIGO.
Mas, as outras tabelas do sistema tbm usam esse esquema de CODIGO + ID. Isso está na especificação e foi pedido pelo cliente, não posso mudar.

Tem uma coisa que voce entendeu errado.
Não existem 2 campos no banco de dados fazendo referencia ao codigo (cidade no exemplo). E eu nao falei que a cidade é um ID de outra tabela.
O banco de dados não reclama em nada das tabelas, quem reclama é o hibernate.

Criei o banco de dados abaixo (do exemplo):

Nesse exemplo, o banco de dados nao reclama em nada, é criado tudo normalmente. Já verificaram a lógica do sistema e não tem nada de errado. Agora mapeia isso com JPA + Hibernate!

^^

Obrigado mais uma vez!

Isso está errado…o certo é:

@JoinColumn(name="ID", referencedColumnName="ID", insertable=false, updatable=false)

Se vc não vai fazer INSERT/UPDATE/DELETE, ou seja, se for só para fazer consultas, deixe assim…

não esquece de sobrescrever equals e hashcode!

Não quero parecer repetitivo, mas se esse for o seu caso real, continua valendo o que eu disse acima, por exemplo, se um dia o funalo do financeiro passa a ser de vendas? Para um campo ser PK ela tem que o identificar e Financeiro não identifica ninguém apenas define onde ele trabalha. Seu problema é bem pequeno, mas vc - ou eu - tem que entender como isso deve ser feito.

Ao dar um Desc na tabela PESSOA vc tem algo assim?

+-----------+---------------+------+-----+
| Field     | Type          | Null | Key |
+-----------+---------------+------+-----+
| id        | int(10)       | NO   | PRI |
| cidade    | varchar(255)  | NO   | PRI |
| id_empresa| int(10)       | NO   |     |
| id_orgao  | int(10)       | NO   |     |
| nome      | varchar(255)  | NO   |     |
+-----------+---------------+------+-----+
5 rows in set (0.00 sec)

Bom Tenta inserir “na mão” um registro nessa sua tabela de pessoa e posso te garantir que o banco vai ficar sobre-escrevendo o campo cidade.

por exemplo:

insert into pessoa (cidade, id_empresa, id_orgao, nome) values ("Cidade da pessoa", 1, 1, "NOME PESSOA");

Lembrando que para testar pessoa, empresa e orgao devem ter cidade diferentes. Verifique qual cidade a base salvou quando vc der um select.

Para funcionar do jeito que vc falou o create deve ser mais ou menos assim:

CREATE TABLE `pessoa` (
`id` int(10) unsigned NOT NULL,
`cidade` varchar(255) NOT NULL,
`id_empresa` int(10) unsigned DEFAULT NULL,
`cidade_e` varchar(255) NOT NULL, // Fica a cidade da empresa
`id_orgao` int(10) unsigned DEFAULT NULL,
`cidade_o` varchar(255) NOT NULL, // fica a cidade do orgao
`nome` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`,`cidade`),
KEY `FK_Pessoa_Empresa` (`id_empresa`,`cidade_e`), 
KEY `FK_Pessoa_Orgao` (`id_orgao`,`cidade_o`),
CONSTRAINT `FK_Pessoa_Orgao` FOREIGN KEY (`id_orgao`, `cidade_o`) REFERENCES `orgao_emissor` (`id`, `cidade`),
CONSTRAINT `FK_Pessoa_Empresa` FOREIGN KEY (`id_empresa`, `cidade_e`) REFERENCES `empresa` (`id`, `cidade`)
) 

Do jeito que vc estava fazendo seria o mesmo que vc não definisse um campo para: id_empresa e id_orgao.

Relaxa, pode ser repetitivo ^^ Vc já ta tentando me ajudar bastante!
De novo, esse esquema de financeiro e vendas foi só do exemplo. Os códigos não tem a ver com isso, coloquei isso só pra vc entender.
Mas mesmo assim, o esquema do banco me entregaram pronto, não posso alterar… Então deixemos essa discussão para trás que não vai dar em nada.

SIM!

Eu já fiz os testes necessários e funcionou tudo certinho. Mas tem uma coisa que eu esqueci de falar aqui:

O sistema deve GARANTIR que a ‘cidade’ (código na verdade) seja a mesma nos registros que são interligados.
Por exemplo, se uma empresa é da “Cidade X” e uma pessoa é dessa empresa, então obrigatoriamente a pessoa é da “Cidade X”.

Uma forma de enxergar tudo isso é a seguinte:
A aplicação será uma só, usando apenas um banco de dados. Mas diversos usuários irão utiliza-la, e em vez de criar um banco de dados separado para cada usuario, a aplicação usa um código para cada usuário para segmentar o banco. Com isso ele imita o comportamento de N bancos para N usuários. Assim, cada usuário só irá saber da existencia dos seus próprios registros. Como a aplicação é uma só e o banco o mesmo, facilita manutenção.

O Hibernate não tem como mapear essa situação. Ele espera uma tabela como a que eu te falei, ou seja, com três campos para “cidade” mesmo que sejam o mesmo valor.

Você não tem como alterar as tabelas?

Não posso… Pelo jeito vou ter que fazer JDBC direto…
:frowning: