(转)Hibernate框架基础——一对多关联关系映射

http://blog.csdn.net/yerenyuan_pku/article/details/52746413

上一篇文章Hibernate框架基础——映射集合属性详细讲解的是值类型的集合(即集合元素是普通类型)。现在从本文开始我们就要介绍实体类型的集合(即集合元素是另一个实体)了。

一对多关联关系映射

我们还是以一个活生生的例子来详解一对多关联关系映射吧!就以部门和员工的关系为例。

  • 单向关联:仅仅建立从Employee到Department的多对一关联,即仅仅在Employee类中定义department属性。或者仅仅建立从Department到Employee的一对多关联,即仅仅在Department类中定义employees集合。
  • 双向关联:既建立从Employee到Department的多对一关联,又建立从Department到Employee的一对多关联。

单向多对一

单向n-1关联只需从n的一端可以访问1的一端。 
从Employee到Department的多对一单向关联需要在Employee类中定义一个Department属性,而在Department类中无需定义存放Employee对象的集合属性。接下来我们理应要创建出这两个持久化类了。 
我们最好新建一个普通java工程,如Hibernate_Test,然后在cn.itcast.f_hbm_oneToManyb包下新建持久化类——Department.java和Employee.java。 
持久化类——Department.java的代码如下:

/**
 * 部门
 * @author li ayun
 *
 */
public class Department {
    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "[Department: id=" + id + ", name=" + name + "]";
    }
}

持久化类——Employee.java的代码如下:

/**
 * 员工
 * @author li ayun
 *
 */
public class Employee {
    private Integer id;
    private String name;

    private Department department; // 关联的部门对象

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    @Override
    public String toString() {
        return "[Employee: id=" + id + ", name=" + name + "]";
    }
}

接着我们就要创建各个持久化类相应的映射配置文件了。但在做之前,我们约莫是要知道数据库中两张表的结构的,我画图表示: 

现在再来写各个持久化类相应的映射配置文件,我想应该会容易得多。先在cn.itcast.f_hbm_oneToMany包中创建Department类对应的映射配置文件——Department.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">

<hibernate-mapping package="cn.itcast.f_hbm_oneToMany">
    <class name="Department" table="department">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="name" />
    </class>
</hibernate-mapping>

然后也是在该包中创建Employee类对应的映射配置文件——Employee.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">

<hibernate-mapping package="cn.itcast.f_hbm_oneToMany">
    <class name="Employee" table="employee">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="name" type="string" column="name" />

        <!--
            department属性,表达的是本类与Department的多对一的关系
            class属性:关联的实体类型
            column属性:外键列(引用关联对象的表的主键)
        -->
        <many-to-one name="department" class="Department" column="departmentId"></many-to-one>
    </class>
</hibernate-mapping>

many-to-one属性:

  • name:设定待映射的持久化类的名字。
  • column:设定和持久化类的属性对应的表的外键。
  • class:设定持久化类的属性的类型。

接下来,我们从以下几个方面来编写代码进行测试:

  • 保存新数据,并有关联关系。
  • 获取数据,可以从多方获取到一方,但从一方获取不到多方。
  • 解除关联关系。
  • 删除对象,看对关联对象的影响。

所以,我们要在cn.itcast.f_hbm_oneToMany包中编写一个单元测试类——Application.java。

public class Application {
    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(Department.class) // 添加Hibernate实体类(加载对应的映射文件)
            .addClass(Employee.class) // 添加Hibernate实体类(加载对应的映射文件)
            .buildSessionFactory();

    // 保存,有关联关系
    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 新建对象
        Department department = new Department();
        department.setName("开发部");

        Employee employee1 = new Employee();
        employee1.setName("张三");

        Employee employee2 = new Employee();
        employee2.setName("李四");

        // 关联起来
        employee1.setDepartment(department); // 告诉员工他属于哪个部门
        employee2.setDepartment(department);

