精通Hibernate:映射一對多關(guān)聯(lián)關(guān)系
在域模型中,類和類之間最普通的關(guān)系就是關(guān)聯(lián)關(guān)系。在UML語言中,關(guān)聯(lián)是有方向的。以客戶(Customer)和訂單(Order)的關(guān)系為例,一個客戶可以發(fā)出多個訂單,而一個訂單只能屬于一個客戶。
從Order到Customer的關(guān)聯(lián)是多對一關(guān)聯(lián),這意味著每個Order對象都會引用一個Customer對象,因此在Order類中應(yīng)該定義一個Customer類型的屬性,來引用所關(guān)聯(lián)的Customer對象。
從Customer到Order的關(guān)聯(lián)是一對多的關(guān)聯(lián),這意味著每個Customer對象都會引用一組Order對象,因此在Customer類中應(yīng)該定義一個集合類型的屬性,來引用所有關(guān)聯(lián)的Order對象。
一、建立多對一的單向關(guān)聯(lián)關(guān)系
如上例中,我們只需在Order類中定義一個customer屬性,而在Customer類中無需定義存放Order對象的集合屬性。
Order.java
- package mypack;
- public class Order implements java.io.Serializable {
- private long id;
- private String orderNumber;
- private Customer customer;//定義一個Customer屬性
- public Order() {
- }
- public Order(Customer customer) {
- this.customer = customer;
- }
- public Order(String orderNumber, Customer customer) {
- this.orderNumber = orderNumber;
- this.customer = customer;
- }
- //省略了id,orderNumber的構(gòu)造方法
- public Customer getCustomer() {
- return this.customer;
- }
- public void setCustomer(Customer customer) {
- this.customer = customer;
- }
- }
Customer類的所有屬性都是和CUSTOMERS表中的字段一一對應(yīng),因此可以直接使用如下的映射代碼:
- <class name="mypack.Customer" table="CUSTOMERS" >
- <id name="id" type="long" column="ID">
- <generator class="increment"/>
- </id>
- <property name="name" type="string" >
- <column name="NAME" length="15" />
- </property>
- </class>
Order類的orderNumber屬性和ORDERS表中ORDER_NUMBER字段對應(yīng),映射代碼和上面類似,此處省去。我們關(guān)注的主要地方是,Order類中的customer屬性,因為他是Customer類型的,是與ORDERS表的外鍵CUSTOMER_ID對應(yīng)的,它的真實值是存在CUSTOMERS表中而ORDERS表存的只是對它的引用,因此customer的映射方法不能如上面一樣。
- <many-to-one
- name="customer"
- column="CUSTOMER_ID"
- class="mypack.Customer"
- not-null="true"
- lazy="false"
- />
使用方法のBussiness.java演示:
- package mypack;
- import org.hibernate.*;
- import org.hibernate.cfg.Configuration;
- import java.util.*;
- public class BusinessService{
- public static SessionFactory sessionFactory;
- static{
- try{
- // 初始化
- Configuration config = new Configuration();
- config.configure();
- sessionFactory = config.buildSessionFactory();
- }catch(RuntimeException e){e.printStackTrace();throw e;}
- }
- /*根據(jù)參數(shù)指定customer的customer_id找出記錄*/
- public List findOrdersByCustomer(Customer customer){
- Session session = sessionFactory.openSession();
- Transaction tx = null;
- try {
- tx = session.beginTransaction();
- List orders=session.createQuery("from Order as o where o.customer.id="+customer.getId())
- .list();
- //Hibernate執(zhí)行:select * from ORDERS where CUSTOMER_ID=customer.getId();
- tx.commit();
- return orders;
- }catch (RuntimeException e) {
- if (tx != null) {
- tx.rollback();
- }
- throw e;
- } finally {
- session.close();
- }
- }
- /*根據(jù)OID找出指定customer_id的記錄*/
- public Customer findCustomer(long customer_id){
- Session session = sessionFactory.openSession();
- Transaction tx = null;
- try {
- tx = session.beginTransaction();
- Customer customer=(Customer)session.get(Customer.class,new Long(customer_id));
- tx.commit();
- return customer;
- }catch (RuntimeException e) {
- if (tx != null) {
- tx.rollback();
- }
- throw e;
- } finally {
- session.close();
- }
- }
- /*
- public void saveCustomerAndOrderWithCascade(){
- Session session = sessionFactory.openSession();
- Transaction tx = null;
- try {
- tx = session.beginTransaction();
- Customer customer=new Customer("Jack");//創(chuàng)建一個Customer持久化對象
- //不保存customer對象,這樣執(zhí)行的話會出現(xiàn)異常
- Order order1=new Order("Jack_Order001",customer);
- Order order2=new Order("Jack_Order002",customer);//創(chuàng)建兩個Order對象
- session.save(order1);
- session.save(order2);
- tx.commit();
- }catch (RuntimeException e) {
- if (tx != null) {
- tx.rollback();
- }
- e.printStackTrace();
- } finally {
- session.close();
- }
- }
- */ public void saveCustomerAndOrder(){
- Session session = sessionFactory.openSession();
- Transaction tx = null;
- try {
- tx = session.beginTransaction();
- Customer customer=new Customer("Tom");//創(chuàng)建一個Customer持久化對象
- session.save(customer);
- Order order1=new Order("Tom_Order001",customer);
- Order order2=new Order("Tom_Order002",customer);//創(chuàng)建兩個Order對象
- session.save(order1);
- session.save(order2);
- // 對同一個customerHibernate執(zhí)行兩次插入ORDERS表
- tx.commit();
- }catch (RuntimeException e) {
- if (tx != null) {
- tx.rollback();
- }
- throw e;
- } finally {
- session.close();
- }
- }
- public void printOrders(List orders){
- for (Iterator it = orders.iterator(); it.hasNext();) {
- Order order=(Order)it.next();
- System.out.println("OrderNumber of "+order.getCustomer().getName()+ " :"+order.getOrderNumber());
- }
- }
- public void test(){
- saveCustomerAndOrder();
- // saveCustomerAndOrderWithCascade();
- Customer customer=findCustomer(1);
- List orders=findOrdersByCustomer(customer);
- printOrders(orders);
- }
- public static void main(String args[]){
- new BusinessService().test();
- sessionFactory.close();
- }
- }
- <span style="font-size:16px;color:#cc33cc;"><strong>
- </strong></span>
上述代碼中方法 saveCustomerAndOrderWithCascade()如果沒有session.save(customer)這一句,
執(zhí)行時會拋出PropertyValueException異常,主要原因是:
在調(diào)用session.save(order1)方法之前,order1和customer對象都是臨時的,臨時對象是由new創(chuàng)建的,都是沒有持久化的對象。假設(shè) session.save(order1)被成功執(zhí)行,order1會被成功持久化,變成持久化對象,但是Hibernate不會自動持久化order1所關(guān)聯(lián)的customer對象。
在執(zhí)行session.save(order1)時,插入ORDERS表記錄的CUSTOMER_ID字段為null,這違反了數(shù)據(jù)庫完整性約束,即ORDERS表中不允許CUSTOMER_ID為null。
疑問假設(shè)ORDERS表中CUSTOMER_ID字段允許為null:
- <many-to-one
- name="customer"
- column="CUSTOMER_ID"
- class="mypack.Customer"
- not-null="false"
- lazy="false"
- />
這樣執(zhí)行的話,能夠成功的向ORDERS表中插入兩條數(shù)據(jù);但是當(dāng)Hibernate自動清理(flush)緩存中所有持久化對象時,又會拋出新的異常
- org.hibernate.TransientObjectException:object references an unsaved transient instance -save the transient instance before flushing :mypack.Customer
所謂清理是指Hibernate按照持久化對象的屬性變化來同步更新數(shù)據(jù)庫。在清理的時候Hibernate會發(fā)現(xiàn)order1和order2都引用臨時對象customer,而在ORDERS表中CUSTOMER_ID字段為null,這就意味著內(nèi)存中持久化對象的屬性和數(shù)據(jù)庫中記錄不一致。之所以會報錯是因為order1中customer屬性引用了一個臨時對象Customer。
由此可見,Hibernate持久化一個對象時,默認(rèn)情況下不會自動持久化所關(guān)聯(lián)的其他對象。但是,我們我們希望當(dāng)Hibernate持久化Order對象時自動持久化所關(guān)聯(lián)的Customer對象,我們可以修改映射文件如下:
- <many-to-one
- name="customer"
- column="CUSTOMER_ID"
- class="mypack.Customer"
- cascade="save-update"
- not-null="false"
- lazy="false"
- />
當(dāng)cascade屬性為“save-update”,表明保存或更新對象時,會級聯(lián)保存或更新與它所關(guān)聯(lián)的對象。如上例中,執(zhí)行saveCustomerAndOrderWithCascade()時,Hibernate會把order1與customer對象一起持久化,此時Hibernate會執(zhí)行
- insert into CUSTOMERS(ID,NAME) values(2,"Jack");
- insert into ORDERS(ID,ORDER_NUMBER,CUSTOMER_ID) value (3,"Jack_Order001",2);
#p#
二、映射一對多雙向關(guān)聯(lián)關(guān)系
類類之間建立了聯(lián)系,就可以很方便地從一個對象導(dǎo)航到另一個或者另一組與它相關(guān)聯(lián)的對象。正如上例中,對于給定的Order對象,如果想獲得與之關(guān)聯(lián)的Customer對象,可以直接如下調(diào)用:
- Customer customer=order.getCustomer();
那么對于給定的Customer對象,如何一次獲得所有與之關(guān)聯(lián)的Order對象呢?由于上例中Customer對象沒有和Order對象關(guān)聯(lián),我們也可以通過Hibernate API去查詢數(shù)據(jù)庫:
- List orders=session.createQuery("from Order as o where o.customer.id="+customer.getId()).list();
顯然這樣做的效率會很低,而且復(fù)雜的關(guān)聯(lián)關(guān)系也會給編程帶來影響。我們可以為Customer類和Order類簡歷一對多的雙向關(guān)聯(lián)。
第一部分我們已經(jīng)建立了Order類和Customer類的多對一關(guān)聯(lián),現(xiàn)在我們再增加Customer到Order類的一對多關(guān)聯(lián)。
Customer.java文件:
- package mypack;
- import java.util.HashSet;
- import java.util.Set;
- //Hibernate要求在持久化類中定義集合類屬性時,必須要把屬性聲明為接口類型。
- public class Customer implements java.io.Serializable {
- private long id;
- private String name;
- private Set orders = new HashSet();//初始化為集合實現(xiàn)類,這樣做可以提高程序的健壯性,同時避免了應(yīng)用程序訪問取詞為null的orders集合的方法而拋出NullPointerException。
- public Customer() {
- }
- public Customer(String name, Set orders) {
- this.name = name;
- this.orders = orders;
- }
- //省略了id,name的get和set訪問方法
- public Set getOrders() {
- return this.orders;
- }
- public void setOrders(Set orders) {
- this.orders = orders;
- }
- }
接下來就是映射文件的配置Customer.hbm.xml:
- <class name="mypack.Customer" table="CUSTOMERS" >
- <id name="id" type="long" column="ID">
- <generator class="increment"/>
- </id>
- <property name="name" type="string" >
- <column name="NAME" length="15" />
- </property>
- <set
- name="orders"
- cascade="save-update"
- <key column="CUSTOMER_ID" />//表示ORDERS表通過外鍵CUSTOMER_ID參照CUSTOMERS表
- <one-to-many class="mypack.Order" />
- </set>
- </class>
使用方法のBussiness.java演示分函數(shù)介紹:
(1)saveCustomerAndOrderWithCascade()方法:當(dāng)映射文件中<set>的屬性為“save-update”時,Hibernate在持久化Customer對象時也會自動持久化其所關(guān)聯(lián)的Order對象
- public void saveCustomerAndOrderWithCascade(){
- Session session = sessionFactory.openSession();
- Transaction tx = null;
- try {
- tx = session.beginTransaction();
- /*創(chuàng)建一個customer對象和order對象*/
- Customer customer=new Customer("Tom",new HashSet());
- Order order=new Order();
- order.setOrderNumber("Tom_Order001");
- /*建立Customer與Order的一對多雙向關(guān)聯(lián)關(guān)系*/
- order.setCustomer(customer);
- customer.getOrders().add(order);
- /*保存Customer對象*/
- session.save(customer);
- /* 當(dāng)映射文件中<set>的屬性為“save-update”時,Hibernate在持久化Customer對象時也會自動持久化其所關(guān)聯(lián)的Order對象
- insert into CUSTOMERS(ID,NAME) values(1,"Tom");
- insert into ORDERS(ID,ORDER_NUMBER,CUSTOMER_ID) values(1,"Tom_Order001",1)*/
- tx.commit();
- idOfTom=customer.getId();
- idOfTomOrder=order.getId();
- }catch (RuntimeException e) {
- if (tx != null) {
- tx.rollback();
- }
- e.printStackTrace();
- } finally {
- session.close();
- }
- }
當(dāng)映射文件中<set>的屬性為“save-update”時,Hibernate在持久化Customer對象時也會自動持久化其所關(guān)聯(lián)的Order對象
- insert into CUSTOMERS(ID,NAME) values(1,"Tom");
- insert into ORDERS(ID,ORDER_NUMBER,CUSTOMER_ID) values(1,"Tom_Order001",1)
(2)printOrdersOfCustomer(Long customerId)方法:打印與指定customerId關(guān)聯(lián)的所有Order對象
- public void printOrdersOfCustomer(Long customerId){
- Session session = sessionFactory.openSession();
- Transaction tx = null;
- try {
- tx = session.beginTransaction();
- Customer customer=(Customer)session.get(Customer.class,customerId);
- printOrders(customer.getOrders());//使用getOrders獲取一個order對象set
- tx.commit();
- }catch (RuntimeException e) {
- if (tx != null) {
- tx.rollback();
- }
- throw e;
- } finally {
- session.close();
- }
- }
其調(diào)用的函數(shù)printOrders(Set orders)
- public void printOrders(Set orders){
- for (Iterator it = orders.iterator(); it.hasNext();) {
- Order order=(Order)it.next();
- System.out.println("OrderNumber of "+order.getCustomer().getName()+ " :"+order.getOrderNumber());
- }
- }
(3)saveCustomerAndOrderWithInverse()方法:演示映射文件<set>屬性為inverse
- public void saveCustomerAndOrderWithInverse(){
- saveCustomerAndOrderSeparately();
- associateCustomerAndOrder();
- }
調(diào)用的函數(shù)saveCustomerAndOrderSeparately():即是分別存儲,與saveCustomerAndOrderWithCascade()方法恰好相反。
- Customer customer=new Customer();
- customer.setName("Jack");
- Order order=new Order();
- order.setOrderNumber("Jack_Order001");
- session.save(customer);
- session.save(order);
- tx.commit();
- idOfJack=customer.getId();
- idOfJackOrder=order.getId();
為了使上述代碼正常執(zhí)行,需要確保Order.hbm.xml文件的<many-to-one>元素的not null取默認(rèn)值false,否則會出現(xiàn)異常;Hibernate會執(zhí)行如下
- insert into CUSTOMERS(ID,NAME) values(2,"Jack");
- insert into ORDERS(ID,ORDER_NUMBER,CUSTOMER_ID) values(2,"Jack_Order001",null);
調(diào)用的函數(shù)associateCustomerAndOrder():該方法加載由saveCustomerAndOrderSeparately()方法持久化Customer和Order對象,然后建立兩者之間的一對多的關(guān)系
- public void associateCustomerAndOrder(){
- Session session = sessionFactory.openSession();
- Transaction tx = null;
- try {
- tx = session.beginTransaction();
- /*加載持久化對象Customer、Order*/
- Customer customer=(Customer)session.load(Customer.class,idOfJack);
- Order order=(Order)session.load(Order.class,idOfJackOrder);
- /*建立Customer和Order的關(guān)聯(lián)關(guān)系*/
- order.setCustomer(customer);
- customer.getOrders().add(order);
- tx.commit();
- }catch (RuntimeException e) {
- if (tx != null) {
- tx.rollback();
- }
- e.printStackTrace();
- } finally {
- session.close();
- }
- }
這樣重復(fù)執(zhí)行多余的SQL語句會影響java應(yīng)用的性能,解決的方法是將<set>的inverse屬性設(shè)為true。因此修改Customer.hbm.xml文件:
- <set
- name="orders"
- inverse="true"
- cascade="save-update"
- >
- <key column="CUSTOMER_ID" />//表示ORDERS表通過外鍵CUSTOMER_ID參照CUSTOMERS表
- <one-to-many class="mypack.Order" />
- </set>
(4)級聯(lián)刪除:
- tx = session.beginTransaction();
- Customer customer=(Customer)session.load(Customer.class,customerId);
- session.delete(customer);
- tx.commit();
如果要刪除Customer所關(guān)聯(lián)的Order對象的話,需要將cascade屬性設(shè)置為delete,如下:
- <set
- name="orders"
- inverse="true"
- cascade="delete"
- >
- <key column="CUSTOMER_ID" />
- <one-to-many class="mypack.Order" />
- </set>
執(zhí)行后,Hibernate會做以下動作:
- delete from ORDERS where CUSTOMER_ID=2;
- delete from CUSTOMERS where ID=2;
如果關(guān)聯(lián)雙方是父子關(guān)系,就可以把復(fù)方的cascade設(shè)置為all-delete-orphan;這樣刪除父方對象時就會級聯(lián)刪除所有關(guān)聯(lián)的子方對象。
#p#
三、映射一對多雙向自身關(guān)聯(lián)關(guān)
Category.java:
- package mypack;
- import java.util.HashSet;
- import java.util.Set;
- public class Category implements java.io.Serializable {
- private long id;
- private String name;
- private Set childCategories = new HashSet(0);
- private Category parentCategory;
- public Category() {
- }
- public Category(String name, Set childCategories, Category parentCategory) {
- this.name = name;
- this.childCategories = childCategories;
- this.parentCategory = parentCategory;
- }
- public long getId() {
- return this.id;
- }
- public void setId(long id) {
- this.id = id;
- }
- public String getName() {
- return this.name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Set getChildCategories() {
- return this.childCategories;
- }
- public void setChildCategories(Set childCategories) {
- this.childCategories = childCategories;
- }
- public Category getParentCategory() {
- return this.parentCategory;
- }
- public void setParentCategory(Category parentCategory) {
- this.parentCategory = parentCategory;
- }
- }
配置文件Category.hbm.xml:
- <?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="mypack.Category" table="CATEGORIES" >
- <id name="id" type="long" column="ID">
- <generator class="increment"/>
- </id>
- <property name="name" type="string" >
- <column name="NAME" length="15" />
- </property>
- <set
- name="childCategories"
- cascade="save-update"
- inverse="true"
- >
- <key column="CATEGORY_ID" />
- <one-to-many class="mypack.Category" />
- </set>
- <many-to-one
- name="parentCategory"
- column="CATEGORY_ID"
- class="mypack.Category"
- />
- </class>
- </hibernate-mapping>
原文鏈接:http://blog.csdn.net/yu422560654/article/details/7176407
【編輯推薦】