你的位置:首页 > Java教程

[Java教程]Hibernate —— 映射关联关系


一、映射多对一关联关系。

1.单向的多对一

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

(2)创建 Customer 和 Order 表。

CREATE TABLE customer ( customer_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY , customer_name VARCHAR(50))CREATE TABLE `order` ( order_id  INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, order_name VARCHAR(50), customer_id INT(11))

Create

(3)用 Intellij Idea 自动生成关联关系,以及对应的 Entitiy.hbm.

说明:

其中 Type 是用来修饰对应的 Attribute Name 的。

在 Order 端,定义 Customer 类,一个订单属于一个客户。而在 Customer 端,一个客户可以有多个订单,因为是单向的,所以这里放弃属性的添加。

在 Join Columns 定义了 Order 和 Customer 之间的关联关系,order 表中的 customer_id 外键和 customer 表中的 customer_id 主键关联。

来看生成的 Schema:

没有勾选 customer_id,是因为 Intellij Idea 没法直接映射为 Customer 类型的 customer。

<hibernate-mapping>  <class name="com.nucsoft.hibernate.Order" table="order" schema="hibernate">    <id name="orderId">      <column name="order_id" sql-type="int(11)"/>      <generator class="native"/>    </id>    <property name="orderName">      <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/>    </property>    <many-to-one name="customer" class="com.nucsoft.hibernate.Customer">      <column name="customer_id" not-null="true"/>    </many-to-one>  </class></hibernate-mapping>

Order.hbm.

使用 <many-to-one> 节点来维护多对一关联关系。

name 属性:多这一端关联的一那一端的属性的名称。

class 属性:关联的一端的属性的类型。

column 属性:一那一端在多的一端对应的数据表中的外键。可以任意命名,但需要和数据表中的字段对应。

(4)单向多对一的 CRUD 以及需要注意的问题。

<1> 新增

①先保存一的一端 Customer,后保存多的一端 Order。

@Testpublic void testMany2OneSave() {  Customer customer = new Customer();  customer.setCustomerName("aa");  Order order = new Order();  order.setOrderName("order1");  order.setCustomer(customer);  Order order2 = new Order();  order2.setOrderName("order2");  order2.setCustomer(customer);  session.save(customer);  session.save(order);  session.save(order2);}

Save.java

打印 SQL:

Hibernate:   insert   into    hibernate.customer    (customer_name)   values    (?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)

Output

结论:发送了3条 INSERT 语句。

②先保存多的一端 Order,再保存一的一端 Customer。

@Testpublic void testMany2OneSave() {  Customer customer = new Customer();  customer.setCustomerName("bb");  Order order = new Order();  order.setOrderName("order3");  order.setCustomer(customer);  Order order2 = new Order();  order2.setOrderName("order4");  order2.setCustomer(customer);  session.save(order);  session.save(order2);  session.save(customer);}

Save2.java

打印 SQL:

Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   insert   into    hibernate.customer    (customer_name)   values    (?)Hibernate:   update    hibernate.order   set    order_name=?,    customer_id=?   where    order_id=?Hibernate:   update    hibernate.order   set    order_name=?,    customer_id=?   where    order_id=?

Output2

结论:发送了3条 INSERT 语句,2条 UPDATE 语句。

总结:在单向多对一的关联关系下,先插入 1 的一端会减少 SQL 语句的执行,性能更高。

<2>删除

先删除1的一端。

@Testpublic void testMany2OneDelete() {  Customer customer = (Customer) session.get(Customer.class, 1);  session.delete(customer);} 

Delete.java

控制台打印:

Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`order`, CONSTRAINT `FK_m6q2ofkj1g5aobtb2p00ajpqg` FOREIGN KEY (`customer_id`)  REFERENCES `customer` (`customer_id`))

结论:在不设置级联关系的前提下,不能删除 1 的一端。

<3>更新

@Testpublic void testMany2OneUpdate() {  Order order = (Order) session.get(Order.class, 1);  order.getCustomer().setCustomerName("aaa");}

Update.java
Hibernate:   select    order0_.order_id as order_id1_1_0_,    order0_.order_name as order_na2_1_0_,    order0_.customer_id as customer3_1_0_   from    hibernate.order order0_   where    order0_.order_id=?Hibernate:   select    customer0_.customer_id as customer1_0_0_,    customer0_.customer_name as customer2_0_0_   from    hibernate.customer customer0_   where    customer0_.customer_id=?Hibernate:   update    hibernate.customer   set    customer_name=?   where    customer_id=?

Output

<4>查询

①查询 n 的一端,但是不使用查询出来关联的 1 的一端的对象。

@Testpublic void testMany2OneGet() {  Order order = (Order) session.get(Order.class, 1);  System.out.println(order.getCustomer().getClass().getName());} 

Hibernate:   select    order0_.order_id as order_id1_1_0_,    order0_.order_name as order_na2_1_0_,    order0_.customer_id as customer3_1_0_   from    hibernate.order order0_   where    order0_.order_id=?order1com.nucsoft.hibernate.Customer_$$_jvst30c_1

②查询 n 的一端,使用查询出来关联的 1 的一端的对象。

@Testpublic void testMany2OneGet() {  Order order = (Order) session.get(Order.class, 1);  System.out.println(order.getCustomer().getClass().getName());  order.getCustomer().getCustomerName();}

