在前面章节介绍了单向多对一映射,本章节将介绍双向多对一关系映射。如果需要了解单向多对一映射请参考“单向多对一映射”;
通常,多对一的关系,可以在多方的表中添加一个外键列来维护双方的关系,如下图:

在双向多对一关系中,从导航性分析,既能从 many 方找到 one 方,也能从 one 方找到 many 方,所以对象的设计应该如下:
(1)教师表对应的 Teacher 实体,代码如下:
@Data
@Entity
@Table
public class Teacher {
@Id
@GeneratedValue
private int id;
@Column
private String name;
// 这是关键
// 教师可以导航到学生
@OneToMany(mappedBy = "teacher")
private List<Student> studentList;
}(2)学生表对应的 Student 实体,代码如下:
@Data
@Entity
@Table
public class Student {
@Id
@GeneratedValue
private int id;
@Column
private String name;
// 学生可以导航到教师
@ManyToOne
@JoinColumn(name = "teacher_id")
private Teacher teacher;
}从上面代码可以看到,在表结构方面,我们应该是在 student(学生)表中添加外键列来维护学生的教师信息。在对象设计方面,在 Student 对象中关联 Teacher 对象,来封装当前学生的教师。同时,在 Teacher 对象中添加 List<Student> 来维护教师拥有的学生。
但是,使用 @One2Many 的时候,OpenJPA 会为我们创建中间表,使用 @Many2One 的时候,OpenJPA 会为我们在 many 方的表中创建外键列。意思是,按照上面的实现方式,我们既有外键列又有中间表,这很明显是没有必要的。
那我们能不能在集合属性上不加 @One2Many 注解,这样不就不会创建中间表了吗?不好意思,这样是不可取的,因为这样的话,在查询的时候,OpenJPA 就不会帮我们去查询 one 方依赖的 many 的数据了(即 Teacher 对象中的 List<Student> studentList)。那该如何是好呢?
使用 @One2Many 中的 mappedBy 属性,该属性的含义是:
(1)让 one 方放弃对关系的维护,意思是,在保存 one 方数据时,不用去维护和 many 方的关系;此时 OpenJPA 就不会为我们创建中间表了;
(2)在查询 one 方依赖的 many 方数据时,直接根据这里配置的 many 方属性对应的数据进行查询;
这样,双向的多对一在最终 sql 的执行上,和单向的多对一基本一致,最终达到关系的双向关联。
(1)persistence.xml 配置
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="demo_manytoone3" transaction-type="RESOURCE_LOCAL"> <!-- JPA提供者 --> <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> <!-- 声明实体类 --> <class>com.hxstrive.openjpa.annotation.demo_manytoone3.Teacher</class> <class>com.hxstrive.openjpa.annotation.demo_manytoone3.Student</class> <!-- 配置JPA数据库属性 --> <properties> <property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost:3306/openjpa_learn?useSSL=false& serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8"/> <property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/> <property name="openjpa.ConnectionUserName" value="root"/> <property name="openjpa.ConnectionPassword" value="aaaaaa"/> <property name="openjpa.Log" value="SQL=TRACE"/> <!-- 自动生成表 --> <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/> <!-- 不使用加载时强化和编译时强化,使用运行时Unenhanced(不能发挥OpenJPA的最大效能,所以也不推荐) --> <property name="openjpa.ClassLoadEnhancement" value="false"/> <property name="openjpa.DynamicEnhancementAgent" value="false"/> <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/> </properties> </persistence-unit> </persistence>
(2)Teacher 教师实体
@Data
@Entity
@Table
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column
private String name;
@OneToMany(mappedBy = "teacher")
private List<Student> studentList;
}(3)Student 学生实体
@Data
@Entity
@Table
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column
private String name;
@ManyToOne
@JoinColumn(name = "teacher_id")
private Teacher teacher;
}(4)客户端代码
public class Demo {
/** 持久化单元名称 */
private static final String NAME = "demo_manytoone3";
private static void insertData(EntityManager em) {
try {
em.getTransaction().begin();
// 教师
Teacher teacher = new Teacher();
teacher.setName("教师1");
// 学生
Student student1 = new Student();
student1.setName("学生1");
student1.setTeacher(teacher);
Student student2 = new Student();
student2.setName("学生2");
student2.setTeacher(teacher);
// 持久化学生和教师信息
em.persist(student1);
em.persist(student2);
em.persist(teacher);
em.getTransaction().commit();
} finally {
em.close();
}
}
private static void selectData(EntityManager em) {
try {
em.getTransaction().begin();
// 查询学生信息
TypedQuery<Student> studentQuery = em.createQuery(
"select s from Student s", Student.class);
for(Student student : studentQuery.getResultList()) {
System.out.println("student_id=" + student.getId());
System.out.println("student_name=" + student.getName());
System.out.println("student_teacher=" +
JSONObject.toJSONString(student.getTeacher()));
}
// 查询教师信息
TypedQuery<Teacher> typedQuery = em.createQuery(
"select t from Teacher t", Teacher.class);
for(Teacher teacher : typedQuery.getResultList()) {
System.out.println("teacher_id=" + teacher.getId());
System.out.println("teacher_name=" + teacher.getName());
System.out.println("teacher_studentList=" +
JSONObject.toJSONString(teacher.getStudentList()));
}
em.getTransaction().commit();
} finally {
em.close();
}
}
public static void main(String[] args) {
EntityManagerFactory emf = null;
try {
emf = Persistence.createEntityManagerFactory(NAME);
// 插入数据
insertData(emf.createEntityManager());
// 查询数据
selectData(emf.createEntityManager());
} finally {
if(null != emf) {
emf.close();
}
System.out.println("finished.");
}
}
}运行输出 SQL 日志如下:
-- 创建序列表、Student 和 Teacher 表
CREATE TABLE OPENJPA_SEQUENCE_TABLE (ID TINYINT NOT NULL, SEQUENCE_VALUE BIGINT, PRIMARY KEY (ID)) ENGINE = innodb
CREATE TABLE Student (id INTEGER NOT NULL, name VARCHAR(255), teacher_id INTEGER, PRIMARY KEY (id)) ENGINE = innodb
CREATE TABLE Teacher (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY (id)) ENGINE = innodb
-- 为序列表创建索引
CREATE INDEX I_STUDENT_TEACHER ON Student (teacher_id)
-- 更新序列表,创建3个ID,用于后面插入数据使用
SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?]
INSERT INTO OPENJPA_SEQUENCE_TABLE (ID, SEQUENCE_VALUE) VALUES (?, ?) [params=?, ?]
SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?]
UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=?, ?, ?]
SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?]
UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=?, ?, ?]
-- 插入学生数据
INSERT INTO Student (id, name, teacher_id) VALUES (?, ?, ?) [params=?, ?, ?]
INSERT INTO Student (id, name, teacher_id) VALUES (?, ?, ?) [params=?, ?, ?]
-- 插入教师数据
INSERT INTO Teacher (id, name) VALUES (?, ?) [params=?, ?]
-- 查询学生信息
SELECT t0.id, t0.name FROM Student t0 WHERE t0.teacher_id = ? [params=?]
student_id=1
student_name=学生1
student_teacher={"id":51,"name":"教师1","studentList":[{"id":1,"name":"学生1","teacher":{"$ref":"$"}},{"id":2,"name":"学生2","teacher":{"$ref":"$"}}]}
student_id=2
student_name=学生2
student_teacher={"id":51,"name":"教师1","studentList":[{"id":1,"name":"学生1","teacher":{"$ref":"$"}},{"id":2,"name":"学生2","teacher":{"$ref":"$"}}]}
-- 查询教师信息
SELECT t0.id, t0.name FROM Teacher t0
teacher_id=51
teacher_name=教师1
teacher_studentList=[{"id":1,"name":"学生1","teacher":{"id":51,"name":"教师1","studentList":[{"$ref":"$[0]"},{"id":2,"name":"学生2","teacher":{"$ref":"$[0].teacher"}}]}},{"$ref":"$[0].teacher.studentList[1]"}]数据库表数据如下图:
