阅读 87

【JPA专题】07.映射关系_一对多单向

映射关系

B.一对多的单向映射关系

这个时候我们的外键的维护关系在一方

基础关系代码:多方学生类

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();
    }
}
复制代码

1.先保存一方再保存多方

@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=?
复制代码

2.先保存多方在保存一方

    @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语句

3.获取某个班级和学生信息

@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=?
起航班级
大黄
小白
复制代码

4.删除某个班级

@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=?
复制代码

5.更新班级信息

手动在数据库添加了班级信息

(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:来自网络

  • 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属性,在删除该实体时会抛出异常,因为另一端可能被其他实体引用。

关注下面的标签,发现更多相似文章
评论