第三章Hibernate关联映射

第三章Hibernate关联映射

一、关联关系

类与类之间最普通的关系就是关联关系,而且关联是有方向的。

以部门和员工为列,一个部门下有多个员工,而一个员工只能属于一个部门,从员工到部门就是多对一关联。

Hibernate关联映射的作用:避免了在对象模型和关系数据模型之间的切换。

缺陷:hibernate不是适合数据链比较多的操作,比如删除外键的关联对象,它要一条一条的删除,效率不高。

1.1建立单项多对一关联关系

     以区县级(District)和街道(Street)为例,介绍如何建立单向多(街道)对一(区县)关联关系。

         第一步:建立持久化实体类,映射关系是多(街道)对一(区县),因此只用在街道类中加入所属的(区县)对象即可。

public class Street {
    private int id;//街道编号
   
private String
name;//街道名称
   
private District
district;//所属区县

}

第二步:编写映射文件Street.hbm.xml

<?xml
version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="jbit.entity.Street" table="street">
        <!--绑定主键-->
       
<id name="id" column="street_id" >
            <generator class="increment">
            </generator>
        </id>
        <property name="name"
column="name" />

<!--many-to-one 包括以下属性

  1. 1.      
    name:设定持久化类的属性名,此处为Street类的district;//所属区县
  2. 2.      
    column:设定持久化类的属性对应的表的外键,此处为street表的外键district_id
  3. 3.      
    class:设定持久化类的属性的类型,此处设定district属性为District类型

-->
        <many-to-one name="district"
class="jbit.entity.District" column="district_id" lazy="proxy" /><!--district_id外键-->
   
</class>
</hibernate-mapping>

         此时,单向的多对一映射配置就完成了。

1.2建立双向一对多关联关系

当类与类之间建立了关联,就可以方便的从一个对象导航到另一个对象,或者通过集合导航到另一组对象。

在面向对象语言编写的程序中,通过关联关系从一个对象导航到另一个对象显然比通过编码到数据库中查询来的更加自然,且无需额外的编码。并且基于关联关系,在增、删、改操作中还可以对相关对象实现自动化的级联处理,同样减少编码工作,提高开发效率。

前面已经建立了单向多(街道)对一(区县)关联关系,下面再增加一(区县)对多(街道)关联,区县类和街道类之间就构成了双向的关联,即双向一对多关联。

第一步:建立持久化实体类,映射关系是一(区县)对多(街道)。

public class District {
    private int id;//区县编号
   
private String name;//区县名称
   
private Set<Street> street=new HashSet<Street>();//街道,常见错误,封装的时候new一个,避免空指针异常
}

在上面使用Set<>集合时因为它是无序的集合,而List<>是有序的,访问需要下标,所以用Set<>.

第二步:编写映射文件District.hbm.xml

<?xml version="1.0"
encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="jbit.entity.District" table="district">
        <!--绑定主键-->
       
<id name="id" column="district_id" >
            <generator class="increment">
            </generator>
        </id>
        <property name="name"
column="name" />
        <!--cascade=" save-update,delete,merge "关联持久化操作支持修改删除和保存,不建设使用all-->
        <!--inverse="true"
被动方维护关联关系, 护关联关系一般给多对一,一的那一方-->
        <!--lazy="extra"
增强延迟加载-->
       
<set name="street" cascade="save-update,delete,merge" inverse="true" lazy="extra">
            <key column="district_id"
/>
            <one-to-many class="jbit.entity.Street"
/>
        </set>

<!-- set元素的属性:

1.key: column属性设定与所关联的持久化类相对应的表的外键,此处为street表的外键district_id

2.ont-to-many:class属性设定所关联的持久化类型,此处为Street类

3.set:表明District类的street属性为jbit.entity.Street集合类型

-->
    </class>
</hibernate-mapping>

1.3双向关联关系下的增删改操作

关联关系除了可以通过对象间的导航实现相关的对象的自动检索外,还可以在对象的增删改操作中,对相关对象实现自动化的级联处理,而无序人工进行相关编码,从而提高了效率。

级联的操作细节可以在持久化类的映射文件中通过cascade属性和inverse属性进行控制。

1.cascade属性

Cascade属性的部分可选值

cascade属性值

描述

none

当session对象操纵当前对象时,忽略其他的关联对象,cascade的默认值。

save-update

