08-hibernate中的几种关系映射

一、映射组成关系

Hibernate 把持久化类的属性分为两种:

  • 值(value)类型: 没有 OID,不能被单独持久化,生命周期依赖于所属的持久化类的对象的生命周期。
  • 实体(entity)类型:有 OID,可以被单独持久化,有独立的生命周期,无法直接用 property 进行映射。Hibernate 使用 <component> 元素来映射组成关系。
<!--映射组成关系-->
<component name="pay" class="Pay">
<!--指定组成关系的组件的属性-->
    <parent name="worker"/> <!--<用于指定组件属性所属的整体类-->
    <property name="monthPay" column="monthPay"></property>
    <property name="yearPay" column="yearPay"></property>
    <property name="vocationWithPay" column="vocationWithPay"></property>
</component>

说明:
<component> 元素来映射组成关系,其中的class属性:设定组成关系属性的类型。在此处表明 pay 属性为 Pay 类型。
<parent> 元素指定组件属性所属的整体类,其中的name属性:整体类在组件类中的属性名。

二、映射一对多关联关系

1、单向一对多的关联关系

  以 Customer 和 Order 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户。从 Order 到 Customer 的关联是多对一关联;而从 Customer 到 Order 是一对多关联。

  • 单向 n-1 关联只需从 n 的一端可以访问 1 的一端
  • 域模型: 从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
  • Hibernate 使用 元素来映射多对一关联关系
<many-to-one> 元素来映射组成关系
name: 设定待映射的持久化类的属性的名字
column: 设定和持久化类的属性对应的表的外键
class:设定待映射的持久化类的属性的类型

实体类Customer:

public class Customer {
    private int customerId;
    private String customerName;

    public Customer() {
    }

  //getter/setter方法
}

实体类映射文件Customer.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="com.zm.n21.Customer" table="customer">
        <id name="customerId" type="java.lang.Integer">
            <column name="customer_id"/>
            <generator class="native"/>
        </id>
        <property name="customerName" type="java.lang.String">
            <column name="customer_name"/>
        </property>
    </class>
</hibernate-mapping>

实体类Orders:

public class Orders {
    private int orderId;
    private String orderName;
    //多对一的映射。多端:Orders;一端:Customer
    Customer customer;

    public Orders() {
    }

   //getter/setter方法
}

实体类映射文件Orders.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 package="com.zm.n21">
  <class name="com.zm.n21.Orders" table="orders">
      <id name="orderId" type="java.lang.Integer">
          <column name="order_id"/>
          <generator class="native"/>
      </id>
      <property name="orderName" type="java.lang.String">
          <column name="order_name"/>
      </property>

      <!--映射多对一的关联关系,使用many-to-one来映射多对一的关联关系
          name:多这一端关联的那一端的属性的名字
          class:一那一端的属性对应的类名
          column:一那一端在多那一端对应的数据表中的外键的名字
      -->
      <many-to-one name="customer" class="Customer" column="customer_id"></many-to-one>
  </class>
</hibernate-mapping>

在hibernate.cfg.xml注册映射文件:

<mapping resource="com/zm/n21/Orders.hbm.xml"/>
<mapping resource="com/zm/n21/Customer.hbm.xml"/>

(1)插入操作的测试

先插入Customer再插入Order:

 @Test
    public void many2One(){
        Customer customer = new Customer();
        customer.setCustomerName("AA");

        Orders orders1 = new Orders();
        orders1.setOrderName("Orders-1");

        Orders orders2 = new Orders();
        orders2.setOrderName("Orders-2");
        //设定关联关系
        orders1.setCustomer(customer);
        orders2.setCustomer(customer);
        //执行save操作
        session.save(customer);

        session.save(orders1);
        session.save(orders2);
    }

3条INSERT语句。总结:先插入1的一端,再插入n的一端,只有INSERT语句
在这里插入图片描述
先插入Order再插入Customer:

  @Test
    public void many2One(){
        Customer customer = new Customer();
        customer.setCustomerName("BB");

        Orders orders1 = new Orders();
        orders1.setOrderName("Order-3");

        Orders orders2 = new Orders();
        orders2.setOrderName("Order-4");

        //设定关联关系
       orders1.setCustomer(customer);
       orders2.setCustomer(customer);
     
        /*先插入Orders,再插入Customer*/
       session.save(orders1);
       session.save(orders2);
       session.save(customer);
    }

