在前面章节介绍了单向多对一映射,本章节将介绍双向多对一关系映射。如果需要了解单向多对一映射请参考“单向多对一映射”;
通常,多对一的关系,可以在多方的表中添加一个外键列来维护双方的关系,如下图:
在双向多对一关系中,从导航性分析,既能从 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]"}]
数据库表数据如下图: