分享知识-快乐自己:Hibernate 关联映射

关联关系映射--概念:

关联关系是使用最多的一种关系,非常重要。在内存中反映为实体关系,映射到DB中为主外键关系。

实体间的关联,即对外键的维护。关联关系的发生,即对外键数据的改变。

外键:外面的主键,即,使用其它表的主键值作为自已的某字段的取值。

1) 基本概念:

  关联属性:Java代码的实体类定义中,声明的另一个实例类类型或其集合类型的属性,称为关联属性。

  关联关系维护:关联关系的维护,也称为外键维护,即为外键字段赋值。Hibernate默认情况下,关联的双方都具有维护权。即在代码中均可通过调用自己关联属性的set方法来建立关联关系。反映到数据库中,即是为外键字段赋值。

         不过,由于外键是建立在多方表中的,所以对于外键的维护方式,即为外键字段赋值的方式,一方维护与多方维护,其底层执行是不同的。

         若关联关系由一方维护,只能通过update语句来完成。若关联关系由多方维护,通过insert语句来完成。

         虽然双方均具有维护权,但一方同时具有放弃维护权的能力。通过对一方关联属性inverse=“true”设置,即可放弃关联关系维护权,将维护权完全交给多方。

  预处理语句:所谓预处理语句,即当前先不执行,等后面条件成熟,或程序运行完毕再执行的语句。当一方具有关联关系的维护权,并且执行save(一方对象)时,会产生一条update预处理语句,用于维护外键值。

        当多方具有关联关系的维护权,并且执行save(多方对象)时,会产生一条insert预处理语句,用于维护外键值。

  级联关系:当对某一类的对象a进行操作,如增加、删除、修改时,同时会自动对另一类的某对象b进行相同的操作。此时称,对象a、b具有级联关系,对象b为对象a的级联对象。

       级联操作是通过映射文件的cascade属性设置的。

       该属性的值较多,其介绍如下:

       none:在保存、更新或删除当前对象时,忽略其他关联的对象,即不使用级联。它是默认值。

       save-update:当通过Session的save()、update()、saveOrUpdate()方法来保存或更新当前对象时,将级联到其他DB中的相关联的表。

       delete:当通过Session的delete()方法删除当前对象时,将级联删除所有关联的对象。

       all:包含save-update及delete级联的所有行为。另外,当对当前对象执行lock()操作时,也会对所有关联的持久化对象执行lock()操作。

       delete-orphan:删除所有和当前对象解除关联关系的对象。

       all-delete-orphan:包含all和delete-orphan级联的所有行为。

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

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

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

总结底层执行:

  一对多关系中,一方维护关联关系,先插入多方数据,后插入一方数据,最后update多方表中的外键值(???是否正确);

  多方维护关联关系,先插入一方数据,后插入多方数据,在插入多方数据的同时插入外键值;

  多对多关系中,哪一方维护关联关系,就是哪一方数据先插入,再插入关联方数据,最后插入中间表数据。

建立(多对一、一对多)关系:

我们以用户和城市为例:

多对一:需要在 用户 User 实体类中定义一个 Address 类属性; 

一对多:需要在 城市类中定义一个 Set<User> 集合元素属性;

User 用户类:

package com.mlq.bena;
import java.io.Serializable;
/**
 * 用户类
 * @author asus
 */
public class User implements Serializable {
    private Integer uId;
    private String userName;
    private String userPwd;
    private String realName;
    private Address address;
    private Integer dId;
    @Override
    public String toString() {
        return "User{" +
                "uId=" + uId +
                ", userName='" + userName + '\'' +
                ", userPwd='" + userPwd + '\'' +
                ", realName='" + realName + '\'' +
                ", address=" + address +
                '}';
    }
    public Integer getdId() {
        return dId;
    }
    public void setdId(Integer dId) {
        this.dId = dId;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public Integer getuId() {
        return uId;
    }
    public void setuId(Integer uId) {
        this.uId = uId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserPwd() {
        return userPwd;
    }
    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }
    public String getRealName() {
        return realName;
    }
    public void setRealName(String realName) {
        this.realName = realName;
    }
}

Address 城市类:

package com.mlq.bena;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
 * 城市类
 * @author asus
 */
public class Address implements Serializable {
  private Integer dId;
  private String addres;
  private Set<User> list;
  public Set<User> getList() {
    return list;
  }
  public void setList(Set<User> list) {
    this.list = list;
  }
  @Override
  public String toString() {
    return "Address{" +
            "dId=" + dId +
            ", addres='" + addres + '\'' +
            '}';
  }
  public Address() {
  }
  public Integer getdId() {
    return dId;
  }
  public void setdId(Integer dId) {
    this.dId = dId;
  }
  public String getAddres() {
    return addres;
  }
  public void setAddres(String addres) {
    this.addres = addres;
  }
}

user.hbm.xml 文件主要内容:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--
package: 需要映射的实体类所在包
class节点:
name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名

table:数据库中的表名,如果表名和实体类名一致,可以省略
id节点:表中的主键
generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库
name:  必须和实体类中的属性名一致
column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略
-->
<!--dynamic-update="false":默认非动态更新-->
<hibernate-mapping package="com.mlq.bena">
    <class name="com.mlq.bena.User" table="`user`" dynamic-update="true">
        <id name="uId" column="uId">
            <!--<generator class="assigned"/>&lt;!&ndash;主键生成策略&ndash;&gt;-->
            <generator class="increment"></generator>
        </id>
        <property name="userName" column="userName"/>
        <property name="userPwd" column="userPwd"/>
        <property name="realName" column="realName"/>
        <!--多对一:给User内置对象映射-->
        <many-to-one name="address" column="`dId`" class="com.mlq.bena.Address" />
    </class>
</hibernate-mapping>
<!--Oracle序列增长-->
<!--<generator class="squence">-->
<!--<param name="squence">SEQ_DEPTNO</param>-->
<!--</generator>-->
<!--数据库中查询最大值+1-->
<!--<generator class="increment"></generator>-->
<!--没有制定使用那种方式:需要结合数据库方言自增-->
<!--<generator class="native"></generator>-->
<!--uuid:生成32位字符串-->
<!--<generator class="uuid"></generator>-->

address.hbm.xml 主要配置内容:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--
package: 需要映射的实体类所在包
class节点:
name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名

table:数据库中的表名,如果表名和实体类名一致,可以省略
id节点:表中的主键
generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库
name:  必须和实体类中的属性名一致
column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略
-->
<!--dynamic-update="false":默认非动态更新-->
<hibernate-mapping package="com.mlq.bena">
    <class name="com.mlq.bena.Address" table="`address`" dynamic-update="true">
        <id name="dId" column="dId">
            <generator class="increment"></generator>
        </id>
        <property name="addres" column="addres"/>
        <set name="list">
            <key column="dId"></key>
            <one-to-many class="com.mlq.bena.User"/>
        </set>
    </class>
</hibernate-mapping>

<many-to-one>元素建立了User表的外键 dId 和 address属性之间的映射。它包括以下属性:

1):name:设定持久化类的属性名,此处为User类的address属性。

2):column:设定持久化的属性对应的外键,此处为 user 表的外键 dId。

3):class:设定持久化类的属性的类型,此处设定address 为Address类型。

到此,User类到Address类的单向多对一映射就完成了。

 

<set>元素的name属性:设定持久化的属性名,此处为Address类的user 属性。

<set>元素:还包含两个子元素:

  <key>元素:cloumn 属性设定与所关联的持久化类相对应的表的外键,此处为User 表的did

<one-to-many>元素:class属性设定所关联的持久化类型,此处为 User类

 

Hibernate 根据以上映射代码获得以下信息:

<set> 元素表明 Address 类的 list 属性为 java.uitl.Set 集合类型。

<one-to-many> 子元素表明 list 集合中存放的是一组 User 对象。

<key>子元素表明 User 表通过外键 dId参照 Address 表。

 

双向关联下的级联操作:cascade 属性:

级联(cascade):

  当Hibernate持久化一个临时对象时,在默认情况下,它不会自动持久化所关联的其他临时对象,而是会抛出TransientObjectException。如果设定many-to-one元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。

如:

<many-to-one 
    name="customer"
    column="CUSTOMER_ID" 
    class="..Customer"
    cascade="save-update"      
    not-null="true" 
/>

级联指的是当主控方执行操作时,关联对象(被动方)是否同步执行同一操作。

Inverse 属性:(维护主外键关系权)

inverse属性 :

  inverse所描述的是对象之间关联关系的维护方式。

  inverse只存在于集合标记的元素中 。

  Hibernate提供的集合元素包括<set/> <map/> <list/> <array /> <bag />

  Inverse属性的作用是:是否将对集合对象的修改反映到数据库中。

  inverse属性的默认值为false,表示对集合对象的修改会被反映到数据库中;inverse=false 的为主动方,由主动方负责维护关联关系。

  inverse=”true” 表示对集合对象的修改不会被反映到数据库中。

为了维持两个实体类(表)的关系,而添加的一些属性,该属性可能在两个实体类(表)或者在一个独立的表里面,这个要看这双方直接的对应关系了: 这里的维护指的是当主控放进行增删改查操作时,会同时对关联关系进行对应的更新。

一对多:

  该属性在多的一方。应该在一方的设置 inverse=true ,多的一方设置 inverse=false(多的一方也可以不设置inverse属性,因为默认值是false),这说明关联关系由多的一方来维护。

  如果要一方维护关 系,就会使在插入或是删除"一"方时去update"多"方的每一个与这个"一"的对象有关系的对象。

  而如果让"多"方面维护关系时就不会有update 操作,因为关系就是在多方的对象中的,直指插入或是删除多方对象就行了。显然这样做的话,会减少很多操作,提高了效率。

注:

  单向one-to-many关联关系中,不可以设置inverse="true",因为被控方的映射文件中没有主控方的信息。

多对多:

   属性在独立表中。inverse属性的默认值为false。在多对多关联关系中,关系的两端 inverse不能都设为false,即默认的情况是不对的,如果都设为false,在做插入操作时会导致在关系表中插入两次关系。

  也不能都设为 true,如果都设为true,任何操作都不会触发对关系表的操作。因此在任意一方设置inverse=true,另一方inverse=false。

一对一:

  其实是一对多的一个特例,inverse 的设置也是一样的,主要还是看关联关系的属性在哪一方,这一方的inverse=false。

多对一:

  也就是一对多的反过来,没什么区别。

 例:

 

建立多对多关联关系: 

多对多就是需要在两个实体类中都存在一个Set集合元素。同时需要一个中间表维护两者的关系。(共三张表)这里以老师和学生为例:

学生表:

中间表:

老师表:

 实体类:Teacher

package com.mlq.bena;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Teacher implements Serializable{
  private String name;
  private Integer tid;
  private Set<Student> students=new HashSet<Student>();
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public Integer getTid() {
    return tid;
  }
  public void setTid(Integer tid) {
    this.tid = tid;
  }
  public Set<Student> getStudents() {
    return students;
  }
  public void setStudents(Set<Student> students) {
    this.students = students;
  }
}

 实体类:Student

