Hibernate 映射关系

映射组成关系

•建立域模型和关系数据模型有着不同的出发点:

  –域模型: 由程序代码组成, 通过细化持久化类的的粒度可提高代码的可重用性, 简化编程
  –在没有数据冗余的情况下, 应该尽可能减少表的数目, 简化表之间的参照关系, 以便提高数据的访问速度

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

  –值(value)类型: 没有 OID, 不能被单独持久化, 生命周期依赖于所属的持久化类的对象的生命周期
  –实体(entity)类型: 有 OID, 可以被单独持久化, 有独立的生命周期

•显然无法直接用 property 映射 pay 属性

•Hibernate 使用 <component> 元素来映射组成关系, 该元素表名 pay 属性是 Worker 类一个组成部分, 在 Hibernate 中称之为组件

        <!-- 映射组成关系 -->
        <component name="pay" class="Pay">
        	<parent name="worker"/>
        	<!-- 指定组成关系的组件的属性 -->
        	<property name="monthlyPay" column="MONTHLY_PAY"></property>
        	<property name="yearPay" column="YEAR_PAY"></property>
        	<property name="vocationWithPay" column="VOCATION_WITH_PAY"></property>
        </component>

  注意parent元素指定组件属性所属的整体类,–name: 整体类在组件类中的属性名。所以在Pay中,要有个属性 Woker worker。

映射一对多关联关系

单向 n-1

  •单向 n-1 关联只需从 n 的一端可以访问 1 的一端

  •域模型: 从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性

  •关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键

  •Hibernate 使用 <many-to-one> 元素来映射多对一关联关系
		<!-- 
			映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系 
			name: 多这一端关联的一那一端的属性的名字
			class: 一那一端的属性对应的类名
			column: 一那一端在多的一端对应的数据表中的外键的名字
		-->
		<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
执行  save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT           
          //先插入 1 的一端, 再插入 n 的一端, 只有 INSERT 语句.        
        先插入 Order, 再插入 Customer. 3 条 INSERT, 2 条 UPDATE
                //先插入 n 的一端, 再插入 1 的一端, 会多出 UPDATE 语句! 因为在插入多的一端时, 无法确定 1 的一端的外键值. 所以只能等 1 的一端插入后, 再额外发送 UPDATE 语句.
             推荐先插入 1 的一端, 后插入 n 的一端

查询操作:若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象. 而没有查询关联的 1 的那一端的对象! 在需要使用到关联的对象时, 才发送对应的 SQL 语句.     在查询 Customer 对象时, 由多的一端导航到 1 的一端时, 若此时 session 已被关闭, 则默认情况下会发生 LazyInitializationException 异常
              获取 Order 对象时, 默认情况下, 其关联的 Customer 对象是一个代理对象!

删除操作: 在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象

 双向 1-n

  •域模型:从 Order 到 Customer 的多对一双向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性

  •关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键

  •当 Session 从数据库中加载 Java 集合时, 创建的是 Hibernate 内置集合类的实例, 因此在持久化类中定义集合属性时必须把属性声明为 Java 接口类型
    –Hibernate 的内置集合类具有集合代理功能, 支持延迟检索策略
    –事实上, Hibernate 的内置集合类封装了 JDK 中的集合类, 这使得 Hibernate 能够对缓存中的集合对象进行脏检查, 按照集合对象的状态来同步更新数据库。

  •在定义集合属性时, 通常把它初始化为集合实现类的一个实例. 这样可以提高程序的健壮性, 避免应用程序访问取值为 null 的集合的方法抛出 NullPointerException

  •Hibernate 使用 <set> 元素来映射 set 类型的属性

        <!-- 映射 1 对多的那个集合属性 -->
        <!-- set: 映射 set 类型的属性, table: set 中的元素对应的记录放在哪一个数据表中. 该值需要和多对一的多的那个表的名字一致 -->
        <!-- inverse: 指定由哪一方来维护关联关系. 通常设置为 true, 以指定由多的一端来维护关联关系 -->
        <!-- cascade 设定级联操作. 开发时不建议设定该属性. 建议使用手工的方式来处理 -->
        <!-- order-by 在查询时对集合中的元素进行排序, order-by 中使用的是表的字段名, 而不是持久化类的属性名  -->
        <set name="orders" table="ORDERS" inverse="true" order-by="ORDER_NAME DESC">
        	<!-- 执行多的表中的外键列的名字 -->
        	<key column="CUSTOMER_ID"></key>
        	<!-- 指定映射类型 -->
        	<one-to-many class="Order"/>
        </set>
		<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>

 映射一对一关联关系

