Hibernate入门系列(四)
映射
映射一对多关联关系
在领域模型中, 类与类之间最普遍的关系就是关联关系.
在 UML 中, 关联是有方向的.
以 Customer 和 Order 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户. 从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联
单向关联
双向关联
单向 n-1
单向 n-1 关联只需从 n 的一端可以访问 1 的一端
域模型: 从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
显然无法直接用 property 映射 customer 属性
Hibernate 使用
映射多对一的关联关系,使用many-to-one来映射多对一的关联关系
name:多这一端关联着一那一端的属性的名字
class:一那一端对应的属性的类名
column:一那一端在多那一端对应的数据表中的外键的名字
<many-to-one name="customer" class="io.hibernate.entities.n21.Customer" column="CUSTOMER_ID">
</many-to-one>
many-to-one
name: 设定待映射的持久化类的属性的名字
column: 设定和持久化类的属性对应的表的外键
class:设定待映射的持久化类的属性的类型
双向 1-n
双向 1-n 与 双向 n-1 是完全相同的两种情形
双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.
域模型:从 Order 到 Customer 的多对一双向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
当 Session 从数据库中加载 Java 集合时, 创建的是 Hibernate 内置集合类的实例, 因此在持久化类中定义集合属性时必须把属性声明为 Java 接口类型
- Hibernate 的内置集合类具有集合代理功能, 支持延迟检索策略
事实上, Hibernate 的内置集合类封装了 JDK 中的集合类, 这使得 Hibernate 能够对缓存中的集合对象进行脏检查, 按照集合对象的状态来同步更新数据库。
在定义集合属性时, 通常把它初始化为集合实现类的一个实例. 这样可以提高程序的健壮性, 避免应用程序访问取值为 null 的集合的方法抛出 NullPointerException/* * 1.声明集合类型时需要使用接口类型,因为hibernate在获取集合类型时,返回的是 * hibernate的内置集合类型,而不是JAVASE一个标准的集合实现 * 2.需要对集合进行初始化,不会出现空指针异常的问题 */ private Set<Order> orders=new HashSet<>();
Hibernate 使用
映射关系如下:
<!-- 映射一对多的集合属性 -->
<!-- set:映射set类型的属性,table:set中的元素对应的记录放在哪个数据表中,该值需要和多对一的那个多的表的名字一致 -->
<!-- inverse:指定由哪一方来维护关联关系,通常设置为true,来指定由多的一方来维护关联关系 -->
<!-- cascade:设定级联操作,开发时不建议设定该属性,建议使用手工的方式来处理 -->
<!-- order-by:在查询时对集合中的元素进行排序,order-by中使用的是表的字段名,而非持久化类的属性名 -->
<set name="orders" table="ORDERS" inverse="true" cascade="delete-orphan" order-by="">
<!-- 执行对的表的外键列的名称 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射类型 -->
<one-to-many class="io.hibernate.entities.n21.both.Order"/>
</set>
各节点的含义:
name: 设定待映射的持久化类的属性的
column: 指定关联表的外键名
class: 指定关联的持久化类的类名
inverse 属性
- 在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
- 在没有设置 inverse=true 的情况下,父子两边都维护父子
关系 - 在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
- 在 1-N 关系中,若将 1 方设为主控方
1.会额外多出 update 语句。
2.插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
cascade 属性
在对象 – 关系映射文件中, 用于映射持久化类之间关联关系的元素,, 和 都有一个 cascade 属性, 它用于指定如何操纵与当前对象关联的其他对象.
order-by 属性方元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序方
order-by 属性中还可以加入 SQL 函数方
映射一对一关联关系
1.域模型
2.关系数据模型
按照外键映射:
按照主键映射:
基于外键映射的 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="io.hibernate.entities.Manager" column="MGR_ID" unique="true"></many-to-one>
另一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
<!-- 映射1—1关联关系,在对应数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
<!--
没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
-->
<one-to-one name="dept" class="io.hibernate.entities.Department"
property-ref="mgr"></one-to-one>
不使用 property-ref 属性的 sql
Hibernate:
select
manager0_.MGR_ID as MGR_ID1_1_1_,
manager0_.MGR_NAME as MGR_NAME2_1_1_,
department1_.DEPT_ID as DEPT_ID1_0_0_,
department1_.DEPT_NAME as DEPT_NAM2_0_0_,
department1_.MGR_ID as MGR_ID3_0_0_
from
MANAGERS manager0_
left outer join
DEPARTMENTS department1_
on manager0_.MGR_ID=department1_.DEPT_ID //明显有误
where
manager0_.MGR_ID=?
使用 property-ref 属性的 sql
Hibernate:
select
manager0_.MGR_ID as MGR_ID1_1_1_,
manager0_.MGR_NAME as MGR_NAME2_1_1_,
department1_.DEPT_ID as DEPT_ID1_0_0_,
department1_.DEPT_NAME as DEPT_NAM2_0_0_,
department1_.MGR_ID as MGR_ID3_0_0_
from
MANAGERS manager0_
left outer join
DEPARTMENTS department1_
on manager0_.MGR_ID=department1_.MGR_ID //正确
where
manager0_.MGR_ID=?
两边都使用外键映射的 1-1
此应用方式会导致引用之间不一一对应,从而产生引用混乱的情况,一般不会这样使用。
基于主键映射的 1-1
基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键. 子元素指定使用当前持久化类的哪个属性作为 “对方”
采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one属性还应增加 constrained=“true” 属性;另一端增加one-to-one元素映射关联属性。
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键
映射多对多关联关系
单向 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
双向 n-n
域模型:
关系数据模型:
双向 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, 否则两端都维护关联关系可能会造成主键冲突.
映射继承关系
对于面向对象的程序设计语言而言,继承和多态是两个最基本的概念。Hibernate 的继承映射可以理解持久化类之间的继承关系。例如:人和学生之间的关系。学生继承了人,可以认为学生是一个特殊的人,如果对人进行查询,学生的实例也将被得到。
Hibernate支持三种继承映射策略:
- 使用 subclass 进行映射:将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。
- 使用 joined-subclass 进行映射: 对于继承关系中的子类使用同一个表,这就需要在数据库表中增加额外的区分子类类型的字段。
- 使用 union-subclass 进行映射:域模型中的每个类映射到一个表,通过关系数据模型中的外键来描述表之间的继承关系。这也就相当于按照域模型的结构来建立数据库中的表,并通过外键来建立表之间的继承关系。
采用 subclass 元素的继承映射
采用 subclass 的继承映射可以实现对于继承关系中父类和子类使用同一张表
因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到低是哪个类的实例—-这个列被称为辨别者列(discriminator).
在这种映射策略下,使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值
所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中
<class name="io.hibernate.entities.subclass.Person" table="PERSONS" discriminator-value="PERSON">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<!-- 配置辨别者列-->
<discriminator column="TYPE" type="string"></discriminator>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="age" type="int">
<column name="AGE" />
</property>
<!-- 映射子类student,使用subclass进行映射 -->
<subclass name="io.hibernate.entities.subclass.Student" discriminator-value="STUDENT">
<property name="school" type="string" column="SCHOOL"></property>
</subclass>
</class>
采用 joined-subclass 元素的继承映射
采用 joined-subclass 元素的继承映射可以实现每个子类一张表
采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。
在这种映射策略下,无须使用鉴别者列,但需要为每个子类使用 key 元素映射共有主键。
子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中
<class name="io.hibernate.entities.joined.subclass.Person" table="PERSONS">
<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>
<property name="age" type="int">
<column name="AGE" />
</property>
<joined-subclass name="io.hibernate.entities.joined.subclass.Student" table="STUDENTS">
<key column="STUDENT_id"></key>
<property name="school" type="string" column="SCHOOL"></property>
</joined-subclass>
</class>
采用 union-subclass 元素的继承映射
<class name="io.hibernate.entities.joined.subclass.Person" table="PERSONS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="increment" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="age" type="int">
<column name="AGE" />
</property>
<union-subclass name="io.hibernate.entities.joined.subclass.Student" table="STUDENTS">
<property name="school"></property>
</union-subclass>
</class>