package com.mlq.bena;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Student implements Serializable {

    private Integer sid;
    private String sname;
    private Integer age;
    private Set<Teacher> teachers = new HashSet<Teacher>();
    public Integer getSid() {
        return sid;
    }
    public void setSid(Integer sid) {
        this.sid = sid;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Set<Teacher> getTeachers() {
        return teachers;
    }
    public void setTeachers(Set<Teacher> teachers) {
        this.teachers = teachers;
    }
}

 提示只需要两张表的实体类就可以:中间表不需要。

 核心映射文件:Student.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--
package: 需要映射的实体类所在包
class节点:
name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名

table:数据库中的表名,如果表名和实体类名一致,可以省略
id节点:表中的主键
generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库
name:  必须和实体类中的属性名一致
column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略
-->
<!--dynamic-update="false":默认非动态更新-->
<hibernate-mapping package="com.mlq.bena">
    <class name="com.mlq.bena.Student" table="`student`" dynamic-update="true">
        <id name="sid" column="sid">
            <generator class="increment"></generator>
        </id>
        <property name="sname" column="sname"/>
        <property name="age" column="age"/>
        <!--cascade级联操作-->
        <!--inverse:默认false,执行外键的update验证,true:不执行update维护外键-->
        <set name="teachers" table="middle" cascade="save-update,delete" inverse="true">
            <key column="sid"></key>
            <many-to-many column="tid" class="com.mlq.bena.Teacher"/>
        </set>
    </class>
</hibernate-mapping>

 核心映射文件:Teacher.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--
package: 需要映射的实体类所在包
class节点:
name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名

table:数据库中的表名,如果表名和实体类名一致,可以省略
id节点:表中的主键
generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库
name:  必须和实体类中的属性名一致
column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略
-->
<!--dynamic-update="false":默认非动态更新-->
<hibernate-mapping package="com.mlq.bena">
    <class name="com.mlq.bena.Teacher" table="`teacher`" dynamic-update="true">
        <id name="tid" column="tid">
            <!--<generator class="assigned"/>&lt;!&ndash;主键生成策略&ndash;&gt;-->
            <generator class="increment"></generator>
        </id>
        <property name="name" column="name"/>
        <!--cascade级联操作-->
        <!--inverse:默认false,执行外键的update验证,true:不执行update维护外键-->
        <set name="students" table="middle" cascade="save-update,delete" inverse="false">
            <key column="tid"></key>
            <many-to-many class="com.mlq.bena.Student" column="sid"/>
        </set>
    </class>
</hibernate-mapping>

 注意:虽然中间表不需要实体类,但是在我们配置文件时,要写上 table 属性,来告诉它这是多对多关联。table 属性值就是中间表。

    注意关联的字段名称保持一致。

 

Lazy 属性:延迟加载:

一.延迟加载的概念

  当Hibernate从数据库中加载某个对象时,不加载关联的对象,而只是生成了代理对象,获取使用session中的load的方法(在没有改变lazy属性为false的情况下)获取到的也是代理对象,所以在上面这几种场景下就是延迟加载。

二.理解立即加载的概念

  当Hibernate从数据库中加载某个对象时,加载关联的对象,生成的实际对象,获取使用session中的get的方法获取到的是实际对象。

三.为什么要使用延迟加载

  延迟加载策略能避免加载应用程序不需要访问的关联对象,以提高应用程序的性能。

四.立即加载的缺点

  Hibernate在查询某个对象时,立即查询与之关联的对象,我们可以看出这种加载策略存在两大不足:

  1.select的语句数目太多,需要频繁的访问数据库,会影响查询的性能。

  2.在应用程序只需要访问要的对象,而不需要访问与他关联的对象的场景下,加载与之关联的对象完全是多余的操作,这些多余的操作是会占内存,这就造成了内存空间的浪费。

五.什么时候使用延迟加载什么时候使用立即加载

  如果程序加载一个持久化对象的目的是为访问他的属性,则可以采用立即加载。如果程序加载一个持久化对象的目的仅仅是为了获得他的引用,则可以采用延迟加载。

六.Hibernate在对象-关系映射问价中配置加载策略

  I.类级别:

    <class>元素中lazy属性的可选值为 true (延迟加载)和 false (立即加载);

    <class>元素中的lazy属性的默认值为true

  II.一对多关联级别:

    <set>元素中的lazy属性的可选值为:

      true(延迟加载)

      extra(增强延迟加载)

      false(立即加载);

    <set>元素中的lazy属性的默认值为true

  III.多对一关联级别:

    <many-to-one>元素中lazy属性的可选值为:

      proxy(延迟加载)

      no-proxy(无代理延迟加载)

      false(立即加载)

    <many-to-one>元素中的lazy属性的默认值为proxy

 

Open Session in View 的使用:

为什么要使用 Open Session in View ?

因为:默认是懒加载机制,当我们去操作查询出来的数据时,会给我们报 会话已断开。(例如调用一个类中关联的 类属性)

解决方式:延迟会话关闭时间。(调用完在关闭)增加过滤器拦截请求。(把之前在Session中关闭会话的代码 删掉就 OK 了

配置如下:

Web.XML 中添加以下配置:

<!--OpenSessionInVie会话配置-->
<filter>
    <filter-name>openSessionInview</filter-name>
    <filter-class>com.mlq.uitl.Fileter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>openSessionInview</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Fileter 自定义拦截器类:

package com.mlq.uitl;
import org.hibernate.HibernateException;
import org.hibernate.Transaction;
import javax.servlet.*;
import java.io.IOException;
public class Fileter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("初始化------");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Transaction transaction=null;
        try{
            //请求达到时,打开Session并启动事务
            transaction=SessionTool.getSession().beginTransaction();
            //执行请求处理链
            chain.doFilter(request,response);
            //返回响应时,提交事务
            transaction.commit();
        }catch (HibernateException ex)
        {
            System.out.println("=======异常");
            ex.printStackTrace();
            transaction.rollback();
        }
    }
    @Override
    public void destroy() {
        System.out.println("销毁--------------");
    }
}

 

posted @ 2018-09-26 14:16  GDBD  阅读(310)  评论(0编辑  收藏  举报