Hibernate one-to-one associação entidade com PK compartilhada entre 3 classes
-
18-09-2019 - |
Pergunta
Eu quero um unidirecional um-para-um entre objetos de 3 classes Java: Pessoa de coração, e de pessoa para fígado. Eu quero os objetos para compartilhar a mesma PK ou seja, cada pessoa tem um coração correspondente e fígado, onde person.person_id = heart.heart_id = liver.liver_id. Eu não quero fundir as 3 mesas em 1 porque cada um tem um monte de campos. Aqui está o meu código (principalmente com base na resposta aceita para esta questão ):
@Entity
public class Person {
public long personId;
private String name;
public Heart heart;
public Liver liver;
// other fields
@Id
@GeneratedValue
public long getPersonId() {return personId;}
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Heart getHeart() {return heart;}
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Liver getLiver() {return liver;}
// other getters and setters and constructors
}
@Entity
public class Heart {
private long heartId;
private int bpm;
private Person person;
// other fields
@Id
@GenericGenerator(
name = "generator",
strategy = "foreign",
parameters = @Parameter(name = "property", value = "person")
)
@GeneratedValue(generator = "generator")
public long getHeartId() {return heardId;}
@OneToOne(mappedBy="heart")
@PrimaryKeyJoinColumn
public Person getPerson() {return person;}
// other getters and setters and constructors
}
@Entity
public class Liver {
private long liverId;
private boolean healthy;
private Person person;
// other fields
// the rest uses the same hibernate annotation as Heart
}
Configuração I a sessão e fazer o seguinte:
Person jack = new Person();
jack.setName("jack");
Heart heart = new Heart();
heart.setBpm(80);
Liver liver = new Liver();
liver.setHealthy(true);
Então, se eu ligar o objeto pessoa com ele é órgãos, e guardá-lo, eu recebo um erro (NOTA: Eu tenho o mesmo comportamento quando eu usei apenas 2 classes por exemplo Pessoa e coração):
jack.setHeart(heart);
jack.setLiver(liver);
session.save(jack);
org.hibernate.id.IdentifierGenerationException: tentou id atribuir a partir nula one-to-one propriedade: pessoa
No entanto ele funciona se eu definir a relação de duas formas:
jack.setHeart(heart);
heart.setPerson(jack);
jack.setLiver(liver);
liver.setPerson(jack);
session.save(jack);
Mas, certamente, isso não deve ser necessário para relacionamentos unidirecionais?
Felicidades
ps. Curiosamente, eu noto que funciona (poupa ambos os objetos para o DB) quando eu uso apenas 2 classes por exemplo Pessoa e coração, e eu apenas definir o link para o outro lado:
heart.setPerson(jack);
session.save(heart);
Eu não tenho idéia por que isso funciona (parece lógico para mim essa pessoa é o objeto pai, como ele gera automaticamente o seu próprio PK, e os outros usam isso, então isso é tudo que você deve ter a configuração), mas de qualquer maneira Eu não consigo descobrir como aplicar este método de trabalho para a minha situação 3-class ...
Solução
Eu odeio dizer isso, mas você tem uma relação bidirecional lá. A pessoa tem uma referência para o coração eo fígado e cada um deles tem um back referência à pessoa. As anotações que você configurou nas propriedades Id do seu coração e fígado estão dizendo especificamente que eles obtenham o valor de sua propriedade Id, delegando em sua propriedade Pessoa. Nos exemplos que você mostrou que não funcionam, você não definir a propriedade Pessoa sobre esses caras ainda e por isso, eles obviamente não pode obter o seu valor Id.
Você pode definir essa relação se como um verdadeiro OneToOne unidirecional, que está documentado na documentação do Hibernate anotações:
@Entity
public class Body {
@Id
public Long getId() { return id; }
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Heart getHeart() {
return heart;
}
...
}
@Entity
public class Heart {
@Id
public Long getId() { ...}
}
ou você pode mudar a nossa entidade objetos ligeiramente para agilizar ligar ambos os lados da relação, tais como:
@Entity
public class Person {
public long personId;
private String name;
public Heart heart;
public Liver liver;
// other fields
@Id
@GeneratedValue
public long getPersonId() {return personId;}
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Heart getHeart() {return heart;}
public void setHeart(Heart heart){
this.heart = heart;
this.heart.setPerson(this);
}
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Liver getLiver() {return liver;}
public void setLiver(Liver liver){
this.liver = liver;
this.liver.setPerson(this);
}
// other getters and setters and constructors
}
Outras dicas
Eu não tentei isso, mas eu diria que ....
A diferença entre os dois lados é que a classe Person
não tem mappedBy
em seu mapeamento.
Assim, para cada um-para-um, Person
tem o valor de referência, o que Hibernate considerar como oficial. Pelo contrário, em outros objetos, o mappedBy
indica ao Hibernate para não usar o valor, mas ir para esse objeto e considerar a propriedade mapeado no objeto.
Para verificar se estou certo, você pode definir apenas os valores que não têm mappedBy
, e salvar o objeto Person
(porque é a única que tem as cascatas), e ver se o resultado está correcto ..; - )