Blog信息 |
blog名称:小鸟吹烟 日志总数:157 评论数量:424 留言数量:-1 访问次数:1253353 建立时间:2006年10月23日 |

| |
[Hibernate 专区]inverse=true 文章收藏, 网上资源
tone 发表于 2007/4/1 18:45:53 |
建议大家好好去看一下这个链接,讲的很详细,虽然是E文,可并不难懂。 http://www.hibernate.org/155.html
这篇文章里没有提到delete的情况,我就one-to-many来做个简单说明:
代码
Parent parent = (Parent) session.load(Parent.class, pid);
session.delete(parent);
1. 爸爸那边设置了cascade=all和inverse=true a. delete from children b. delete from parent
2. 爸爸那边设置了cascade=all,没有设inverse=true a. update children set parent_id=null where parent_id=? b. delete from children c. delete from parent
因为此时inverse=false,所以爸爸要负责维护relationship,所以它要去把children中的连接信息都清空。但是,如果children的parent_id的constraint设置的是not-null的话,那么很不幸,hibernate执行到2.a就会throw exception了。
如果你要问,这个inverse=true到底是在源代码中的哪儿判断的呀?请看代码:
代码
......
SessionImpl.flush();
SessionImpl.execute();
SessionImpl.executeAll(collectionRemovals);
executable.execute();
ScheduledCollectionRemove.execute();
getPersister().remove( getId(), getSession() );
OneToManyPersister(即AbstractCollectionPersister).remove();
if ( !isInverse ) {
PreparedStatement st = session.getBatcher().prepareBatchStatement( getSQLDeleteString() );
}
OneToManyPersister.getSQLDeleteString();
return "update children set parent_id=null where parent_id=?";
...... |
|
回复:inverse=true 文章收藏, 网上资源
dfg(游客)发表评论于2009/12/10 9:15:35 |
|
回复:inverse=true 文章收藏, 网上资源
tone发表评论于2007/4/1 19:10:51 |
在多對一、一對多中都是單向關聯,也就是其中一方關聯到另一方,而另一方不知道自己被關聯。
如果讓雙方都意識到另一方的存在,這就形成了雙向關聯,在多對一、一對多的例子可以改寫一下,重新設計User類別如下:
User.java
package onlyfun.caterpillar;
public class User {
private Integer id;
private String name;
private Room room;
public User() {
}
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;
}
public Room getRoom() {
return room;
}
public void setRoom(Room room) {
this.room = room;
}
}
Room類別如下:
Room.java
package onlyfun.caterpillar;
import java.util.Set;
public class Room {
private Integer id;
private String address;
private Set users;
public Room() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Set getUsers() {
return users;
}
public void setUsers(Set users) {
this.users = users;
}
public void addUser(User user) {
users.add(user);
}
public void removeUser(User user) {
users.remove(user);
}
}
如此,User實例可參考至Room實例而維持多對一關係,而Room實例記得User實例而維持一對多關係。
在映射文件方面,可以如下撰寫:
User.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="user">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"/>
</id>
<property name="name" column="name" type="java.lang.String"/>
<many-to-one name="room"
column="room_id"
class="onlyfun.caterpillar.Room"
cascade="save-update"
outer-join="true"/>
</class>
</hibernate-mapping>
Room.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.Room" table="room">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="address"
column="address"
type="java.lang.String"/>
<set name="users" table="user" cascade="save-update">
<key column="room_id"/>
<one-to-many class="onlyfun.caterpillar.User"/>
</set>
</class>
</hibernate-mapping>
映射文件雙方都設定了cascade為save-update,所以您可以用多對一的方式來維持關聯:
User user1 = new User();
user1.setName("bush");
User user2 = new User();
user2.setName("caterpillar");
Room room1 = new Room();
room1.setAddress("NTU-M8-419");
user1.setRoom(room1);
user2.setRoom(room1);
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(user1);
session.save(user2);
tx.commit();
session.close();
或是反過來由一對多的方式來維持關聯:
User user1 = new User();
user1.setName("bush");
User user2 = new User();
user2.setName("caterpillar");
Room room1 = new Room();
room1.setUsers(new HashSet());
room1.setAddress("NTU-M8-419");
room1.addUser(user1);
room1.addUser(user2);
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(room1);
tx.commit();
session.close();
這邊有個效率議題可以探討,上面的程式片段Hibernate將使用以下的SQL進行儲存:
Hibernate: insert into room (address) values (?)
Hibernate: insert into user (name, room_id) values (?, ?)
Hibernate: insert into user (name, room_id) values (?, ?)
Hibernate: update user set room_id=? where id=?
Hibernate: update user set room_id=? where id=?
上面的程式寫法表示關聯由Room單方面維持,而主控方也是Room,User不知道Room的room_id是多少,所以必須分別儲存Room與User之後,再更新user的room_id。
在一對多、多對一形成雙向關聯的情況下,可以將關聯維持的控制權交給多的一方,這樣會比較有效率,理由不難理解,就像是在公司中,老闆要記住多個員工的姓名快,還是每一個員工都記得老闆的姓名快。
所以在一對多、多對一形成雙向關聯的情況下,可以在「一」的一方設定控制權反轉,也就是當儲存「一」的一方時,將關聯維持的控制權交給「多」的一方,以上面的例子來說,可以設定Room.hbm.xml如下:
Room.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.Room" table="room">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="address"
column="address"
type="java.lang.String"/>
<set name="users" table="user" cascade="save-update" inverse="true">
<key column="room_id"/>
<one-to-many class="onlyfun.caterpillar.User"/>
</set>
</class>
</hibernate-mapping>
由於關聯的控制權交給「多」的一方了,所以直接儲存「一」方前,「多」的一方必須意識到「一」的存在,所以程式片段必須改為如下:
User user1 = new User();
user1.setName("bush");
User user2 = new User();
user2.setName("caterpillar");
Room room1 = new Room();
room1.setUsers(new HashSet());
room1.setAddress("NTU-M8-419");
room1.addUser(user1);
room1.addUser(user2);
// 多方必須意識到單方的存在
user1.setRoom(room1);
user2.setRoom(room1);
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(room1);
tx.commit();
session.close();
上面的程式片段Hibernate將使用以下的SQL:
Hibernate: insert into room (address) values (?)
Hibernate: insert into user (name, room_id) values (?, ?)
Hibernate: insert into user (name, room_id) values (?, ?)
如果控制權交給另一方了,而另一方沒有意識到對方的存在的話會如何?試著將上面的程式片段中user1.setRoom(room1);與user2.setRoom(room1);移去,執行之後,您會發現資料庫中room_id會出現null值,這種結果就好比在多對一中,您沒有分配給User一個Room,理所當然的,room_id會出現null。 |
|
回复:inverse=true 文章收藏, 网上资源
tone发表评论于2007/4/1 18:49:18 |
http://hi.baidu.com/injava/blog/item/acb67b2214c3c3a44723e84f.html
1、到底在哪用cascade="..."? cascade属性并不是多对多关系一定要用的,有了它只是让我们在插入或删除对像时更方便一些,只要在cascade的源头上插入或是删除,所有 cascade的关系就会被自己动的插入或是删除。便是为了能正确的cascade,unsaved-value是个很重要的属性。Hibernate通 过这个属性来判断一个对象应该save还是update,如果这个对象的id是unsaved-value的话,那说明这个对象不是 persistence object要save(insert);如果id是非unsaved-value的话,那说明这个对象是persistence object(数据库中已存在),只要update就行了。saveOrUpdate方法用的也是这个机制。 2、到底在哪用inverse="ture"? “set的inverse属性决定是否把对set的改动反映到数据库中去。inverse=false————反映;inverse=true————不反映”inverse属性默认为false inverse属性默认是false的,就是说关系的两端都来维护关系。这个意思就是说,如有一个Student, Teacher和TeacherStudent表,Student和Teacher是多对多对多关系,这个关系由TeacherStudent这个表来表 现。那么什么时候插入或删除TeacherStudent表中的记录来维护关系呢?在用hibernate时,我们不会显示的对 TeacherStudent表做操作。对TeacherStudent的操作是hibernate帮我们做的。hibernate就是看hbm文件中指 定的是"谁"维护关系,那个在插入或删除"谁"时,就会处发对关系表的操作。前提是"谁"这个对象已经知道这个关系了,就是说关系另一头的对象已经set 或是add到"谁"这个对象里来了。前面说过inverse默认是false,就是关系的两端都维护关系,对其中任一个操作都会处发对表系表的操作。当在 关系的一头,如Student中的bag或set中用了inverse="true"时,那就代表关系是由另一关维护的(Teacher)。就是说当这插 入Student时,不会操作TeacherStudent表,即使Student已经知道了关系。只有当Teacher插入或删除时才会处发对关系表的 操作。所以,当关系的两头都用inverse="true"是不对的,就会导致任何操作都不处发对关系表的操作。当两端都是inverse= "false"或是default值是,在代码对关系显示的维护也是不对的,会导致在关系表中插入两次关系。 在一对多关系中inverse就更有意义了。在多对多中,在哪端inverse="true"效果差不多(在效率上)。但是在一对多中,如果要一方维护关 系,就会使在插入或是删除"一"方时去update"多"方的每一个与这个"一"的对象有关系的对象。而如果让"多"方面维护关系时就不会有update 操作,因为关系就是在多方的对象中的,直指插入或是删除多方对象就行了。当然这时也要遍历"多"方的每一个对象显示的操作修关系的变化体现到DB中。不管 怎样说,还是让"多"方维护关系更直观一些。 (1)对one-to-many而言,改变set,会让hibernate执行一系列的update语句, 不会delete/insert数据 (2)对many-to-many而言,改变set,只修改关系表的数据,不会影响many-to-many的另一方。 (3)虽然one-to-many和many-to-many的数据库操作不一样,但目的都是一个:维护数据的一致性。 3、cascade和inverse有什么区别? 可以这样理解,cascade定义的是关系两端对象到对象的级联关系;而inverse定义的是关系和对象的级联关系。 inverse只对set+one-to-many(或many-to-many)有效,对many-to-one, one-to-one无效。cascade对关系标记都有效。 inverse对集合对象整体起作用,cascade对集合对象中的一个一个元素起作用,如果集合为空,那么cascade不会引发关联操作。 比如将集合对象置为null, school.setStudentSet(null) inverse导致hibernate执行:udpate STUDENT set SCHOOL_ID=null where SCHOOL_ID=? cascade则不会执行对STUDENT表的关联更新, 因为集合中没有元素。 再比新增一个school, session.save(school) inverse导致hibernate执行: for( 对(school的每一个student ){ udpate STUDENT set SCHOOL_ID=? where STUDENT_ID=? //将学生的school_id改为新的school的id } cascade导致hibernate执行: for( 对school的每一个student ){ session.save(aStudent); //对学生执行save操作 } extends:如果改变集合中的部分元素(比如新增一个元素), inverse: hibernate先判断哪些元素改变了,对改变的元素执行相应的sql cascade: 它总是对集合中的每个元素执行关联操作。 (在关联操作中,hibernate会判断操作的对象是否改变) 两个起作用的时机不同: cascade:在对主控方操作时,级联发生。 inverse: 在flush时(commit会自动执行flush),对session中的所有set,hibernate判断每个set是否有变化, 对有变化的set执行相应的sql,执行之前,会有个判断:if( inverse == true ) return;可以看出cascade在先,inverse在后。 inverse 对set + one-to-many 和 set + many-to-many 起的作用不同。hibernate生成的sql不同。 对one-to-many,hibernate对many方的数据库表执行update语句。 对many-to-many, hibernate对关系表执行insert/update/delte语句,注意不是对many方的数据库表而是关系表。 cascase 对set都是一致的,不管one-to-many还是many-to-many。都简单地把操作传递到set中的每个元素。所以它总是更新many方的数据库表。 4、cascade和inverse有什么相同? 这两个属性本身互不影响,但起的作用有些类似,都能引发对关系表的更新。 5、 建议:只对set + many-to-many设置inverse=false,其他的标记不考虑inverse属性,都设为inverse=true。对cascade,一 般对many-to-one,many-to-many,constrained=true的one-to-one 不设置级联删除。 |
|
回复:inverse=true 文章收藏, 网上资源
tone发表评论于2007/4/1 18:47:38 |
http://tonyyl.javaeye.com/post/243217
看了很多关于inverse的文章,理解得也不是很透彻,这次做了这个项目,遇到这方面问题,就总结了一样关于inverse的配置,
1.关系parent和children的例子也已经说了怎么去配置一对多的关系,这个例子已经说明了在set端设置inverse=true,原因很简单,child长大了,不需要什么事情都要parent带到
其实在我看来,inverse=true实际上是去解放了one那一方,在这儿就是指parent对象,他不需要在每次更新的时候,都把children都加载出来。像在下面的代码中,不会有什么问题,应该都在一个session中完成的
ParentManager manager=(ParentManager) getBean("parentManager"); Parent parent=manager.getParent("pppp"); parent.setName("bob");manager.save(parent);
所以不会觉得有什么问题。但是在WEB应用的三层结构中,通常要把模型传递到表现层中,完成更新操作,那这个时候inverse=true的优势就明显了,他可以不用考虑Children就完成自身的更新
2.当inverse=false会发生什么?
那天也是无意注意到这个问题,突然把系统的所有inverse都设置成了false
当在插入一个新的对象的时候,不会出现什么问题。但是在更新父对象的时候,却发生了我不想看到的,Customer和Order是一对多的关系
Hibernate: update customer.name=?..........where customerName=? and version=?
Hibernate: update orders set customer=null where customer=?
在更新了customer信息后,同时把订单中该用户的用户置为null,换句话说就是断了两者之间的联系。再看看代码
CustomerForm customerForm = (CustomerForm) form; Customer customer = new Customer(); convertObject(customerForm, customer); CustomerManager manager = (CustomerManager) getBean("customerManager");
manage.save(customer);
customerForm是通过struts的form理到来自表单的数据。最后通过业务层的代码调用Hibernate的saveOrUpdate()方法进行保存。这个时候我发现我并未去加载和customer关系的order,
然后我修改了代码,试着把所有的该customer的order都提出来,并在保存前调用了
customer .setOrder(order);
结果发生异常
org.springframework.orm.hibernate3.HibernateSystemException: a different object with the same identifier value was already associated with the session: [com.us.ebuy.model.Customer#123]; nested exception is org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.us.ebuy.model.Customer#123]org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.us.ebuy.model.Customer#123] at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:629) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:258) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:216) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70) at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:531) at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:523) at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:519) at org.springframework.orm.hibernate3.HibernateTemplate$18.doInHibernate(HibernateTemplate.java:690) at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:365) at org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:687) at com.us.ebuy.dao.hibernate.BaseDAOHibernate.saveObject(BaseDAOHibernate.java:55) at com.us.ebuy.users.dao.hibernate.CustomerDAOHibernate.saveCustomer(CustomerDAOHibernate.java:64) at com.us.ebuy.users.service.impl.CustomerManagerImpl.saveCustomer(CustomerManagerImpl.java:52) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:335) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:181) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:148) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:176) at $Proxy2.saveCustomer(Unknown Source) at com.us.ebuy.users.action.CustomerAction.save(CustomerAction.java:133) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.apache.struts.actions.DispatchAction.dispatchMethod(DispatchAction.java:274) at org.apache.struts.actions.DispatchAction.execute(DispatchAction.java:194) at com.us.ebuy.action.BaseAction.execute(BaseAction.java:143) at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:419) at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:224) at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1196) at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) at javax.servlet.http.HttpServlet.service(HttpServlet.java:709) at javax.servlet.http.HttpServlet.service(HttpServlet.java:802) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173) at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:174) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:75) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:178) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:126) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:107) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869) at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664) at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527) at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80) at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684) at java.lang.Thread.run(Thread.java:595)
可以看出,在更新的时候,他执行了插入操作,才会认为有重复的key,后来我想到了使用hibernate的merge方法,通过合作,才能完成customer的更新操作,但这这种更新方法显然是我们不愿意看到的。后来通过检测配置文件才发现,set 元素的inverse=false,最后我想是这儿的原因,改为false后,还是通过saveOrUpdate()方法更新对象,问题解决了。
通过上面的问题,可以更进一步的清楚了,Hibernate在对对待持久化的对象和游离状态的对象是不一样的,上述问题通常都是发生在处理游离状态的对象时候,而在三层结构中,我们会经常处理这种问题的,看来是要重新理解对象的状态问题。
|
|
» 1 »
|