65.9K
CodeProject 正在变化。 阅读更多。
Home

关于 Hibernate - 多对多

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020年1月3日

CPOL

3分钟阅读

viewsIcon

5221

downloadIcon

62

这是关于 Hibernate 多对多映射的笔记。

引言

这是关于 Hibernate 多对多映射的笔记。

背景

在我之前的笔记中,我讨论了 Hibernate 中的一对多映射。在本笔记中,我将讨论多对多映射。

studentcourse 关系是一个典型的多对多关系。以下情况在所有多对多关系中非常常见。

  • student 在注册任何 course 之前,先在 school 注册。因此,student[Student] 表中存在,然后才会在 [Student_Course] 表中有相应的条目。
  • courseschool 设计,然后才有任何注册的 student。因此,course[Course] 表中存在,然后才会在 [Student_Course] 表中有相应的条目。
  • 多对多关系最常见的操作只是在 [Student_Course] 中插入和删除行,以显示 student 是否注册或退出了 course

本笔记旨在展示如何在 Hibernate 中操作多对多关系的示例。

数据库

与我之前的笔记一样,我使用 MySQL 数据库。如果您不熟悉 MySQL,您可以查看我之前的笔记。在本笔记中,我在 Linux Mint 机器上使用了 5.5.62-0ubuntu0.14.04.1 版本。您可以执行以下脚本来创建数据库和表。

DROP DATABASE IF EXISTS experimentB;
CREATE DATABASE experimentB;
    
USE experimentB;
    
CREATE TABLE `Student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `Name` varchar(100) CHARACTER SET utf8 NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
    
CREATE TABLE `Course` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) CHARACTER SET utf8 NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
    
CREATE TABLE `Student_Course` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `studentId` int(11) NOT NULL,
  `courseId` int(11) NOT NULL,
  `score` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_constraint` (`studentId`,`courseId`),
  KEY `fk_Student_Course_Student_idx` (`studentId`),
  KEY `fk_Student_Course_Course_idx` (`courseId`),
  CONSTRAINT `fk_Student_Course_Course` FOREIGN KEY (`courseId`)
    REFERENCES `Course` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `fk_Student_Course_Student` FOREIGN KEY (`studentId`)
    REFERENCES `Student` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

实体和 Hibernate 配置

附件是一个 Maven 项目。它的结构与之前的笔记完全相同。

以下类将表映射到实体。

package com.song.example.hibernate.entities;
    
import java.util.ArrayList;
import java.util.List;
    
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
    
@Entity
@Table(name = "Student")
public class Student {
    private Integer id;
    private String name;
    private List<StudentCourse> studentCourses = new ArrayList<StudentCourse>();
    
    public Student() {}
    public Student(String name) { this.name = name; }
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Id")
    public Integer getId() { return this.id; }
    public void setId(Integer id) { this.id = id; }
    
    @Column(name = "Name", length = 100)
    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }
    
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "student")
    public List<StudentCourse> getStudentCourses() { return this.studentCourses; }
    public void setStudentCourses(List<StudentCourse> studentCourses) { 
        this.studentCourses = studentCourses; 
    }
}
package com.song.example.hibernate.entities;
    
import java.util.ArrayList;
import java.util.List;
    
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
    
@Entity
@Table(name = "Course")
public class Course {
    private Integer id;
    private String name;
    private List<StudentCourse> studentCourses = new ArrayList<StudentCourse>();
    
    public Course() {}
    public Course(String name) { this.name = name; }
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Id")
    public Integer getId() { return this.id; }
    public void setId(Integer id) { this.id = id; }
    
    @Column(name = "Name", length = 100)
    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }
    
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "course")
    public List<StudentCourse> getStudentCourses() { return this.studentCourses; }
    public void setStudentCourses(List<StudentCourse> studentCourses) {
        this.studentCourses = studentCourses; 
    }
}
package com.song.example.hibernate.entities;
    
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
    
@Entity
@Table(name = "Student_Course")
public class StudentCourse {
    private Integer id;
    private Integer score;
    
    private Student student;
    private Course course;
    
    public StudentCourse() {}
    public StudentCourse(Student student, Course course) {
        this.student = student;
        this.course = course;
    }
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Id")
    public Integer getId() { return this.id; }
    public void setId(Integer id) { this.id = id; }
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "studentId", nullable = false)
    public Student getStudent() { return this.student; }
    public void setStudent(Student student) { this.student = student; }
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "courseId", nullable = false)
    public Course getCourse() { return this.course; }
    public void setCourse(Course course) { this.course = course; }
    
    @Column(name = "score", length = 100)
    public Integer getScore() { return this.score; }
    public void setScore(Integer score) { this.score = score; }
}