3条INSERT语句,2条UPDATE语句。先插入n的一端,再插入1的一端,会多出UPDATE语句。因为在插入多的一端时,无法确定1的一端的外键值,只能等1的一端插入后,再额外发送UPDATE语句。
在这里插入图片描述
总的来说,推荐先插入1的一端,再插入n的一端。

(2)多对一单向映射查询操作时

  • 若查询多的一端的一个对象,默认情况下,只查询了多的一端的对象,而没有查询关联的1的那端的对象。
    在这里插入图片描述
  • 在需要使用到关联的对象时,才发送对应的SQL语句
    在这里插入图片描述
  • 在查询Customer对象时,由多的一端导航到1的一端时,若此时session已经被关闭,则在默认情况下可能会发生懒加载异常(LazyInitializationException)
  • 获取Order对象时,默认情况下,其关联的Customer对象是一个代理对象

(3)多对一单向映射修改操作时

  修改1那一端的数据时,会发送两个SELECT语句(一个是查询多端对象,一个是查询1端对象),然后再发送一个UPDATE语句。
在这里插入图片描述

(4)多对一单向映射删除操作时

  在不设定级联关系的情况下,且1的这一端的对象有n的对象在引用,不能直接删除1这一端的对象。
在这里插入图片描述

三、双向一对多映射

  • 双向 1-n 与 双向 n-1 是完全相同的两种情形
  • 双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.
  • 域模型:从 Order 到 Customer 的多对一双向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
  • 关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
  • Hibernate 使用 元素来映射 set 类型的属性
  <!-- set: 映射 set 类型的属性;name:一的这一端关联的多的那一端的属性名;
  table: set 中的元素对应的记录放在哪一个数据表中。该值需要和多对一的多的那个表的名字一致 -->
<set name="orders" table="ORDERS">
     <!-- 指定关联的表中的外键列的名字 -->
     <key column="CUSTOMER_ID"></key>
     <!-- 指定映射类型 -->
     <one-to-many class="Order"/>
 </set>

几个注意点:

  • 当 Session 从数据库中加载 Java 集合时,创建的是 Hibernate 内置集合类的实例,因此在持久化类中定义集合属性时必须把属性声明为 Java 接口类型。例如应该声明为Set而不是HashSet。
      ①Hibernate 的内置集合类具有集合代理功能,支持延迟检索策略。类似于在单向n-1关系的get操作,如果在双向1-n的get操作中获取了customer对象,如果不使用它存放order的集合,那么这个集合就不会被加载,只有使用到时才会加载。
      ② 事实上,Hibernate 的内置集合类封装了 JDK 中的集合类,这使得 Hibernate 能够对缓存中的集合对象进行脏检查,按照集合对象的状态来同步更新数据库。
  • 在定义集合属性时,通常把它初始化为集合实现类的一个实例。这样可以提高程序的健壮性,避免应用程序访问取值为 null 的集合的方法抛出 NullPointerException

代码实例如下:

实体类对象Order:

public class Order {
    private Integer orderId;
    private String orderName;
    private Customer customer;
    
   //getter/setter方法
}

实体类对象Customer:

public class Customer {
    private Integer customerId;
    private String customerName;

    /*注意两点:
     * 1. 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取
     * 集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的集合实现.
     * 2. 需要把集合进行初始化, 可以防止发生空指针异常
     */
    private Set<Order> orders = new HashSet<>();

 //getter/setter方法
}

实体类对象对应的映射文件:

Order.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 package="com.zm.n21.both">
    <class name="Order" table="ORDERS">

        <id name="orderId" type="java.lang.Integer">
            <column name="ORDER_ID" />
            <generator class="native" />
        </id>

        <property name="orderName" type="java.lang.String">
            <column name="ORDER_NAME" />
        </property>

        <!--
            映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系
            name: 多这一端关联的一那一端的属性的名字
            class: 一那一端的属性对应的类名
            column: 一那一端在多的一端对应的数据表中的外键的名字
        -->
        <many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
    </class>
</hibernate-mapping>