        // 保存
        session.save(department); // 保存部门,记住:被依赖的一般放在前面保存,需要依赖别人的一般放在后面保存
        session.save(employee1);
        session.save(employee2);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    // 获取,可以获取到关联的对方
    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 获取一方,并显示另一方信息
        Employee employee = (Employee) session.get(Employee.class, 1);
        System.out.println(employee);
        System.out.println(employee.getDepartment());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    // 解除关联关系
    @Test
    public void testRemoveRelation() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 从员工方解除
        Employee employee = (Employee) session.get(Employee.class, 1);
        employee.setDepartment(null);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    // 删除对象,对关联对象的影响
    @Test
    public void testDelete() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 删除员工方(多方),对对方没有影响
        Employee employee = (Employee) session.get(Employee.class, 1);
        session.delete(employee);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

测试,顺利通过,大发!

单向一对多

单向1–n关联只需从1的一端可以访问n的一端。在实际开发里面,关于一对多和多对一,有一个设计原则:尽量避免去使用一对多这种关系,而应该多使用多对一这种关系。但有时候是不可避免的,例如,订单和订单项就是一对多这种关系,一个订单里面有多个订单项,在显示订单的时候是一定要显示多方的数据的,即订单项,这时候我们就必须设计这种关系了。如果你没有设计这种关系,这时候从数据库里面找出订单的基本信息,人家一看订单,只能看到订单的基本信息,不能看到订单项,这是不行的。如果这时候不可避免地要设计这种关系(一对多这种关系),并且订单项有上千万条的数据,那么千万不能直接将其找出来,一定要采用分页的方式去找。 
所以,单向一对多的这种关联关系,我就不打算介绍了。

一对多/多对一双向关联

在一方记住了多方,在多方也记住了一方,我们这样设计就是双向的关联,在实际开发里面,这种双向的关联是不推荐使用的!而且一对多这种关系最好就不要设计,即不要在一方记住了多方,如果你设计了,在找出一方的数据时就要取出多方的数据,如果多方的数据超多的话,很容易导致内存溢出。 
虽然在实际开发里面,一对多/多对一双向关联不推荐使用,但是我们还是应该有所了解的。为了介绍这种关联关系,我们应该修改持久化类——Department.java的代码为:

/**
 * 部门
 * @author li ayun
 *
 */
public class Department {
    private Integer id;
    private String name;

    private Set<Employee> employees = new HashSet<Employee>(); // 关联的很多个员工

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(Set<Employee> employees) {
        this.employees = employees;
    }

    @Override
    public String toString() {
        return "[Department: id=" + id + ", name=" + name + "]";
    }
}

相应地,接下来应该修改该类所对应的映射配置文件——Department.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">

<hibernate-mapping package="cn.itcast.f_hbm_oneToMany">
    <class name="Department" table="department">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="name" />
        <!--
            employees属性,Set集合,表达的是本类与Employee的一对多的关系
            class属性:关联的实体类型
            key子元素:对方表中的外键列(多方的哪个表)
        -->
        <set name="employees">
            <key column="departmentId"></key>
            <one-to-many class="Employee"/>
        </set>
    </class>
</hibernate-mapping>

<set>元素来映射持久化类的set类型的属性。

  • name:设定待映射持久化类的属性名。
  • key子属性:设定与所关联的持久化类对应的表的外键。 
    • column:指定关联表的外键名。
  • one-to-many子属性:设定所关联的持久化类(集合中存放的对象)。 
    • class:指定关联的持久化类的类名。

接下来,我们从以下几个方面来编写代码进行测试:

  • 保存新数据,并有关联关系。
  • 获取数据,可以获取到关联的对方。
  • 解除关联关系。
  • 删除对象,看对关联对象的影响。

我们先从保存新数据,并有关联关系这一个方面开始测试,测试代码如下:

public class Application {
    private static SessionFactory sessionFactory = new Configuration() //
            .configure() //
            .addClass(Department.class) // 添加Hibernate实体类(加载对应的映射文件)
            .addClass(Employee.class) // 添加Hibernate实体类(加载对应的映射文件)
            .buildSessionFactory();