Hibernate 配置添加到 hibernate.cfg.xml 文件中。

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
        
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">
            com.mysql.cj.jdbc.Driver
        </property>
        <property name="connection.url">
            jdbc:mysql:///experimentB
        </property>
        <property name="connection.username">root</property>
        <property name="connection.password">password</property>
    
        <property name="connection.pool_size">100</property>
        <property name="dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
    
        <property name="cache.provider_class">
            org.hibernate.cache.NoCacheProvider
        </property>
        
        <property name="org.hibernate.flushMode">MANUAL</property>
    
        <property name="show_sql">false</property>
        <property name="format_sql">false</property>
        
        <mapping class="com.song.example.hibernate.entities.Student" />
        <mapping class="com.song.example.hibernate.entities.Course" />
        <mapping class="com.song.example.hibernate.entities.StudentCourse" />
        
    </session-factory>
</hibernate-configuration>

hibernate.cfg.xml 文件中,我添加了用户名和密码条目。如果要运行该示例,您需要确保用户名具有操作表的必要权限。

单元测试

在本笔记中,我使用 TestNG 运行单元测试。所有测试都写在 SC_Test.java 文件中。

package com.song.example.hibernate;
    
import java.sql.Statement;
import java.util.List;
    
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
    
import com.song.example.hibernate.entities.Course;
import com.song.example.hibernate.entities.Student;
import com.song.example.hibernate.entities.StudentCourse;
    
public class SC_Test {
    private final SessionFactory sessionFactory
        = SessionFactoryInstance.Instance;
    
    @BeforeTest
    public void Init() {
        final String test_name = "Init()";
        Reporter.log(test_name, true);
        
        try (Session session = sessionFactory.openSession()) {
            FlushMode flushmode = session.getHibernateFlushMode();
            Assert.assertEquals(flushmode, FlushMode.MANUAL);
            
            session.doWork(connection -> {
    
                try (Statement statement = connection.createStatement()) {
                    statement.execute("SET FOREIGN_KEY_CHECKS = 0;");
                    statement.execute("TRUNCATE TABLE Student_Course;");
                    statement.execute("TRUNCATE TABLE Student;");
                    statement.execute("TRUNCATE TABLE Course;");
                    statement.execute("SET FOREIGN_KEY_CHECKS = 1;");
                }
            });
        }
        
        try (Session session = sessionFactory.openSession()) {
            Transaction tx = session.getTransaction();
            
            try {
                tx.begin();
                session.save(new Student("Student No.1"));
                session.save(new Student("Student No.2"));
                session.save(new Course("Course No.1"));
                session.save(new Course("Course No.2"));
                session.flush();
                tx.commit();
            }
            catch(Exception e) {
                tx.rollback();
                Assert.fail(test_name + " - Add students and courses");
            }
        }
        
        // Validate students and courses are added
        try (Session session = sessionFactory.openSession()) {
            
            List<Student> students = session
                    .createQuery("SELECT s FROM Student s", Student.class)
                    .getResultList();
            List<Course> courses = session
                    .createQuery("SELECT c FROM Course c", Course.class)
                    .getResultList();
            
            Assert.assertEquals(students.size(), 2);
            Assert.assertEquals(courses.size(), 2);
        }
    }
    
    @Test
    public void Add_Student_To_Course_Test() {
        final String test_name = "Assign_Student_To_Course_Test()";
        Reporter.log(test_name, true);
        
        final int studentId = 1, courseId = 1;
        final String courseName = "Course No.1";
        
        try (Session session = sessionFactory.openSession()) {
            Transaction tx = session.getTransaction();
            
            try {
                tx.begin();
                Student student = session.get(Student.class, studentId);
                Course course = session.get(Course.class, courseId);
                
                StudentCourse studentCourse = new StudentCourse(student, course);
                student.getStudentCourses().add(studentCourse);
                session.save(studentCourse);
                
                session.flush();
                tx.commit();
            } catch(Exception e) {
                tx.rollback();
                Assert.fail(test_name + " - Failed to commit the changes");
            }
        }
        
        // Validate the student assigned to the course
        try (Session session = sessionFactory.openSession()) {
            Student student = session.get(Student.class, studentId);
            
            List<StudentCourse> studentCourses = student.getStudentCourses();
            Assert.assertEquals(studentCourses.size(), 1);
            
            StudentCourse studentCourse = studentCourses.get(0);
            Assert.assertNull(studentCourse.getScore(),
                    test_name + " - score should not have been assigned");
            
            Course course = studentCourse.getCourse();
            Assert.assertEquals(course.getName(), courseName);
        }
    }
    