Customer.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 package="com.zm.n21.both">
    <class name="Customer" table="CUSTOMERS">
        <id name="customerId" type="java.lang.Integer">
            <column name="CUSTOMER_ID" />
            <generator class="native" />
        </id>

        <property name="customerName" type="java.lang.String">
            <column name="CUSTOMER_NAME" />
        </property>

        <!-- 映射 1 对多的那个集合属性 -->
        <!-- set: 映射 set 类型的属性, name:一的这一端关联的多的那一端的属性名,table: set 中的元素对应的记录放在哪一个数据表中. 该值需要和多对一的多的那个表的名字一致 -->
        <set name="orders" table="ORDERS">
            <!-- 执行多的表中的外键列的名字 -->
            <key column="CUSTOMER_ID"></key>
            <!-- 指定映射类型 -->
            <one-to-many class="Order"/>
        </set>

    </class>
</hibernate-mapping>

set元素中的3个重要属性

(1)set元素中的inverse属性:

  • 指定由哪一方来维护关联关系。 inverse = false 的为主动方,inverse = true 的为被动方。由主动方负责维护关联关系通常设置为true,以指定由多的一端来维护关联关系。在没有设置 inverse=true 的情况下,父子两边都维护父子。
  • 在 1-n 关系中,将 n 方设为主控方将有助于性能改善关系 。在 1-N 关系中,若将 1 方设为主控方,会额外多出 update 语句。插入数据时无法同时插入外键列,因而无法为外键列添加非空约束。

(2)set元素中的cascade属性:

  • 在对象 – 关系映射文件中, 用于映射持久化类之间关联关系的元素,<set><many-to-one><one-to-one> 都有一个 cascade 属性,它用于指定如何操纵与当前对象关联的其他对象。
    在这里插入图片描述
    在开发时不建议设定该属性,建议使用手工的方式来处理。

(3)集合排序属性

  • 元素有一个 order-by 属性,如果设置了该属性,当 Hibernate 通过 select 语句到数据库中检索集合对象时,利用 order by 子句进行排序
  • order-by 属性中还可以加入 SQL 函数

在hibernate.cfg.xml文件中注册:

 <mapping resource="com/zm/n21/both/Customer.hbm.xml"/>
    <mapping resource="com/zm/n21/both/Order.hbm.xml"/>

1、多对1双向映射保存操作

(1)执行save操作:先插入Customer,再插入Order。3条INSERT,2条UPDATE。因为1的一端和多额一端都维护关联关系,所以会多出uPDATE。可以在1的一端的set节点指定inverse=true,来使1的一端放弃维护关联关系!建议设定set的inverse=true,建议先插入1的一端,后插入多的一端。好处:不会多出UPDATE语句。

@Test
 public void testManytoOneSave(){
     Customer customer = new Customer();
     customer.setCustomerName("AA");

     Order order1 = new Order();
     order1.setOrderName("Order-1");

     Order order2 = new Order();
     order2.setOrderName("Order-2");

     //设定关联关系
     order1.setCustomer(customer);
     order2.setCustomer(customer);

     customer.getOrders().add(order1);
     customer.getOrders().add(order2);

     //执行save操作:先插入Customer,再插入Order
     session.save(customer);
     session.save(order1);
     session.save(order2);
 }

在这里插入图片描述
(2)执行save操作:先插入Order,再插入Customer。3条INSERT,4条UPDATE。

  //执行save操作:先插入Order,再插入Customer
  session.save(order1);
  session.save(order2);
  session.save(customer);

在这里插入图片描述

2、双向1对多的获取操作

 @Test
 public void testOneToMany(){
     //1. 对n的一端的集合使用延迟加载
     Customer customer = (Customer) session.get(Customer.class, 1);
     System.out.println(customer.getCustomerName());
     //2.返回的多的一端的集合是Hibenrate内置的集合类型,该类型具有延迟加载和存放代理对象的功能
     //class org.hibernate.collection.internal.PersistentSet
     System.out.println(customer.getOrders().getClass());
     
     //session.close();
     //3.可能会抛出懒加载异常LazyInitializationException
     
     //4.需要使用集合中元素的时候进行初始化
 }

3、双向1对多修改操作

在这里插入图片描述

四、基于外键的1对1关系映射

  • 对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键的一端,增加many-to-one元素。为many-to-one元素增加unique=“true” 属性来表示为1-1关联。
 <!--使用外键的方式来映射1-1关联关系-->
 <many-to-one name="manager" class="com.zm.one2one.foreignkey.Manager" column="manger_id"
               unique="true"></many-to-one>
  • 另一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段。
 <!--映射1-1的关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