    // 保存,有关联关系
    @Test
    public void testSave() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 新建对象
        Department department = new Department();
        department.setName("开发部");

        Employee employee1 = new Employee();
        employee1.setName("张三");

        Employee employee2 = new Employee();
        employee2.setName("李四");

        // 关联起来
        employee1.setDepartment(department); // 告诉员工他属于哪个部门
        employee2.setDepartment(department);
        department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
        department.getEmployees().add(employee2);

        // 保存
        session.save(department); // 保存部门,记住:被依赖的一般放在前面保存,需要依赖别人的一般放在后面保存
        session.save(employee1);
        session.save(employee2);

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

此时测试testSave()方法,Eclipse的控制台打印如下的sql语句:

Hibernate: insert into department (name) values (?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: update employee set departmentId=? where id=?
Hibernate: update employee set departmentId=? where id=?

其中,我们推测这两句sql语句

Hibernate: update employee set departmentId=? where id=?
Hibernate: update employee set departmentId=? where id=?

是由这两句代码

department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);

产生的。 
如果我们将以上代码注释掉,则就不会产生两条多余的update语句。但是我们在写代码的时候,双方都知道对方的存在会比较好一点,即需要关联双方:

employee1.setDepartment(department); // 告诉员工他属于哪个部门
employee2.setDepartment(department);
department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);

这个时候我们又不希望产生那两条多余的update语句,那就需要引入inverse属性了。 
inverse属性: 
在hibernate中通过对inverse属性的值决定是由双向关联的哪一方来维护表和表之间的关系。inverse=false的为主动方,inverse=true的为被动方,由主动方负责维护关联关系。 
在没有设置inverse=true的情况下,父子两边都维护父子关系。 
在1-n关系中,将n方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)。 
在1-n关系中,若将1方设为主控方会额外多出update语句。插入数据时无法同时插入外键列,因而无法为外键列添加非空约束。 
引入inverse属性之后,持久化类(Department.java)对应的映射配置文件——Department.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">

<hibernate-mapping package="cn.itcast.f_hbm_oneToMany">
    <class name="Department" table="department">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="name" />
        <!--
            employees属性,Set集合,表达的是本类与Employee的一对多的关系
            class属性:关联的实体类型
            key子元素:对方表中的外键列(多方的哪个表)
            inverse属性:
                默认为false,表示本方维护关联关系。
                如果为true,表示本方不维护关联关系。
                只是影响是否能设置外键列的值(设成有效值或是null值),对获取信息没有任何影响。
        -->
        <set name="employees" inverse="true">
            <key column="departmentId"></key>
            <one-to-many class="Employee"/>
        </set>
    </class>
</hibernate-mapping>

此时,再次执行testSave()方法,Eclipse的控制台就会打印如下的sql语句:

Hibernate: insert into department (name) values (?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: insert into employee (name, departmentId) values (?, ?)

发现那两条多余的update语句没有了。 
这时,如若我们注释掉如下两句代码:

department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);

Eclipse的控制台仍将打印同样的sql语句:

