hibernate映射技巧one-to-many(单项一对多)

    一对多关系在系统实现中也很常见。典型的例子就是父亲与孩子的关系。 而在我们现在的这个示例中,每个用户(TUser)都关联到多个地址(TAddress),如一个用户可能拥有办公室地址、家庭地址等多个地址属性。这样,在系统中,就反应为一个“一对多”关联。


一对多关系分为单向一对多关系和双向一对多关系。


单向一对多关系只需在“一”方进行配置,双向一对多关系需要在关联双方均加以配置。

 

Ø 单向一对多关系

配置:
对于主控方(TUser):
TUser.hbm.xml:
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<set
name="addresses"
table="t_address"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
order-by="zipcode asc"
>
<key
column="user_id"
>
</key>
<one-to-many
class="org.hibernate.sample.TAddress"
/>
</set>
……
</class>
</hibernate-mapping>

被动方(Taddress)的记录由Hibernate 负责读取,之后存放在主控方指定的Collection类型属性中。
对于one-to-many 关联关系, 我们可以采用java.util.Set ( 或者net.sf.hibernate.collection.Bag)类型的Collection,表现在XML 映射文件中也就是<set>…</set>(或<bag>…</bag>)节点。关于Hibernate的Collection实现,请参见Hibernate Reference.
one-to-many 节点有以下属性:

属性描述类型
name映射属性TextY
table目标关联数据库表。TextY
lazy是否采用延迟加载。TextN
inverse用于标识双向关联中的被动方一端。
inverse=false的一方(主控方)负责
维护关联关系。
默认值: false
BoolN
cascade操作级联(cascade)关系。
可选值:
all : 所有情况下均进行级联操作。
none:所有情况下均不进行级联操作。
save-update:在执行save-update时
进行级联操作。
delete:在执行delete时进行级联操作。
TextN
sort排序类型。
可选值:
unsorted :不排序(默认)
natural :自然顺序(避免与order-by
搭配使用)
comparatorClass :指以某个实现了
java.util.Comparator接口的类作为排
序算法。
TextN
order-by指定排序字段及其排序方式。
(JDK1.4以上版本有效)。
对应SQL中的order by子句。
避免与sort 的 “natural”模式同时使
用。
TextN
where数据甄选条件,如果只需要处理库表中某
些特定数据的时候,可通过此选项设定结
果集限定条件。
TextN
outer-join是否使用外联接。
true:总是使用outer-join
false:不使用outer-join
auto(默认) :如果关联对象没有采用
Proxy机制,则使用outer-join.
TextN
batch-size采用延迟加载特性时(Lazy Loading)
一次读入的数据数量。
此处未采用延迟加载机制,因此此属性忽
略。
IntN
access属性值的读取方式。
可选项:
field
property(默认)
ClassName
TextN

 

    通过单向一对多关系进行关联相对简单,但是存在一个问题。由于是单向关联,为了保持关联关系,我们只能通过主控方对被动方进行级联更新。且如果被关联方的关联字段为“NOT NULL”,当Hibernate创建或者更新关联关系时,还可能出现约束违例。

   例如我们想为一个已有的用户“Erica”添加一个地址对象:
Transaction tx = session.beginTransaction();
TAddress addr = new TAddress();
Hibernate Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?
addr.setTel("1123");
addr.setZipcode("233123");
addr.setAddress("Hongkong");
user.getAddresses().add(addr);
session.save(user);//通过主控对象级联更新
tx.commit();
为了完成这个操作,Hibernate会分两步(两条SQL)来完成新增t_address记录的操作:
1. save(user)时:
insert into t_address (user_id, address, zipcode, tel)
values (null, "Hongkong", "233123", "1123")
2. tx.commit()时
update t_address set user_id=”1”, address="Hongkong",
zipcode="233123", tel="1123" where id=2
第一条SQL用于插入新的地址记录。
第二条SQL用于更新t_address,将user_id设置为其关联的user对象的id值。问题就出在这里,数据库中,我们的t_address.user_id字段为“NOT NULL”型,当Hibernate执行第一条语句创建t_address记录时,试图将user_id字段的值设为null,于是引发了一个约束违例异常:
net.sf.hibernate.PropertyValueException: not-null property
references a null or transient value:
org.hibernate.sample.TAddress.userId

因为关联方向是单向,关联关系由TUser对象维持,而被关联的addr对象本身并不知道自己与哪个TUser对象相关联,也就是说,addr对象本身并不知道user_id应该设为什么数值。

因此,在保存addr时,只能先在关联字段插入一个空值。之后,再由TUser对象将自身的id值赋予关联字段addr.user_id,这个赋值操作导致addr对象属性发生变动,在事务提交时,hibernate会发现这一改变,并通过update sql将变动后的数据保存到数据库。

第一个步骤中,企图向数据库的非空字段插入空值,因此导致了约束违例。
既然TUser对象是主控方,为什么就不能自动先设置好下面的TAddress对象的关俩字段值再一次做Insert操作呢?莫名其妙?Ha,don’t ask me ,go to ask
Hibernate TeamJ。
Hibernate Developer’s Guide Version 1.0
September 2, 2004 So many open source projects. Why not Open your Documents?


我们可以在设计的时候通过一些手段进行调整,以避免这样的约束违例,如将关联字段设为允许NULL值、直接采用数值型字段作为关联(有的时候这样的调整并不可行,很多情况下我们必须针对现有数据库结构进行开发),或者手动为关联字段属性
赋一个任意非空值(即使在这里通过手工设置了正确的user_id也没有意义,hibernate还是会自动再调用一条Update语句进行更新)。

甚至我们可以将被动方的关联字段从其映射文件中剔除(如将user_id字段的映射从TAddress.hbm.xml中剔除)。

这样Hibernate在生成第一条insert语句的时候就不会包含这个字段(数据库会使用字段默认值填充),如:之后update语句会根据主控方的one-to-many映射配置中的关联字段去更新被动方关联字段的内容。在我们这里的例子中,如果将user_id字段从TAddress.hbm.xml文件中剔除,Hibernate在保存数据时会生成下面几条SQL:
1. insert into t_address (address, zipcode, tel) values
('Hongkong', '233123', '1123')
2. update t_address set user_id=1 where id=7


生成第一条insert语句时,没有包含user_id字段,数据库会使用该字段的默认值(如果有的话)进行填充。因此不会引发约束违例。之后,根据第一条语句返回的记录id,再通过update语句对user_id字段进行更新。

但是,纵使采用这些权益之计,由于Hibernate实现机制中,采用了两条SQL进行一次数据插入操作,相对单条insert,几乎是两倍的性能开销,效率较低,因此,对于性能敏感的系统而言,这样的解决方案所带来的开销可能难以承受。

针对上面的情况,我们想到,如果addr对象知道如何获取user_id字段的内容,那么执行insert语句的时候直接将数据植入即可。这样不但绕开了约束违例的可能,而且还节省了一条Update语句的开销,大幅度提高了性能。


双向一对多关系的出现则解决了这个问题。它除了避免约束违例和提高性能的好处之外,还带来另外一个优点,由于建立了双向关联,我们可以在关联双方中任意一方,访问关联的另一方(如可以通过TAddress对象直接访问其关联的TUser对象),这提供了更丰富灵活的控制手段。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值