单向一对多和双向一对多

《深入浅出Hibernate》中对单向一对多和双向一对多进行了一些介绍,其中提到:“由于是单向关联,为了保持关联关系,只能通过主控方对被控方进行及联更新。”也就是说,对被关联对象进行任何操作,都只能通过主控方进行及联更新。附书中例子:(其中TUser为主控方,TAddress为被控方)
 
    Transaction tx = session.beginTransaction();

    TAddress addr = new TAddress();
    addr.setTel("1123");
    ……
    ……
   
    user.getAddresses().add(addr);
    session.save(user);

    tx.commit();

    书中提到,为完成这个操作,Hibernate会依次执行以下两条SQL来完成新增TAddress的操作:
insert into t_address(……) values(……)
update t_address set …… ……
而问题是,由于关联关系由TUser对象单向维持,addr对象不知道自己的user_id是什么数值,因此在执行第一条语句时,其user_id字段为null,违反约束。解决办法是将该字段设置为not null=false或从hbm.xml文件中剔除该字段等等。纵使使用这些方法解决问题,由于采用两条SQL语句进行一次插入操作,性能开销几乎是单条insert的两倍,效率较低。

    而为了很好的解决这个问题,书中提出:“如果addr对象知道如何获取user_id字段的内容,那么执行insert语句的时候直接将数据植入即可”。为了实现这个想法,书中提出了双向一对多关联。

    下面的问题是,真的只能用双向一对多关联解决这个问题吗?真的无论在什么时候,只要遇到一对多关联的情形都要使用双向一对多关联吗?

    首先,我对以下这句话产生了怀疑:“由于是单向关联,为了保持关联关系,只能通过主控方对被控方进行及联更新。”结合例子,也就是说,要新增一条addr记录,必须要通过save(user)对象进行。原因为何?因为主控方是user方,addr方不知道自身与哪一个user相关联,即不知道的user_id值是什么,那么我们直接手工植入如何呢?更改例子如下:
   
    Transaction tx = session.beginTransaction();

    TAddress addr = new TAddress();
    addr.setTel("1123");
    ……
    //手工植入user_id值
    addr.setUserid(user_id);   

    session.save(addr);

    tx.commit();

    此处仅更改测试代码,其余如数据库结构、hbm.xml文件等等,均与书中一致

    在实际应用中,大多数情况下,要新增子表数据,都是在得到该字表的父表对象之后,例如,在一个BBS应用中发表一篇新帖子,步骤是先进入该BBS的某一分类(此时得到主表Sort的对象),然后在该分类下新增帖子(之后再进行新增子表记录操作),也就是说在新增子表内容之前,通过httpRequest很容易就可以得到父类的id。

    测试上面修改过的例子,执行以下sql语句完成新增操作:
insert into t_address(……) values(……)
   
    接下来测试对子表的修改、删除操作,无任何问题。在接下来测试对父表的删除操作,出现问题。

代码:
    Transaction tx = session.beginTransaction();

    TUser user = (TUser)sess.get(TUser.class,userId);

    sess.delete(user);

    tx.commit();
  
异常:
    Caused by:java.sql.SQLException: ORA-041407:无法更新(addr.user_id) 为 NULL

    分析原因,由于主表User的配置文件中并未声明inverse属性,也就是默认为false,将user作为主控方,对user进行操作的同时,user作为主控方对被空方addr进行了及联操作,而此时user对象已经被删除,及联操作将把addr对象的user_id值更改为null,违反约束。

    那么在User.hbm.xml中把set节点的inverse属性设置为true,把控制权交还给广大人民:)。重新执行上面的删除父表数据的测试,顺利通过。

    接下来测试对父表数据的修改,如果对父表除user_id以外的字段进行修改,单向一对多操作可以顺利完成,可是如果需要更改user_id怎么办呢?由于控制方是addr,之间的关联关系由addr进行维护,那么仅仅修改user类的user_id显然是不行的,这个时候,好像就应该要用到双向一对多关联了。(还好,我的实际项目中需要更改父表主键值的时候很少,嘿嘿)   

    所谓双向一对多关联,其实是“一对多”和“多对一”的结合(分别对应主类配置文件中的one-to-many和子类配置文件中的many-to-one)。它和单向一对多的区别在于,单向一对多中,子类只将外键作为本身的属性,而双向一对多中,子类把父类作为自己的一个属性,并通过many-to-one进行关联。这样就实现了在关联双方中任意一方访问另一方的双向关联(在父类中,通过one-to-many访问子类,而在子类中通过many-to-one访问父类),相对于单向一对多关联,双向一对多提供了更加丰富灵活的控制手段。

    同时,由于子类中关于双方关联的属性是父类而不是外键,因此,要得到一个父类下属的所有子类,必须先对父类进行查询,再通过父类中子类的set得到所有子类,无法利用外键查询直接得到。如果父类的结构比较简单,这样的操作尚可,可是如果父类结构复杂(字段非常多或者包含如Lob类型之类的大量数据),就会带来不小的性能损耗。

    通过上面的例子可以看到,并非任何时候都要使用双向一对多关联,到底使用双向还是单向,在更大程度上取决于实际需求。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值