(转)Hibernate关联映射——一对多(多对一)
http://blog.csdn.net/yerenyuan_pku/article/details/70152173
Hibernate关联映射——一对多(多对一)
我们以客户(Customer)与订单(Order)为例来讲解Hibernate关联映射中的一对多关联关系。
首先肯定是搭建好Hibernate的开发环境,我在此也不过多赘述,读者自行实践。接着在src目录下创建一个cn.itheima.oneToMany包,并在该包下创建两个实体类,如下:
-
客户类
// 客户 ---- 一的一方 public class Customer { private Integer id; // 主键 private String name; // 姓名 // 描述客户可以有多个订单 private Set<Order> orders = new HashSet<Order>(); public Set<Order> getOrders() { return orders; } public void setOrders(Set<Order> orders) { this.orders = orders; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
- 1
-
订单类
// 订单 ---- 多的一方 public class Order { private Integer id; private Double money; private String receiverInfo; // 收货地址 // 订单与客户关联 private Customer c; // 描述订单属于某一个客户 public Customer getC() { return c; } public void setC(Customer c) { this.c = c; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } public String getReceiverInfo() { return receiverInfo; } public void setReceiverInfo(String receiverInfo) { this.receiverInfo = receiverInfo; } }
- 1
完成之后,再在cn.itheima.oneToMany包下分别编写这两个类的映射配置文件。
-
Customer.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="cn.itheima.oneToMany.Customer" table="t_customer"> <id name="id" column="c_id"> <generator class="identity" /> </id> <property name="name" column="c_name" length="20" /> <!-- 一个客户关联多个订单 --> <!-- 使用set来描述在一的一方中关联的多,也即Set<Order> 它的name属性就是Set集合的名称:orders key:它主要描述关联的多的一方产生的外键名称,注意要与多的一方定义的外键名称相同 one-to-many:主要描述集合中的类型 --> <set name="orders"> <key column="c_customer_id" /> <one-to-many class="cn.itheima.oneToMany.Order" /> </set> </class> </hibernate-mapping>
- 1
-
Order.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="cn.itheima.oneToMany.Order" table="t_order"> <id name="id" column="c_id"> <generator class="identity" /> </id> <property name="money" column="c_money" /> <property name="receiverInfo" column="c_receiverInfo" length="50" /> <!-- 多对一 --> <!-- name属性它描述的是Order类中的一的一方的属性名称:Customer c; class属性代表的是一的一方的类型 column属性描述的是一对多,在多的一方产生的外键的名称:c_customer_id --> <many-to-one name="c" class="cn.itheima.oneToMany.Customer" column="c_customer_id"> </many-to-one> </class> </hibernate-mapping>
- 1
测试双向关联保存
现在我们来测试保存的操作,在src目录下创建一个cn.itheima.test包,并在该包下编写一个OneToManyTest单元测试类,然后在该类中编写一个用于测试保存的操作,如下:
public class OneToManyTest {
// 测试保存,如果我做的是双向的关联,没有用cascade去做级联,那么就存在一个浪费的环节。
@Test
public void test1() {
// 1.得到Session
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 2.操作
// 2.1创建客户
Customer c = new Customer();
c.setName("张三");
// 2.2创建两个订单
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("武汉");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("上海");
// 2.3建立关系
// 2.3.1订单关联客户
o1.setC(c);
o2.setC(c);
// 2.3.2客户关联订单
c.getOrders().add(o1);
c.getOrders().add(o2);
session.save(o1);
session.save(o2);
session.save(c);
// 3.事务提交,并关闭session
session.getTransaction().commit();
session.close();
}
}
测试test1方法,运行正常。其实上面测试保存的操作就是一种双向关联关系,如果做的是双向的关联,而没有用cascade去做级联,那么就存在一个浪费的环节,后面会讲。
顺其自然地,我们就会想可不可以只保存订单或只保存客户就能完成保存的操作呢?答案是不言而喻的。下面我就来简单地讲讲。
测试单向关联保存
现在我们只想保存订单,然后顺便保存客户,可以预想到的是我们可能会这样写代码:
public class OneToManyTest {
// 测试保存 --- 单向操作(只演示保存订单,然后顺便保存客户)
@Test
public void test2() {
// 1.得到Session
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 2.操作
// 2.1创建客户
Customer c = new Customer();
c.setName("张三");
// 2.2创建两个订单
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("武汉");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("上海");
// 2.3建立关系
// 2.3.1订单关联客户
o1.setC(c);
o2.setC(c);
session.save(o1); // 这时o1是一个持久化对象
session.save(o2); // 这时o2也是一个持久化对象,而我们的o1和o2也关联了Customer对象,又Customer对象是一个瞬时对象,所以
// 在这时操作时,会报异常。
// 3.事务提交,并关闭session
session.getTransaction().commit();
session.close();
}
}
这时测试以上方法,会发现报如下异常:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: cn.itheima.oneToMany.Customer
at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:279)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:455)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:281)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:291)
at org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:296)
at org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:4081)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:532)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:215)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:142)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
at cn.itheima.test.OneToManyTest.test2(OneToManyTest.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java: