单向多对一(n - 1)
单向 n-1 关联只需从 n 的一端可以访问 1 的一端
下面介绍 n - 1 时,以 Order (订单)和用户(Customer) 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户. 从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联。
步骤
在Order 中添加 Customer 的关联关系
-
在Order 的映射文件中配置映射多对一的关联关系。
代码如下:下面是两种书写方式,效果是相同的<!-- 映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系 name: 多这一端关联的一那一端的属性的名字 class: 一那一端的属性对应的类名 column: 一那一端在多的一端对应的数据表中的外键的名字 --> <!-- <many-to-one name="customer" class="com.cfox.hibernate.n21.Customer" column="CUSTOMER_ID"/> --> <many-to-one name="customer" class="com.cfox.hibernate.n21.Customer"> <column name="CUSTOMER_ID"/> </many-to-one>
<many-to-one> 属性说明
属性 | 描述 | |
---|---|---|
name | 属性名。 | |
column (可选) | 外间字段名。它也可以通过嵌套的 <column> 元素指定。 | |
class (可选 ) | 默认是通过反射得到属性类型): 关联的类的名字。 | |
cascade(级联) (可选) | 指明哪些操作会从父对象级联到关联的对象。 | |
fetch (可选 - 默认为 select ) | 在外连接抓取(outer-join fetching)和序列选择抓取(sequential select fetching)两者中选择其一。 | |
update, insert (可选 - defaults to true ) | 指定对应的字段是否包含在用于UPDATE 和/或 INSERT 的SQL语句中。如果二者都是false ,则这是一个纯粹的 “外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他属性得到 或者通过trigger(触发器)、或其他程序。 | |
property-ref | (可选) 指定关联类的一个属性,这个属性将会和本外键相对应。 | 如果没有指定,会使用对方关联类的主键。 |
access (可选 - 默认是 property ) | Hibernate用来访问属性的策略。 | |
unique (可选) | 使用DDL为外键字段生成一个唯一约束。此外, 这也可以用作property-ref 的目标属性。这使关联同时具有 一对一的效果。 | |
not-null (可选) | 使用DDL为外键字段生成一个非空约束。 | |
optimistic-lock (可选 - 默认为 true ) | 指定这个属性在做更新时是否需要获得乐观锁定(optimistic lock)。 换句话说,它决定这个属性发生脏数据时版本(version)的值是否增长。 | |
lazy (可选 - 默认为 proxy ) | 默认情况下,单点关联是经过代理的。lazy="true" 指定此属性应该在实例变量第一次被访问时应该延迟抓取(fetche lazily)(需要运行时字节码的增强)。lazy="false" 指定此关联总是被预先抓取。 | |
not-found (可选 - 默认为 exception ) | 指定外键引用的数据不存在时如何处理: ignore 会将数据不存在作为关联到一个空对象(null)处理。 | |
entity-name (optional) | 被关联的类的实体名。 |
完整示例代码
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
//对应变量的get 和set 方法省略
}
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2017-10-19 10:20:11 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.cfox.hibernate.n21.Order" table="ORDERS">
<id name="orderId" type="java.lang.Integer">
<column name="ORDERID" />
<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="com.cfox.hibernate.n21.Customer" column="CUSTOMER_ID"/> -->
<many-to-one name="customer" class="com.cfox.hibernate.n21.Customer">
<column name="CUSTOMER_ID"/>
</many-to-one>
</class>
</hibernate-mapping>
public class Customer {
private Integer customerId;
private String name;
//对应变量的get 和set 方法省略
}
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2017-10-19 10:20:11 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.cfox.hibernate.n21.Customer" table="CUSTOMERS">
<id name="customerId" type="java.lang.Integer">
<column name="CUSTOMERID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
</class>
</hibernate-mapping>
双向一对多(1 - n)
双向一对多喝双向多对一是完全相同的,从多的一端可以访问多的一端,从一的一端也可以访问多的一端。
下面还是使用 Order 和Customer 来介绍双向一对多
先看示例代码:
实体bean
public class Customer {
private Integer customerId;
private String customerName;
private Set<Order> orders = new HashSet<Order>();
public Customer() {
}
public Customer(String customerName) {
this.customerName = customerName;
}
// 省略set 和 get 方法
}
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
public Order() {
}
public Order(String orderName) {
this.orderName = orderName;
}
// 省略set 和 get 方法
}
映射文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.cfox.hibernate.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="customer" class="com.cfox.hibernate.Customer">
<column name="CUSTOMER_ID"/>
</many-to-one>
</class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping >
<class name="com.cfox.hibernate.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>
<set name="orders" table="ORDERS" inverse="true" order-by="ORDER_NAME DESC">
<!-- 设定与所关联的持久化类对应的表的外键
column: 指定关联表的外键名
-->
<key column="CUSTOMER_ID"></key>
<!-- 设定集合属性中所关联的持久化类
class: 指定关联的持久化类的类名
-->
<one-to-many class="com.cfox.hibernate.Order"/>
</set>
</class>
</hibernate-mapping>
介绍一下上面的几个元素:
- <set>: 映射持久化类的,也就是一的一端存放多的一端的映射
- name :设定待映射的持久化类的属性的
- <key>:设定与所关联的持久化类对应的表的外键
- column: 指定关联表的外键名
映射关系图
下面介绍<set> 元素中的 inverse 属性
- 在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
- 在没有设置 inverse=true 的情况下,父子两边都维护父子
关系 - 在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
- 在 1-N 关系中,若将 1 方设为主控方
- 会额外多出 update 语句。
- 插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
下面介绍<set> 元素中的 order-by 属性
- 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序
- order-by 属性中还可以加入 SQL 函数
在操作中注意
-
保存操作
public void testMany2OneSave(){ 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, 3 条 INSERT, 2 条 UPDATE //因为 1 的一端和 n 的一端都维护关联关系. 所以会多出 UPDATE //可以在 1 的一端的 set 节点指定 inverse=true, 来使 1 的一端放弃维护关联关系! //建议设定 set 的 inverse=true, 建议先插入 1 的一端, 后插入多的一端 //好处是不会多出 UPDATE 语句 session.save(customer); // session.save(order1); // session.save(order2); //先插入 Order, 再插入 Cusomer, 3 条 INSERT, 4 条 UPDATE // session.save(order1); // session.save(order2); // // session.save(customer); }
-
查询操作
通过多的一端查询 1 的一端public void testMany2OneGet(){ //1. 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象. 而没有查询关联的 1 的那一端的对象! Order order = (Order) session.get(Order.class, 1); System.out.println(order.getOrderName()); System.out.println(order.getCustomer().getClass().getName()); //session.close(); //2. 在需要使用到关联的对象时, 才发送对应的 SQL 语句. Customer customer = order.getCustomer(); System.out.println(customer.getCustomerName()); //3. 在查询 Customer 对象时, 由多的一端导航到 1 的一端时, //若此时 session 已被关闭, 则默认情况下 //会发生 LazyInitializationException 异常 //4. 获取 Order 对象时, 默认情况下, 其关联的 Customer 对象是一个代理对象! }
通过 1 的一端查询多的一端
public void testOne2ManyGet(){ //1. 对 n 的一端的集合使用延迟加载 Customer customer = (Customer) session.get(Customer.class, 7); System.out.println(customer.getCustomerName()); //2. 返回的多的一端的集合时 Hibernate 内置的集合类型. //该类型具有延迟加载和存放代理对象的功能. System.out.println(customer.getOrders().getClass()); //session.close(); //3. 可能会抛出 LazyInitializationException 异常 System.out.println(customer.getOrders().size()); //4. 再需要使用集合中元素的时候进行初始化. }
-
删除操作
public void testDelete(){ //在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象 Customer customer = (Customer) session.get(Customer.class, 1); session.delete(customer); }
-
修改操作
public void testCascade(){ Customer customer = (Customer) session.get(Customer.class, 3); customer.getOrders().clear(); }