<one-to-one name="dept" class="com.zm.one2one.foreignkey.Department" property-ref="manager"></one-to-one>

相关实例如下:

实体类对象Department:

public class Department {
    private Integer deptId;
    private String deptName;

    private Manager manager;

   //getxxx/setxxx方法
}

对应的映射文件Department.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="com.zm.one2one.foreignkey.Department" table="DEPARTMENTS">
       <id name="deptId" type="java.lang.Integer">
           <column name="dept_id"/>
           <generator class="native"/>
       </id>
       <property name="deptName" type="java.lang.String">
           <column name="dept_name"/>
       </property>
       <!--使用外键的方式来映射1-1关联关系-->
       <many-to-one name="manager" class="com.zm.one2one.foreignkey.Manager" column="manger_id"
                    unique="true"></many-to-one>
   </class>
</hibernate-mapping>

实体类对象Manager:

public class Manager {
    private Integer mangerId;
    private String managerName;
    private Department dept;

   //getxxx/setxxx方法
}

对应的映射文件Manager.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="com.zm.one2one.foreignkey.Manager" table="MANAGERS">
      <id name="mangerId" type="java.lang.Integer">
          <column name="manger_id"/>
          <generator class="native"/>
      </id>
      <property name="managerName" type="java.lang.String">
          <column name="manager_name"/>
      </property>
      <!--映射1-1的关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射
    没有外键的一端需要使用one-to-one元素,该元素使用property-ref属性指定使用被关联主键以外的字段作为关联字段-->
      <one-to-one name="dept" class="com.zm.one2one.foreignkey.Department" property-ref="manager"></one-to-one>
  </class>
</hibernate-mapping>

在hibernate的配置文件中注册对应的映射文件:

<!--需要关联的hibernate映射文件.hbm.xml-->
<mapping resource="com/zm/one2one/foreignkey/Department.hbm.xml" />
<mapping resource="com/zm/one2one/foreignkey/Manager.hbm.xml"/>

1、保存操作

  @Test
public void testSave(){
    Department dept = new Department();
   // dept.setDeptName("Dept-AA");
    dept.setDeptName("Dept-BB");

    Manager manager = new Manager();
   // manager.setManagerName("Manager-AA");
    manager.setManagerName("Manager-BB");

    //设定关联关系
    manager.setDept(dept);
    dept.setManager(manager);
  //先保存的是没有外键列的对象,再保存有外键列的对象。2条INSERT语句,没有UPDATE语句
   // Hibernate: insert into MANAGERS (manager_name) values (?)
   // Hibernate: insert into DEPARTMENTS (dept_name, manger_id) values (?, ?)
//        session.save(manager);
//        session.save(dept);
  
    //先保存有外键列对象,在保存无外键列对象。2条INSERT语句,1条UPDATE语句
    //Hibernate: insert into DEPARTMENTS (dept_name, manger_id) values (?, ?)
    //Hibernate: insert into MANAGERS (manager_name) values (?)
    //Hibernate: update DEPARTMENTS set dept_name=?, manger_id=? where dept_id=?
    session.save(dept);
    session.save(manager);
}

总结:

  • 先保存没有外键列的对象,再保存有外键列的对象:2条INSERT语句,没有UPDATE语句。先保存有外键列对象,在保存无外键列对象:2条INSERT语句,1条UPDATE语句
  • 推荐先保存无外键列的对象,在保存有外键列的对象。

2、查询操作

@Test
public void testGet(){
    //1.默认情况下对关联属性使用懒加载
    //Hibernate: select department0_.dept_id as dept_id1_0_0_, department0_.dept_name as dept_nam2_0_0_,
    // department0_.manger_id as manger_i3_0_0_ from DEPARTMENTS department0_ where department0_.dept_id=?
    //Dept-AA
    Department department = (Department) session.get(Department.class, 1);
    System.out.println(department.getDeptName());
   //2.关闭session后,再获取对象的属性值,会出现懒加载异常LazyInitializationException
//        session.close();
//        Manager manager = department.getManager();
//        System.out.println(manager.getClass());
//        System.out.println(manager.getManagerName());

    //在one-to-one中没有设置property-ref属性的值时,查询Manager对象的连接条件会出现错误,
    // 应该是dept.manager_id=manager.manager_id,而不是dept.dept_id=manager.manager_id
    //在添加了property-ref的属性设置时正确
    Manager manager = department.getManager();
    System.out.println(manager.getManagerName());
}