Hibernate:   select    order0_.order_id as order_id1_1_0_,    order0_.order_name as order_na2_1_0_,    order0_.customer_id as customer3_1_0_   from    hibernate.order order0_   where    order0_.order_id=?com.nucsoft.hibernate.Customer_$$_jvst30c_1Hibernate:   select    customer0_.customer_id as customer1_0_0_,    customer0_.customer_name as customer2_0_0_   from    hibernate.customer customer0_   where    customer0_.customer_id=?

总结:可以发现,采用的是懒加载机制,即获取到的 1 的一端的对象是一个代理对象。只有在使用这个对象的属性的情况下,才会发送 SQL 语句。

③ 由懒加载机制引发的 懒加载异常。

@Testpublic void testMany2OneGet() {  Order order = (Order) session.get(Order.class, 1);  System.out.println(order.getCustomer().getClass().getName());  session.close();  order.getCustomer().getCustomerName();}

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

在需要使用对象之前,关闭了 Session 连接,由此会引发 LazyInitializationException 异常。

2.双向的多对一

(1)还是以 Order 和 Customer 为例:双向的多对一不仅仅要在 Order 类中定义一个 Customer 属性,而在 Customer 类中也需定义存放 Order 对象的集合属性。

(2)创建 Order  和 Customer 表和创建单向多对一相同。

(3)通过 Intellij Idea 生成简单的持久化类和 Entity.hbm.

<1>生成简单的持久化类 和 Entity.hbm.

