@ManyToMany要求两个张表之间有关联表,与@oneToMany一样,@ManyToMany也具有单向和双向关联。

单向@ManyToMany保存

@Entity
@Table
public class User extends AbstractEntity implements Serializable {

	private static final long serialVersionUID = 1L;

	@Column(name = "name")
	private String name;

	@Column(name = "email", nullable = false, length = 250)
	private String email;
	
	@NotNull
	@Range(max = 150, min = 1)
	@Column
	private Integer age;
	
	@CreationTimestamp
	@Column
	@Temporal(TemporalType.TIMESTAMP)
	private Date createDatetime;
	
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "unit_id")
	private Unit unit;
	
	@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
	private List<Address> addresses = new ArrayList<>();

    // Getters and setters are omitted for brevity
}
@Entity
@Table
public class Address extends AbstractEntity implements Serializable {

	private static final long serialVersionUID = 1L;

	@Column
	private String number;
	
	@Column
	private String street;

    // Getters and setters are omitted for brevity
}
User user1 = new User("user1", "user1@163.com", 22, null);
		
User user2 = new User("user2", "user2@163.com", 32, null);

Address address1 = new Address("number1", "street1");
Address address2 = new Address("number2", "street2");

user1.getAddresses().add(address1);
user1.getAddresses().add(address2);

user2.getAddresses().add(address1);

entityManager.persist(user1);
entityManager.persist(user2);

entityManager.flush();

user1.getAddresses().remove(address1);

entityManager.flush();

Hibernate将执行以下SQL语句:

insert into user (id, age, create_datetime, email, name, unit_id) values (null, ?, ?, ?, ?, ?)
insert into address (id, number, street) values (null, ?, ?)
insert into address (id, number, street) values (null, ?, ?)
insert into user (id, age, create_datetime, email, name, unit_id) values (null, ?, ?, ?, ?, ?)
insert into user_addresses (user_id, addresses_id) values (?, ?)
insert into user_addresses (user_id, addresses_id) values (?, ?)
insert into user_addresses (user_id, addresses_id) values (?, ?)
delete from user_addresses where user_id=?
insert into user_addresses (user_id, addresses_id) values (?, ?)

数据库表关系:

image-20200523191803243

就像@OneToMany关联一样,中间表由拥有方控制。

@ManyToMany删除集合中的实体时,Hibernate只需删除中间表加入的记录。可惜此操作需要删除与给定父级关联的所有记录,并重新创建当前正在运行的持久性上下文中列出的记录。

GitHub

单向@ManyToMany删除

User user = entityManager.find(User.class, 1);
entityManager.remove(user);
entityManager.flush();

Hibernate将执行以下SQL语句:

select user0_.id as id1_13_0_, user0_.age as age2_13_0_, user0_.create_datetime as create_d3_13_0_, user0_.email as email4_13_0_, user0_.name as name5_13_0_, user0_.unit_id as unit_id6_13_0_ from user user0_ where user0_.id=?
delete from user_addresses where user_id=?
delete from user where id=?

通过简单的删除父实体,Hibernate可以安全地删除关联的记录。

GitHub

双向@ManyToMany保存

@Table
@Entity
public class Teacher extends AbstractEntity implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@Column
	private String name;
	
	@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
	private List<Course> courses = new ArrayList<>();
	
	// Getters and setters are omitted for brevity
	
	public void addCourse(Course course) {
		courses.add(course);
		course.getTeachers().add(this);
	}

	public void removeCourse(Course course) {
		courses.remove(course);
		course.getTeachers().remove(this);
	}

	@Override
	public boolean equals(Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		}
		Teacher teacher = (Teacher) o;
		return Objects.equals(getId(), teacher.getId());
	}

	@Override
	public int hashCode() {
		return Objects.hash(getId());
	}
}
@Table
@Entity
public class Course extends AbstractEntity implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@Column
	private String name;
	
	@ManyToMany(mappedBy = "courses")
	private List<Teacher> teachers = new ArrayList<>();
	
    // Getters and setters are omitted for brevity
	
    public void addTeacher(Teacher teacher) {
		teachers.add(teacher);
		teacher.getCourses().add(this);
	}

	public void removeTeacher(Teacher teacher) {
		teachers.remove(teacher);
		teacher.getCourses().remove(this);
	}
    
	@Override
	public boolean equals(Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		}
		Course course = (Course) o;
		return Objects.equals(getId(), course.getId());
	}

	@Override
	public int hashCode() {
		return Objects.hash(getId());
	}
}
Teacher teacher1 = new Teacher("teacher1");
Teacher teacher2 = new Teacher( "teacher2");

Course course1 = new Course("course1");
Course course2 = new Course("course2");

teacher1.addCourse(course1);
teacher1.addCourse(course2);