Hibernate: insert into department (name) values (?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: insert into employee (name, departmentId) values (?, ?)

结论

  1. 在映射一对多的双向关联关系时,应该在one方把inverse属性设为true,这可以提高性能。
  2. 在建立两个对象的关联时,应该同时修改关联两端的相应属性:
    employee1.setDepartment(department); // 告诉员工他属于哪个部门
    employee2.setDepartment(department);
    department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
    department.getEmployees().add(employee2);
  3. 这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码不受Hibernate实现类的影响。同理,当删除双向关联的关系时,也应该修改关联两端的对象的相应属性:
    department.getEmployees().clear(); // 清空
    employee.setDepartment(null);
  4. 只有集合标记(set/map/list/array/bag)才有inverse属性。
  5. 维护关联关系是指设置外键列的值(设成有效值或是null值)

接下来我们从获取数据,可以获取到关联的对方第二个方面来进行测试。

public class Application {
    private static SessionFactory sessionFactory = ...

    // 获取,可以获取到关联的对方
    @Test
    public void testGet() {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // -------------------------------------------

        // 获取一方,并显示另一方信息
        Department department = (Department) session.get(Department.class, 1);
        System.out.println(department);
        System.out.println(department.getEmployees());

        // Employee employee = (Employee) session.get(Employee.class, 1);
        // System.out.println(employee);
        // System.out.println(employee.getDepartment());

        // -------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

结论是:无论是从哪一方获取数据,都能获取到关联对方的数据。 
紧接着我们从解除关联关系第三个方面来进行测试。

  • 从员工方解除

    public class Application {
        private static SessionFactory sessionFactory = ...
    
        // 解除关联关系
        @Test
        public void testRemoveRelation() {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            // -------------------------------------------
    
            // 从员工方解除
            Employee employee = (Employee) session.get(Employee.class, 1);
            employee.setDepartment(null);
    
            // -------------------------------------------
            session.getTransaction().commit();
            session.close();
        }
    }
  • 测试,顺利通过,大发!
  • 从部门方解除 
    与inverse属性的值有关系,只有inverse为false时才可以解除。
    public class Application {
        private static SessionFactory sessionFactory = ...
    
        // 解除关联关系
        @Test
        public void testRemoveRelation() {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            // -------------------------------------------
    
            // 从部门方解除(与inverse有关系,inverse为false时可以解除)
            Department department = (Department) session.get(Department.class, 1);
            department.getEmployees().clear(); // 清空
    
            // -------------------------------------------
            session.getTransaction().commit();
            session.close();
        }
    }

最后,我们从删除对象,看对关联对象的影响第四个方面来进行测试。

  • 删除员工方(多方),对对方没有影响

    public class Application {
        private static SessionFactory sessionFactory = ...
    
        // 解除关联关系
        @Test
        public void testRemoveRelation() {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            // -------------------------------------------
    
            // 删除员工方(多方),对对方没有影响
            Employee employee = (Employee) session.get(Employee.class, 1);
            session.delete(employee);
    
            // -------------------------------------------
            session.getTransaction().commit();
            session.close();
        }
    }
  • 删除部门方(一方) 
    a,如果没有关联的员工:能删除。 
    b,如果有关联的员工且inverse=true,由于不能维护关联关系,所以直接执行删除,就会有异常。 
    c,如果有关联的员工且inverse=false,由于可以维护关联关系,它就会先把关联的员工的外键列设为null值,再删除自己。
    public class Application {
        private static SessionFactory sessionFactory = ...
    
        // 解除关联关系
        @Test
        public void testRemoveRelation() {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            // -------------------------------------------
    
            // 删除部门方(一方)
            // a,如果没有关联的员工:能删除
            // b,如果有关联的员工且inverse=true,由于不能维护关联关系,所以会直接执行删除,就会有异常
            // c,如果有关联的员工且inverse=false,由于可以维护关联关系,它就会先把关联的员工的外键列设为null值,再删除自己。
            Department department = (Department) session.get(Department.class, 1);
            session.delete(department);
    
            // -------------------------------------------
            session.getTransaction().commit();
            session.close();
        }
    }
时间: 2024-10-14 12:32:12

(转)Hibernate框架基础——一对多关联关系映射的相关文章

(转)Hibernate框架基础——多对多关联关系映射

http://blog.csdn.net/yerenyuan_pku/article/details/52756536 多对多关联关系映射 多对多的实体关系模型也是很常见的,比如学生和课程的关系.一个学生可以选修多门课程,一个课程可以被多名学生选修.在关系型数据库中对于多对多关联关系的处理一般采用中间表的形式,将多对多的关系转化成两个一对多的关系. 为了详细介绍多对多关联关系映射,终究还是应以一个例子来说明比较印象深刻.我们以老师和学生的关系为例来说明这种多对多关联关系映射. 双向多对多 我们最

SSH:Hibernate框架(七种关联关系映射及配置详解)

概念 基本映射是对一个实体进行映射,关联映射就是处理多个实体之间的关系,将关联关系映射到数据库中,所谓的关联关系在对象模型中有一个或多个引用. 分类 关联关系分为上述七种,但是由于相互之间有各种关系,可以简化,例如:多对一与一对多映射,只是侧重的角度不对而已. 映射技巧 映射技巧是小编写映射文件的过程,总结的经典内容,总共分为四步,咋看不是特别易懂,但是效果很好.下面我们以实例看技巧. (1)写注释 格式为:?属性,表达的是本对象与?的?关系. 解释:在写映射文件之前先写注释,将问号的地方填上相

Hibernate框架基础

Hibernate框架基础 Hibernate框架 ORM概念 O, Object 对象 R, Realtion 关系 (关系型数据库: MySQL, Oracle-) M,Mapping 映射 ORM, 对象关系映射! ORM, 解决什么问题? 存储: 能否把对象的数据直接保存到数据库? 获取: 能否直接从数据库拿到一个对象? 想做到上面2点,必须要有映射! 总结: Hibernate与ORM的关系? Hibernate是ORM的实现! Hibernate HelloWorld案例 搭建一个H

Hibernate One-to-Many Mappings 一对多关系映射

Hibernate One-to-Many Mappings 一对多关系映射 关键点:一对多关系使用 Set 实现, 例子:一个员工可以有多个学证书. Hibernate框架的使用步骤: 1.创建Hibernate的配置文件(hibernate.cfg.xml) 2.创建持久化类,即其实例需要保存到数据库中的类(Employee.java) 3.创建对象-关系映射文件(Employee.hbm.xml) 4.通过Hibernate API编写访问数据库的代码 创建Hibernate的配置文件(h

hibernate实体xml一对多关系映射

单向一对多关系映射: 一个房间对应多个使用者,也就是Room實例知道User實例的存在,而User實例則沒有意識到Room實例. 用户表: package onlyfun.caterpillar; public class User { private Long id; private String name; public User() {} public Long getId() { return id; } public void setId(Long id) { this.id = id

(转)Hibernate框架基础——Hibernate API及Hibernate主配置文件

Hibernate的学习路线图 Hibernate API简介 Configuration Configuration类负责管理Hibernate的配置信息,包括如下内容: Hibernate运行的底层信息:数据库的URL.用户名.密码.JDBC驱动类,数据库Dialect,数据库连接池等(对应hibernate.cfg.xml文件). 持久化类与数据表的映射关系(*.hbm.xml文件). 创建Configuration的两种方式: 属性文件(hibernate.properties) Con

(转)Hibernate框架基础——映射集合属性

http://blog.csdn.net/yerenyuan_pku/article/details/52745486 集合映射 集合属性大致有两种: 单纯的集合属性,如像List.Set或数组等集合属性. Map结构的集合属性,每个属性值都有对应的Key映射. 集合映射的元素大致有如下几种: list:用于映射List集合属性. set:用于映射Set集合属性. map:用于映射Map集合性. array:用于映射数组集合属性. bag:用于映射无序集合. idbag:用于映射无序集合,但为集

Hibernate框架单向多对一关联映射关系

建立多对一的单向关联关系    Emp.java            private Integer empNo //员工编号            private String empName //员工姓名            //private Integer deptNo;  //部门编号            private Dept dept;    //所属部门    Dept.java            private Byte deptNo;              

框架 day32 Hibernate,一级缓存,关联关系映射(一对多,多对多)

一级缓存 概念 *在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java集合构成了Session缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期 *当session的save()方法持久化一个对象时,该对象被载入缓存, 以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中. 当试图get(). load()对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库.没有再查询数据库 *Session 能够在