SSH框架之Hibernate5专题4:关联关系映射

  • 关联关系,是使用最多的一种关系,非常重要。在内存中反映为实体关系,映射到DB中为主外键关系。实体间的关联,即对外键的维护。关联关系的发生,即对外键数据的改变。
  • 外键:外面的主键,即,使用其他表的主键值作为自己的某字段的取值。
  • 在一对多关联关系中,外键总是被定义在多方表中。例如,国家Country与城市City间的关系就属于一对多关联关系,外键字段一般情况下是被定义在City表中的。
    SSH框架之Hibernate5专题4:关联关系映射

    1 基本概念

    1.1 关联属性

  • java代码的实体类定义中,声明的另一个实体类类型或者其集合类型的属性,称之为关联属性。
    SSH框架之Hibernate5专题4:关联关系映射

    1.2 级联操作

  • 当对某一类的对象a进行操作,如增加、删除、修改时,同时会对另一类的某对象b进行相同的操作。此时称,对象a、b具有级联关系,对象b为对象a的级联对象。
  • 级联操作是通过映射文件中的cascade属性设置的。该属性的值较多,其介绍如下:
    1、save:在保存、更新或者删除当前对象时,忽略其他关联的对象,即不使用级联。它是默认值。
    2、save-update:当通过Session的save()、update()、saveOrUpdate()方法来保存或者更新当前对象时,将级联到其他DB中的相关联的表。
    3、delete:当通过Session的delete()方法删除当前对象时,将级联删除所有关联的对象。
    4、all:包含save-update以及delete级联的所有行为。另外,当对当前对象执行lock()操作时,也会对所有关联的持久化对象执行lock()操作。
    5、delete-orphan:删除所有和当前对象解除关联关系的对象。
    6、all-delete-orphan:包含all和delete-orphan级联的所有行为。

    1.3 关联关系维护

  • 关联关系的维护,也称之为外键维护,即为外键字段赋值。Hibernate默认情况下,关联的双方都具有维护权。即在代码中均可通过自己关联属性的set方法来建立关联关系。反映到数据库中,即是为外键字段赋值。
  • 在1:n关系中,例如Country和部长Minister的关系中:
    SSH框架之Hibernate5专题4:关联关系映射
    SSH框架之Hibernate5专题4:关联关系映射
  • Country对象可以调用自己的setMinister()方法来建立关联关系,Minister也可以调用自己的setCountry()方法来建立关联关系。
  • 不过,由于外键是建立在多方表minister中的,所以对于外键的维护方式,即为外键字段赋值的方式,一方维护与多方维护,其底层执行是不同的。
  • 若关联关系由一方维护,即Country对象执行country.setMinisters(ministers)方法,其实质是country对象为minister表的外键countryId赋值,底层是通过update语句来完成的。
  • 底层为什么是通过update来完成维护的呢?country要主动关联ministers,则需要在country对象产生之前,先在DB的minister表中将即被关联的minister先插入完成。此时DB的表中的minister的countryId字段值一定为null。当country对象产生后,需要执行update语句来修改这个minister表的countryId的值。
    SSH框架之Hibernate5专题4:关联关系映射
  • 若关联关系由多方维护,即Minister对象执行minister.setCountry(country)方法,其实质是minister对象为自己的表的外键赋值,则可在插入minister数据时一并完成,即通过insert语句来完成。
  • 为什么这里又是通过inster语句完成关联关系维护的呢?minister要主动关联country,那么在minister出现之前就需要先在DB的表中插入完毕将要被关联country,后再插入minister。所以,在Insert这个主关联对象minister的同时,将countryId的值也放入了DB中。
  • 虽然双方均具有维护权,但是一方同时具有放弃维护权的特权。通过对一方关联属性inverse="true"设置,即可放弃关联关系维护权,将维护权完全交给多方。
    SSH框架之Hibernate5专题4:关联关系映射

    1.4 预处理语句

  • 所谓预处理的语句,即该语句当前先产生,但是暂时不执行,等后面条件成熟,或者程序运行完毕再执行的语句。
  • 当一方具有关联关系的维护权,并且执行save(一方对象)时,会产生一条update预处理语句,用于维护外键值。那么,为什么这个update为预处理语句,而不是立即执行呢?因为该语句所要update的这条多方表中记录还未被插入,即还不存在。只有当这个多方对象也insert完毕后,即在多方表中出现这条语句时,才会引发预处理update的执行,将多方表中的外键字段值填上。
  • 当多方具有关联关系的维护权,并且执行save(多方对象)时,会产生一条insert预处理语句,用于维护外键值。那么,为什么这个insert也为预处理语句,而不是立即执行的呢?因为该语句所要insert的这条多方数据,其所关联的一方对象还未被插入,即还不存在。所以其外键字段值还未出现。只有当这个一方对象也insert完毕后,即在一方表中出现这条记录时,才会引发对多方对象的预处理语句insert的执行,将多方表中的外键字段值同多方表中的其他普通属性值一同插入。

    1.5 关联方向

    1.5.1 单向关联

  • 指具有关联关系的实体对象间的加载和访问关系是单向的。即只有一个实体对象可以加载和访问对象,但是对方是看不到另一方的。

    1.5.2 双向关联

  • 指具有关联关系的实体对象间的加载和访问关系是双向的。即任何一方均可加载和访问另一方。

    1.6 关联数量

  • 实体对象间的关系,从数量上可以划分为:1:1、1:n、n:1、m:n。

    2 关系映射

  • 以下双向关联举例代码中,在定义实体类的toString()方法时需要注意,对关联属性的输出,最好是只有一方进行输出,而另一方不进行关联属性输出。因为双方均进行输出,有可能出现循环引用问题,会抛出栈溢出错误StackOverflowError。

    2.1 1:n-单向关联

  • 举例:one2many_s 国家(Country)和部长(Minister)
    1、实体类中的定义

    package com.eason.hibernate.po;
    import java.util.HashSet;
    import java.util.Set;
    public class Country {
    private Integer cid;
    private String cname;
    private Set<Minister> ministers;
    
    //setter and getter()
    
    public Country(String cname) {
        this();
        this.cname = cname;
    }
    public Country() {
        super();
        ministers = new HashSet<>();
    }
    @Override
    public String toString() {
        return "Country [cid=" + cid + ", cname=" + cname + ", ministers=" + ministers + "]";
    }
    }
    package com.eason.hibernate.po;
    public class Minister {
    private Integer mid;
    private String mname;
    
    //setter and getter()
    
    public Minister(String mname) {
        super();
        this.mname = mname;
    }
    public Minister() {
        super();
        // TODO Auto-generated constructor stub
    }
    @Override
    public String toString() {
        return "Minister [mid=" + mid + ", mname=" + mname + "]";
    }
    }

    2、映射文件中的配置

  • Country类的关联属性再映射文件中配置如下:
    <class name="Country" table="t_country">
        <id name="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname"></property>
        <set name="ministers" cascade="save-update">
            <key column="country_id"></key>
            <one-to-many class="Minister"/>
        </set>
    </class>
  • set:指明name指定关联属性ministers的映射为集合映射;
  • <one-to-many>与<key>:在Minister类的映射表中产生名为country_Id的外键。注意,Minister表中并无此映射,是由这里指定生成的。one-to-many标签,指明当前类Country与class指定类Minister的关系为1:n。
  • Minister类的关联属性再映射文件中的配置如下:
    <class name="Minister" table="t_minister">
        <id name="mid">
            <generator class="native"></generator>
        </id>
        <property name="mname"></property>
    </class>

    2.1.1 代码运行分析一

    1、运行条件:Country映射文件<set>中不设置cascade="save-update",测试类中在save(country)之前,也不做save(minister)。

            Minister minister = new Minister("aaa");
            Set<Minister> ministers = new HashSet<Minister>();
            ministers.add(minister);
            Country country = new Country("USA");
            country.setMinisters(ministers);
            session.save(country);

    2、运行结果:运行报错,对象引用了一个未保存的瞬时态实例。
    SSH框架之Hibernate5专题4:关联关系映射
    3、过程分析:

  • 在save(country)时,发现inverse为false,即会产生一条update预处理语句。当执行对country的insert后,程序执行完毕,此时会执行预处理语句。而预处理语句是要对t_minister表操作,而此前无任何对Minister对象的insert语句,即DB中是不存在该对象的。该对象现只存在于普通内存中,与session无关,DB中没有,即处于瞬时态。所以报错:引用了没有保存的瞬时态实例。
    SSH框架之Hibernate5专题4:关联关系映射
    2.1.2 代码运行分析二
    1、运行条件:测试类中在save(country)之前,先执行save(minister);或者在country的映射文件中的关联属性映射中增加级联操作cascade="save-update"。
  • 测试类中在save(country)之前,先执行save(minister)。
            Minister minister = new Minister("aaa");
            Set<Minister> ministers = new HashSet<Minister>();
            ministers.add(minister);
            Country country = new Country("USA");
            country.setMinisters(ministers);
            session.save(minister);
            session.save(country);
  • 或者country的映射文件中增加cascade="save-update"。

