精通Hibernate:映射一对多关联关系(1)
在域模型中,类和类之间最普通的关系就是关联关系。在UML语言中,关联是有方向的。以客户Customer)和订单Order)的关系为例,一个客户可以发出多个订单,而一个订单只能属于一个客户。
从Order到Customer的关联是多对一关联,这意味着每个Order对象都会引用一个Customer对象,因此在Order类中应该定义一个Customer类型的属性,来引用所关联的Customer对象。
从Customer到Order的关联是一对多的关联,这意味着每个Customer对象都会引用一组Order对象,因此在Customer类中应该定义一个集合类型的属性,来引用所有关联的Order对象。
一、建立多对一的单向关联关系
如上例中,我们只需在Order类中定义一个customer属性,而在Customer类中无需定义存放Order对象的集合属性。
Order.java
packagemypack;
publicclassOrderimplementsjava.io.Serializable {
privatelongid;
privateString orderNumber;
privateCustomer customer;//定义一个Customer属性
publicOrder() {
}
publicOrder(Customer customer) {
this.customer = customer;
}
publicOrder(String orderNumber, Customer customer) {
this.orderNumber = orderNumber;
this.customer = customer;
}
//省略了id,orderNumber的构造方法
publicCustomer getCustomer() {
returnthis.customer;
}
publicvoidsetCustomer(Customer customer) {
this.customer = customer;
}
}
Customer类的所有属性都是和CUSTOMERS表中的字段一一对应,因此可以直接使用如下的映射代码:
Order类的orderNumber属性和ORDERS表中ORDER_NUMBER字段对应,映射代码和上面类似,此处省去。我们关注的主要地方是,Order类中的customer属性,因为他是Customer类型的,是与ORDERS表的外键CUSTOMER_ID对应的,它的真实值是存在CUSTOMERS表中而ORDERS表存的只是对它的引用,因此customer的映射方法不能如上面一样。
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
not-null="true"
lazy="false"
/>
使用方法のBussiness.java演示:
packagemypack;
importorg.hibernate.*;
importorg.hibernate.cfg.Configuration;
importjava.util.*;
publicclassBusinessService{
publicstaticSessionFactory sessionFactory;
static{
try{
// 初始化
Configuration config =newConfiguration();
config.configure();
sessionFactory = config.buildSessionFactory();
}catch(RuntimeException e){e.printStackTrace();throwe;}
}
/*根据参数指定customer的customer_id找出记录*/
publicList 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();
returnorders;
}catch(RuntimeException e) {
if(tx !=null) {
tx.rollback();
}
throwe;
}finally{
session.close();
}
}
/*根据OID找出指定customer_id的记录*/
publicCustomer findCustomer(longcustomer_id){
Session session = sessionFactory.openSession();
Transaction tx =null;
try{
tx = session.beginTransaction();
Customer customer=(Customer)session.get(Customer.class,newLong(customer_id));
tx.commit();
returncustomer;
}catch(RuntimeException e) {
if(tx !=null) {
tx.rollback();
}
throwe;
}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();
}
}
*/publicvoidsaveCustomerAndOrder(){
Session session = sessionFactory.openSession();
Transaction tx =null;
try{
tx = session.beginTransaction();
Customer customer=newCustomer("Tom");//创建一个Customer持久化对象
session.save(customer);
Order order1=newOrder("Tom_Order001",customer);
Order order2=newOrder("Tom_Order002",customer);//创建两个Order对象
session.save(order1);
session.save(order2);
// 对同一个customerHibernate执行两次插入ORDERS表
tx.commit();
}catch(RuntimeException e) {
if(tx !=null) {
tx.rollback();
}
throwe;
}finally{
session.close();
}
}
publicvoidprintOrders(List orders){
for(Iterator it = orders.iterator(); it.hasNext();) {
Order order=(Order)it.next();
System.out.println("OrderNumber of "+order.getCustomer().getName()+" :"+order.getOrderNumber());
}
}
publicvoidtest(){
saveCustomerAndOrder();
// saveCustomerAndOrderWithCascade();
Customer customer=findCustomer(1);
List orders=findOrdersByCustomer(customer);
printOrders(orders);
}
publicstaticvoidmain(String args[]){
newBusinessService().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:
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
not-null="false"
lazy="false"
/>
这样执行的话,能够成功的向ORDERS表中插入两条数据;但是当Hibernate自动清理flush)缓存中所有持久化对象时,又会抛出新的异常
org.hibernate.TransientObjectException:object references an unsavedtransientinstance -save thetransientinstance before flushing :mypack.Customer
所谓清理是指Hibernate按照持久化对象的属性变化来同步更新数据库。在清理的时候Hibernate会发现order1和order2都引用临时对象customer,而在ORDERS表中CUSTOMER_ID字段为null,这就意味着内存中持久化对象的属性和数据库中记录不一致。之所以会报错是因为order1中customer属性引用了一个临时对象Customer。
由此可见,Hibernate持久化一个对象时,默认情况下不会自动持久化所关联的其他对象。但是,我们我们希望当Hibernate持久化Order对象时自动持久化所关联的Customer对象,我们可以修改映射文件如下:
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);