双向多对一映射

在前面章节介绍了单向多对一映射,本章节将介绍双向多对一关系映射。如果需要了解单向多对一映射请参考“单向多对一映射”;

表结构设计

通常,多对一的关系,可以在多方的表中添加一个外键列来维护双方的关系,如下图:

对象设计

在双向多对一关系中,从导航性分析,既能从 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&amp;
					  serverTimezone=UTC&amp;useUnicode=true&amp;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]"}]

数据库表数据如下图:

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号