Una mappatura StackOverflow (ibernazione)
Domanda
Supponi una mappatura come questa
@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 una domanda e risposta mappata da una classe Info
@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;
}
}
In getAnswer uso ManyToOne invece di OneToOne perché alcuni problemi relativi alla mappatura di OneToOne. Un OneToOne può essere mappato come ManyToOne (unique = true in @JoinColumn). INFO_INDEX non è collegato a nessuno scopo specifico. Solo una chiave per supportare una chiave primaria composta in un sistema LEGACY.
Prima di rispondere, prenditi cura di quanto segue:
Se un oggetto ha un identificatore assegnato, o una chiave composita, l'identificatore DOVREBBE ESSERE ASSEGNATO all'istanza dell'oggetto PRIMA di chiamare save ()
Quindi devo mappare JoinColumn (name = " USER_ID " ;, referencedColumnName = " USER_ID " ;, insertable = false, aggiornabile = false) in getAnswer perché Hibernate non consente la condivisione di due proprietà mutabili la stessa colonna (userId utilizza anche USER_ID) altrimenti otterrò USER_ID nella proprietà di risposta deve essere mappato con insertable = false, updateable = false
Ora dai un'occhiata alla mappatura di getAnswer
@ManyToOne
JoinColumns({
JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false),
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false)
})
Perché, Hibernate si lamenta che non puoi mescolare diversi inseribili e aggiornabili
Cosa devo fare per passarlo?
Fai attenzione, è un sistema LEGACY.
saluti,
Soluzione 2
Secondo la domanda nella mappatura getAnswer
@ManyToOne
JoinColumns({
JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false),
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false)
})
Hibernate si lamenterà perché non consente di mescolare diversi inseribili e aggiornabili. Notare un inseribile e aggiornabile in USER_ID JoinColumn e inseribile solo in ANSWER_INDEX JoinColumn.
Quindi, al fine di passarlo, ho impostato ANSWER_INDEX JoinCollumn accortding su
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false, updateable=false)
In questo modo, Hibernate non si lamenterà.
E ho impostato una nuova proprietà chiamata answerIndex
private Integer answerIndex;
@Column(name="ANSWER_INDEX", insertable=false)
public void getAnswerIndex() {
return this.answerIndex;
}
Quindi in User addAnswer
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());
}
Altri suggerimenti
Dovresti drasticamente semplificare la tua mappatura rinunciando all'ID incorporato e usando invece un PK surrogato.
Se hai bisogno di usare InfoId.index
per qualche scopo (per ordinare domande / risposte? puoi specificarlo nella mappatura list
), tenerlo come una normale proprietà .
UserId
sarà sostituito da ManyToOne
su User
; l'altra estremità dell'associazione (nella classe User
) sarà mappata come @OneToMany(mappedBy="User")
Perché la risposta è mappata come ManyToOne
? Non dovrebbe essere OneToMany
(ad es. 1 domanda a molte risposte)? In entrambi i casi, mappare la classe su se stesso è tutt'altro che ideale: in pratica stai implementando un "elenco di adiacenza" gerarchia di modelli inefficiente; inoltre nel tuo caso dovrai assicurarti che non superi i 2 livelli. Mapperei Question
e Answer
come classi separate; puoi farli implementare un'interfaccia comune o estendere la stessa classe base (possibilmente astratta); in entrambi i casi potrai godere del polimorfismo implicito fornito da Hibernate.