Um mapeamento Stackoverflow (hibernação)
Pergunta
Suponha que um mapeamento como este
@Entity
public class User {
private Integer id
private List<Info> infoList;
@Id
public getId() {
return this.id;
}
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="USER_ID", insertable=false, updateable=false, nullable=false)
public getInfoList() {
return this.infoList;
}
public void addQuestion(Info question) {
info.setInfoCategory(InfoCategory.QUESTION);
info.setInfoId(new InfoId(getId(), getInfoList().size()));
getInfoList().add(question);
}
public void addAnswer(InfoRepository repository, Integer questionIndex, Info answer) {
Info question = repository.getInfoById(new InfoId(getId(), questionIndex));
if(question.getInfoCategory().equals(InfoCategory.ANSWER))
throw new RuntimeException("Is not a question");
if(question.getAnswer() != null)
throw new RuntimeException("You can not post a new answer");
answer.setInfoCategory(InfoCategory.ANSWER);
answer.setInfoId(new InfoId(getId(), getInfoList().size()));
getInfoList().add(answer);
question.setAnswer(answer);
}
}
E uma pergunta e resposta mapeados por uma classe Informações
@Entity
public class Info implements Serializable {
private InfoId infoId;
private Info answer;
private InfoCategory infoCategory;
public Info() {}
@Embeddable
public static class InfoId {
private Integer userId;
private Integer index;
public InfoId(Integer userId, Integer index) {
this.userId = userId;
this.index = index;
}
@Column("USER_ID", updateable=false, nullable=false)
public getUserId() {
return this.userId;
}
@Column("INFO_INDEX", updateable=false, nullable=false)
public getIndex() {
return this.index;
}
// equals and hashcode
}
// mapped as a ManyToOne instead of @OneToOne
@ManyToOne
JoinColumns({
JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false),
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false)
})
public Info getAnswer() {
return this.answer;
}
@EmbeddedId
public InfoId getInfoId() {
return this.infoId;
}
}
Em getAnswer eu uso ManyToOne vez de OneToOne porque algumas questões relacionadas ao mapeamento OneToOne. Um OneToOne pode ser mapeada como um ManyToOne (original = verdadeiro em @JoinColumn). INFO_INDEX não está relacionado com qualquer finalidade específica. Apenas uma chave, a fim de suportes uma chave primária composta em um sistema legado.
Antes de responder, cuidar do seguinte:
Se um objeto tem um identificador atribuído, ou uma chave composta, o identificador deve ser atribuído à instância do objeto antes de chamar save ()
Então eu tenho para mapear JoinColumn (name = "USER_ID", referencedColumnName = "USER_ID", inserível = false, atualizável = false) em getAnswer porque o Hibernate não permite duas propriedades mutáveis ??compartilham o mesmo collumn (userId também usa USER_ID), caso contrário vou receber USER_ID na propriedade resposta deve ser mapeada com inserível = false, atualizável = false
Agora, dê uma olhada mapeamento getAnswer
@ManyToOne
JoinColumns({
JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false),
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false)
})
Porque, Hibernate reclama que você não pode misturar diferentes inserível e atualizável
O que devo fazer para passá-lo?
Tome cuidado que é um sistema legado.
Atenciosamente,
Solução 2
De acordo com a questão no mapeamento getAnswer
@ManyToOne
JoinColumns({
JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false),
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false)
})
Hibernate vai reclamar porque não permite misturar diferentes inserível e atualizável. Observe um inserível e atualizável em USER_ID JoinColumn e só inserível em ANSWER_INDEX JoinColumn.
Assim, a fim de passá-lo, i configurar ANSWER_INDEX JoinCollumn accortding para
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false, updateable=false)
Desta forma, o Hibernate não vai reclamar.
E eu configurar uma nova propriedade chamada answerIndex
private Integer answerIndex;
@Column(name="ANSWER_INDEX", insertable=false)
public void getAnswerIndex() {
return this.answerIndex;
}
Então, em addAnswer usuário
public void addAnswer(InfoRepository repository, Integer questionIndex, Info answer) {
Info question = repository.getInfoById(new InfoId(getId(), questionIndex));
if(question.getInfoCategory().equals(InfoCategory.ANSWER))
throw new RuntimeException("Is not a question");
if(question.getAnswer() != null)
throw new RuntimeException("You can not post a new answer");
answer.setInfoCategory(InfoCategory.ANSWER);
answer.setInfoId(new InfoId(getId(), getInfoList().size()));
getInfoList().add(answer);
// Added in order to set up AnswerIndex property
question.setAnswerIndex(answer.getInfoId().getIndex());
}
Outras dicas
Você faria drasticamente simplificar o seu mapeamento por renunciar id incorporado e utilizando um PK substituto em seu lugar.
Se você precisa usar InfoId.index
para algum propósito (a ordem as perguntas / respostas? Você pode especificar que no mapeamento list
), mantê-lo como uma propriedade regular.
UserId
será substituído por ManyToOne
em User
; a outra extremidade da associação (na classe User
) vai ser mapeada como @OneToMany(mappedBy="User")
Por que a resposta mapeada como ManyToOne
? ele não deve ser OneToMany
(por exemplo, 1 pergunta a muitas respostas)? De qualquer maneira, classe de mapeamento em si é inferior a ideal - você está implementando, basicamente, uma "lista de adjacência" hierarquia do modelo que é ineficiente; plus no seu caso, você vai precisar para se certificar de que não mais de 2 níveis é. Eu mapear Question
e Answer
como classes separadas; você pode tê-los implementar uma interface comum ou estender a mesma base (possivelmente abstrato) classe; de qualquer forma você vai ser capaz de desfrutar de polimorfismo implícito fornecido pelo Hibernate.