views:

305

answers:

2

Hi,

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,

+2  A: 

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.

ChssPly76
Good Chss, In fact is ManyToOne. I use it 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. I will try it tomorrow. regards,
Arthur Ronald F D Garcia
A: 

Hi,

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());
}

regards,

Arthur Ronald F D Garcia