这个时候我们的外键的维护关系在一方
基础关系代码:多方学生类
package com.os.model; import javax.persistence.*; @Entity @Table(name = "jpa_student") public class Student { private Integer studentId; private String studentName; private String sex; public Student() {} public Student(String studentName, String sex) { this.studentName = studentName; this.sex = sex; } @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer getStudentId() { return studentId; } public void setStudentId(Integer studentId) { this.studentId = studentId; } @Column(name = "student_name") public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } } 复制代码
基础关系代码:一方班级类
package com.os.model; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity @Table(name = "jpa_class") public class ClassInfo { private Integer classId; private String className; private Set<Student> studentSet = new HashSet<>(); public ClassInfo(){} public ClassInfo( String className) { this.className = className; } @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer getClassId() { return classId; } public void setClassId(Integer classId) { this.classId = classId; } @Column(name = "class_name") public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } /* 映射单向 一对多 的关联关系 使用 @OneToMany 来映射 一对多 的关联关系 使用 @JoinColumn(name = "class_id") 来映射多方的外键字段名称 */ @OneToMany @JoinColumn(name = "class_id") public Set<Student> getStudentSet() { return studentSet; } public void setStudentSet(Set<Student> studentSet) { this.studentSet = studentSet; } } 复制代码
关联关系建立请看注释说明,特别说明:没有生成物理关联,原因不明
基础的测试固定代码
public class JPATest { private EntityManagerFactory factory ; private EntityManager entityManager ; private EntityTransaction tx; @Before public void init(){ factory = Persistence.createEntityManagerFactory("jpa03"); entityManager = factory.createEntityManager(); tx = entityManager.getTransaction(); tx.begin(); } @After public void close(){ tx.commit(); entityManager.close(); factory.close(); } } 复制代码
@Test public void test01(){ ClassInfo classInfo = new ClassInfo("起航班级"); Student s1 = new Student("小白","男"); Student s2 = new Student("大黄","男"); //一方维护关系 classInfo.getStudentSet().add(s1); classInfo.getStudentSet().add(s2); //先保存一方,在保存多方 entityManager.persist(classInfo); entityManager.persist(s1); entityManager.persist(s2); } 复制代码
产生了5条SQL语句
Hibernate: insert into jpa_class (class_name) values (?) Hibernate: insert into jpa_student (sex, student_name) values (?, ?) Hibernate: insert into jpa_student (sex, student_name) values (?, ?) Hibernate: update jpa_student set class_id=? where id=? Hibernate: update jpa_student set class_id=? where id=? 复制代码
@Test public void test02(){ ClassInfo classInfo = new ClassInfo("杨帆班级"); Student s1 = new Student("小智","男"); Student s2 = new Student("哒哒","男"); //一方维护关系 classInfo.getStudentSet().add(s1); classInfo.getStudentSet().add(s2); //先保存一方,在保存多方 entityManager.persist(s1); entityManager.persist(s2); entityManager.persist(classInfo); } 复制代码
产生了5条SQL语句
Hibernate: insert into jpa_student (sex, student_name) values (?, ?) Hibernate: insert into jpa_student (sex, student_name) values (?, ?) Hibernate: insert into jpa_class (class_name) values (?) Hibernate: update jpa_student set class_id=? where id=? Hibernate: update jpa_student set class_id=? where id=? 复制代码
通过上述的测试,我们能看出来使用一方维护关系是不合适的,会产生额外的UPDATE语句
@Test public void test03(){ ClassInfo classInfo =entityManager.find(ClassInfo.class,1); System.out.println(classInfo.getClassName()); for (Student s1 : classInfo.getStudentSet()){ System.out.println(s1.getStudentName()); } } 复制代码
默认情况下,关联的多方是使用懒加载的策略,我们可以通过fetch属性修改加载策略
Hibernate: select classinfo0_.id as id1_0_0_, classinfo0_.class_name as class_na2_0_0_ from jpa_class classinfo0_ where classinfo0_.id=? 起航班级 Hibernate: select studentset0_.class_id as class_id4_1_0_, studentset0_.id as id1_1_0_, studentset0_.id as id1_1_1_, studentset0_.sex as sex2_1_1_, studentset0_.student_name as student_3_1_1_ from jpa_student studentset0_ where studentset0_.class_id=? 大黄 小白 复制代码
修改策略后的SQL语句
/* 映射单向 一对多 的关联关系 使用 @OneToMany 来映射 一对多 的关联关系 使用 @JoinColumn(name = "class_id") 来映射多方的外键字段名称 */ @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "class_id") public Set<Student> getStudentSet() { return studentSet; } 复制代码
Hibernate: select classinfo0_.id as id1_0_0_, classinfo0_.class_name as class_na2_0_0_, studentset1_.class_id as class_id4_1_1_, studentset1_.id as id1_1_1_, studentset1_.id as id1_1_2_, studentset1_.sex as sex2_1_2_, studentset1_.student_name as student_3_1_2_ from jpa_class classinfo0_ left outer join jpa_student studentset1_ on classinfo0_.id=studentset1_.class_id where classinfo0_.id=? 起航班级 大黄 小白 复制代码
@Test public void test04(){ ClassInfo classInfo =entityManager.find(ClassInfo.class,1); entityManager.remove(classInfo); } 复制代码
因为有外键的约束关系,删除班级信息必须要跟对应的学生信息解除关系才能进行删除操作,所有产生的SQL语句如下
Hibernate: select classinfo0_.id as id1_0_0_, classinfo0_.class_name as class_na2_0_0_, studentset1_.class_id as class_id4_1_1_, studentset1_.id as id1_1_1_, studentset1_.id as id1_1_2_, studentset1_.sex as sex2_1_2_, studentset1_.student_name as student_3_1_2_ from jpa_class classinfo0_ left outer join jpa_student studentset1_ on classinfo0_.id=studentset1_.class_id where classinfo0_.id=? 解除关系的操作 Hibernate: update jpa_student set class_id=null where class_id=? Hibernate: delete from jpa_class where id=? 复制代码
问题:我想删除班级信息的时候,同时也删除对应的学生信息,怎么处理?
答案:使用级联cascade属性进行处理
@OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL) @JoinColumn(name = "class_id") public Set<Student> getStudentSet() { return studentSet; } 复制代码
@Test public void test04(){ ClassInfo classInfo =entityManager.find(ClassInfo.class,2); entityManager.remove(classInfo); } 复制代码
Hibernate: update jpa_student set class_id=null where class_id=? Hibernate: delete from jpa_student where id=? Hibernate: delete from jpa_student where id=? Hibernate: delete from jpa_class where id=? 复制代码
手动在数据库添加了班级信息
(1)持久化状态的班级信息,修改班级名称
@Test public void test05(){ ClassInfo classInfo =entityManager.find(ClassInfo.class,100); System.out.println(classInfo.getClassName()); classInfo.setClassName("我的班级"); } 复制代码
产生的SQL语句如下
Hibernate: select classinfo0_.id as id1_0_0_, classinfo0_.class_name as class_na2_0_0_, studentset1_.class_id as class_id4_1_1_, studentset1_.id as id1_1_1_, studentset1_.id as id1_1_2_, studentset1_.sex as sex2_1_2_, studentset1_.student_name as student_3_1_2_ from jpa_class classinfo0_ left outer join jpa_student studentset1_ on classinfo0_.id=studentset1_.class_id where classinfo0_.id=? 大白班级 Hibernate: update jpa_class set class_name=? where id=? 复制代码
(2)持久化状态的班级信息,修改学生性别为女
@Test public void test05(){ ClassInfo classInfo =entityManager.find(ClassInfo.class,100); System.out.println(classInfo.getClassName()); classInfo.setClassName("我的班级"); } 复制代码
产生的SQL语句如下
Hibernate: select classinfo0_.id as id1_0_0_, classinfo0_.class_name as class_na2_0_0_, studentset1_.class_id as class_id4_1_1_, studentset1_.id as id1_1_1_, studentset1_.id as id1_1_2_, studentset1_.sex as sex2_1_2_, studentset1_.student_name as student_3_1_2_ from jpa_class classinfo0_ left outer join jpa_student studentset1_ on classinfo0_.id=studentset1_.class_id where classinfo0_.id=? Hibernate: update jpa_student set sex=?, student_name=? where id=? Hibernate: update jpa_student set sex=?, student_name=? where id=? 复制代码
(3)游离状态的班级信息,并且多方使用延迟加载
@OneToMany/*(fetch = FetchType.EAGER,cascade = CascadeType.ALL)*/ @JoinColumn(name = "class_id") public Set<Student> getStudentSet() { return studentSet; } 复制代码
@Test public void test07(){ ClassInfo classInfo =entityManager.find(ClassInfo.class,100); tx.commit(); entityManager.close(); //classInfo处于游离状态 System.out.println(classInfo.getClassName()); classInfo.setClassName("无敌班级"); entityManager = factory.createEntityManager(); tx = entityManager.getTransaction(); tx.begin(); entityManager.merge(classInfo); } 复制代码
产生的SQL语句如下
Hibernate: select classinfo0_.id as id1_0_0_, classinfo0_.class_name as class_na2_0_0_ from jpa_class classinfo0_ where classinfo0_.id=? 哈哈班级哈哈 Hibernate: select classinfo0_.id as id1_0_0_, classinfo0_.class_name as class_na2_0_0_ from jpa_class classinfo0_ where classinfo0_.id=? Hibernate: update jpa_class set class_name=? where id=? 复制代码
CascadeType.PERSIST 官方文档的说明:Cascade persist operation 看到网上很多博客对这一枚举值的解释是:级联持久化(保存)操作(持久保存拥有方实体时,也会持久保存该实体的所有相关数据。) 我的内心OS是:妈蛋。我也知道是级联persist操作啊关键是怎么操作啊。妈蛋。拥有方实体是个什么玩意儿,该实体又是个什么玩意儿。 经过实践检验,我的理解是:**给当前设置的实体操作另一个实体的权限。**这个理解可以推广到每一个CascadeType。因此,其余CascadeType枚举值将不再一一详细解释。 For example:
public class Student { @ManyToMany(cascade=CascadeType.PERSIST,fetch=FetchType.LAZY) private Set<Course> courses = new HashSet<>(); //其他代码略。 } 复制代码
可以看到,我们在上面的代码中给了Student对Course进行级联保存(cascade=CascadeType.PERSIST)的权限。此时,若Student实体持有的Course实体在数据库中不存在时,保存该Student时,系统将自动在Course实体对应的数据库中保存这条Course数据。而如果没有这个权限,则无法保存该Course数据。
CascadeType.REMOVE Cascade remove operation,级联删除操作。 删除当前实体时,与它有映射关系的实体也会跟着被删除。
CascadeType.MERGE Cascade merge operation,级联更新(合并)操作。 当Student中的数据改变,会相应地更新Course中的数据。
CascadeType.DETACH Cascade detach operation,级联脱管/游离操作。 如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。
CascadeType.REFRESH Cascade refresh operation,级联刷新操作。 假设场景 有一个订单,订单里面关联了许多商品,这个订单可以被很多人操作,那么这个时候A对此订单和关联的商品进行了修改,与此同时,B也进行了相同的操作,但是B先一步比A保存了数据,那么当A保存数据的时候,就需要先刷新订单信息及关联的商品信息后,再将订单及商品保存。(来自良心会痛的评论)
CascadeType.ALL Cascade all operations,清晰明确,拥有以上所有级联操作权限。
级联标签有以下几个属性: CascadeType.PERSIST 、CascadeType.MERGE、CascadeType.REMOVE、CascadeType.DETACH、CascadeType.ALL。 “Only the parent side of an association makes sense to cascade its entity state transitions to children.”官方文档中明确说明,只有parent端声明cascade属性有效。其中ManyToMany如果声明为CascadeType.ALL属性,在删除该实体时会抛出异常,因为另一端可能被其他实体引用。