hibernate的中的查询与级联操作
Posted J.M.Liu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hibernate的中的查询与级联操作相关的知识,希望对你有一定的参考价值。
1.Criteria查询接口适用于组合多个限制条件来搜索一个查询集。
要使用Criteria,需要遵循以下步骤:
*创建查询接口: Criteria criteria=session.createCriteria(User.class);
*设置查询条件: criteria.add(Restrictions.gt(“age”,10);
*查询数据: List<User> list=criteria.list();
2.关系映射:一个用户(cust_customer)对应多个联系人(cust_linkman)
*在CustCustomer.hbm.xml中配置
<!--设置与多方的联系--> <!-- name:javabean中set集合的名称 key:column:外键名称 one-to-many class:set集合中类的全路径--> <set name="linkMans"> <!--外键--> <key column="lkm_cust_id"/> <!--对应关系--> <one-to-many class="edu.whu.swe.lxl.learn.hibernate.model.CstLinkman"/> </set>
*在CustLinkman.hbm.xml中配置
<!--设置一对多--> <!-- name:javabean的属性名 class:属性的类的全名 column:外键名--> <many-to-one name="customer" class="edu.whu.swe.lxl.learn.hibernate.model.CustCustomer" column="lkm_cust_id"/>
3.双向关联:既保存主表,也保存从表
*java代码:
public void testBathDirectSave(){ // 新建一个用户和多个联系人 CustCustomer customer=new CustCustomer(); customer.setCustName("马蓉"); CstLinkman lkm1 = new CstLinkman(); lkm1.setLkmName("强哥"); CstLinkman lkm2 = new CstLinkman(); lkm2.setLkmName("小宋"); // 双向关联 // 客户关联联系人 customer.getLinkMans().add(lkm1); customer.getLinkMans().add(lkm2); // 联系人关联客户 lkm1.setCustomer(customer); lkm2.setCustomer(customer); session.save(customer); session.save(lkm1); session.save(lkm2); }
*SQL输出:
**Hibernate: insert into cust_customer (cust_name, cust_user_id, cust_create_id, cust_source, cust_industry, cust_level, cust_linkman, cust_phone, cust_mobile) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: insert into cst_linkman (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: insert into cst_linkman (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=? **Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?
可以看到,双向保存时,对从表其实是做了更新操作的。
4.级联保存:级联保存只需要保存一方关系,多方关系会自动保存。先测试只保存主表:
*配置:通过在一方关系的hbm.xml中配置set的cascade属性为save-update,从而使得多方关系中的Javabean从Transient状态自动转为Persistent态。
<set name="linkMans" cascade="save-update"> <!--外键--> <key column="lkm_cust_id"/> <!--对应关系--> <one-to-many class="edu.whu.swe.lxl.learn.hibernate.model.CstLinkman"/> </set>
*java代码:
public void testSingleDirectSave(){ // 新建一个用户和多个联系人 CustCustomer customer=new CustCustomer(); customer.setCustName("马蓉1"); CstLinkman lkm1 = new CstLinkman(); lkm1.setLkmName("强哥1"); CstLinkman lkm2 = new CstLinkman(); lkm2.setLkmName("小宋1"); // 单向关联 // 只需要客户关联联系人 customer.getLinkMans().add(lkm1); customer.getLinkMans().add(lkm2); // 不需要联系人关联客户 // 只需要保存客户,联系人由框架自动保存 session.save(customer); }
*SQL操作:
**Hibernate: insert into cust_customer (cust_name, cust_user_id, cust_create_id, cust_source, cust_industry, cust_level, cust_linkman, cust_phone, cust_mobile) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: insert into cst_linkman (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: insert into cst_linkman (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=? **Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?
这种方法虽然java代码中只需要单项保存就可以了,但是仍然先对多方从表进行insert操作后在进行update操作,一共有5次SQL操作。
5.级联保存,将级联保存配置在从表,在java中也只进行从表的保存操作。这种方法可以减少SQL语句。
*配置,同样,也需要在多端的hbm.xml中配置cascade属性为save-update:
<many-to-one name="customer" class="edu.whu.swe.lxl.learn.hibernate.model.CustCustomer" column="lkm_cust_id" cascade="save-update"/>
*Java代码:
public void testSingleDirectInManySave(){ // 新建一个用户和多个联系人 CustCustomer customer=new CustCustomer(); customer.setCustName("马蓉2"); CstLinkman lkm1 = new CstLinkman(); lkm1.setLkmName("强哥2"); CstLinkman lkm2 = new CstLinkman(); lkm2.setLkmName("小宋2"); // 单向关联 // 这是只关联联系人,而不对客户方进行操作 lkm1.setCustomer(customer); lkm2.setCustomer(customer); // 不需要联系人关联客户 // 只保存联系人,客户由框架自动保存 session.save(lkm1); session.save(lkm2); }
*SQL语句:
**Hibernate: insert into cust_customer (cust_name, cust_user_id, cust_create_id, cust_source, cust_industry, cust_level, cust_linkman, cust_phone, cust_mobile) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: insert into cst_linkman (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?) **Hibernate: insert into cst_linkman (lkm_name, lkm_gender, lkm_phone, lkm_mobile, lkm_email, lkm_qq, lkm_position, lkm_memo, lkm_cust_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
可以看到,这种在多端执行级联保存的方式,没有进行update操作,而是一步到位只进行insert操作,减少数据库的读写。
6.cascade设置有以下几种:
1)all: 包含出了delete-orphan外的所有情况,即save-update和delete。
2)none: 所有情况下均不进行关联操作。这是默认值。
3)save-update: 在执行save/update/saveOrUpdate时进行关联操作。
4)delete: 在执行delete 时进行关联操作。
5)delete-orphan: 孤儿删除,只能配置在一端。在delete的基础之上,当需要把外键设置为null时,直接删除外键对应的多端,一般用以对remove的支持。
6)all-delete-orphan: 当一个节点在对象图中成为孤儿节点时,删除该节点。
我们使用得是save-update,当执行级联保存操作时,如果相关联的对象在表中没有记录,则会一起save,即执行insert SQL操作,如果有(说明在快照区有相关联的对象的副本),则看是否发生改变,在觉得是否update相关联的对象。
所谓孤儿,只有在一对多的情况中才存在,指的是一端已经没有了,多端还有一些存在,它的外键是null。
7. inverse设置是否让主表来update从表的值来,且只对save和update有效。
这个是我比较难理解的一个点。
inverse的值是boolean值,也就是能设置为true或false。 如果一方的映射文件中设置为true,说明在映射关系(一对多,多对多等)中让从表自己来维护外键值。如果为false,就主表来设置从表的外键值。默认值是false,也就是会去update从表的外键值。 并且这属性只能在一端设置(inverse是set的属性)。比如一对多,这个一端。也就是在有set集合的这方设置。
维护关系:维护什么关系呢?包括两个方面
1、也就是维护外键的关系了,通俗点讲,就是哪一方去设置这个被外键约束的字段的值。就拿上面这个例子来说,cust_customer和cst_linkman两张表不管进行什么操作,只要关系到了另一张表,就不可避免的要操作外键字段,比如,linkman查询自己所对应的客户,就得通过被外键约束的字段值到customer中的主键中查找,如果customer想查询自己有哪些联系人,就得拿着自己的主键值跟linkman中的外键字段做比较,找到相同的值则是属于自己的联系人。 这个是查询操作, 现在如果是添加操作呢,linkman表中添加一条记录,并且对应于customer表中的一个客户,linkman中有被外键约束修饰的字段,那是通过CstLinkman(java)的insert语句就对这个外键字段赋值,还是让CustCustomer(java)对象使用update语句对其赋值呢,两个都能对这个外键字段的值进行操作,谁去操作呢?如果不做设置,两个都会操作,就出现了上面说的这种有5次SQL操作的情况(对于linkman表,有两次insert是有CstLinkman发出的,还有两次update是有CustCustomer发出的)。虽然不会出现问题,但是会影响性能。其实只需要CstLinkman发出的两次insert就可以插入两条联系人记录了。如果让对方维护外键关系,则自己这方就不维护了。
2、维护级联的关系,就是cascade的那几种设置了。
如果在一端的CustCustomer.hbm.xml中加入set的inverse=”true”属性,这时候无论在java中执行哪方的保存,都不会出现多余的update SQL操作,说明一端(主表)不会去维护外键关系,而只有让从表去维护。再次强调,inverse不影响是否级联操作,只是设置谁来维护外键的值。很多文章都误以为是设置cascade是否有效,这是错误的,误导人。
但是,inverse不是乱设置的,inverse如果设置为true,那么一端是不会去维护外键的值的,它会留给多端去维护,但如果多端没有通过setCuster()方法来设置相应的外键值,则为java类属性默认值Null,此时外键的值就会为null。所以一定要记得为从表的对象也设置好外键值。
由此可以看出,hibernate的级联保存方式,指的是如何发送SQL语句,而不会对java对象本身的赋值行为执行任何的操作,对任何java对象的赋值,都需要程序员自己去执行。
Hibernate唯一能自动对java对象赋值的操作是查询操作。
参考文献:https://www.cnblogs.com/whgk/p/6135591.html
8.在级联删除中,应把cascade=delete级联配置在一端,如果配置在多端,可能会导致多端数据删除不赶紧,留下孤点数据,原因如下:
当delete配置在多端时,则由多端去维护delete的一致性,所以当删除多端的某一条数据时,级联删除通过外键值查询到一端表的主键,找到对应的记录,并把这个记录删除。删除一端的数据之后,数据库如果设置的外键约束是 on delete set null,则留下了孤点数据。
而且,根据一般的业务逻辑,也应该是删除了一端数据之后,才删除所有的多端记录。而删除某一个多端记录时,是不需要级联删除一端的数据的。举个例子:建筑-房间是一对多关系,删除了多端的房间,是不需要整栋建筑的。但是建筑如果删除了,房间自然就不存在了,所有的房间都应该被删除。所以delete应该配置在一端。
9.delete和delete-orphan的区别,stackoverflow上已经有人说的很明白了:
Cascade DELETE means if this entity is deleted, delete the related entity or entities.
DELETE_ORPHAN means if an entity is removed from a related one-to-many collection, then not only disassociate it from the current entity, but delete it.
To give you an example, consider two entities: House and Room.
DELETE on the Room list on House means that if you delete the House then delete all it\'s Rooms.
DELETE_ORPHAN on the Room list on House means if you remove a Room from that collection, delete it entirely. Without it, the Room would still exist but not be attached to anything (hence "orphan").
In UML and OO modelling terms, this is basically the difference between composition and aggregation. The House->Room relationship is an example of composition. A Room is part of a House and doesn\'t exist independently.
An example of aggregation is, say, Class (parent) to Student (child). Delete the Class and the Student still exists (undoubtedly in other classes). Removing the Student from the Class doesn\'t typically mean deleting him or her.
大致的意思就是,delete只在删除一端的记录时,会删除多端的所有记录;而delete-orphan在当处于持久态的一端对象对他的set执行了remove时,会删除所remove的多端对象对应的从表记录。
在UML中,delete和delete-orphan的使用场景分别是“聚合”和“组成”。学生-班级关系是“聚合”,因此学生允许孤儿(没有班级),所以使用delete比较合理。建筑-房间关系是“组成”,如果一栋建筑没有了某个房间,那么这栋房间就不存在了,也就说说房间是不可能是脱离建筑的孤儿,所以当一个房间从建筑中remove时,就应该把房间从从表中删除,所以使用delete-orphan比较合适。
值得注意的是,delete-orphan包括了delete的效果。
参考:https://stackoverflow.com/questions/1377585/what-is-the-difference-between-delete-orphan-and-delete
总结:级联保存通常配置在多端(从表),级联删除配置在一端(主表)delete-orphan或delete。这样做的好处是:
1)当保存数据时,只需要设置多端(从表)的外键属性值并保存多端,主表会通过级联保存自动保存,因为主表无需维护外键,因此可以无需设置主表的set值,让它为null就行。
2)当删除时,通过删除主表来删除一端记录,并且级联删除所有的关联的多端(从表)记录。
3)删除多端(从表)的某一记录之后,主表不受影响,可以防止因主表级联删除之后,引起从表中其他记录的外键为null,甚至因no action或restrict而导致无法级联删除主表记录(与预期不一致)。
4)如果要通过一端(主表)来级联保存从表,也可以在一端(主表)中配置级联保存,并把reverse=true设置打开,此时,虽然不用手动session.save()多端对象(从表记录),但是需要在java代码中手动设置从表的外键值,否则外键的值为null。另外,在一端执行remove之后,也不会对多端执行delete操作(因为一端不去维护外键了);如果要让一端(主表)来维护从表的外键值,则reverse=false(默认就是false),这样在级联保存从表记录(inset)之后,还会发出update SQL来维护从表的外键值,这样会降低程序的性能。
以上是关于hibernate的中的查询与级联操作的主要内容,如果未能解决你的问题,请参考以下文章