关于hibernate 的 长事务的问题

03-07-25 frank
Session session1 = sessionFactory.openSession();
		Employee employeeFromSession2 =
			(Employee) session2.load(Employee.class, new Integer(employeeId));
		session1.flush();
		session1.connection().commit();
		session1.close();


(a*)。。。。。。。。。。。。。。。。。
		String newEmail = "something@different.com";
。。。。。。。。。。。。。。。。。。。。。。。。
		employeeFromSession2.setEmail(newEmail);
		session2.flush();
		session2.connection().commit();
		session2.close();
<p>

在web application中,当a 用户运行到了以上代码(a*)处 返回页面供用户修改,此时另外一个用户b 修改了同一个emplyee的name! 并提交。然后用户a 修改了email提交,作完后我发现b用户的提交又被a用户里的name(其实已经是脏数据了)复写回去了,b等于什么也没做。这样显得及不合理,不知道各位有什么好的解决办法,hibernate 里好像有version的,但不知道如何用,请教各位大虾

1
robbin
2003-07-25 15:08
首先你的程序逻辑让我看不懂。我看不出来你的session1有任何用处。

其次,你是否想说的是,两个并发用户a和b,从数据库中load数据,此时a和b都拿到同样的数据。然后b先修改数据并且提交事务关闭session。此时a保持的还是原始的旧数据,然后a修改数据,之后提交数据库,这样把b用户修改的数据给覆盖掉了。

如果我理解你的意思是这样的话,我只能告诉你这根本不关Hibernate的事,任何ORM都无能为力,是数据库的事情,你要保证你想要的情况,必须修改数据库的隔离级别。

但是有一种情况,Hibernate不会修改数据库。假设a和b都load同样的数据,然后b修改数据提交数据库。此时数据库中是b修改后的新数据,a中是旧的数据,然后a用户在界面没有修改,之后a用户提交数据库。

在此种情况下,Hibernate会判断出a的数据没有被更新过,即使显式强制提交,Hibernate也不会向数据库发送sql,也就是说只要a的旧数据没有被修改过,并不会发生脏数据复写的问题。

我怀疑你想说的是后一种情况。

frank
2003-07-25 15:44
是这样的:一张表有两个字段 a b ,用户A 需要修改a 字段,用户B 需要修改b字段,那么 两个操作都需要先将字段抽出给用户看,然后用户在界面上作了修改,然后提交给服务器,更新数据库! 由于是同一张表 所以两个用户肯定用到的是同一个class的不同的实例,总是存在这样的可能用户A B 同时LOAD ! 然后显示给用户看,此时不可能保存事务,只有断开连接,然后再保存的时候,再连接 ,就有可能相互覆盖了,有什么好的解决方法?

我以前用过jbuilder 的dataexpress dataset它就可以监测出这种情况

shenli
2003-07-25 15:59
对象version或timestamp,好像hibernate的doc上就有

robbin
2003-07-25 16:14
你可以这样试试看,我没有试过,但觉得应该可以。

表的Mapping文件中a字段和b字段的映射,添加属性:

<property
        name="propertyName"       
        column="column_name"      
        type="typename"           
        update="false"     
            
/>
<p>

默认update属性是true,改成false以后,session.update(...)的时候,如果该字段对应的类属性没有修改,就不会出现在sql语句里面,应该就会解决你这个问题了。

http://hibernate.bluemars.net/hib_docs/reference/html/or-mapping.htmlor-mapping-s1-7

robbin
2003-07-25 16:45
试过了,不行。update语句是在Hibernate的Configuration的时候生成的,不能动态改变。Hibernate只能做到对PO的状态管理,还不能细化到单独对PO属性的状态管理。虽然Hibernate不是没有解决办法,比如说自己写一个ClassPersister实现类,但感觉比较麻烦,不如直接用JDBC来的痛快。

robbin
2003-07-25 16:54
其实你遇到的这个问题和事务没有什么关系,是一个PO状态管理的问题。ORM都是以Persistent Object为最小不可分割的可管理单元,所以如果碰到同一个PO的两个属性分别存在不同的状态的时候,ORM就无能为力了,就是用PO的version也没有用,version是针对PO的,不是针对单个属性的。

如果你不嫌麻烦,可以自己写一个ClassPersister实现类,不用全部自己实现,继承EntityPersister,覆盖一下其中update就可以了,主要根据修改了的属性产生update,那些没有改变的属性就不放到update里面。

如果怕麻烦,直接用JDBC得了。

frank
2003-07-25 17:12
我看了hibernate 生成的sql ,其实我觉得,hibernate 为什么不在update的同时,把原来的所有属性都加在where上呢?