在查询没有外键的实体对象时,使用的是左外连接查询,一并查出其关联的对象,并且已经初始化。

  @Test
    public void testGet2(){
        //在查询没有外键的实体对象时,使用的是左外连接查询,一并查出其关联的对象,并且已经初始化
        Manager manager = (Manager) session.get(Manager.class,1);
        System.out.println(manager.getManagerName());
        System.out.println(manager.getDept().getDeptName());
    }

在这里插入图片描述

五、基于主键映射的1对1

  • 基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键。 子元素指定使用当前持久化类的哪个属性作为 “对方”。
<id name="deptId" type="java.lang.Integer">
    <column name="dept_id"/>
    <!--使用外键的方式生成当前的主键-->
    <generator class="foreign">
        <!--property属性指定使用当前持久化类的哪一个属性的主键作为外键-->
        <param name="property">manager</param>
    </generator>
</id>
  • 采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one属性还应增加 constrained=“true” 属性;另一端增加one-to-one元素映射关联属性。
  • constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键。
<!--采用foreign主键生成器策略的一端增加one-to-one元素映射关联属性,其one-to-one节点还应
    增加constrained="true"属性,以使当前的主键上添加外键约束-->
<one-to-one name="manager" class="Manager" constrained="true"/>

实例代码:

实体类对象Department:

public class Department {
    private Integer deptId;
    private String deptName;

    private Manager manager;

  //getxxx/setxxx方法
}

实体类对象Manager:

public class Manager {
    private Integer mangerId;
    private String managerName;
    private Department dept;

  //getxxx/setxxx方法
  }  

对应的映射文件:
Department.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 package="com.zm.one2one.primarykey">
<class name="Department" table="DEPARTMENTS">
    <id name="deptId" type="java.lang.Integer">
        <column name="dept_id"/>
        <!--使用外键的方式生成当前的主键-->
        <generator class="foreign">
            <!--property属性指定使用当前持久化类的哪一个属性的主键作为外键-->
            <param name="property">manager</param>
        </generator>
    </id>
    <property name="deptName" type="java.lang.String">
        <column name="dept_name"/>
    </property>
    <!--采用foreign主键生成器策略的一端增加one-to-one元素映射关联属性,其one-to-one节点还应
    增加constrained="true"属性,以使当前的主键上添加外键约束-->
   <one-to-one name="manager" class="Manager" constrained="true"/>
</class>
</hibernate-mapping>

Manager.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 package="com.zm.one2one.primarykey">
  <class name="Manager" table="MANAGERS">
      <id name="mangerId" type="java.lang.Integer">
          <column name="manger_id"/>
          <generator class="native"/>
      </id>
      <property name="managerName" type="java.lang.String">
          <column name="manager_name"/>
      </property>
      <!--映射1-1的关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
      <one-to-one name="dept" class="Department"></one-to-one>
  </class>
</hibernate-mapping>

在hibernate的配置文件中注册:

<mapping resource="com/zm/one2one/primarykey/Department.hbm.xml" />
<mapping resource="com/zm/one2one/primarykey/Manager.hbm.xml"/>

1、保存操作

不论是否先插入有外键列的一端,都只有INSERT语句。

@Test
    public void testSave(){
        Department dept = new Department();
         dept.setDeptName("Dept-AA");
        //dept.setDeptName("Dept-BB");

        Manager manager = new Manager();
        manager.setManagerName("Manager-AA");
        //manager.setManagerName("Manager-BB");

        //设定关联关系
        dept.setManager(manager);
        manager.setDept(dept);

        //先插入哪一个都一样,只有INSERT语句
        session.save(manager);
        session.save(dept);
//        session.save(dept);
//        session.save(manager);
    }

六、映射多对多关联关系

1、单向多对多关联关系

在这里插入图片描述

  • n-n 的关联必须使用连接表
  • 与 1-n 映射类似,必须为 set 集合元素添加 key 子元素,指定 CATEGORIES_ITEMS 表中参照 CATEGORIES 表的外键为 CATEGORIY_ID。与 1-n 关联映射不同的是,建立 n-n 关联时, 集合中的元素使用 many-to-manymany-to-many 子元素的 class 属性指定 items 集合中存放的是 Item 对象, column 属性指定 CATEGORIES_ITEMS 表中参照 ITEMS 表的外键为 I_ID
