在域模型中,类和类之间最普通的关系就是关联关系。在UML语言中,关联是有方向的。以客户(Customer)和订单(Order)的关系为例,一个客户可以发出多个订单,而一个订单只能属于一个客户。
从Order到Customer的关联是多对一关联,这意味着每个Order对象都会引用一个Customer对象,因此在Order类中应该定义一个Customer类型的属性,来引用所关联的Customer对象。
从Customer到Order的关联是一对多的关联,这意味着每个Customer对象都会引用一组Order对象,因此在Customer类中应该定义一个集合类型的属性,来引用所有关联的Order对象。
一、建立多对一的单向关联关系
如上例中,我们只需在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的构造方法
public Customer getCustomer() {
return this.customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
Customer类的所有属性都是和CUSTOMERS表中的字段一一对应,因此可以直接使用如下的映射代码:
<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字段对应,映射代码和上面类似,此处省去。我们关注的主要地方是,Order类中的customer属性,因为他是Customer类型的,是与ORDERS表的外键CUSTOMER_ID对应的,它的真实值是存在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;}
}
/*根据参数指定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执行: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();
}
}
/*根据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");//创建一个Customer持久化对象
//不保存customer对象,这样执行的话会出现异常
Order order1=new Order("Jack_Order001",customer);
Order order2=new Order("Jack_Order002",customer);//创建两个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");//创建一个Customer持久化对象
session.save(customer);
Order order1=new Order("Tom_Order001",customer);
Order order2=new Order("Tom_Order002",customer);//创建两个Order对象
session.save(order1);
session.save(order2);
// 对同一个customerHibernate执行两次插入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();
}
}
上述代码中方法 saveCustomerAndOrderWithCascade()如果没有session.save(customer)这一句,
执行时会抛出PropertyValueException异常,主要原因是:
在调用session.save(order1)方法之前,order1和customer对象都是临时的,临时对象是由new创建的,都是没有持久化的对象。假设 session.save(order1)被成功执行,order1会被成功持久化,变成持久化对象,但是Hibernate不会自动持久化order1所关联的customer对象。
在执行session.save(order1)时,插入ORDERS表记录的CUSTOMER_ID字段为null,这违反了数据库完整性约束,即ORDERS表中不允许CUSTOMER_ID为null。
假设ORDERS表中CUSTOMER_ID字段允许为null:
<many-to-one
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
not-null="false"
lazy="false"
/>
这样执行的话,能够成功的向ORDERS表中插入两条数据;但是当Hibernate自动清理(flush)缓存中所有持久化对象时,又会抛出新的异常
org.hibernate.TransientObjectException:object references an unsaved transient instance -save the transient instance before flushing :mypack.Customer
所谓清理是指Hibernate按照持久化对象的属性变化来同步更新数据库。在清理的时候Hibernate会发现order1和order2都引用临时对象customer,而在ORDERS表中CUSTOMER_ID字段为null,这就意味着内存中持久化对象的属性和数据库中记录不一致。之所以会报错是因为order1中customer属性引用了一个临时对象Customer。
由此可见,Hibernate持久化一个对象时,默认情况下不会自动持久化所关联的其他对象。但是,我们我们希望当Hibernate持久化Order对象时自动持久化所关联的Customer对象,我们可以修改映射文件如下:
<many-to-one
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
cascade="save-update"
not-null="false"
lazy="false"
/>
当cascade属性为“save-update”,表明保存或更新对象时,会级联保存或更新与它所关联的对象。如上例中,执行saveCustomerAndOrderWithCascade()时,Hibernate会把order1与customer对象一起持久化,此时Hibernate会执行
insert into CUSTOMERS(ID,NAME) values(2,"Jack");
insert into ORDERS(ID,ORDER_NUMBER,CUSTOMER_ID) value (3,"Jack_Order001",2);
二、映射一对多双向关联关系
类类之间建立了联系,就可以很方便地从一个对象导航到另一个或者另一组与它相关联的对象。正如上例中,对于给定的Order对象,如果想获得与之关联的Customer对象,可以直接如下调用:
Customer customer=order.getCustomer();
那么对于给定的Customer对象,如何一次获得所有与之关联的Order对象呢?由于上例中Customer对象没有和Order对象关联,我们也可以通过Hibernate API去查询数据库:
List orders=session.createQuery("from Order as o where o.customer.id="+customer.getId()).list();
显然这样做的效率会很低,而且复杂的关联关系也会给编程带来影响。我们可以为Customer类和Order类简历一对多的双向关联。
第一部分我们已经建立了Order类和Customer类的多对一关联,现在我们再增加Customer到Order类的一对多关联。
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();//初始化为集合实现类,这样做可以提高程序的健壮性,同时避免了应用程序访问取词为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演示分函数介绍:
(1)saveCustomerAndOrderWithCascade()方法:当映射文件中<set>的属性为“save-update”时,Hibernate在持久化Customer对象时也会自动持久化其所关联的Order对象
public void saveCustomerAndOrderWithCascade(){
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
/*创建一个customer对象和order对象*/
Customer customer=new Customer("Tom",new HashSet());
Order order=new Order();
order.setOrderNumber("Tom_Order001");
/*建立Customer与Order的一对多双向关联关系*/
order.setCustomer(customer);
customer.getOrders().add(order);
/*保存Customer对象*/
session.save(customer);
/* 当映射文件中<set>的属性为“save-update”时,Hibernate在持久化Customer对象时也会自动持久化其所关联的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();
}
}
当映射文件中<set>的属性为“save-update”时,Hibernate在持久化Customer对象时也会自动持久化其所关联的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关联的所有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();
}
}
其调用的函数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();
}
- 调用的函数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();
为了使上述代码正常执行,需要确保Order.hbm.xml文件的<many-to-one>元素的not null取默认值false,否则会出现异常;Hibernate会执行如下
insert into CUSTOMERS(ID,NAME) values(2,"Jack");
insert into ORDERS(ID,ORDER_NUMBER,CUSTOMER_ID) values(2,"Jack_Order001",null);
- 调用的函数associateCustomerAndOrder():该方法加载由saveCustomerAndOrderSeparately()方法持久化Customer和Order对象,然后建立两者之间的一对多的关系
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的关联关系*/
order.setCustomer(customer);
customer.getOrders().add(order);
tx.commit();
}catch (RuntimeException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
}
这样重复执行多余的SQL语句会影响java应用的性能,解决的方法是将<set>的inverse属性设为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)级联删除:
tx = session.beginTransaction();
Customer customer=(Customer)session.load(Customer.class,customerId);
session.delete(customer);
tx.commit();
如果要删除Customer所关联的Order对象的话,需要将cascade属性设置为delete,如下:
<set
name="orders"
inverse="true"
cascade="delete"
>
<key column="CUSTOMER_ID" />
<one-to-many class="mypack.Order" />
</set>
执行后,Hibernate会做以下动作:
delete from ORDERS where CUSTOMER_ID=2;
delete from CUSTOMERS where ID=2;
如果关联双方是父子关系,就可以把复方的cascade设置为all-delete-orphan;这样删除父方对象时就会级联删除所有关联的子方对象。
三、映射一对多双向自身关联关系
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>