多对多关系
多对多关系的判断方式是,站在两张表无论哪一张表的角度上,其中的一条数据都对应另外一张表的多个数据,就是多对多关系。现实中比较典型的关系是学生与课程的关系,一个学生会选多门课程,一门课程会有多个学生上课。
多对多关系显然无法用两张表互相外键关联来实现,在实践中的多对多关系是通过一张中间表,将两张数据表的id进行互相对应来实现的。这张中间表实际上有两个外键各自关联到两张表的id上(当然,多对多不仅局限于两张表,更多表的多对多关系也可以,但是一般不会采用这么复杂的业务逻辑)
我们现在就新增一个student表和一个中间表来为课程与学生的多对多关系做准备。
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(45) DEFAULT NULL,
`last_name` varchar(45) DEFAULT NULL,
`email` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
CREATE TABLE `course_student` (
`course_id` int(11) NOT NULL,
`student_id` int(11) NOT NULL,
PRIMARY KEY (`course_id`,`student_id`),
KEY `FK_STUDENT_idx` (`student_id`),
CONSTRAINT `FK_COURSE_05` FOREIGN KEY (`course_id`)
REFERENCES `course` (`id`)
ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_STUDENT` FOREIGN KEY (`student_id`)
REFERENCES `student` (`id`)
ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
可以看到新建了一个简单的学生表,然后创建了一个中间表course_student,其中两个外键分别关联到course表和student表。
操作多对多关系
一对多和一对一关系通过外键在哪一侧和操作哪个对象来区分Bi和Uni方式,而多对多就没有区分这两种关系了,在任何一侧应该都要能查到另一侧的关联数据,先来更新Course类:
@ManyToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
@JoinTable(name = "course_student",
joinColumns = @JoinColumn(name = "course_id"),
inverseJoinColumns = @JoinColumn(name = "student_id"))
private List<Student> students;
public void addStudent(Student student) {
if (students == null) {
students = new ArrayList<>();
}
students.add(student);
}
首先我们知道,肯定是取多个学生对象,所以是一个List泛型。然后就是注解@ManyToMany
,以及下边关键的@JoinTable
注解。
@JoinTable
注解中的表名设置的是中间表的名称,然后joinColumns = @JoinColumn(name = "course_id")
表示中间表中链接到自己所在的表的外键列是course_id
。
inverseJoinColumns = @JoinColumn(name = "student_id")
则表示inverse那一方的外键列是student_id,其对应的类是List泛型中的Student类。
在数据库中,对于这种多对多关系,我们现在所在的类是Course,就称Student类为inverse,也就是另外一方的意思。
由于多对多不区分Bi和Uni,很容易想到,Student类里也要如此设置,只不过次序正好相反。来创建Student类:
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "email")
private String email;
@ManyToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
@JoinTable(name = "course_student",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private List<Course> courses;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Student() {
}
public Student(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
'}';
}
public void addCourse(Course course) {
if (courses == null) {
courses = new ArrayList<>();
}
courses.add(course);
}
}
可见Student类中的外键属性表名不变,把两个外键倒换了一下位置,因为从Student类来说,另外一方的类是Course类。
还要注意设置多对多的级联关系,很显然,删除一个课程不能把对应的学生对象全部删除掉,而删除学生也不能把课程删除掉,因为都会影响其他数据。
还有需要注意的,就是Course.addStudent(Student student)
和Student.addCourse(Course course)
方法,这两个方法只能将对方添加进来,不要去调用对方的方法,否则会无限循环互相添加。
然后就可以来编写操作代码了,先创建一些学生对象,然后设置好与取得的course对象的关系,之后写入数据库:
public class MainApp {
public static void main(String[] args) {
SessionFactory factory = new Configuration().configure("hibernate.cfg.xml").addAnnotatedClass(Instructor.class).addAnnotatedClass(InstructorDetail.class).addAnnotatedClass(Course.class).addAnnotatedClass(Review.class).addAnnotatedClass(Student.class).buildSessionFactory();
Session session = factory.getCurrentSession();
Random rand = new Random();
try {
session.beginTransaction();
//获取随机Course对象,给Course对象添加student
Course course = session.get(Course.class, rand.nextInt(41) + 1);
for (int k = 0; k < rand.nextInt(10) + 1; k++) {
Student student = new Student(getRandomString(), getRandomString(), getRandomString());
//这里由于新建对象处于transient,状态,没有与session关联,需要先关联一下
session.save(student);
//关联之后,再将student添加到course中。
course.addStudent(student);
}
session.getTransaction().commit();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
session.close();
factory.close();
}
}
//
public static String getRandomString() {
String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
Random rand = new Random();
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 6; i++) {
stringBuffer.append(base.charAt(rand.nextInt(62)));
}
return stringBuffer.toString();
}
}
在新建了Student对象之后,需要进行save一下,否则无法直接与Course进行关联,在保存的时候也会出错。如果Course也是新建对象,也需要先save一下。之后把student对象设置到course中,这样就完成了一个course对应多个Student的操作。
如果一个学生选了多门课,就反过来写,将course关联给student对象:
try {
session.beginTransaction();
//模拟学生选课,随机选一个学生对象,然后随机选三门课
Student student = session.get(Student.class, rand.nextInt(58) + 1);
for (int j = 0; j < rand.nextInt(5) + 1; j++) {
Course course1 = session.get(Course.class, rand.nextInt(41) + 1);
student.addCourse(course1);
}
session.getTransaction().commit();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
session.close();
factory.close();
}
注意,这里采用了随机数选出三个课程然后设置给student对象,如果三个课程里有重复的话,会报Duplicate entry错误。实际开发中肯定需要获得确定的Course id才可以,虽然也可以编写一个产生不同数字的函数,不过懒得写了。
然后是删除的部分,注意,刚才已经说了,根据现实情况,不能级联删除course和student表里的数据,只是清除对应关系,也就是清除中间表的内容。
现在course_student表里有id为22的course和41的student对象有关联,来删除课程看看:
//删除课程和关联关系
Course course = session.get(Course.class, 22);
session.delete(course);
很简单,直接删除就可以,Hibernate会同时自动到中间表里删除这个课程的id对应的记录。删除学生的操作也是一样:
//删除学生和关联关系
Student student = session.get(Student.class, 49);
session.delete(student);
只要不设置级联删除,删除都只影响本来的对象和中间表。
查询就很简单了,只要获取了对象之后显示就可以了。
Course course = session.get(Course.class, 9);
for (Student s : course.getStudents()) {
System.out.println(s);
}
//模拟通过学生查课程
Student student = session.get(Student.class, 53);
for (Course c : student.getCourses()) {
System.out.println(c);
}
至此Hibernate的基本操作结束了,其实Hibernate远不止这些内容,每一个大版本更新也有很多变化,以后再通过《Java Persistence with Hibernate, Second Edition》学习吧。