我正在尝试对以下内容进行建模:
学生可以参加考试并获得成绩。
应该可以将同一个成绩实体分配给多个学生。
成绩存储在 map 中(在“考试”类中),其注释如下:
@ManyToMany
@MapKeyClass(value = Person.class)
@JoinTable(name = "t1")
private Map<Person, Grade> grades;
JPA 创建以下连接表:
T1:
EXAM_ID: stores the id of the exam, is part of the primary key
GRADES_ID: stores the id of the grade, is part of the primary key
GRADES_KEY: stores the id of the student, is not part of the primary key
然而,我期望的表格会有一个包含学生和考试的主键。
我现在面临的问题是当我尝试以下操作时:
grades.put(student1, grade1);
grades.put(student2, grade1);
...我会得到一个异常,告诉我我违反了数据库约束。
我如何正确保留此映射?
我使用 eclipselink 2.6.0 作为 JPA 提供程序。该应用程序在带有 derby 数据库的 glassfish 服务器上运行。
感谢您的阅读,祝您有愉快的一天:)
更新:
这些是创建数据库的 SQL 查询、eclipselink 调用:
...
CREATE TABLE EXAM_GRADE (Exam_ID BIGINT NOT NULL, grades_ID BIGINT NOT NULL, grades_KEY BIGINT, PRIMARY KEY (Exam_ID, grades_ID))
...
ALTER TABLE EXAM_GRADE ADD CONSTRAINT EXAMGRADEgrades_ID FOREIGN KEY (grades_ID) REFERENCES GRADE (ID)
ALTER TABLE EXAM_GRADE ADD CONSTRAINT EXAMGRADEgradesKEY FOREIGN KEY (grades_KEY) REFERENCES PERSON (ID)
ALTER TABLE EXAM_GRADE ADD CONSTRAINT EXAM_GRADE_Exam_ID FOREIGN KEY (Exam_ID) REFERENCES EXAM (ID)
...
最佳答案
好吧,我们这里遇到了几个问题:我在 Hibernate 上试过了,但得到的结果与你不同。我认为 Hibernate 做的是正确的,因此我对更新的第一个回答。
首先,您显示的代码不应产生您所获得的结果。当您声称 GRADES_ID
是 join table
中的 primary key
的一部分时,这不应该是真的。当我运行该示例时,我得到了 primary key (Exam_id, grades_KEY)
。
其次,您的 grades
字段中应该有一个 OneToMany
关系,而不是一个 ManyToMany
关系。当您为两个学生插入相同的成绩时,具有 ManyToMany
关系不应导致约束冲突
。当你有一个 OneToMany
关系时,将创建一个 constraint
for unique (grades_id)
,这确实会阻止你插入相同的成绩两个学生:
grades.put(student1, grade1);
grades.put(student2, grade1);
具有约束
是可取的,因为它可以防止一个学生的成绩也分配给另一个学生的编码错误。
当您考虑关系时,请考虑实体
之间的关系。在这种情况下,您有一个 Exam
和许多 Student
,因此您应该有一个 OneToMany
。
最后,MapKeyClass
是多余的,没有充分的理由自己命名您的连接表
,尤其是t1
。
你们的关系应该很简单:
@OneToMany(fetch=FetchType.EAGER)
private Map<Student, Grade> grades;
当您创建考试
时,请确保为每个学生保存唯一的成绩:
public void testCreateExam() {
Student student1 = new Student("Karl");
Grade grade1 = new Grade(85);
Student student2 = new Student("Debbie");
Grade grade2 = new Grade(90);
Map<Student, Grade> grades = new HashMap<Student, Grade>();
grades.put(student1, grade1);
grades.put(student2, grade2);
examService.createExam("Biology", grades);
}
并确保您首先坚持每个 Student
和 Grade
(或制定适当的逻辑):
public Exam createExam(String name, Map<Student, Grade> grades) {
for(Student student: grades.keySet()) {
em.persist(student);
Grade grade = grades.get(student);
em.persist(grade);
}
Exam exam = new Exam(name);
exam.setGrades(grades);
em.persist(exam);
return exam;
}
更新:
我查看了 EclipseLink 2.6。哇,完全是 FUBAR。首先,它甚至不允许我用 OneToMany
注释 Map,这是完全错误的。其次,它处理 ManyToMany
的方式与我描述的 Hibernate 处理 OneToMany
的方式完全相同。所以,我得到了 Eclipselink
版本来像 Hibernate
使用 ManyToMany
那样工作,但是我必须生成 Schema 脚本,通过手,并使用它们来创建数据库。几乎不是开发代码的好方法。
// Change the primary key to be the Exam_Id and the grades_KEY
CREATE TABLE EXAM_GRADE (Exam_ID BIGINT NOT NULL, grades_ID BIGINT NOT NULL, grades_KEY BIGINT, PRIMARY KEY (Exam_ID, grades_KEY))
// removed the constraint for grades_ID references GRADE (ID)
ALTER TABLE EXAM_GRADE ADD CONSTRAINT EXAMGRADEgradesKEY FOREIGN KEY (grades_KEY) REFERENCES PERSON (ID)
ALTER TABLE EXAM_GRADE ADD CONSTRAINT EXAM_GRADE_Exam_ID FOREIGN KEY (Exam_ID) REFERENCES EXAM (ID)
在进行这些更改后,我能够为不同的 Student
编写和读取相同的 Grade
。这些模式脚本现在正是 Hibernate 生成它们的方式。我认为在 Eclipselink 上发生了一些有趣的事情,提交一个关于这个的错误是个好主意。另外,请注意,虽然您可能想为不同的学生分配相同的分数,但这在问题领域没有意义,但这当然是我的拙见。我意识到 Java Map
将能够让不同的 keys
引用相同的 value
,所以对于某些问题来说这当然是一个可以想象的要求, Ecpliselink
似乎无法处理。
关于java - JPA:从实体到实体的 HashMap 在连接表中获取错误的主键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35380705/