基于外键映射的 1-1

•对于基于外键的1-1关联,其外键可以存放在任意一边在需要存放外键一端,增加many-to-one元素。为many-to-one元素增加unique=“true” 属性来表示为1-1关联

		<!-- 使用 many-to-one 的方式来映射 1-1 关联关系 -->
		<many-to-one name="mgr" class="com.atguigu.hibernate.one2one.foreign.Manager" 
			column="MGR_ID" unique="true"></many-to-one>

另一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段

        <one-to-one name="dept" 
        	class="com.atguigu.hibernate.one2one.foreign.Department"
        	property-ref="mgr"></one-to-one>

 注意: 在两个持久化类里都要有另一方的成员变量及Getter,Setter 方法。

保存操作:建议先保存没有外键列的那个对象. 这样会减少 UPDATE 语句

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

注意:两边都使用外键映射的 1-1容易造成混乱,所以不建议使用。

基于主键映射的 1-1

•基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键. <param> 子元素指定使用当前持久化类的哪个属性作为 “对方”

•采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one属性还应增加 constrained=“true” 属性;另一端增加one-to-one元素映射关联属性

•constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键

 1 <hibernate-mapping package="com.atguigu.hibernate.one2one.primary">
 2 
 3     <class name="Department" table="DEPARTMENTS">
 4 
 5         <id name="deptId" type="java.lang.Integer">
 6             <column name="DEPT_ID" />
 7             <!-- 使用外键的方式来生成当前的主键 -->
 8             <generator class="foreign">
 9                 <!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
10                 <param name="property">mgr</param>
11             </generator>
12         </id>
13         
14         <property name="deptName" type="java.lang.String">
15             <column name="DEPT_NAME" />
16         </property>
17         
18         <!--  
19         采用 foreign 主键生成器策略的一端增加 one-to-one 元素映射关联属性,
20         其 one-to-one 节点还应增加 constrained=true 属性, 以使当前的主键上添加外键约束
21         -->
22         <one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>
23                             
24     </class>
25 </hibernate-mapping>
Department.hbm.xml
 1 <hibernate-mapping>
 2 
 3     <class name="com.atguigu.hibernate.one2one.primary.Manager" table="MANAGERS">
 4         
 5         <id name="mgrId" type="java.lang.Integer">
 6             <column name="MGR_ID" />
 7             <generator class="native" />
 8         </id>
 9         
10         <property name="mgrName" type="java.lang.String">
11             <column name="MGR_NAME" />
12         </property>
13         
14         <one-to-one name="dept" 
15             class="com.atguigu.hibernate.one2one.primary.Department"></one-to-one>
16         
17     </class>
18     
19 </hibernate-mapping>
Manager.hbm.xml

 注意: 同样, 在两个持久化类里都要有另一方的成员变量及Getter,Setter 方法。

保存操作: 先插入哪一个都不会有多余的 UPDATE,以为Dept表的主键要Manager 生成后才有。

映射多对多关联关系

单向 n-n

n-n 的关联必须使用连接表

•与 1-n 映射类似,必须为 set 集合元素添加 key 子元素,指定 CATEGORIES_ITEMS 表中参照 CATEGORIES 表的外键为 CATEGORIY_ID. 与 1-n 关联映射不同的是,建立 n-n 关联时, 集合中的元素使用 many-to-many. many-to-many 子元素的 class 属性指定 items 集合中存放的是 Item 对象, column 属性指定  CATEGORIES_ITEMS 表中参照 ITEMS 表的外键为 ITEM_ID

        <!-- 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>

 而 Item 的映射文件里只要正常的属性就行了。

双向 n-n

•双向 n-n 关联需要两端都使用集合属性

•双向n-n关联必须使用连接表

在双向 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.hbm.xml上,Item.hbm.xm

        <set name="categories" table="CATEGORIES_ITEMS" inverse="true">
        	<key column="I_ID"></key>
        	<many-to-many class="com.atguigu.hibernate.n2n.Category" column="C_ID"></many-to-many>
        </set>

 映射继承关系

继承映射

  对于面向对象的程序设计语言而言,继承和多态是两个最基本的概念。Hibernate 的继承映射可以理解持久化类之间的继承关系。

采用 subclass 元素的继承映射

•采用 subclass 的继承映射可以实现对于继承关系中父类和子类使用同一张表

•因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到低是哪个类的实例----这个列被称为辨别者列(discriminator).

•在这种映射策略下,使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值