package com.nucsoft.hibernate;/** * @author solverpeng * @create 2016-10-11-13:23 */public class Customer {  private Integer customerId;  private String customerName;  public Integer getCustomerId() {    return customerId;  }  public void setCustomerId(Integer customerId) {    this.customerId = customerId;  }  public String getCustomerName() {    return customerName;  }  public void setCustomerName(String customerName) {    this.customerName = customerName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Customer customer = (Customer) o;    if(customerId != null ? !customerId.equals(customer.customerId) : customer.customerId != null) {      return false;    }    if(customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = customerId != null ? customerId.hashCode() : 0;    result = 31 * result + (customerName != null ? customerName.hashCode() : 0);    return result;  }}

Customer.java
package com.nucsoft.hibernate;/** * @author solverpeng * @create 2016-10-11-13:23 */public class Order {  private Integer orderId;  private String orderName;  public Integer getOrderId() {    return orderId;  }  public void setOrderId(Integer orderId) {    this.orderId = orderId;  }  public String getOrderName() {    return orderName;  }  public void setOrderName(String orderName) {    this.orderName = orderName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Order order = (Order) o;    if(orderId != null ? !orderId.equals(order.orderId) : order.orderId != null) {      return false;    }    if(orderName != null ? !orderName.equals(order.orderName) : order.orderName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = orderId != null ? orderId.hashCode() : 0;    result = 31 * result + (orderName != null ? orderName.hashCode() : 0);    return result;  }}

Order.java
<??><!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.nucsoft.hibernate.Customer" table="customer" schema="hibernate">    <id name="customerId">      <column name="customer_id" sql-type="int(11)"/>    </id>    <property name="customerName">      <column name="customer_name" sql-type="varchar(50)" length="50" not-null="true"/>    </property>  </class></hibernate-mapping>

Customer.hbm.
<??><!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.nucsoft.hibernate.Order" table="order" schema="hibernate">    <id name="orderId">      <column name="order_id" sql-type="int(11)"/>    </id>    <property name="orderName">      <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/>    </property>  </class></hibernate-mapping>

Order.hbm.

<2>手动建立关联关系

①在 Order 一端建立多对一的关联关系。

  • 在 Order 持久化类中添加 Customer 类型的一个属性 customer。
  • 在 Order.hbm.

②在 Customer 一端建立一对多的关联关系。

  • 在 Customer 持久化类中添加 Order 的一个集合 orders。
  • 在 Customer.hbm.

③详细说明:在 Customer.hbm.

  • 当 Session 从数据库中加载 Java 集合时,创建的是 Hibernate 内置的集合类的实例。因此在持久化类中定义集合属性时需要定义成接口类型,不能是具体的某个实现类。
    • Hibernate 内置的集合具有集合代理功能,因为有代理功能,所以支持延迟检索策略。  
  • 在定义集合的时候,通常将其初始化为集合实现类的一个实例,防止 NullPointerException。
  • Hibernate 使用 <set> 元素来映射 Set 类型的属性。
  • 1 的一端的 Set 类型属性数据还是存放在 n 的一端。

④ set 元素

  • name 属性:待映射的 Set 类型的属性的属性名称。
  • table 属性:待映射的 Set 属性的泛型类型所对应的表。
  • key 子元素:column 属性,多的一端的外键名称。
  • one-to-many 子元素:class 属性,n 的一端的持久化类名称。

对应关系如图。

⑤最终的实体类和 Entity.hbm.

package com.nucsoft.hibernate;import java.util.HashSet;import java.util.Set;/** * @author solverpeng * @create 2016-10-11-13:23 */public class Customer {  private Integer customerId;  private String customerName;  private Set<Order> orders = new HashSet<>();  public Set<Order> getOrders() {    return orders;  }  public void setOrders(Set<Order> orders) {    this.orders = orders;  }  public Integer getCustomerId() {    return customerId;  }  public void setCustomerId(Integer customerId) {    this.customerId = customerId;  }  public String getCustomerName() {    return customerName;  }  public void setCustomerName(String customerName) {    this.customerName = customerName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Customer customer = (Customer) o;    if(customerId != null ? !customerId.equals(customer.customerId) : customer.customerId != null) {      return false;    }    if(customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = customerId != null ? customerId.hashCode() : 0;    result = 31 * result + (customerName != null ? customerName.hashCode() : 0);    return result;  }}

Customer.java
package com.nucsoft.hibernate;/** * @author solverpeng * @create 2016-10-11-13:23 */public class Order {  private Integer orderId;  private String orderName;  private Customer customer;  public Customer getCustomer() {    return customer;  }  public void setCustomer(Customer customer) {    this.customer = customer;  }  public Integer getOrderId() {    return orderId;  }  public void setOrderId(Integer orderId) {    this.orderId = orderId;  }  public String getOrderName() {    return orderName;  }  public void setOrderName(String orderName) {    this.orderName = orderName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Order order = (Order) o;    if(orderId != null ? !orderId.equals(order.orderId) : order.orderId != null) {      return false;    }    if(orderName != null ? !orderName.equals(order.orderName) : order.orderName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = orderId != null ? orderId.hashCode() : 0;    result = 31 * result + (orderName != null ? orderName.hashCode() : 0);    return result;  }}

Order.java
<??><!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.nucsoft.hibernate">  <class name="Customer" table="customer" schema="hibernate">    <id name="customerId">      <column name="customer_id" sql-type="int(11)"/>      <generator class="native"/>    </id>    <property name="customerName">      <column name="customer_name" sql-type="varchar(50)" length="50" not-null="true"/>    </property>    <set name="orders" table="order">      <key column="customer_id"/>      <one-to-many class="Order"/>    </set>  </class></hibernate-mapping>

Customer.hbm.
<??><!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.nucsoft.hibernate">  <class name="Order" table="order" schema="hibernate">    <id name="orderId">      <column name="order_id" sql-type="int(11)"/>      <generator class="native"/>    </id>    <property name="orderName">      <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/>    </property>    <many-to-one name="customer" class="Customer" column="customer_id"/>  </class></hibernate-mapping>

Order.hbm.

(4)通过 Intellij Idea 直接生成双向的多对一的关联关系。

<1>为生成的每个 Entity.hbm.

<2>为 Customer 类中的 orders 属性进行初始化。

<3>最终的持久化类和 Entity.hbm.

package com.nucsoft.hibernate;import java.util.HashSet;import java.util.Set;/** * @author solverpeng * @create 2016-10-11-14:01 */public class Customer {  private Integer customerId;  private String customerName;  private Set<Order> orders = new HashSet<>();  public Integer getCustomerId() {    return customerId;  }  public void setCustomerId(Integer customerId) {    this.customerId = customerId;  }  public String getCustomerName() {    return customerName;  }  public void setCustomerName(String customerName) {    this.customerName = customerName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Customer customer = (Customer) o;    if(customerId != null ? !customerId.equals(customer.customerId) : customer.customerId != null) {      return false;    }    if(customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = customerId != null ? customerId.hashCode() : 0;    result = 31 * result + (customerName != null ? customerName.hashCode() : 0);    return result;  }  public Set<Order> getOrders() {    return orders;  }  public void setOrders(Set<Order> orders) {    this.orders = orders;  }}

Customer.java
package com.nucsoft.hibernate;/** * @author solverpeng * @create 2016-10-11-14:01 */public class Order {  private Integer orderId;  private String orderName;  private Customer customer;  public Integer getOrderId() {    return orderId;  }  public void setOrderId(Integer orderId) {    this.orderId = orderId;  }  public String getOrderName() {    return orderName;  }  public void setOrderName(String orderName) {    this.orderName = orderName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Order order = (Order) o;    if(orderId != null ? !orderId.equals(order.orderId) : order.orderId != null) {      return false;    }    if(orderName != null ? !orderName.equals(order.orderName) : order.orderName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = orderId != null ? orderId.hashCode() : 0;    result = 31 * result + (orderName != null ? orderName.hashCode() : 0);    return result;  }  public Customer getCustomer() {    return customer;  }  public void setCustomer(Customer customer) {    this.customer = customer;  }}

Order.java
<??><!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.nucsoft.hibernate.Customer" table="customer" schema="hibernate">    <id name="customerId">      <column name="customer_id" sql-type="int(11)"/>      <generator class="native"/>    </id>    <property name="customerName">      <column name="customer_name" sql-type="varchar(50)" length="50" not-null="true"/>    </property>    <set name="orders" inverse="true">      <key>        <column name="customer_id" not-null="true"/>      </key>      <one-to-many not-found="ignore" class="com.nucsoft.hibernate.Order"/>    </set>  </class></hibernate-mapping>

Customer.hbm.
<??><!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.nucsoft.hibernate.Order" table="order" schema="hibernate">    <id name="orderId">      <column name="order_id" sql-type="int(11)"/>      <generator class="native"/>    </id>    <property name="orderName">      <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/>    </property>    <many-to-one name="customer" class="com.nucsoft.hibernate.Customer">      <column name="customer_id" not-null="true"/>    </many-to-one>  </class></hibernate-mapping>

Order.hbm.

<4>对比发现,通过 Intellij Idea 自动生成的 Customer.hbm.

(5)双向多对一的 CRUD 和需要注意的问题

<1>新增

①双方都维护关联关系,即没有设置 inverse 属性,且没有添加非空约束。

先保存 1 的一端,再保存 n 的一端。

@Testpublic void testMany2OneBothSave() {  Customer customer = new Customer();  customer.setCustomerName("aa");  Order order = new Order();  order.setOrderName("order1");  order.setCustomer(customer);  Order order2 = new Order();  order2.setOrderName("order2");  order2.setCustomer(customer);  customer.getOrders().add(order);  customer.getOrders().add(order2);  session.save(customer);  session.save(order);  session.save(order2);  }

Save.java

打印SQL:

Hibernate:   insert   into    hibernate.customer    (customer_name)   values    (?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   update    hibernate.order   set    customer_id=?   where    order_id=?Hibernate:   update    hibernate.order   set    customer_id=?   where    order_id=?

Output

结果:打印了 3 条 INSERT 语句,2 条 UPDATE 语句

先保存 n 的一端,再保存 1 的一端。

@Testpublic void testMany2OneBothSave() {  Customer customer = new Customer();  customer.setCustomerName("cc");  Order order = new Order();  order.setOrderName("order5");  order.setCustomer(customer);  Order order2 = new Order();  order2.setOrderName("order6");  order2.setCustomer(customer);  customer.getOrders().add(order);  customer.getOrders().add(order2);  session.save(order);  session.save(order2);  session.save(customer);}

Save2.java

打印 SQL :

Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   insert   into    hibernate.customer    (customer_name)   values    (?)Hibernate:   update    hibernate.order   set    order_name=?,    customer_id=?   where    order_id=?Hibernate:   update    hibernate.order   set    order_name=?,    customer_id=?   where    order_id=?Hibernate:   update    hibernate.order   set    customer_id=?   where    order_id=?Hibernate:   update    hibernate.order   set    customer_id=?   where    order_id=?

Output2

结果:打印了 3 条 INSERT 语句,4 条 UPDATE 语句。原因,双方都维护这关联关系。

②双方都维护关联关系,即没有设置 inverse 属性,对 order 表中的 customer_id 列添加非空约束(需要更改两个地方)。

先保存 n 的一端,再保存 1 的一端,会抛出异常。

org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.nucsoft.hibernate.Order.customer -> com.nucsoft.hibernate.Customer

③ 1 的一端放弃维护关联关系,只由 n 的一端来维护。即设置 Customer.hbm.

先保存 1 的一端,后保存 n 的一端。

Hibernate:   insert   into    hibernate.customer    (customer_name)   values    (?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)Hibernate:   insert   into    hibernate.order    (order_name, customer_id)   values    (?, ?)

Output

结果:只会发送3条 INSERT 语句。

④总结:

介绍了双向的多对一的下的保存操作,若都维护关联关系,则会多出 UPDATE 语句。且若外键存在非空约束时,不能先保存 n 的一端。

所以在进行 Hibernate 双向多对一保存的时候,最好的做法就是:

1 的一端放弃维护关联关系,即 设置 set 节点的 inverse 属性为  true。同时在保存的时候先保存 1 的一端,后保存 n 的一端。

<2>删除

@Testpublic void testMany2OneBothDelete() {  Customer customer = (Customer) session.get(Customer.class, 5);  session.delete(customer);}

Delete

同删除单向的多对一相同,会抛出异常:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails

存在外键约束。

<3>更新

@Testpublic void testMany2OneBothUpdate() {  Customer customer = (Customer) session.get(Customer.class, 5);  System.out.println(customer.getOrders().iterator().next().getOrderName());  customer.getOrders().iterator().next().setOrderName("order@@");}

Update
Hibernate:   select    customer0_.customer_id as customer1_0_0_,    customer0_.customer_name as customer2_0_0_   from    hibernate.customer customer0_   where    customer0_.customer_id=?Hibernate:   select    orders0_.customer_id as customer3_0_0_,    orders0_.order_id as order_id1_1_0_,    orders0_.order_id as order_id1_1_1_,    orders0_.order_name as order_na2_1_1_,    orders0_.customer_id as customer3_1_1_   from    hibernate.order orders0_   where    orders0_.customer_id=?order4Hibernate:   update    hibernate.order   set    order_name=?,    customer_id=?   where    order_id=?

Output

<4>查询

@Testpublic void testMany2OneBothGet() {  Customer customer = (Customer) session.get(Customer.class, 5);  System.out.println(customer.getOrders().getClass());}

打印结果:

Hibernate:   select    customer0_.customer_id as customer1_0_0_,    customer0_.customer_name as customer2_0_0_   from    hibernate.customer customer0_   where    customer0_.customer_id=?class org.hibernate.collection.internal.PersistentSet

并没有查询关联的 Order 集合,实际类型为 Hibernate 内置的一个 Set 实现类。

证明了:

当 Session 从数据库中加载 Java 集合时,创建的是 Hibernate 内置的集合类的实例。因此在持久化类中定义集合属性时需要定义成接口类型,不能是具体的某个实现类。

也证明了:

Hibernate 内置的集合具有集合代理功能,因为有代理功能,所以支持延迟检索策略。

<5>说明

这里没有介绍 cascade 属性,是因为在实际的项目中,为了保护数据,很少设置 cascade 属性,而是手动去处理。

二、映射一对一关联关系。

1.这里只介绍双向的一对一关联关系如何映射。单向的一对一只需要把没有外键的一端去掉就好了。

2.基于外键映射的双向一对一

(1)以 Dempartment 和 Manager 为例。一个部门只能有一个经理,一个经理职能管理一个部门。

(2)创建 Department 和 Manager 表。在 Department 表建立 Manager 表的外键。

CREATE TABLE department(  dept_id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,  dept_name VARCHAR(50),  manager_id_fk INT(11));

department.sql
CREATE TABLE manager(  manager_id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,  manager_name VARCHAR(50));

Manager.

(3)通过 Intellij Idea 自动生成的双向 1 对 1 无法映射列。这里通过 Intellij Idea 生成简单的持久化类和 Entity.hbm.

package com.nucsoft.hibernate;/** * @author solverpeng * @create 2016-10-12-9:59 */public class Department {  private Integer deptId;  private String deptName;  private Manager manager;  public Integer getDeptId() {    return deptId;  }  public void setDeptId(Integer deptId) {    this.deptId = deptId;  }  public String getDeptName() {    return deptName;  }  public void setDeptName(String deptName) {    this.deptName = deptName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Department that = (Department) o;    if(deptId != null ? !deptId.equals(that.deptId) : that.deptId != null) {      return false;    }    if(deptName != null ? !deptName.equals(that.deptName) : that.deptName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = deptId != null ? deptId.hashCode() : 0;    result = 31 * result + (deptName != null ? deptName.hashCode() : 0);    return result;  }  public Manager getManager() {    return manager;  }  public void setManager(Manager manager) {    this.manager = manager;  }}

Department.java
package com.nucsoft.hibernate;/** * @author solverpeng * @create 2016-10-12-9:59 */public class Manager {  private Integer managerId;  private String managerName;  private Department dept;  public Integer getManagerId() {    return managerId;  }  public void setManagerId(Integer managerId) {    this.managerId = managerId;  }  public String getManagerName() {    return managerName;  }  public void setManagerName(String managerName) {    this.managerName = managerName;  }  @Override  public boolean equals(Object o) {    if(this == o) {      return true;    }    if(o == null || getClass() != o.getClass()) {      return false;    }    Manager manager = (Manager) o;    if(managerId != null ? !managerId.equals(manager.managerId) : manager.managerId != null) {      return false;    }    if(managerName != null ? !managerName.equals(manager.managerName) : manager.managerName != null) {      return false;    }    return true;  }  @Override  public int hashCode() {    int result = managerId != null ? managerId.hashCode() : 0;    result = 31 * result + (managerName != null ? managerName.hashCode() : 0);    return result;  }  public Department getDept() {    return dept;  }  public void setDept(Department dept) {    this.dept = dept;  }}

Manager.java
<??><!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.nucsoft.hibernate.Department" table="department" schema="hibernate">    <id name="deptId" column="dept_id">      <generator class="native"/>    </id>    <property name="deptName" column="dept_name"/>    <many-to-one name="manager" class="com.nucsoft.hibernate.Manager" column="manager_id_fk" unique="true"/>  </class></hibernate-mapping>

Department.hbm.
<??><!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.nucsoft.hibernate.Manager" table="manager" schema="hibernate">    <id name="managerId" column="manager_id">      <generator class="native"/>    </id>    <property name="managerName" column="manager_name"/>    <one-to-one name="dept" class="com.nucsoft.hibernate.Department" property-ref="manager"/>  </class></hibernate-mapping>

Manager.hbm.

(4)说明:

在 department  表中来维护外键。在映射的时候选择 <many-to-one > 元素,通过 unique 属性来达到 一对一的效果。

在 manager 表中没有维护外键。在映射时候选择 <one-to-one>元素,至于属性 property-ref 稍后在查询的时候说明。

(5)CRUD 以及需要注意的地方。

<1>save

@Testpublic void testSave() {  Department department = new Department();  department.setDeptName("dept-4");  Manager manager = new Manager();  manager.setManagerName("manager=DD");  department.setManager(manager);  manager.setDept(department);  session.save(manager);  session.save(department);}

Save1.java
Hibernate:   insert   into    hibernate.manager    (manager_name)   values    (?)Hibernate:   insert   into    hibernate.department    (dept_name, manager_id_fk)   values    (?, ?) 

Output1
@Testpublic void testSave() {  Department department = new Department(); department.setDeptName("dept-2");  Manager manager = new Manager();  manager.setManagerName("manager=BB");  department.setManager(manager);  manager.setDept(department);  session.save(department);  session.save(manager);}

Save2.java
Hibernate:   insert   into    hibernate.department    (dept_name, manager_id_fk)   values    (?, ?)Hibernate:   insert   into    hibernate.manager    (manager_name)   values    (?)Hibernate:   update    hibernate.department   set    dept_name=?,    manager_id_fk=?   where    dept_id=?

Output2

对比发现,先保存没有外键列的对象,会减少 UPDATE 语句的发送,提高性能。

<2>delete

@Testpublic void testDelete() {  Department department = (Department) session.get(Department.class, 4);  session.delete(department);}

Delete1.java
Hibernate:   select    department0_.dept_id as dept1_0_0_,    department0_.dept_name as dept2_0_0_,    department0_.manager_id_fk as manager3_0_0_   from    hibernate.department department0_   where    department0_.dept_id=?Hibernate:   delete   from    hibernate.department   where    dept_id=?

Output1
@Testpublic void testDelete() {  Manager manager = (Manager) session.get(Manager.class, 5);  session.delete(manager);}

Delete2.java

Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`department`, CONSTRAINT `FK_kpcmf8csabfn9epikikcfqbk0` FOREIGN KEY (`manager_id_fk`) REFERENCES `manager` (`manager_id`))

外键关联对象存在的情况下,不能先删除拥有外键的对象。

<3>update

@Testpublic void testUpdate() {  Department department = (Department) session.get(Department.class, 6);  department.getManager().setManagerName("@@");}

Update.java
Hibernate:   select    department0_.dept_id as dept1_0_0_,    department0_.dept_name as dept2_0_0_,    department0_.manager_id_fk as manager3_0_0_   from    hibernate.department department0_   where    department0_.dept_id=?Hibernate:   select    manager0_.manager_id as manager1_1_1_,    manager0_.manager_name as manager2_1_1_,    department1_.dept_id as dept1_0_0_,    department1_.dept_name as dept2_0_0_,    department1_.manager_id_fk as manager3_0_0_   from    hibernate.manager manager0_   left outer join    hibernate.department department1_       on manager0_.manager_id=department1_.dept_id   where    manager0_.manager_id=?Hibernate:   update    hibernate.manager   set    manager_name=?   where    manager_id=?

Output

<4>get

@Testpublic void testGet() {  Department department = (Department) session.get(Department.class, 6);  System.out.println(department);  System.out.println(department.getManager().getClass());  System.out.println("---------------");  System.out.println(department.getManager().getManagerName());}

Get1.java
Hibernate:   select    department0_.dept_id as dept1_0_0_,    department0_.dept_name as dept2_0_0_,    department0_.manager_id_fk as manager3_0_0_   from    hibernate.department department0_   where    department0_.dept_id=?com.nucsoft.hibernate.Department@b0688765class com.nucsoft.hibernate.Manager_$$_javassist_1---------------Hibernate:   select    manager0_.manager_id as manager1_1_1_,    manager0_.manager_name as manager2_1_1_,    department1_.dept_id as dept1_0_0_,    department1_.dept_name as dept2_0_0_,    department1_.manager_id_fk as manager3_0_0_   from    hibernate.manager manager0_   left outer join    hibernate.department department1_       on manager0_.manager_id=department1_.dept_id   where    manager0_.manager_id=?@@ 

Output1

可以看到, 查询拥有外键对象关联的对象时,采用的还是懒加载机制。此种情况下,若 session 关闭,再去调用关联对象的某个属性,会发生懒加载异常。

查询双向一对一中没有外键的一端:

<one-to-one name="dept" />

@Testpublic void testGet2() {  Manager manager = (Manager) session.get(Manager.class, 6);  System.out.println(manager);}

Get2.java
Hibernate:   select    manager0_.manager_id as manager1_1_1_,    manager0_.manager_name as manager2_1_1_,    department1_.dept_id as dept1_0_0_,    department1_.dept_name as dept2_0_0_,    department1_.manager_id_fk as manager3_0_0_   from    hibernate.manager manager0_   left outer join    hibernate.department department1_       on manager0_.manager_id=department1_.dept_id   where    manager0_.manager_id=?com.nucsoft.hibernate.Manager@8ba

<one-to-one name="dept" property-ref="manager"/>

Hibernate:   select    manager0_.manager_id as manager1_1_1_,    manager0_.manager_name as manager2_1_1_,    department1_.dept_id as dept1_0_0_,    department1_.dept_name as dept2_0_0_,    department1_.manager_id_fk as manager3_0_0_   from    hibernate.manager manager0_   left outer join    hibernate.department department1_       on manager0_.manager_id=department1_.manager_id_fk   where    manager0_.manager_id=?com.nucsoft.hibernate.Manager@8ba

可以发现,在第一次查询时,没有设置 property-ref 属性。左外链接查询时,虽然结果正确,但是连接条件不正确。

至于说,为什么查询 Manager 对象的时候,使用了左外链接而不是懒加载,因为 Manager 端没有 Deparment 的外键。它不知道谁与它有关系。只能通过左外链接查询一次查询。

3.基于主键的双向的一对一

(1)本质上和基于外键的双向一对一关联一样,只不过是以主键作为了外键来使用的。

(2)还是以 Department 和 Manager 为例。

(3)deparment 表做了改动,因为是基于主键的映射,这里将 department 表中 manager_id_fk 去掉了。

(4)Department.hbm.

<hibernate-mapping>  <class name="com.nucsoft.hibernate.Department" table="department" schema="hibernate">    <id name="deptId" column="dept_id">      <generator class="foreign">        <param name="property">manager</param>      </generator>    </id>    <property name="deptName" column="dept_name"/>    <one-to-one name="manager" class="com.nucsoft.hibernate.Manager" constrained="true"/>  </class></hibernate-mapping>

对于 Department.hbm.

需要注意的是,需要在 <one-to-one>节点添加 constrained 属性为true。

<hibernate-mapping>  <class name="com.nucsoft.hibernate.Manager" table="manager" schema="hibernate">    <id name="managerId" column="manager_id">      <generator class="native"/>    </id>    <property name="managerName" column="manager_name"/>    <one-to-one name="dept" class="com.nucsoft.hibernate.Department"/>  </class></hibernate-mapping>

将 Manager.hbm.

(5)CRUD及需要注意的问题

<1>save

@Testpublic void testSave() {  Department department = new Department();  department.setDeptName("dept=1");  Manager manager = new Manager();  manager.setManagerName("manager-1");  manager.setDept(department);  department.setManager(manager);  session.save(manager);  session.save(department);}

Save1.java
@Testpublic void testSave() {  Department department = new Department();  department.setDeptName("dept=2");  Manager manager = new Manager();  manager.setManagerName("manager-2");  manager.setDept(department);  department.setManager(manager);  session.save(department);  session.save(manager);} 

Save2.java
Hibernate:   insert   into    hibernate.manager    (manager_name)   values    (?)Hibernate:   insert   into    hibernate.department    (dept_name, dept_id)   values    (?, ?)

Output

结论:

发现不论是先保存 department ,还是先保存 manager,都是先插入的 manager。因为 department 的主键生成方式是依赖于 manager 的。

<2>update

@Testpublic void testUpdate() {  Department department = (Department) session.get(Department.class, 1);  System.out.println(department.getManager().getManagerName());  department.getManager().setManagerName("@@");}

Update.java
Hibernate:   select    department0_.dept_id as dept1_0_0_,    department0_.dept_name as dept2_0_0_   from    hibernate.department department0_   where    department0_.dept_id=?Hibernate:   select    manager0_.manager_id as manager1_1_1_,    manager0_.manager_name as manager2_1_1_,    department1_.dept_id as dept1_0_0_,    department1_.dept_name as dept2_0_0_   from    hibernate.manager manager0_   left outer join    hibernate.department department1_       on manager0_.manager_id=department1_.dept_id   where    manager0_.manager_id=?manager-1Hibernate:   update    hibernate.manager   set    manager_name=?   where    manager_id=?

Output
@Testpublic void testUpdate() {  Manager manager = (Manager) session.get(Manager.class, 1);  System.out.println(manager.getDept().getDeptName());  manager.getDept().setDeptName("@@Dept");} 

Update2.java
Hibernate:   select    manager0_.manager_id as manager1_1_1_,    manager0_.manager_name as manager2_1_1_,    department1_.dept_id as dept1_0_0_,    department1_.dept_name as dept2_0_0_   from    hibernate.manager manager0_   left outer join    hibernate.department department1_       on manager0_.manager_id=department1_.dept_id   where    manager0_.manager_id=?dept=1Hibernate:   update    hibernate.department   set    dept_name=?   where    dept_id=?

Output2

<3>get

@Testpublic void testGet() {  Department department = (Department) session.get(Department.class, 1);  System.out.println(department.getManager().getClass());  System.out.println("--------------------------------------");  Manager manager = (Manager) session.get(Manager.class, 1);}

Get.java
Hibernate:   select    department0_.dept_id as dept1_0_0_,    department0_.dept_name as dept2_0_0_   from    hibernate.department department0_   where    department0_.dept_id=?class com.nucsoft.hibernate.Manager_$$_javassist_1--------------------------------------Hibernate:   select    manager0_.manager_id as manager1_1_1_,    manager0_.manager_name as manager2_1_1_,    department1_.dept_id as dept1_0_0_,    department1_.dept_name as dept2_0_0_   from    hibernate.manager manager0_   left outer join    hibernate.department department1_       on manager0_.manager_id=department1_.dept_id   where    manager0_.manager_id=?

Output

通过 department 获取到的 manager 采用的是懒加载机制。而从 manager 获取 dept ,是通过左外链接查询的。

至于原因,第一小点已经提到,本质上和基于外键的双向一对一关联一样,只不过是以主键作为了外键来使用的。

<4>delete

@Testpublic void testDelete() {  Manager manager = (Manager) session.get(Manager.class, 1);  session.delete(manager);}

Delete.java

Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`department`, CONSTRAINT `FK_8hf3vewo7w3v9doungcc51wwy` FOREIGN KEY (`dept_id`) REFERENCES `manager` (`manager_id`))

若先删除 manager ,且存在和此 manager 关联的 department ,需要先删除关联的 department 记录。

三、映射 n 对 n 关联关系

1.单向的 n 对 n 关联

(1)必须使用中间表

(2)以 Category 和 Item 为例。一个分类下可以有多个商品,一个商品可以属于多个分类。以 Category 下存在 Set<Item> 集合为例来测试单向的 n 对 n 映射。

(3)创建 category、item、categories_items 表

CREATE TABLE category ( category_id  INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, category_name VARCHAR(50));CREATE TABLE item ( item_id  INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, item_name VARCHAR(50));CREATE TABLE categories_items ( category_id INT(11) NOT NULL, item_id   INT(11) NOT NULL);

Sql

(4)通过 Intellij Idea 生成持久化类和 Entity.hbm.

生成的持久化类和 Category.hbm.

public class Category {  private Integer categoryId;  private String categoryName;  private Set<Item> items;}

Category.java
public class Item {  private Integer itemId;  private String itemName;}

Item.java
<hibernate-mapping>  <class name="com.nucsoft.hibernate.Item" table="item" schema="hibernate">    <id name="itemId" column="item_id">      <generator class="native"/>    </id>    <property name="itemName" column="item_name"/>  </class></hibernate-mapping>

Item.hbm.
<hibernate-mapping>  <class name="com.nucsoft.hibernate.Category" table="category" schema="hibernate">    <id name="categoryId" column="category_id">      <generator class="native"/>    </id>    <property name="categoryName" column="category_name"/>    <!-- table 指中间表 -->    <set name="items" table="categories_items" schema="hibernate">      <key>        <!-- category 在中间表中的列名 -->        <column name="category_id"/>      </key>      <!-- class Set 集合中持久化类的类名, column Set集合中的持久化类咋中间表的外键列的名称 -->      <many-to-many not-found="ignore" class="com.nucsoft.hibernate.Item">        <column name="item_id"/>      </many-to-many>    </set>  </class></hibernate-mapping>

注释已经讲的很明白了。

(5)CRUD 以及需要注意的地方。

<1>save

@Testpublic void testSave() {  Item item = new Item();  item.setItemName("item-aa");  Item item2 = new Item();  item2.setItemName("item-bb");  Category category = new Category();  category.setCategoryName("cate-1");  category.getItems().add(item);  category.getItems().add(item2);  session.save(item);  session.save(item2);  session.save(category);}

Save1.java
Hibernate:   insert   into    hibernate.item    (item_name)   values    (?)Hibernate:   insert   into    hibernate.item    (item_name)   values    (?)Hibernate:   insert   into    hibernate.category    (category_name)   values    (?)Hibernate:   insert   into    hibernate.categories_items    (category_id, item_id)   values    (?, ?)Hibernate:   insert   into    hibernate.categories_items    (category_id, item_id)   values    (?, ?)

Output1
@Testpublic void testSave() {  Item item = new Item();  item.setItemName("item-cc");  Item item2 = new Item();  item2.setItemName("item-dd");  Category category = new Category();  category.setCategoryName("cate-2");  category.getItems().add(item);  category.getItems().add(item2);  session.save(category);  session.save(item);  session.save(item2);}

Save2.java
Hibernate:   insert   into    hibernate.category    (category_name)   values    (?)Hibernate:   insert   into    hibernate.item    (item_name)   values    (?)Hibernate:   insert   into    hibernate.item    (item_name)   values    (?)Hibernate:   insert   into    hibernate.categories_items    (category_id, item_id)   values    (?, ?)Hibernate:   insert   into    hibernate.categories_items    (category_id, item_id)   values    (?, ?)

Output2

结论:

对比发现,不论是先保存 Item 还是先保存 Category 对象,都不会多发送 UPDATE 语句,因为最终的关联关系是通过中间表进行维护的。

<2>get

@Testpublic void testGet() {  Category category = (Category) session.get(Category.class, 1);  System.out.println(category.getItems().getClass());  System.out.println(category.getItems().size());}

Get.java
Hibernate:   select    category0_.category_id as category1_1_0_,    category0_.category_name as category2_1_0_   from    hibernate.category category0_   where    category0_.category_id=?class org.hibernate.collection.internal.PersistentSetHibernate:   select    items0_.category_id as category1_1_1_,    items0_.item_id as item2_0_1_,    item1_.item_id as item1_2_0_,    item1_.item_name as item2_2_0_   from    hibernate.categories_items items0_   inner join    hibernate.item item1_       on items0_.item_id=item1_.item_id   where    items0_.category_id=?2

Output

还是懒加载,但是查询关联的 Items 的时候,内连接关联了中间表。

<3>delete

@Testpublic void testDelete() {  Category category = (Category) session.get(Category.class, 1);  session.delete(category);}

Delete1.java
Hibernate:   select    category0_.category_id as category1_1_0_,    category0_.category_name as category2_1_0_   from    hibernate.category category0_   where    category0_.category_id=?Hibernate:   delete   from    hibernate.categories_items   where    category_id=?Hibernate:   delete   from    hibernate.category   where    category_id=?

删除 category 的时候,先删除的中间表,然后才删除的 category表。表明,先解除关系,然后删除。

@Testpublic void testDelete2() {  Item item = (Item) session.get(Item.class, 4);  session.delete(item);} 

Delete2.java

Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`categories_items`, CONSTRAINT `FK_pxxtlg5rb8it8ewp5lxhss3c5` FOREIGN KEY (`item_id`) REFERENCES `item` (`item_id`))

 删除 item 时,因为有关联关系的存在,且维护关联关系是由 Category 维护的,所以无法删除。

 2.映射双向的 n 对 n

(1)还以 Category 和 Item 为例。Category 中拥有 Set<Item> 类型的属性, Item 中拥有 Set<Category> 类型的属性。

(2)通过 Intellij Idea 生成持久化类和 Entity.hbm.

对应生成的文件:

public class Category {  private Integer categoryId;  private String categoryName;  private Set<Item> items;}

Category.java
public class Item {  private Integer itemId;  private String itemName;  private Set<Category> categories;}

Item.java
<hibernate-mapping package="com.nucsoft.hibernate" schema="hibernate">  <class name="Item" table="item">    <id name="itemId" column="item_id">      <generator class="native"/>    </id>    <property name="itemName" column="item_name"/>    <set name="categories" inverse="true" table="categories_items">      <key>        <column name="item_id"/>      </key>      <!-- 交叉对应 -->      <many-to-many not-found="ignore" class="Category">        <column name="category_id"/>      </many-to-many>    </set>  </class></hibernate-mapping>

<hibernate-mapping package="com.nucsoft.hibernate" schema="hibernate">  <class name="Category" table="category">    <id name="categoryId" column="category_id">      <generator class="native"/>    </id>    <property name="categoryName" column="category_name"/>    <set name="items" table="categories_items">      <key>        <column name="category_id"/>      </key>      <!-- 交叉对应 -->      <many-to-many not-found="ignore" class="Item">        <column name="item_id"/>      </many-to-many>    </set>  </class></hibernate-mapping>

(3)注意:

  • 需要两端都使用集合属性
  • 必须使用中间表
  • 两边都需要指定中间表表名以及在中间表中外键列的列名。
  • 必须把其中一端 的 inverse 属性设置为 true

四、总结

介绍了Hibernate如何映射 单双向的多对一,基于外键、基于主键的一对一,以及单双向的多对多的关联关系。包括在 Intellij Idea下如何操作,以及各个类型映射下的 CRUR 以及需要注意的地方。

没有介绍 Hibernate 是如何映射继承关系,是因为在真实的生产环境下还没有遇到这种情况,这里不做说明。

文章篇幅较长,所以摘录了一个附录出来,以便查询方便。http://www.cnblogs.com/solverpeng/p/5953536.html

写文章不易,若转载,请标明出处。

 

注意:每次通过 Intellij Idea 的 Database Schema 生成 Entity.hbm.