<!--table:指定中间表-->
<set name="items" table="CATEGORIES_ITEMS">
     <key>
         <column name="C_ID"/>
     </key>
     <!--使用many-to-many来指定多对多的关联关系,column指定Set集合中的持久化类在中间表的外键列的名称-->
    <many-to-many class="com.zm.n2n.Item" column="I_ID"></many-to-many>
</set>

实例代码:

实体类Category:

package com.zm.n2n;

import java.util.HashSet;
import java.util.Set;

public class Category {
    private Integer categoryId;
    private String categoryName;
    private Set<Item> items = new HashSet<>();

    //getxxx/setxxx方法
}

实体类Item:

public class Item {
    private Integer id;
    private String name;

   //getxxx/setxxx方法
}

实体类对应的映射文件:

Category.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="com.zm.n2n.Category" table="CATEGORIES">
       <id name="categoryId" type="java.lang.Integer">
           <column name="category_id"/>
           <generator class="native"/>
       </id>
       <property name="categoryName" type="java.lang.String">
           <column name="category_name"/>
       </property>
       <!--table:指定中间表-->
       <set name="items" table="CATEGORIES_ITEMS">
           <key>
               <column name="C_ID"/>
           </key>
           <!--使用many-to-many来指定多对多的关联关系,column指定Set集合中的持久化类在中间表的外键列的名称-->
          <many-to-many class="com.zm.n2n.Item" column="I_ID"></many-to-many>
       </set>
   </class>
</hibernate-mapping>

Item.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="com.zm.n2n.Item" table="ITEMS">
      <id name="id" type="java.lang.Integer">
          <column name="ID"/>
          <generator class="native"/>
      </id>
      <property name="name" type="java.lang.String">
          <column name="NAME"/>
      </property>
  </class>
</hibernate-mapping>

在hibernate的配置文件中注册映射文件:

 <mapping resource="com/zm/n2n/Category.hbm.xml" />
 <mapping resource="com/zm/n2n/Item.hbm.xml"/>

2、双向多对多关联关系

  • 双向 n-n 关联需要两端都使用集合属性
  • 双向n-n关联必须使用连接表
  • 集合属性应增加 key 子元素用以映射外键列,集合元素里还应增加many-to-many子元素关联实体类
  • 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名。两个集合元素 set 的 table 元素的值必须指定,而且必须相同。set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,其中,key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a。
  • 对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true。否则两端都维护关联关系可能会造成主键冲突.
    在这里插入图片描述

实例代码:

实体类Category:

public class Category {
    private Integer categoryId;
    private String categoryName;
    private Set<Item> items = new HashSet<>();

//setxxx/getxxx方法
}

实体类Item:

public class Item {
    private Integer id;
    private String name;
    private Set<Category> categories = new HashSet<>();

    //getxxx/setxxx方法
}

对应的映射文件:
Category.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 package="com.zm.n2n.both">
   <class name="Category" table="CATEGORIES">
       <id name="categoryId" type="java.lang.Integer">
           <column name="category_id"/>
           <generator class="native"/>
       </id>
       <property name="categoryName" type="java.lang.String">
           <column name="category_name"/>
       </property>
       <!--table:指定中间表-->
       <set name="items" table="CATEGORIES_ITEMS">
           <key>
               <column name="C_ID"/>
           </key>
           <!--使用many-to-many来指定多对多的关联关系,column指定Set集合中的持久化类在中间表的外键列的名称-->
          <many-to-many class="Item" column="I_ID"></many-to-many>
       </set>
   </class>
</hibernate-mapping>

Item.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 package="com.zm.n2n.both">
  <class name="Item" table="ITEMS">
      <id name="id" type="java.lang.Integer">
          <column name="ID"/>
          <generator class="native"/>
      </id>
      <property name="name" type="java.lang.String">
          <column name="NAME"/>
      </property>
      <!--将items表作为主动方-->
      <set name="categories" table="CATEGORIES_ITEMS" inverse="true">
          <key column="I_ID"></key>
          <many-to-many class="Category" column="C_ID"></many-to-many>
      </set>
  </class>
</hibernate-mapping>

