java - JPA:从实体到实体的 HashMap 在连接表中获取错误的主键

标签 java sql jpa dictionary eclipselink

我正在尝试对以下内容进行建模:
学生可以参加考试并获得成绩。
应该可以将同一个成绩实体分配给多个学生。
成绩存储在 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_IDjoin 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);
}

并确保您首先坚持每个 StudentGrade(或制定适当的逻辑):

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/

相关文章:

java - 如何在 Matlab 中使用 java.nio?

php - 为什么 MySQL 的 LEFT JOIN 在使用 WHERE 子句时返回 "NULL"条记录?

java - 使用 JPQL 和 Hibernate 的嵌套获取连接

java - 为什么我的 bean 验证消息没有被接收到?

java - 我可以在 OptaPlanner 中使用多个 @PlanningEntityCollectionProperty 吗?

java - 使用 Jersey 2 的 REST API

sql - 插入日期时间时从字符串转换日期和/或时间时转换失败

mysql - 如何优化此 MySQL 报告查询的时间范围和聚合?

Hibernate.cfg.xml 与 Persistence.xml

java - 如何在@Transactional 方法中手动强制提交?