Hibernate温习(11)--多事务并发访问控制
?? 在并发环境,一个数据库系统会同时为各种各样的客户程序提供服务,也就是说,在同一时刻,会有多个客户程序同时访问数据库系统,这多个客户程序中的失误访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种各样的并发问题的发生,这些并发问题可归纳为以下几类
??多个事务并发引起的问题:
1)第一类丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。
2)脏读:一个事务读到另一个事务未提交的更新数据。
3) 幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
4)不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
5)第二类丢失更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据。
?事务隔离级别
为了解决多个事务并发会引发的问题。数据库系统提供了四种事务隔离级别供用户选择。
1) Serializable:串行化。隔离级别最高
2) Repeatable Read:可重复读。--MySQL默认是这个
3) Read Committed:读已提交数据。--Oracle默认是这个
4) Read Uncommitted:读未提交数据。隔离级别最差。--sql server默认是这个
数据库系统采用不同的锁类型来实现以上四种隔离级别,具体的实现过程对用户是透明的。用户应该关心的是如何选择合适的隔离级别。
对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读,而且具有较好的并发性能。
每个数据库连接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别。JDBC数据库连接使用数据库系统默认的隔离级别。在Hibernate的配置文件中可以显示地设置隔离级别。每一种隔离级别对应着一个正整数。
Read Uncommitted: 1
Read Committed: 2
Repeatable Read: 4
Serializable: 8
在hibernate.cfg.xml中设置隔离级别如下:
?? ?<session-factory>
<!-- 设置JDBC的隔离级别 -->
<property name="hibernate.connection.isolation">2</property>
</session-factory>
设置之后,在开始一个事务之前,Hibernate将为从连接池中获得的JDBC连接设置级别。需要注意的是,在受管理环境中,如果Hibernate使用的数据库连接来自于应用服务器提供的数据源,Hibernate不会改变这些连接的事务隔离级别。在这种情况下,应该通过修改应用服务器的数据源配置来修改隔离级别。
并发控制
当数据库系统采用Red Committed隔离级别时,会导致不可重复读和第二类丢失更新的并发问题,在可能出现这种问题的场合。可以在应用程序中采用悲观锁或乐观锁来避免这类问题。
?? ? 乐观锁(Optimistic Locking):
乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此不作数据库层次上的锁定。为了维护正确的数据,乐观锁使用应用程序上的版本控制(由程序逻辑来实现的)来避免可能出现的并发问题。
唯一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、 或者时间戳来检测更新冲突(并且防止更新丢失)。
三种方式。
1)Version版本号
2)时间戳
3)自动版本控制。
这里不建议在新的应用程序中定义没有版本或者时间戳列的版本控制:它更慢,更复杂,如果你正在使用脱管对象,它则不会生效。
通过在表中及POJO中增加一个version字段来表示记录的版本,来达到多用户同时更改一条数据的冲突
数据库脚本:?
?Hibernate.cfg.xml
?
测试代码
可以看到,第二个“用户”session2修改数据时候,记录的版本号已经被session1更新过了,所以抛出了红色的异常,我们可以在实际应用中处理这个异常,例如在处理中重新读取数据库中的数据,同时将目前的数据与数据库中的数据展示出来,让使用者有机会比较一下,或者设计程序自动读取新的数据
注意:
?? ?要注意的是,由于乐观锁定是使用系统中的程式来控制,而不是使用资料库中的锁定机制,因而如果有人特意自行更新版本讯息来越过检查,则锁定机制就会无效,例如在上例中自行更改stu的version属性,使之与资料库中的版本号相同的话就不会有错误,像这样版本号被更改,或是由于资料是由外部系统而来,因而版本资讯不受控制时,锁定机制将会有问题,设计时必须注意。
?? ?如果手工设置stu.setVersion()自行更新版本以跳过检查,则这种乐观锁就会失效,应对方法可以将Student.java的setVersion设置成private
?
如果是注解方式的,POJO应为这样
?
?也就是说,后面那个用户就一直在等待,.只要第一个用户没有提交.他就无法继续运行....这就是悲观锁...
悲观锁的缺点显而易见..他是彻底的占用了这个资源....所以,我们一般需要用这个来解决短事务,也就是周期比较短的事务..否则,第一个用户如果一直不操作,后面任何用户都无法进行...
经过测试,还有一个结论就是:使用悲观锁,session的load方法的延迟加载机制失效
总结:尽管悲观锁能够方式丢失更新和不可重复读之类并发问题的发生的,但是它影响并发性能。因此不建议使用悲观锁,尽量使用乐观锁
?