所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中

 1 <hibernate-mapping package="com.atguigu.hibernate.subclass">
 2     <class name="Person" table="PERSONS" discriminator-value="PERSON">
 3 
 4         <id name="id" type="java.lang.Integer">
 5             <column name="ID" />
 6             <generator class="native" />
 7         </id>
 8         
 9         <!-- 配置辨别者列 -->
10         <discriminator column="TYPE" type="string"></discriminator>
11 
12         <property name="name" type="java.lang.String">
13             <column name="NAME" />
14         </property>
15         
16         <property name="age" type="int">
17             <column name="AGE" />
18         </property>
19         
20         <!-- 映射子类 Student, 使用 subclass 进行映射 -->
21         <subclass name="Student" discriminator-value="STUDENT">
22             <property name="school" type="string" column="SCHOOL"></property>
23         </subclass>
24     </class>
25 </hibernate-mapping>
Person.hbm.xml

插入操作: 

  * 1. 对于子类对象只需把记录插入到一张数据表中.  
  * 2. 辨别者列有 Hibernate 自动维护.

查询: 

  * 1. 查询父类记录, 只需要查询一张数据表  
  * 2. 对于子类记录, 也只需要查询一张数据表

缺点: 

  * 1. 使用了辨别者列.  
  * 2. 子类独有的字段不能添加非空约束.  
  * 3. 若继承层次较深, 则数据表的字段也会较多.

采用 joined-subclass 元素的继承映射

•采用 joined-subclass 元素的继承映射可以实现每个子类一张表

•采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。

•在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用 key 元素映射共有主键

子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中

 1 <hibernate-mapping package="com.atguigu.hibernate.joined.subclass">
 2 
 3     <class name="Person" table="PERSONS">
 4 
 5         <id name="id" type="java.lang.Integer">
 6             <column name="ID" />
 7             <generator class="native" />
 8         </id>
 9         
10         <property name="name" type="java.lang.String">
11             <column name="NAME" />
12         </property>
13         
14         <property name="age" type="int">
15             <column name="AGE" />
16         </property>
17         
18         <joined-subclass name="Student" table="STUDENTS">
19             <key column="STUDENT_id"></key>
20             <property name="school" type="string" column="SCHOOL"></property>
21         </joined-subclass>
22         
23     </class>
24 </hibernate-mapping>
Person.hbm.xml

插入操作:  对于子类对象至少需要插入到两张数据表中.

查询:

  * 1. 查询父类记录, 做一个左外连接查询    
  * 2. 对于子类记录, 做一个内连接查询.

优点:

  * 1. 不需要使用了辨别者列.  
  * 2. 子类独有的字段能添加非空约束.  
  * 3. 没有冗余的字段.

采用 union-subclass 元素的继承映射

•采用 union-subclass 元素可以实现将每一个实体对象映射到一个独立的表中

子类增加的属性可以有非空约束 --- 即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。

子类实例的数据仅保存在子类表中, 而在父类表中没有任何记录

•在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段、加子类增加属性的总和

•在这种映射策略下,既不需要使用鉴别者列,也无须使用 key 元素来映射共有主键.

使用 union-subclass 映射策略是不可使用 identity 的主键生成策略, 因为同一类继承层次中所有实体类都需要使用同一个主键种子, 即多个持久化实体对应的记录的主键应该是连续的. 受此影响, 也不该使用 native 主键生成策略, 因为 native 会根据数据库来选择使用 identity 或 sequence.

 1 <hibernate-mapping package="com.atguigu.hibernate.union.subclass">
 2 
 3     <class name="Person" table="PERSONS">
 4 
 5         <id name="id" type="java.lang.Integer">
 6             <column name="ID" />
 7             <generator class="hilo" />
 8         </id>
 9         
10         <property name="name" type="java.lang.String">
11             <column name="NAME" />
12         </property>
13         
14         <property name="age" type="int">
15             <column name="AGE" />
16         </property>
17     
18         <union-subclass name="Student" table="STUDENTS">
19             <property name="school" column="SCHOOL" type="string"></property>
20         </union-subclass>        
21         
22     </class>
23 </hibernate-mapping>
Person.hbm.xml

插入操作: 对于子类对象只需把记录插入到一张数据表中.

查询:

  * 1. 查询父类记录, 需把父表和子表记录汇总到一起再做查询. 性能稍差.   
  * 2. 对于子类记录, 也只需要查询一张数据表

缺点:
     * 1. 存在冗余的字段
     * 2. 若更新父表的字段, 则更新的效率较低

优点:
     * 1. 无需使用辨别者列.
     * 2. 子类独有的字段能添加非空约束.

三种继承映射方式的比较

  

 
posted @ 2016-08-27 22:35  岳灵珊  阅读(203)  评论(0编辑  收藏  举报