tags:

views:

63

answers:

1

I'm not too used to ORM mapping, having come from a PHP background. I'm trying to achieve a link between a series of questions and their possible answers, along with the correct answer from that set.

I'm trying to create a Question object that can be queried for it's corresponding Answers, but unfortunately, the best Java classes I've crafted lead to duplicate (technically unneccessary) fields.

The ideal database schema would look like this:

CREATE TABLE `question` (
    `question_id`           BIGINT AUTO_INCREMENT,
    `questionText`          VARCHAR(255),
    `correctAnswerIndex`    INT,
    PRIMARY KEY (`question_id`),
    INDEX correct_answer_idx (`question_id`, `correctAnswerIndex`),
    FOREIGN KEY `correct_answer_idx` (`question_id`, `correctAnswerIndex`) REFERENCES answer(`question_id`, `index`)
);

CREATE TABLE `answer` (
    `question_id`           BIGINT,
    `index`                 INT,
    `answerText`            VARCHAR(32),
    PRIMARY KEY (`question_id`, `index`),
    INDEX question_idx (`question_id`),
    FOREIGN KEY `question_idx` (`question_id`) REFERENCES `question`(`question_id`)
);

Unfortunately, the Java classes I'm about to list will create an extra set of (question_id, index) for the Map. This would be acceptable if I could use the fields as the primary key. In addition, correctAnswer will generate an unneccessary reference to Question when using the current id would be fine.

The following are the classes that I'm using to create the structure:

@javax.persistence.Entity
@javax.persistence.Table(name = "question")
public class Question implements Serializable {
    @Id
    @TableGenerator(name = "question_seq", table = "hibernate_sequences", allocationSize = 5)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "question_seq")
    private Long id;

    @ManyToOne(optional = false)
    @JoinColumns({
        @JoinColumn(name = "correct_answer_index", referencedColumnName = "index"),
        @JoinColumn(name = "correct_answer_question", referencedColumnName = "question_id")
    })
    private Answer correctAnswer;

    @CollectionOfElements(fetch = FetchType.LAZY)
    @JoinTable(name = "answer", joinColumns = @JoinColumn(name = "question_id"))
    @MapKey(columns = { @Column(name = "index") })
    private Map<Integer, Answer> answers = new HashMap<Integer, Answer>();

    @Column(length = 255)
    private String questionText;

    //... Accessors, Equals(), Hashcode(), etc...
}

@javax.persistence.Entity
@javax.persistence.Table(name = "answer")
@AssociationOverrides( {
    @AssociationOverride(name = "pk.index", joinColumns = @JoinColumn(name = "index")),
    @AssociationOverride(name = "pk.question", joinColumns = @JoinColumn(name = "question_id")),
})
public class Answer implements Serializable {
    @EmbeddedId
    private AnswerPK pk = new AnswerPK();

    @Column(length = 32)
    private String answerText;

    //... Accessors, Equals(), Hashcode(), etc...
}

@Embeddable
public class AnswerPK implements Serializable {
    @ManyToOne
    @ForeignKey(name = "answer_for_question")
    Question question;

    Integer index;

    //... Accessors, Equals(), Hashcode(), etc...
}

Any help or advice would be greatly appreciated.

A: 

Don't use a Map but a List for the answers and use index as the sort order. Also assign each answer a unique ID (just like with the questions) instead of using a composite key. Hibernate will use this ID in the question object (instead of the index).

The only drawback of this design is that you must make sure that the correct answer is in fact an item in the list of answers to this question. On the positive side, this will make your code and mapping much more simple.

Aaron Digulla
This seems to work quite well and thankfully, constraining the correct answer is relatively easy to do. Thanks!
Steven Blom