目录结构,看图:
Student.java
package cn.itcast.bean;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
@Entity
public class Student {
private Integer id;
private String name;
private Set<Teacher> teachers = new HashSet<Teacher>();
public Student() {
}
public Student(String name) {
this.name = name;
}
@Id
@GeneratedValue
// id作为实体标识符,并且采用数据库的id自增长方式生成主键值。
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(length = 10, nullable = false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToMany(cascade = CascadeType.REFRESH)
@JoinTable(name = "student_teacher", inverseJoinColumns = @JoinColumn(name = "teacher_id"), joinColumns = @JoinColumn(name = "student_id"))
public Set<Teacher> getTeachers() {
return teachers;
}
/*
假如不对关联表里的字段做任何设定,那么表里面的字段默认由JPA的实现产品来帮我们自动生成。
inverseJoinColumns:inverse中文是反转的意思,但是觉得太恶心了,在JPA里,可以理解为被维护端。
inverseJoinColumns:被维护端外键的定义。
@JoinColumn:外键名称(中间表跟teacher表的主键关联的那个外键名称)。
joinColumns:关系维护端的定义。
*/
public void setTeachers(Set<Teacher> teachers) {
this.teachers = teachers;
}
public void addTeacher(Teacher teacher) {
this.teachers.add(teacher);
}
public void removeTeacher(Teacher teacher) {
if(this.teachers.contains(teacher)){ //凭什么判断teacher在集合teachers中呢?是根据teacher的id。这就要求必要重写Teacher.java的hasCode和equals方法,通过这两个方法来判断对象是否相等。
this.teachers.remove(teacher);
}
}
}
Teacher.java
package cn.itcast.bean;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
@Entity
public class Teacher {
private Integer id;
private String name;
private Set<Student> students = new HashSet<Student>();
public Teacher() {
}
public Teacher(String name) {
this.name = name;
}
@Id
@GeneratedValue
// id作为实体标识符,并且采用数据库的id自增长方式生成主键值。
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(length = 10, nullable = false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToMany(cascade = CascadeType.REFRESH, mappedBy = "teachers")
public Set<Student> getStudents() {
return students;
}
/*
cascade: CascadeType.PERSIST:级联保存不要,学生没来之前,老师就已经在了。
CascadeType.MERGE:级联更新不要,把学生的信息改了,没必要修改相应的老师的信息,压根就没这业务需求。
CascadeType.REMOVE:级联删除更不要,如果双方都设了级联删除,加入删除学生,会删除相应的老师,被删除的老师又跟学生发生千丝万缕的关系,又把一批学生删掉.....没完没了...最终的结果可能是数据里面所有的记录都被清掉。
所以在多对多关系中,级联删除通常是用不上的 这里只需设置级联涮新CascadeType.PERSIST就可以了,事实上refresh方法也很少使用。
mappedBy: 通过这个属性来说明老师是关系被维护端 fetch: 加载行为默认是延迟加载(懒加载),凭Many。 这里不需要设置。
*/
public void setStudents(Set<Student> students) {
this.students = students;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
//判断的依据是,如果id不为null的话,就返回id的哈希码。
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Teacher other = (Teacher) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
ManyToManyTest.java
package junit.test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.junit.BeforeClass;
import org.junit.Test;
import cn.itcast.bean.Student;
import cn.itcast.bean.Teacher;
public class ManyToManyTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@Test
public void save() {
EntityManagerFactory factory = Persistence
.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
em.persist(new Student("小张同学"));
em.persist(new Teacher("李勇老师"));
em.getTransaction().commit();
em.close();
factory.close();
}
/*
* 建立学生跟老师的关系
*/
@Test
public void buildTS() {
EntityManagerFactory factory = Persistence
.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.find(Student.class, 1); // 首先要得到学生,因为学生是关系维护端,通过关系维护端来建立关系。
student.addTeacher(em.getReference(Teacher.class, 1)); //这方法在业务意义上,就代表建立跟老师的关系。
//所谓建立跟老师的关系,无非就是把老师加进集合里面去。
//建立关系,体现在JDBC上面,就是添加一条记录进中间表。
em.getTransaction().commit();
em.close();
factory.close();
}
/*
* 解除学生跟老师的关系
*/
@Test
public void deleteTS() {
EntityManagerFactory factory = Persistence
.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.find(Student.class, 1); // 首先要得到学生,因为学生是关系维护端,通过关系维护端来建立关系。
student.removeTeacher(em.getReference(Teacher.class, 1)); //这方法在业务意义上,就代表解除跟老师的关系。
//所谓解除跟老师的关系,无非就是把老师从集合里面删去。
//解除关系,体现在JDBC上面,就是在中间表删除一条记录。
em.getTransaction().commit();
em.close();
factory.close();
}
/*
* 删除老师,老师已经跟学生建立起了关系(错误写法)
*/
@Test
public void deleteTeacher1() {
EntityManagerFactory factory = Persistence
.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
em.remove(em.getReference(Teacher.class, 1));
//并不需要发生数据装载行为,只需要一个托管状态的实体,所以用getReference可以提供性能。
em.getTransaction().commit();
em.close();
factory.close();
}
/*
该方法会出错,因为中间表中已经有记录了,会抛出以下错误:
Caused by: java.sql.BatchUpdateException:
Cannot delete or update a parent row: a foreign key constraint fails
(`itcast/student_teacher`, CONSTRAINT `FKD4E389DE1D49449D` FOREIGN KEY (`teacher_id`)
REFERENCES `teacher` (`id`))
关系被维护端没有权力更新外键,所以不会删除中间表的记录。
*/
/*
* 删除老师,老师已经跟学生建立起了关系(正确写法)
*/
@Test
public void deleteTeacher2() {
EntityManagerFactory factory = Persistence
.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.find(Student.class, 1);
Teacher teacher = em.getReference(Teacher.class, 1);
//并不需要发生数据装载行为,只需要一个托管状态的实体,所以用getReference可以提供性能。
student.removeTeacher(teacher);
//student是关系维护端,有权利删除外键,只要在对象中删除了teacher,那么中间表中相关外键记录也就被删除了。
//想要删除teacher记录,必须先通过student解除关系才行。
em.remove(teacher);
em.getTransaction().commit();
em.close();
factory.close();
}
/*
* 删除学生,老师已经跟学生建立起了关系
*/
@Test
public void deleteStudent() {
EntityManagerFactory factory = Persistence
.createEntityManagerFactory("itcast");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student student = em.getReference(Student.class, 1);
em.remove(student); //这样是可以删除学生的,尽管目前是有关系,中间表有关联记录,有外键约束。但是我们现在要删除的是关系维护端,关系维护端是有权力对外键进行增删改查操作的。删除的时候,Hibernate会判断student是关系维护端,然后去中间表把关联记录先删除掉。再删掉student对象。
em.getTransaction().commit();
em.close();
factory.close();
}
}