一对多关联和多对一关联在实际应用中式非常普遍的。例如一个会员(Member)可以有多个订单(Order),而每个订单只能属于某个特定的会员,这便是一个典型的一对多关联。
本示例要用到的两个POJO类如下:
public class Member {
private String id;
private String name;
private Integer age;
private Set<Order> orders = new HashSet<Order>();
......
}
public class Order {
private Integer id;
private String name;
private String num;
private String memberId;
......
}
会员(Member)拥有多个订单(Order),而每个订单只能属于某个特定的会员。
首先来演示和学习一对多的单向关联。
两个人POJO的映射文件如下:
Member.hbm.xml:
<hibernate-mapping package="org.louis.domain">
<class name="Member" table="TEST_MEMBER">
<id name="id" column="ID">
<generator class="uuid.hex"></generator>
</id>
<property name="age" column="AGE"></property>
<property name="name" column="NAME"></property>
<!--set元素,就是定义一个集合,它的name属性值是对应的POJO中的相关属性名称-->
<set name="orders" cascade="all">
<key column="MEMBER_ID"></key><!--指定“ 多” 的一段的外键,与“一”端得主键相关联-->
<one-to-many class="Order"/><!--指定了“多”端对应的类-->
</set>
</class>
</hibernate-mapping>
Order.hbm.xml:
<hibernate-mapping package="org.louis.domain">
<class name="Order" table="TEST_ORDER">
<id name="id" column="ID">
<generator class="native"></generator>
</id>
<property name="name" column="NAME"></property>
<property name="num" column="NUM"></property>
<!--外键-->
<property name="memberId" column="MEMBER_ID"></property>
</hibernate-mapping>
在Member.hbm.xml中主要的是它的<set>元素,它定义了Order作为它的一个集合属性。而Order.hbm.xml则和普通的映射文件没有什么不同。
下面进行测试:
a、插入数据
public void insert() {
Session session = HibernateSessionFactory.getSessionFactory()
.getCurrentSession();
session.beginTransaction();
Member m = new Member();
m.setAge(24);
m.setName("Louis");
Order order = new Order();
order.setName("order 1");
order.setNum("order num 1");
m.getOrders().add(order);
session.save(m);
session.getTransaction().commit();
}
查看Hibernate在后台执行的SQL语句如下:
Hibernate:
insert
into
TEST_MEMBER
(AGE, NAME, ID)
values
(?, ?, ?)
Hibernate:
insert
into
TEST_ORDER
(NAME, NUM, MEMBER_ID)
values
(?, ?, ?)
Hibernate:
update
TEST_ORDER
set
MEMBER_ID=?
where
ID=?
总共执行了3条Sql语句,前两条是进行数据的插入,第三条将Order的外键更新为Member的主键值。因为关联关系时单向的,关联关联关系由Member来维护,而“多”的一方并不知道它自己和Member有任何关系。在这个例子中,Hibernate首先向数据库插入Member的数据,然后插入Order的数据(这时候Order的外键为NULL,如果设置其外键不允许为NULL的话就会报错),最后Hibernate会发送一条更新Order外键的Sql语句,其值由Member的主键值而来。由此看出采用这种单向关联存在着很大的问题,包括性能、是否为空等问题。
b、加载数据
public void getMemberById(String id) {
Session session = HibernateSessionFactory.getSessionFactory()
.getCurrentSession();
session.beginTransaction();
Member m = (Member)session.load(Member.class, id);
System.out.println(m+"/n"+m.getOrders().size());
session.getTransaction().commit();
}
查看后台的SQL语句:
Hibernate:
select
member0_.ID as ID5_1_,
member0_.AGE as AGE5_1_,
member0_.NAME as NAME5_1_,
idcard1_.ID as ID4_0_,
idcard1_.NUM as NUM4_0_,
idcard1_.MEMBER_ID as MEMBER3_4_0_
from
TEST_MEMBER member0_
left outer join
TEST_IDCARD idcard1_
on member0_.ID=idcard1_.MEMBER_ID
where
member0_.ID=?
Hibernate:
select
orders0_.MEMBER_ID as MEMBER4_1_,
orders0_.ID as ID1_,
orders0_.ID as ID6_0_,
orders0_.NAME as NAME6_0_,
orders0_.NUM as NUM6_0_,
orders0_.MEMBER_ID as MEMBER4_6_0_
from
TEST_ORDER orders0_
where
orders0_.MEMBER_ID=?
这里有点要注意:m.getOrders()在这里必须要在加载Member对象的Session范围内,否则得话就会出错,因为Session已经关闭。
c、删除数据
public void delete(String id) {
Session session = HibernateSessionFactory.getSessionFactory()
.getCurrentSession();
session.beginTransaction();
Member m = (Member) session.load(Member.class, id);
session.delete(m);
session.getTransaction().commit();
}
查看Hibernate后台SQL语句如下:
Hibernate:
select
member0_.ID as ID5_1_,
member0_.AGE as AGE5_1_,
member0_.NAME as NAME5_1_,
idcard1_.ID as ID4_0_,
idcard1_.NUM as NUM4_0_,
idcard1_.MEMBER_ID as MEMBER3_4_0_
from
TEST_MEMBER member0_
left outer join
TEST_IDCARD idcard1_
on member0_.ID=idcard1_.MEMBER_ID
where
member0_.ID=?
Hibernate:
select
orders0_.MEMBER_ID as MEMBER4_1_,
orders0_.ID as ID1_,
orders0_.ID as ID6_0_,
orders0_.NAME as NAME6_0_,
orders0_.NUM as NUM6_0_,
orders0_.MEMBER_ID as MEMBER4_6_0_
from
TEST_ORDER orders0_
where
orders0_.MEMBER_ID=?
Hibernate:
update
TEST_ORDER
set
MEMBER_ID=null
where
MEMBER_ID=?
Hibernate:
delete
from
TEST_ORDER
where
ID=?
Hibernate:
delete
from
TEST_MEMBER
where
ID=?
因为我根据一个Member的主键来加载到它的一个对象实例,然后将其删除,所以Hibernate首先加载Member对象及其Order集合;因为要删除Order,所以又更新了Order的外键为NULL,最后删除Order和Member。可见删除一个会员会执行大量的SQL语句,对性能的影响可想而知,并且如果Order的外键不允许为NULL,那么在更新Order时就会报错,这些都是要考虑的。而理想的情况应该是这样的:首先取出Member和其相关的集合,让后逐个删除与Member关联的对象,最后删除Member,比起上面的少了大量的更新语句,显然性能上要有所提高。
为了能够达到上述理想情况,使用这种单向关联是不行的,那就要通过双向关联来实现。
注:如果Member有上千个订单的话就要一条一条的删除订单,性能上也会受影响,幸好,Hibernate提高了一个系统属性可以设置成用来进行批量更新。hibernate.jdbc.batch_size,Hibernate官方建议取值在5和30之间。只有当要执行的SQL语句到指定的数目后,Hibernate才将其提交执行,这样减少了与数据交互的次数,从而提高性能。
关与双向关联请看下章的介绍。