关于 Hibernate - 多对多
这是关于 Hibernate 多对多映射的笔记。
引言
这是关于 Hibernate 多对多映射的笔记。
背景
在我之前的笔记中,我讨论了 Hibernate 中的一对多映射。在本笔记中,我将讨论多对多映射。
student
和 course
关系是一个典型的多对多关系。以下情况在所有多对多关系中非常常见。
student
在注册任何course
之前,先在school
注册。因此,student
在[Student]
表中存在,然后才会在[Student_Course]
表中有相应的条目。course
由school
设计,然后才有任何注册的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()
- 此测试更新student
在course
中的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 日:首次修订