比如update sampltable set a=newa where b = beloadedb ,当发现b 不是原先LOAD时的b 就抛出例外。(因为b有没有发生改变 那个类应该可以察觉到)。我注意到HIBERNATE的文档里有个关于VERSION的设置,但是不知道如何用,可能它是解决问题的方法?

frank
2003-07-25 17:18
在我做的项目中经常遇到此类问题!其实这是个同步的问题,最明显的就是订票系统,我觉得version或许是良方,你能给我具体讲讲version的作用么?

frank
2003-07-25 17:28
让我觉得奇怪的是hibernate为什么把没有改变过的属性也update那?难道他本身不是一种双层结构?load后作为原值,改变后的这另外存储,这样就可以对比出那些改变过,那些没有,如果一张表由40个字段,如果我只想更新一个却更新是不是很浪费数据库的资源?

robbin
2003-07-25 18:07
或者我没有正确理解你的意思,或者你还没有搞清楚状况。

如果我确实正确理解了你的原来的需求的话,我还是要告诉你,这既不是事务管理的问题,也不是同步的问题,是一个状态管理的颗粒度的问题。Hibernate的状态管理细化到PO这一级,并没有细化到PO的单个属性(表字段)这一级。所以你不用白费力气了,这个问题看来属于那Hibernate不准备处理的5%。

PO的version只能跟踪整个PO的状态,一旦某个属性值改变,那么整个PO的状态就被标记,update的时候所有的字段一起更新。我原想设置映射文件的update属性应该可以解决属性更新的问题,但试过以后才知道不行。

你说的为什么update的时候所有的属性都一起update,而不是只更新改变字段,其实这是一个比较值得探讨的问题。

如果想要做到只更新改变了的字段,必须做到两点:

1、对PO的每个属性设置version进行状态跟踪

2、update语句是动态生成的,在实际向数据库更新的时候,依次检查每个属性的version,决定哪些属性需要更新,动态构造update语句。

单单要做到上述第一点,就势必要给PO的操作带来沉重的负担,每次属性的存取都要判断version,严重影响PO的性能。会带来整个Hibernate性能的降低。而要做到第二点,临时构造update语句时间消耗很客观,也会极大降低update的速度。

Hibernate的PO状态管理是当任何属性值改变的时候,version就被标记,表明PO被更新了。实现很简单而有效,效率非常高。而update语句insert,delete和某几个select语句是在Hibernate初始化过程中就构造好了,不需要用的时候临时构造。

综合评价得与失,对PO属性进行状态管理固然可以稍微降低数据库负担,但是会极大影响Hibernate的运行效率,终究得不偿失。

BTW,如果一个表有40个字段的话,那么这个表肯定会有多种复杂的关系,在这种情况下,你应该针对每种不同的关系映射不同的PO,也就是说,这张表会映射好几个不同的class,每个class代表不同的构成关系,每个class都只用到了这张表的某几个字段而已。class越细,可扩展性越好。

guty
2003-07-25 18:44
robbin, 这个是个很典型的需要使用optimistic transaction的问题,一般做法也就是用version检查了。

用户访问数据时,取到当前的version,当写入时,判断和先前的version是否相同,如果相同就更新并增加version,否则报错,程序可以根据错误,选择返回给用户,还是修改version后提交。

hibernate的做法是在object中加入一个version属性,version的赋值和判断都由框架管理,程序员不用自己操心。

yyanghhong
2003-07-26 01:48
在oracle中, 在第一个用户得到该记录后, 调用dbms_lock.request创建

一个lock, 完成任务后释放他, 其他用户检查这个lock, 如果存在就只能

查看, 不可修改.

dbms_lock的机制的优点是基于session的, 即使第一个用户的程序在中间

出错了, lock也会自动释放.

frank
2003-07-28 09:31
re guty:

optimitic transaction 是不是也不够保险,他只是把错误的概率降到最低,hibernate的version比较是取version的时候有没有用到for update ,这样可能会更好一点

bruce
2003-08-04 03:24
想纠正一下两位大腕的说法:

To guty:

<<robbin, 这个是个很典型的需要使用optimistic transaction的问题,一般做法也就是用version检查了。<<

应该是optimistic lock

To Robbin

<<如果我理解你的意思是这样的话,我只能告诉你这根本不关Hibernate的事,任何ORM都无能为力,是数据库的事情,你要保证你想要的情况,必须修改数据库的隔离级别。<<

应该是事务的隔离级别.

希望没有说错

猜你喜欢