teacher2.addCourse(course2);


entityManager.persist(teacher1);
entityManager.persist(teacher2);

entityManager.flush();

teacher1.removeCourse(course1);

entityManager.flush();

Hibernate将执行以下SQL语句:

insert into teacher (id, name) values (null, ?)
insert into course (id, name) values (null, ?)
insert into course (id, name) values (null, ?)
insert into teacher (id, name) values (null, ?)
insert into teacher_courses (teachers_id, courses_id) values (?, ?)
insert into teacher_courses (teachers_id, courses_id) values (?, ?)
insert into teacher_courses (teachers_id, courses_id) values (?, ?)
delete from teacher_courses where teachers_id=?
insert into teacher_courses (teachers_id, courses_id) values (?, ?)

数据库表关系:

image-20200524163148817

双向@ManyToMany关联由父实体和mappedBy方维护关系。为了保持双方之间的同步,可以提供用于添加或删除子实体的辅助方法。使用辅助方法addCourseremoveCourse,可以简化同步操作。

@OneToMany在删除或更改子实体时性能比@ManyToMany更好,由于@ManyToMany外键不受控制,为了防止此种情况,必须配置中间表实体,并将@ManyToMany关联分为两个双向@OneToMany关系。

GitHub

具有中间表实体的双向多对多

@Table
@Entity
public class Student extends AbstractEntity implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@Column
	private String name;
	
	@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
	private List<StudentCourse> studentCourses = new ArrayList<>();

	// Getters and setters are omitted for brevity
	
	public void addCourse(Course course) {
		StudentCourse studentCourse = new StudentCourse(this, course);
		studentCourses.add(studentCourse);
		course.getStudentCourses().add(studentCourse);
	}

	public void removeCourse(Course course) {
		StudentCourse studentCourse = new StudentCourse(this, course);
		course.getStudentCourses().remove(studentCourse);
		studentCourses.remove(studentCourse);
		studentCourse.setCourse(null);
		studentCourse.setStudent(null);
	}

	@Override
	public boolean equals(Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		}
		Student student = (Student) o;
		return Objects.equals(getId(), student.getId());
	}

	@Override
	public int hashCode() {
		return Objects.hash(getId());
	}
}
@Table
@Entity
public class StudentCourse implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@Id
	@ManyToOne
	private Student student;
	
	@Id
	@ManyToOne
	private Course course;

	// Getters and setters are omitted for brevity
	
	@Override
	public boolean equals(Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		}
		StudentCourse that = (StudentCourse) o;
		return Objects.equals(student, that.getStudent()) &&
				Objects.equals(course, that.getCourse());
	}

	@Override
	public int hashCode() {
		return Objects.hash(student, course);
	}
}
@Table
@Entity
public class Course extends AbstractEntity implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@Column
	private String name;
	
	@ManyToMany(mappedBy = "courses", cascade = {CascadeType.MERGE, CascadeType.PERSIST})
	private List<Teacher> teachers = new ArrayList<>();
	
	@OneToMany(mappedBy = "course", cascade = CascadeType.ALL, orphanRemoval = true)
	private List<StudentCourse> studentCourses = new ArrayList<>();
	
	// Getters and setters are omitted for brevity

	@Override
	public boolean equals(Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		}
		Course course = (Course) o;
		return Objects.equals(getId(), course.getId());
	}

	@Override
	public int hashCode() {
		return Objects.hash(getId());
	}
}
Student student1 = new Student("student1");
Student student2 = new Student("student2");

Course course1 = new Course("course1");
Course course2 = new Course("course2");

entityManager.persist(student1);
entityManager.persist(student2);

entityManager.persist(course1);
entityManager.persist(course2);

student1.addCourse(course1);
student1.addCourse(course2);

student2.addCourse(course1);

entityManager.flush();

student1.removeCourse(course1);

entityManager.flush();

Hibernate将执行以下SQL语句:

insert into student (id, name) values (null, 'student1')
insert into student (id, name) values (null, 'student2')
insert into course (id, name) values (null, 'course1')
insert into course (id, name) values (null, 'course2')
insert into student_course (student_id, course_id) values (1, 1)
insert into student_course (student_id, course_id) values (1, 2)
insert into student_course (student_id, course_id) values (2, 1)
delete from student_course where student_id=1 and course_id=1

数据库表关系:

image-20200524233638322

无论是Student还是Course都设置了 @OneToManymappedBy属性,而StudentCourse则设置了StudentCourse@ManyToOne。因为此映射是由两个双向关联构成的,所以辅助方法addCourseremoveCourse更加简化了同步操作。

与之前的双向@ManyToMany情况相比,可以更好地管理实体状态,删除实体时仅执行一条delete语句。

GitHub