    @Test(dependsOnMethods = {"Add_Student_To_Course_Test"})
    public void Assign_Score_To_Student_Test() {
        final String test_name = "Assign_Score_To_Student_Test()";
        Reporter.log(test_name, true);
        
        final int studentId = 1, score = 95;
        
        try (Session session = sessionFactory.openSession()) {
            Transaction tx = session.getTransaction();
            
            try {
                tx.begin();
                Student student = session.get(Student.class, studentId);
                StudentCourse studentCourse = student.getStudentCourses().get(0);
                
                studentCourse.setScore(score);
                session.flush();
                tx.commit();
            } catch(Exception e) {
                tx.rollback();
                Assert.fail(test_name + " - Failed to commit the changes");
            }
        }
        
        // Validate the score updated
        try (Session session = sessionFactory.openSession()) {
            Student student = session.get(Student.class, studentId);
            
            List<StudentCourse> studentCourses = student.getStudentCourses();
            StudentCourse studentCourse = studentCourses.get(0);
            Assert.assertEquals(studentCourse.getScore().intValue(), score);
        }
    }
    
    @Test(dependsOnMethods = {"Assign_Score_To_Student_Test"})
    public void Drop_And_Add_Student_To_Course_Test() {
        final String test_name = "Drop_And_Add_Student_To_Course_Test()";
        Reporter.log(test_name, true);
        
        final int studentId = 1, courseId = 2, score = 100;
        final String courseName = "Course No.2";
        
        try (Session session = sessionFactory.openSession()) {
            Transaction tx = session.getTransaction();
            
            try {
                tx.begin();
                Student student = session.get(Student.class, studentId);
                List<StudentCourse> studentCourses = student.getStudentCourses();
                
                StudentCourse existingCourse = studentCourses.get(0);
                studentCourses.remove(existingCourse);
                
                Course course = session.get(Course.class, courseId);
                StudentCourse newCourse = new StudentCourse(student, course);
                newCourse.setScore(score);
                studentCourses.add(newCourse);
                
                session.delete(existingCourse);
                session.save(newCourse);
                
                session.flush();
                tx.commit();
            } catch(Exception e) {
                tx.rollback();
                Assert.fail(test_name + " - Failed to commit the changes");
            }
        }
        
        // Validate course dropped and added
        try (Session session = sessionFactory.openSession()) {
            Student student = session.get(Student.class, studentId);
            
            List<StudentCourse> studentCourses = student.getStudentCourses();
            Assert.assertEquals(studentCourses.size(), 1);
            
            StudentCourse studentCourse = studentCourses.get(0);
            Assert.assertEquals(studentCourse.getScore().intValue(), score);
            
            Assert.assertEquals(studentCourse.getCourse().getName(), courseName);
        }
    }
}
  • public void Init() - 在我们开始测试之前,在数据库中添加了 2 个 student 和 2 个 course
  • public void Add_Student_To_Course_Test() - 此测试将 student 注册到 course
  • public void Assign_Score_To_Student_Test() - 此测试更新 studentcourse 中的 score;
  • public void Drop_And_Add_Student_To_Course_Test() - 此测试同时从 class 中删除 student 并注册到另一个 class

此单元测试类还表明,查询数据库以加载 student 注册的所有 course 非常容易。

运行单元测试

要运行单元测试,您需要运行 MySQL 服务器。您可以通过以下命令在 Linux 系统中启动 MySQL 服务器

sudo service mysql start

然后,您可以通过 Maven 命令运行单元测试。

mvn test

如果您将项目加载到 Eclipse 中,您也可以通过 TestNG 插件运行单元测试。

关注点

  • 这是关于 Hibernate 多对多映射的笔记。
  • 希望您喜欢我的帖子,也希望这篇笔记能以某种方式帮助到您。

历史

  • 2020 年 1 月 3 日:首次修订
© . All rights reserved.