2、运行结果:两种情况的运行过程和结果完全相同,运行均成功。


3、过程分析:

  • 先执行minister的insert,此时还没有country对象,所以,也就不会为外键赋值,仅仅为mname赋值。
  • 再执行country的insert,发现inverse为默认值false,所以产生预处理的update,并完成insert。
  • 当country的insert完成后,对于minister外键的维护条件完成,所以执行预处理的update。
    SSH框架之Hibernate5专题4:关联关系映射

    2.1.3 代码运行分析三

    1、运行条件:

  • 在country的映射文件中的关联属性映射中增加控制反转设置inverse="true";
  • 在country的映射文件中的关联属性映射中增加设置级联操作,或者在测试类中进行save(minister)。

2、运行成功,但是t_minister表中外键值为null。
SSH框架之Hibernate5专题4:关联关系映射
3、过程分析:

  • 当执行country的insert,发现inverse为true,故将外键维护权交给多方,即minister,完成country的insert。
  • 当country的insert完成后,进行级联保存minister,即要执行对minister的insert,不过,此时minister具有对外键的维护权,需要为外键赋值。但是由于是单向关联,Minister看不到Country,即没有setCountry()方法,所以只能插入mname的值,外键没有赋值,即为null。
    SSH框架之Hibernate5专题4:关联关系映射

    2.2 1:n-双向关联

  • 举例:one2many_d 国家(Country)与部长(Minister)
  • 本例中在进行两个实体定义时需要注意,若Country的toString()方法中对其关联属性mimisters进行了输出,那么Minister的toString()方法就不要再输出其关联属性country了。
    1、定义实体类:

    package com.eason.hibernate.po;
    import java.util.HashSet;
    import java.util.Set;
    
    public class Country {
        private Integer cid;
        private String cname;
        private Set<Minister> ministers;
    
        //setter and getter()
    
        public Country(String cname) {
            this();
            this.cname = cname;
        }
        public Country() {
            super();
            ministers = new HashSet<>();
        }
        @Override
        public String toString() {
            return "Country [cid=" + cid + ", cname=" + cname + ", ministers=" + ministers + "]";
        }
    
    }
    package com.eason.hibernate.po;
    
    public class Minister {
        private Integer mid;
        private String mname;
        private Country country;
    
        //setter and getter()
    
        public Minister(String mname) {
            super();
            this.mname = mname;
        }
        public Minister() {
            super();
            // TODO Auto-generated constructor stub
        }
        @Override
        public String toString() {
            return "Minister [mid=" + mid + ", mname=" + mname + "]";
        }
    }

    2、映射文件中的设置:

  • 在Minister类的映射文件中关联关系映射如下:
    <class name="Minister" table="t_minister">
        <id name="mid">
            <generator class="native"></generator>
        </id>
        <property name="mname"></property>
        <many-to-one name="country" class="Country" column="country_id"></many-to-one>
    </class>
  • 其中,name指的是关联属性;column指的是关联属性对应的关联字段,即minister表的外键字段,该字段名为country映射文件<set/>中的<key/>字段同名;class指的是关联属性所对应的类型。
  • 在Country类的映射文件中关联关系映射如下:
    <class name="Country" table="t_country">
        <id name="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname"></property>
        <set name="ministers" cascade="save-update" inverse="true">
            <key column="country_id"></key>
            <one-to-many class="Minister"/>
        </set>
    </class>
  • 一对多双向关联在设置多方的级联时需要注意,一般不设置删除级联。避免删除多方中的一个元素,而将所有内容全删。

    2.2.1 代码运行分析一

    1、运行条件:

  • 在country的映射文件中的关联属性映射中增加控制反转设置inverse="true"与级联操作cascade="save-update"。
  • 在minister的映射文件中增加<many-to-one/>标签。
  • 测试类中增加minister.setCountry(country)。
  • 测试类中值save(country),而不进行save(minister)。
                Minister minister = new Minister("aaa");
                Set<Minister> ministers = new HashSet<Minister>();
                ministers.add(minister);
                Country country = new Country("USA");
                country.setMinisters(ministers);
                minister.setCountry(country);
                session.save(country);

    2、运行结果:运行成功,表中数据正确。
    SSH框架之Hibernate5专题4:关联关系映射
    3、过程分析:

  • 当执行country的insert,发现inverse为true,故将外键维护权交给多方,即minister。完成country的insert。
  • 当country的insert完成后,进行级联保存minister,即要执行对minister的insert。代码中minister执行setCountry()方法,且minister具有外键维护权,所以在插入时直接将外键值写入DB中。

    2.2.2 代码运行分析二

    1、运行条件:

  • 在country的映射文件中的关联属性映射中增加控制反转设置“inverse="true"与级联操作。
  • 在minister的映射文件中增加<many-to-one/>标签中增加级联操作。
  • 测试类中只save(minister),而不进行save(country)。
    <class name="Country" table="t_country">
        <id name="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname"></property>
        <set name="ministers" cascade="save-update" inverse="true">
            <key column="country_id"></key>
            <one-to-many class="Minister"/>
        </set>
    </class>
    <class name="Minister" table="t_minister">
        <id name="mid">
            <generator class="native"></generator>
        </id>
        <property name="mname"></property>
        <many-to-one name="country" class="Country" column="country_id" cascade="save-update"></many-to-one>
    </class>
    Minister minister = new Minister("aaa");
                Set<Minister> ministers = new HashSet<Minister>();
                ministers.add(minister);
                Country country = new Country("USA");
                country.setMinisters(ministers);
                minister.setCountry(country);
                session.save(minister);

    2、运行结果:运行成功,表中的数据正确。
    SSH框架之Hibernate5专题4:关联关系映射
    3、过程分析:

  • 当执行minister的insert,发现外键维护权交由自己,即多方控制,故需要将外键值和普通数据一起插入DB。而此时尚无外键关联的对象Country,所以先将minister的insert语句变成预处理语句存起来等条件成熟再执行。故真正执行的是其级联的对Country的insert。
  • 对country的insert执行完毕,预处理insert执行条件完成,执行该预处理insert语句。

    2.3 自关联

  • 所谓自关联是指,机子即充当一方,又充当多方,是1:n的变型。例如,对于新闻栏目Column,可以充当一方,即父栏目,也可以充当多方,即子栏目。而反映到DB表中,只有一张表,这张表中具有一个外键,用于表示该栏目的父栏目。一级栏目的外键值为NULL,而子栏目则具有外键值。
    SSH框架之Hibernate5专题4:关联关系映射
  • 举例:one2many_oneself

    2.3.1 定义实体类

    package com.eason.hibernate.po;
    import java.util.HashSet;
    import java.util.Set;
    public class NewColumn {
        private Integer id;
        private String name;   //栏目名称
        private String content;   //栏目内容
        private NewColumn parentNewColumn;  //父栏目
        private Set<NewColumn> childrenNewColumn;  //子栏目
    
        public NewColumn() {
            childrenNewColumn = new HashSet<NewColumn>();
        }
    
        public NewColumn(String name) {
            this();
            this.name = name;
        }
    
        //setter and getter()
    
        @Override
        public String toString() {
            return "NewColumn [id=" + id + ", name=" + name + ", content=" + content + ", parentNewColumn="
                    + parentNewColumn + "]";
        }
    }

    2.3.2 定义映射文件

    <class name="NewColumn" table="t_column">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="name"></property>
        <property name="content"></property>
        <!-- 多方关联属性,多对一映射 -->
        <many-to-one name="parentNewColumn" class="NewColumn" column="pid" cascade="save-update"></many-to-one>
        <!-- 一方关联属性 -->
        <set name="childrenNewColumn" cascade="save-update">
            <key column="pid"></key>
            <one-to-many class="NewColumn"/>
        </set>
    </class>

    2.3.3 定义测试类

        NewColumn footballNewColumn = new NewColumn("足球栏目");
        footballNewColumn.setContent("足球栏目足球栏目足球栏目");
    
        NewColumn basketballNewColumn = new NewColumn("篮球栏目");
        basketballNewColumn.setContent("篮球栏目篮球栏目篮球栏目");
    
        NewColumn sportsNewColumn = new NewColumn("体育栏目");
        sportsNewColumn.setContent("体育栏目体育栏目体育栏目");
        sportsNewColumn.getChildrenNewColumn().add(footballNewColumn);
        sportsNewColumn.getChildrenNewColumn().add(basketballNewColumn);
    
        session.save(sportsNewColumn);

    2.4 n:1单向关联

  • 举例:many2one_s2 部长(Minister)和国家(Country)

    2.4.1 定义实体类

    package com.eason.hibernate.po;
    
    public class Minister {
        private Integer mid;
        private String mname;
        private Country country;
    
        //setter and getter()
    
        public Minister(String mname) {
            super();
            this.mname = mname;
        }
        public Minister() {
            super();
            // TODO Auto-generated constructor stub
        }
        @Override
        public String toString() {
            return "Minister [mid=" + mid + ", mname=" + mname + ", country=" + country + "]";
        }
    }
    package com.eason.hibernate.po;
    import java.util.HashSet;
    import java.util.Set;
    public class Country {
        private Integer cid;
        private String cname;
    
        //setter and getter()
    
        public Country(String cname) {
            this.cname = cname;
        }
        public Country() {
            super();
        }
    }

    2.4.2 映射文件中的设置

  • 在Minister类的映射文件中关联关系映射如下:
    <class name="Minister" table="t_minister">
        <id name="mid">
            <generator class="native"></generator>
        </id>
        <property name="mname"></property>
        <many-to-one name="country" class="Country" column="country_id" cascade="save-update"></many-to-one>
    </class>
  • 在Country类的映射文件中关联关系映射如下:
    <class name="Country" table="t_country">
        <id name="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname"></property>
    </class>

    2.4.3 定义测试类

        Minister minister = new Minister("aaa");
        Country country = new Country("USA");
        minister.setCountry(country);
        session.save(minister);

    2.5 n:m-单向关联

  • 举例:many2many_s 学生(Student)与课程(Course)
  • 多对多的关联关系是通过增加一个中间表的方式来实现的。如,本例增加了选课表t_middle作为中间表。

    2.5.1 定义实体类

    package com.eason.hibernate.po;
    import java.util.HashSet;
    import java.util.Set;
    public class Student {
        private Integer sid;
        private String sname;
        private Set<Course> courses;
    
        public Student() {
            courses = new HashSet<>();
        }
        public Student(String sname) {
            this();
            this.sname = sname;
        }
    
        //setter and getter()
    
        public Student(Integer sid, String sname, Set<Course> courses) {
            super();
            this.sid = sid;
            this.sname = sname;
            this.courses = courses;
        }
    }
    package com.eason.hibernate.po;
    public class Course {
        private Integer cid;
        private String cname;
    
        public Course() {
        }
        public Course(String cname) {
            super();
            this.cname = cname;
        }
        @Override
        public String toString() {
            return "Course [cid=" + cid + ", cname=" + cname + "]";
        }
    
        //setter and getter()
    }

    2.5.2 映射文件中的设置

  • 在Student类的映射文件中关联关系映射如下:
    <class name="Student" table="t_student">
        <id name="sid">
            <generator class="native"></generator>
        </id>
        <property name="sname"></property>
        <set name="courses" table="t_middle" cascade="save-update">
            <key column="student_id"></key>
            <many-to-many class="Course" column="course_id"></many-to-many>
        </set>
    </class>
  • 在Course类的映射文件中关联关系映射如下:

    <class name="Course" table="t_course">
        <id name="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname"></property>
    </class>

    2.5.3 定义测试类

            Course course1 = new Course("Struts2");
            Course course2 = new Course("Hibernate");
    
            Student student = new Student("aaa");
            student.getCourses().add(course1);
            student.getCourses().add(course2);
            session.save(student);

    2.6 n:m-双向关联

  • 举例:many2many_d 学生与课程(Course)。
  • 多对多的双向关联,使得双方地位完全相同。由于双方配置相同,所以在测试类中只要设置好了关联关系,对哪一方进行save()操作均可完成对双方的保存。

    2.6.1 定义实体类

    package com.eason.hibernate.po;
    import java.util.HashSet;
    import java.util.Set;
    public class Student {
        private Integer sid;
        private String sname;
        private Set<Course> courses;
    
        public Student() {
            courses = new HashSet<>();
        }
        public Student(String sname) {
            this();
            this.sname = sname;
        }
    
     //setter and getter()
    
        public Student(Integer sid, String sname, Set<Course> courses) {
            super();
            this.sid = sid;
            this.sname = sname;
            this.courses = courses;
        }
    }
    package com.eason.hibernate.po;
    
    import java.util.HashSet;
    import java.util.Set;
    
    public class Course {
        private Integer cid;
        private String cname;
        private Set<Student> students;
    
        public Course() {
            students = new HashSet<>();
        }
        public Course(String cname) {
            this();
            this.cname = cname;
        }
        @Override
        public String toString() {
            return "Course [cid=" + cid + ", cname=" + cname + "]";
        }
    
        //setter and getter()
    }

    2.6.2 定义配置文件

  • 在Student类的映射文件中关联关系映射如下:
    <class name="Student" table="t_student">
        <id name="sid">
            <generator class="native"></generator>
        </id>
        <property name="sname"></property>
        <set name="courses" table="t_middle" cascade="save-update">
            <key column="student_id"></key>
            <many-to-many class="Course" column="course_id"></many-to-many>
        </set>
    </class>
  • 在Course类的映射文件中关联关系映射如下:
    <class name="Course" table="t_course">
        <id name="cid">
            <generator class="native"></generator>
        </id>
        <property name="cname"></property>
        <set name="students" table="t_middle" cascade="save-update">
            <key column="course_id"></key>
            <many-to-many class="Student" column="student_id"></many-to-many>
        </set>
    </class>

    2.6.3 定义测试类

            Course course = new Course("Spring");   
            Student student = new Student("aaa");
            course.getStudents().add(student);
            session.save(course);

转载于:https://blog.51cto.com/12402717/2086393

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值