通过session的save(),update()和saveOrUpdate()的方法,保存所有关联的瞬时状态的对象,

并且级联更新所有关联的游离状态的对象。

delete

当通过session的delete()方法时,会级联删除所有关联的对象

all

包含所有的行为,出于安全性的考虑,一般避免使用

marge

执行session的marge()方法

<set name="street" cascade="save-update,delete,merge"> cascade属性可以赋值多个,用逗号隔开。

cascade建议用在一对多或者多对一,一的一方,避免不必要的麻烦。

2.<set>元素的inverse属性

“inverse”意思为“反转”,在Hibernate中,inverse属性指定了关联关系中的方向。

inverse属性值有两个,即true和false,默认时false。就是不反转,就是不交出维护外键的权力,因此会执行相关对象关联的外键的HQL语句。从而保证数据的可靠性。

如果设置成true,则不会再执行相关的修改外键的HQL语句。在编码中必须建立对象的双向关联关系。

操作示例:

         要求:1.添加区县的同时添加两条街道;2.设置inverse的属性为true,从某区县中移走一条街道。

         public class DistrictBiz {
    /**
     *
增加区县信息
     */
   
public
Serializable
saveDistrict(District district){
        Session session= Util.openSession();
        Transaction
transaction=session.beginTransaction();
        Serializable save = null;
        try {
            save=session.save(district);
            transaction.commit();
        }catch
(Exception e){
            transaction.rollback();
        }finally
{
            Util.closeSession(session);
        }
        return
save;
    }

/**
     *
修改街道信息
     * @param district
    
*/
   
public
void
updateDistrict(District
district){
        Session session= Util.openSession();
        Transaction
transaction=session.beginTransaction();
        try {
            session.update(district);
            transaction.commit();
        }catch
(Exception e){
            transaction.rollback();
        }finally
{
            Util.closeSession(session);
        }
    }

}