七、继承关系映射

  Hibernate 的继承映射可以理解持久化类之间的继承关系。例如:人和学生之间的关系。学生继承了人,可以认为学生是一个特殊的人,如果对人进行查询,学生的实例也将被得到。
在这里插入图片描述
父类Person:

public class Person {
    private Integer id;
    private String name;
    private Integer age;
    //getxxx/setxxx方法
}

子类Student:

public class Student extends Person{
    private String school;
    //getxxx/setxxx方法
}

Hibernate支持三种继承映射策略:

1、 subclass

将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。

  • 采用 subclass 的继承映射可以实现对于继承关系中父类和子类使用同一张表
  • 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到低是哪个类的实例----这个列被称为辨别者列(discriminator)
  • 在这种映射策略下,使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值
  • 所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中
    在这里插入图片描述
    Person.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 package="com.zm.inherit.subclass">
    <class name="Person" table="PERSON" discriminator-value="PERSON">
        <id name="id" type="java.lang.Integer">
            <column name="ID"/>
            <generator class="native"/>
        </id>

        <!--配置辨别者列-->
        <discriminator column="TYPE" type="java.lang.String"></discriminator>

        <property name="name" type="java.lang.String">
            <column name="NAME"/>
        </property>
        <property name="age" type="java.lang.Integer">
            <column name="AGE"/>
        </property>
        <!--映射子类Student,使用subclass进行映射
        discriminator-value:用于指定辨别者列的值
        -->
        <subclass name="Student" discriminator-value="STUDENT">
            <property name="school" type="java.lang.String" column="SCHOOL"></property>
        </subclass>
    </class>
</hibernate-mapping>

(1)插入操作

 /*插入操作:
    * 1.对于子类对象只需把记录插入到一张数据表中
    * 2.辨别者列有Hibernate自动维护
    * */
 @Test
    public void testSave(){
        Person person = new Person();
        person.setName("AA");
        person.setAge(21);
        session.save(person);

        Student student = new Student();
        student.setName("Stu_AA");
        student.setAge(12);
        student.setSchool("超星学院");
        session.save(student);
    }

所插入的数据在一张表中,以TYPE字段区分对象:
在这里插入图片描述

(2)查询操作

    /*查询:
    * 1.查询父类记录,只需要查询一张数据表
    * 2.对于子类记录,也只需要查询一张数据表(根据type)
    * */
    @Test
    public void testGet(){
        List<Person> persons = session.createQuery("from Person").list();
        System.out.println(persons.size());

        List<Student> students = session.createQuery("from Student").list();
        System.out.println(students.size());
    }

缺点:
① 使用了辨别者列
② 子类独有的字段不能添加非空约束
③ 若继承层次较深,则数据表的字段也会较多

2、joined-subclass

  • 采用 joined-subclass 元素的继承映射可以实现每个子类一张表
  • 采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。
  • 在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用 key 元素映射共有主键
  • 子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

(1)插入操作

对于子类对象至少需要插入到两张表中。

(2)查询操作

① 查询父类记录,做一个左外连接
② 对于子类记录,做一个内连接

优点:
① 不需要使用辨别者列
② 子类独有的字段能添加非空约束
③ 没有冗余字段

3、 union-subclass

  • 采用 union-subclass 元素可以实现将每一个实体对象映射到一个独立的表中
  • 子类增加的属性可以有非空约束 — 即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。
  • 子类实例的数据仅保存在子类表中, 而在父类表中没有任何记录
  • 在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段、加子类增加属性的总和
  • 在这种映射策略下,既不需要使用鉴别者列,也无须使用 key 元素来映射共有主键
  • 使用 union-subclass 映射策略时不可使用 identity 的主键生成策略,因为同一类继承层次中所有实体类都需要使用同一个主键种子, 即多个持久化实体对应的记录的主键应该是连续的。受此影响,也不该使用 native 主键生成策略。因为 native 会根据数据库来选择使用 identitysequence.
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

(1)保存操作

对于子类对象只需把记录插入到一张数据表中。

(2)查询操作

① 查询父类记录,需把父表和子表汇总到一起再做查询,性能较差
② 对于子类记录,只需要查询一张数据表

优点:
① 无需使用辨别者列
② 子类独有的字段能添加非空约束
缺点:
① 存在冗余字段
② 若更新父表的字段,则更新的效率较低

三种继承映射方式的比较

在这里插入图片描述
推荐使用union-subclasss和joined-subclass

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章