A Stackoverflow mapping (Hibernate)
Question
Suppose a mapping like this one
@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);
}
}
And a question and answer mapped by a Info class
@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 i use ManyToOne instead of OneToOne because some issues related to OneToOne mapping. A OneToOne can be mapped as a ManyToOne (unique=true in @JoinColumn). INFO_INDEX is not related to any specific purpose. Just a key in order to supports a compound primary key in a LEGACY system.
Before answering, take care of the following:
If an object has an assigned identifier, or a composite key, the identifier SHOULD BE ASSIGNED to the object instance BEFORE calling save()
So i have to map JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false) in getAnswer because Hibernate does not allow two mutable properties share the same collumn (userId also uses USER_ID) otherwise i will get USER_ID in answer property must be mapped with insertable=false, updateable=false
Now take a look at getAnswer mapping
@ManyToOne
JoinColumns({
JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false),
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false)
})
Because it, Hibernate complains you can not mix different insertable and updatable
What should i do in order to pass it ?
Take care it is a LEGACY system.
regards,
Solution 2
According to question in getAnswer mapping
@ManyToOne
JoinColumns({
JoinColumn(name="USER_ID", referencedColumnName="USER_ID", insertable=false, updateable=false),
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false)
})
Hibernate will complain because it does not allows mix different insertable and updatable. Notice a insertable and updateable in USER_ID JoinColumn and only insertable in ANSWER_INDEX JoinColumn.
So in order to pass it, i set up ANSWER_INDEX JoinCollumn accortding to
JoinColumn(name="ANSWER_INDEX", referencedColumnName="INFO_INDEX", insertable=false, updateable=false)
This way, Hibernate will not complain.
And i set up a new property called answerIndex
private Integer answerIndex;
@Column(name="ANSWER_INDEX", insertable=false)
public void getAnswerIndex() {
return this.answerIndex;
}
Then 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());
}
OTHER TIPS
You would drastically simplify your mapping by forgoing embedded id and using a surrogate PK instead.
If you need to use InfoId.index
for some purpose (to order questions / answers? you can specify that in list
mapping), keep it as a regular property.
UserId
will be replaced by ManyToOne
on User
; the other association end (in User
class) will be mapped as @OneToMany(mappedBy="User")
Why is answer mapped as ManyToOne
? Shouldn't it be OneToMany
(e.g. 1 question to many answers)? Either way, mapping class onto itself is less than ideal - you're basically implementing an "adjacency list" model hierarchy which is inefficient; plus in your case you'll need to make sure that it's no more than 2 levels. I'd map Question
and Answer
as separate classes; you can have them implement a common interface or extend the same base (possibly abstract) class; either way you'll be able to enjoy implicit polymorphism provided by Hibernate.