测试:

         public class Test2 {
    public
static void
main(String[] args) {
        //1.添加区县的同时添加两条街道;

        DistrictBiz
biz=new DistrictBiz();
        // 创建对象
       
District
district=new District();
        district.setName("盐田区");
        Street street1 = new Street( "大梅沙街道");
        Street street2 = new Street("梧桐山街道");
        //绑定双向关系
       
district.getStreet().add(street1);
        street1.setDistrict(district);
       
district.getStreet().add(street2);
        street2.setDistrict(district);
        //执行增加
       
Serializable
serializable = biz.saveDistrict(district);
        System.out.println("********新增加区县信息"+serializable);
        System.out.println(district+"\n");
        Iterator
iterator=district.getStreet().iterator();
        while
(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //2.从某区县中移走一条街道
       
street1.setDistrict(null);
        district.getStreet().remove(street1);
        biz.updateDistrict(district);
        System.out.println("********移出区县的街道后:");
        System.out.println(district+"\n");
        Iterator
iteratoraa=district.getStreet().iterator();
        while
(iteratoraa.hasNext()){
            System.out.println(iteratoraa.next());
        }
    }
}

 

1.4建立多对多关系

多对多关系除了两张“多”方的表之外,还需要一张而外的表,即关系表,通过外键分别引用两张“多”方的主键来实现多对多的关联。

下面通过项目表(Project)和员工表(Employee),一个项目对应多个员工,一个员工参与多个项目,双向多对多来示例:
第一步:编写持久化化类Project,和其映射文件。

public class Project {
     private Integer
proid;
     private String
proname;
     private Set<Employee>
employees=new
HashSet<Employee>();

}

<?xml
version="1.0" encoding="UTF-8"?><!—映射文件-->
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="jbit.entity.Project" table="PROJECT">
       
<!--绑定主键-->
       
<id name="proid" column="PROID" >
            <generator class="increment">
            </generator>
        </id>
        <property name="proname"
column="PRONAME" />
        <!--PROEMP,第三方关联关系表-->
        <!--inverse="true"
被动方维护关联关系-->
        <!--<key
column="REMPID" />
和column="RPROID都是第三方关系型表的字段-->
       
<set name="employees"   table="PROEMP"
cascade="merge,save-update ">
            <key column="RPROID" />
            <many-to-many class="jbit.entity.Employee"
column="REMPID" />
        </set>
    </class>
</hibernate-mapping>

重点:set元素的业务逻辑

  1. 1.       关系表PROEMP,(关系)它包含 REMPID和RPROID两个列外键分别关联项目表和员工表的主键。
  2. 2.       Key:根据项目编号RPROID在PROEMP表中查找关联的REMPID(员工编号)
  3. 3.       根据员工编号REMPID在员工表(Employee)中查询员工信息,类型为class: jbit.entity.Employee。

使用注意事项:

         cascade="merge,save-update
"
是合理的,不建议把cascade属性设置为”all”和”delete”,如果删除一个项目对象时,级联会删除所有与它相关的员工信息,会破坏数据库的完整性。

第二步:编写持久化化类Employee,和其映射文件。

public class Employee {
    private Integer
empid;
    private String
empname;
    private Set<Project>
projects=new
HashSet<Project>();
}

<?xml
version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="jbit.entity.Employee" table="EMPLOYEE">
        <!--绑定主键-->
       
<id name="empid" column="EMPID" >
            <generator class="increment">
            </generator>
        </id>
        <property name="empname"
column="EMPNAME" />
        <!--PROEMP,第三方关联关系表-->
        <!--inverse="true"
被动方维护关联关系-->
        <!--<key column="REMPID"
/>
和column="RPROID都是第三方关系型表的字段-->
       
<set name="projects"  inverse="true"
table="PROEMP">
            <key column="REMPID" />
            <many-to-many class="jbit.entity.Project"
column="RPROID" />
        </set>
    </class>
</hibernate-mapping>

注意事项:
1. 双向关联时,必须要有一方放弃控制反转权,即设置inverse=”teue”
2. 保存的时候,一定要保存有控制权的一方。
   第三步:多对都的持久化操作:1.添加项目的时候同时添加员工,2.把某一个员工加入到另一个项目组,3.项目结束时,把员工从该项目移走。

         public class ProjectService {
    /**
     *
保存或修改项目信息
     */
   
public
void
saveorUpdate(Project
project){
        Session session= Util.openSession();
        Transaction
transaction=session.beginTransaction();
        try {
           session.saveOrUpdate(project);
            transaction.commit();
        }catch
(Exception e){
            transaction.rollback();
        }finally
{
            Util.closeSession(session);
        }
     }
 }

public class Test3 {
    public
static void
main(String[] args) {

//1.添加项目的时候同时添加员工
        ProjectService service=new ProjectService();
        Project project=new Project();
        project.setProname("易买网");
        Employee employee=new Employee();
        employee.setEmpname("王胖子");
        Employee employee1=new Employee();
        employee1.setEmpname("邓矮子");
        //建立对象间的双向关联
       
project.getEmployees().add(employee);
       
employee.getProjects().add(project);
       
project.getEmployees().add(employee1);
       
employee1.getProjects().add(project);
        //保存对象
       
service.saveorUpdate(project);
        System.out.println("*********\n添加项目的时候添加员工");
        System.out.println(project);
        Iterator
iterator=project.getEmployees().iterator();
        while
(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //2.把某一个员工加入到另一个项目组
        Project project1=new Project();
        project1.setProname("英雄连大");
       
project1.getEmployees().add(employee);
       
employee.getProjects().add(project1);
        service.saveorUpdate(project1);
        System.out.println("********\n把第一个员工加入到另一个项目组");
        System.out.println(project1);
        Iterator
iterator1=project1.getEmployees().iterator();
        while
(iterator1.hasNext()){
            System.out.println(iterator1.next());
        }

//3.项目结束时把员工从该项目中删除
       
project1.getEmployees().remove(employee);
       
employee.getProjects().remove(project1);
        service.saveorUpdate(project1);
        System.out.println("********\n项目结束时把员工从该项目中删除");
        System.out.println(project1);
        System.out.println("项目"+project1.getProname()+"的员工数量为:"+project1.getEmployees().size());
   }
}

  多对多的总结:

  1. 1.       多对多都是set,并且使用set的时候new 一个HashSet<>()避免空指针异常
  2. 2.       通过第三张“关系表”映射
  3. 3.       需要把一方的inverse设置为true,放弃维护外键的权力
  4. 4.       可以分解成两个一对多的映射,这种情况时出现在关系表中除了外键列,还有其他的业务字段需要使用时。
 
   
   
 

二、使用MyEclipse反向工程工具映射关联关系

详细操作步骤见https://jingyan.baidu.com/article/948f59242b9d18d80ef5f956.html

在界面通过”Enable many-to-many detection”选项选择映射的形式,创建多对多,不勾选则创建两个一对多关系。

三、延迟加载

立即加载存在两大不足:

(1)、会执行不必要的查询,影响查询性能。

(2)、可能会加载大量不需要的对象,增加系统开销,浪费内存空间。

为了解决以上问题Hibernatet提供了延迟加载策略,避免加载应用程序不需要访问的对象。

Hibernate允许在对象—关系映射文件中使用lazy属性配置加载策略,并且可以分为类级关联级两个级别,分别进行控制。

Session的list()和get()方法都是立即执行,不够lazy(懒加载)的影响。

Lazy属性

级别

Lazy属性取值

类级别

<class>元素中lazy的可选值为true(延迟加载)和false(立刻加载)。默认为true,推荐使用默认值(不设置)。

一对多和多对多

关联级别

<set>元素中的lazy可选值为true(延迟加载)和extra(增强延迟加载)和false(立即加载),默认值为true,推荐使用extra(增强延迟加载)

多对一关联级别

<many-to-one>元素lazy属性的可选值为proxy(延迟加载)和no-proxy(无代理延迟加载)和false(立即加载),默认值为proxy(延迟加载),推荐使用默认值(不设置)。

注意:选择no-proxy时需要编译期间进行字节码增强操作,否则和proxy效果一样。

 

3.1类级别的查询策略

Lazy(懒加载)的控制权大于load()方法。

<class lazy=”false”>此时 load()方法效果等同于get()

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

3.2一对多和多对多的查询策略

推荐使用<set lazy=”extra“>增强延迟加载,如果使用延迟加载,那么在访问一个集合的个数size()属性时,Hibernate会查询出所有的集合元素,从而浪费了资源。

3.3多对一的查询策略

在<many-to-one>中设置lazy属性,一般推荐使用默认值,也就是proxy,懒加载,也就是延迟加载,用的时候再加载。

四、Open  Session In View模式

Open Session in View模式是为了解决一些数据在延迟加载(lazy懒加载)时,会话(Session)已经关闭而引发的错误。

这个模式的主要思想是:在用户进行每次请求的时候,始终保持有一个Session对象处于开启状态。

Open Session in View模式的具体实现有以下三个步骤:

一、   把Session绑定到当前的线程,要保证每次的请求中只有一个Session对象。Dao层的HibernateUtil.currentSession()方法使用SessionFactory的getCurrentSession()方法获得Session,可以保证每次的请求只有一个Session对象存在。

二、   用Fiter过滤器在请求到达时打开Session,在页面生成完毕时关闭Session。

三、   调整业务层代码,删除和会话及事务管理的相关代码,仅保留业务逻辑代码。

关键代码:

Spring中http://blog.csdn.net/fooe84/article/details/680449

OpenSessionInViewFilter在web.xml中的配置http://blog.csdn.net/zft1045911520/article/details/60466986

方法二:编写OpenSessionInViewFilter类

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import org.hibernate.HibernateException;

import org.hibernate.Session;

import org.hibernate.Transaction;

import cn.jbit.houserent.dao.HibernateUtil;

public class OpenSessionInViewFilter implements Filter {

@Override

public void destroy() {

// TODO Auto-generated method stub

}

@Override

public void doFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2) throws IOException, ServletException {

Session session = null;

Transaction tx = null;

try {

// 请求到达时,打开Session并启动事务

session = HibernateUtil.currentSession();

tx = session.beginTransaction();

// 执行请求处理链

arg2.doFilter(arg0, arg1);

// 返回响应时,提交事务

tx.commit();

} catch (HibernateException e) {

e.printStackTrace();

if (tx != null)

tx.rollback();

} finally {

// 关闭session

HibernateUtil.closeSession();

}

}

@Override

public void init(FilterConfig arg0) throws ServletException {

// TODO Auto-generated method stub

}

}

在web.xml中的配置

<filter>

<filter-name>openSessionInView</filter-name>

<filter-class>

Cn.jbit.util(该类全限定路径).OpenSessionInViewFilter

</filter-class>

</filter>

<filter-mapping>

<filter-name>openSessionInView</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

上一篇:(转)Hibernate关联映射——对象的三种关系


下一篇:学习MVC之租房网站(十二)-缓存和静态页面