`

J2EE事务并发控制策略总结

阅读更多
转自:http://yuquan-nana.iteye.com/admin/blogs/295639

本文结合Hibernate以及JPA标准,对J2EE当前持久层设计所遇到的几个问题进行总结:
    事务并发访问控制策略
    当前J2EE项目中,面临的一个共同问题就是如果控制事务的并发访问,虽然有些持久层框架已经为我们做了很多工作,但是理解原理,对于我们开发来说还是很有用处的。
    事务并发访问主要可以分为两类,分别是同一个系统事务和跨事务访问的并发访问控制,其中同一个系统事务可以采取乐观锁以及悲观锁策略,而跨多个系统事务时则需要乐观离线锁和悲观离线锁。在讨论这四种并发访问控制策略之前,先需要明确一下数据库事务隔离级别的问题,ANSI标准规定了四个数据库事务隔离级别,它们分别是:
    读取未提交(Read Uncommitted)
    这是最低的事务隔离级别,读事务不会阻塞读事务和写事务,写事务也不会阻塞读事务,但是会阻塞写事务。这样造成的一个结果就是当一个写事务没有提交的时候,读事务照样可以读取,那么造成了脏读的现象。
    读取已提交(Read Committed)
    采用此种隔离界别的时候,写事务就会阻塞读事务和写事务,但是读事务不会阻塞读事务和写事务,这样因为写事务会阻塞读取事务,那么从而读取事务就不能读到脏数据,但是因为读事务不会阻塞其它的事务,这样还是会造成不可重复读的问题。
    可重复读(Repeatable Read)
    采用此种隔离级别,读事务会阻塞写事务,但是读事务不会阻塞读事务,但是写事务会阻塞写事务和读事务。因为读事务阻塞了写事务,这样以来就不会造成不可重复读的问题,但是这样还是不能避免幻影读问题。
    序列化(serializable)
    此种隔离级别是最严格的隔离级别,如果设置成这个级别,那么就不会出现以上所有的问题(脏读,不可重复读,幻影读)。但是这样以来会极大的影响到我们系统的性能,因此我们应该避免设置成为这种隔离级别,相反的,我们应该采用较低的隔离界别,然后再采用并发控制策略来进行事务的并发访问控制)。
    其实我们也可以把事务隔离级别设置为serializable,这样就不需要采用并发控制策略了,数据库就会为我们做好一切并发控制,但是这样以来会严重影响我们系统的伸缩性和性能,所以在实践中,我们一般采用读取已提交或者更低的事务隔离级别,配合各种并发访问控制策略来达到并发事务控制的目的。下面总结一下常用的控制策略:
    1 乐观锁
    乐观锁是在同一个数据库事务中我们常采取的策略,因为它能使得我们的系统保持高的性能的情况下,提高很好的并发访问控制。乐观锁,顾名思义就是保持一种乐观的态度,我们认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。它的基本思想就是每次提交一个事务更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败,。
    最后我们需要明确一个问题,因为乐观锁其实并不会锁定任何记录,所以如果我们数据库的事务隔离级别设置为读取已提交或者更低的隔离界别,那么是不能避免不可重复读问题的(因为此时读事务不会阻塞其它事务),所以采用乐观锁的时候,系统应该要容许不可重复读问题的出现。
    了解了乐观锁的概念以后,那么当前我们系统中又是如何来使用这种策略的呢?一般可以采用以下三种方法:
    版本(Version)字段:在我们的实体中增加一个版本控制字段,每次事务更新后就将版本字段的值加1.
    时间戳(timestamps):采取这种策略后,当每次要提交更新的时候就会将系统当前时间和实体加载时的时间进行比较,如果不一致,那么就报告乐观锁失败,从而回滚事务或者重新尝试提交。采用时间戳有一些不足,比如在集群环境下,每个节点的时间同步也许会成问题,并且如果并发事务间隔时间小于当前平台最小的时钟单位,那么就会发生覆盖前一个事务结果的问题。因此一般采用版本字段比较好。
    基于所有属性进行检测:采用这种策略的时候,需要比较每个字段在读取以后有没有被修改过,所以这种策略实现起来比较麻烦,要求对每个属性都进行比较,如果采用hiernate的话,因为Hibernate在一级缓存中可以进行脏检测,那么可以判断哪些字段被修改过,从而动态的生成sql语句进行更新。
    下面再总结一下如何在JDBC和Hibernate中使用乐观锁:
   JDBC中使用乐观锁:如果我们采用JDBC来实现持久层的话,那么就可以采用以上将的三种支持乐观锁的策略,在实体中增加一个version字段或者一个Date字段,也可以采用基于所有属性的策略,下面就采用version字段来做一演示:
    假如系统中有一个Account的实体类,我们在Account中多加一个version字段,那么我们JDBC Sql语句将如下写:
Select a.version....from Account as a where (where condition..)
Update Account set version = version+1.....(another field) where version =?...(another contidition)
    这样以来我们就可以通过更新结果的行数来进行判断,如果更新结果的行数为0,那么说明实体从加载以来已经被其它事务更改了,所以就抛出自定义的乐观锁定异常(或者也可以采用Spring封装的异常体系)。具体实例如下:
Java代码
.......  
int rowsUpdated = statement.executeUpdate(sql);  
If(rowsUpdated= =0){  
throws new OptimisticLockingFailureException();  
}  
........ 

.......
int rowsUpdated = statement.executeUpdate(sql);
If(rowsUpdated= =0){
throws new OptimisticLockingFailureException();
}
........
    在使用JDBC API的情况下,我们需要在每个update语句中,都要进行版本字段的更新以及判断,因此如果稍不小心就会出现版本字段没有更新的问题,相反当前的 ORM框架却为我们做好了一切,我们仅仅需要做的就是在每个实体中都增加version或者是Date字段。
    Hibernate中使用乐观锁:如果我们采用Hibernate做为持久层的框架,那么实现乐观锁将变得非常容易,因为框架会帮我们生成相应的sql语句,不仅减少了开发人员的负担,而且不容易出错。下面同样采用version字段的方式来总结一下:
同样假如系统中有一个Account的实体类,我们在Account中多加一个version字段,
Java代码
public class Account{  
Long id ;  
.......  
@Version //也可以采用XML文件进行配置  
Int version  
.......  


public class Account{
Long id ;
.......
@Version //也可以采用XML文件进行配置
Int version
.......
}
    这样以来每次我们提交事务时,hibernate内部会生成相应的SQL语句将版本字段加1,并且进行相应的版本检测,如果检测到并发乐观锁定异常,那么就抛出StaleObjectStateException.
    2 悲观锁
    所谓悲观锁,顾名思义就是采用一种悲观的态度来对待事务并发问题,我们认为系统中的并发更新会非常频繁,并且事务失败了以后重来的开销很大,这样以来,我们就需要采用真正意义上的锁来进行实现。悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等以前的事务提交或者回滚解除锁。
    最后我们还是需要明确一个问题,假如我们数据库事务的隔离级别设置为读取已提交或者更低,那么通过悲观锁,我们控制了不可重复读的问题,但是不能避免幻影读的问题(因为要想避免我们就需要设置数据库隔离级别为Serializable,而一般情况下我们都会采取读取已提交或者更低隔离级别,并配合乐观或者悲观锁来实现并发控制,所以幻影读问题是不能避免的,如果想避免幻影读问题,那么你只能依靠数据库的serializable隔离级别(幸运的是幻影读问题一般情况下不严重)。
    下面就分别以JDBC和Hibernate来总结一下:
    JDBC中使用悲观锁:在JDBC中使用悲观锁,需要使用select for update语句,假如我们系统中有一个Account的类,我们可以采用如下的方式来进行:
Select * from Account where ...(where condition).. for update.
    当使用了for update语句后,每次在读取或者加载一条记录的时候,都会锁住被加载的记录,那么当其他事务如果要更新或者是加载此条记录就会因为不能获得锁而阻塞,这样就避免了不可重复读以及脏读的问题,但是其他事务还是可以插入和删除记录,这样也许同一个事务中的两次读取会得到不同的结果集,但是这不是悲观锁锁造成的问题,这是我们数据库隔离级别所造成的问题。
    最后还需要注意的一点就是每个冲突的事务中,我们必须使用select for update 语句来进行数据库的访问,如果一些事务没有使用select for update语句,那么就会很容易造成错误,这也是采用JDBC进行悲观控制的缺点。
    Hibernate中使用悲观锁:相比于JDBC使用悲观锁来说,在Hibernate中使用悲观锁将会容易很多,因为Hibernate有API让我们来调用,从而避免直接写SQL语句。下面就Hibernate使用悲观锁做一总结:
    首先先要明确一下Hibernate中支持悲观锁的两种模式LockMode.UPGRADE以LockMode.UPGRADE_NO_WAIT.(PS:在JPA中,对应的锁模式是LockModeType.Read,这与Hibernate是不一样的呵呵)
假如我们系统中有一个Account的类,那么具体的操作可以像这样:
Java代码
.......  
session.lock(account, LockMode.UPGRADE);  
......  
或者也可以采用如下方式来加载对象:  
session.get(Account.class,identity,LockMode.UPGRADE). 

.......
session.lock(account, LockMode.UPGRADE);
......
或者也可以采用如下方式来加载对象:
session.get(Account.class,identity,LockMode.UPGRADE).
    这样以来当加载对象时,hibernate内部会生成相应的select for update语句来加载对象,从而锁定对应的记录,避免其它事务并发更新。
    以上两种策略都是针对同一个事务而言的,如果我们要实现跨多个事务的并发控制就要采用其它两种并发控制策略了,下面做一总结:
    C++与java是两种完全不同风格的东西,C++是由程序员创造的,由程序员完善的,然后才出的标准的,也就是说C++的标准完全落后与C++的发展。java恰好相反,它是先有标准(可能还没有实现),然后后有的实现,而且它是由公司主导开发的,虽然现在开源了,但是标准并不是谁都能定的。这就造就了C++是百花齐放,博大精深,很少有人敢说自己C++很厉害。java却是另外的一种感觉,一切都规定好了,你只需要按照规定去做,符合标准才可以的。所以C++是那种既可以做的堂堂正正,博大精深(比如标准库),又可以实现的匪夷所思,天马行空(写 Boost库的人太牛了)。java不行,java要求如此只能如此,不能越雷池一步。

分享到:
评论

相关推荐

    学习J2EE事务并发控制策略总结.pdf

    综上所述,J2EE事务并发控制策略涉及到对数据库事务隔离级别的选择和使用乐观锁、悲观锁等机制来防止数据不一致性。理解这些概念并灵活应用是开发高效、稳定J2EE应用程序的关键。在实际开发中,应根据系统需求和预期...

    J2EE事务并发控制策略总结汇编.pdf

    本文主要围绕J2EE环境下的持久层设计,特别是针对事务并发访问控制进行总结。事务并发访问控制分为两类:同一系统事务内的并发控制和跨系统事务的并发控制。在讨论具体策略之前,首先需要理解数据库事务的四种隔离...

    J2EE控制策略

    J2EE控制策略主要关注的是在多用户并发环境中如何有效地管理事务和资源访问,以确保数据的一致性和完整性。在J2EE应用中,控制策略主要包括两类:同一系统事务内的并发访问控制和跨系统事务的并发访问控制。下面将...

    jee事务控制.pdf

    ### J2EE事务控制详解 #### 一、引言 在现代J2EE项目中,事务控制是一项核心技能。为了确保数据的一致性与可靠性,理解事务管理的基础知识至关重要。本文将结合Hibernate和JPA标准,深入探讨J2EE持久层设计中遇到...

    J2EE开发中常见的问题总结

    J2EE应用可能面临高并发、大数据量的挑战。开发者需要掌握内存管理、缓存策略、数据库查询优化、负载均衡和集群配置等技巧,以提高应用性能。 5. **JSP与Servlet交互**: JSP用于视图层,Servlet处理逻辑,两者...

    J2EE软件工程师全部培训课程总结

    - **事务与并发处理**:学习事务管理和并发控制策略。 - **映射关系**:深入理解Hibernate的高级映射概念,如一对多、多对多等关系映射。 #### 五、Servlets与JSP:Web应用开发基础 - **Servlets生命周期**:了解...

    J2EE性能优化

    **总结**:J2EE性能优化是一个系统性工程,涉及到多个层面,包括但不限于对象管理、并发控制、数据库优化、网络通信优化等。通过细致的分析、测试和调整,可以显著提高J2EE应用的性能,以满足高并发、高吞吐量和高...

    J2EE进销存管理系统

    开发J2EE进销存管理系统时,开发者需要关注以下关键点:数据一致性(通过事务处理保证)、并发控制(处理多用户同时操作同一数据的情况)、性能优化(例如,缓存策略、数据库索引设计等)和安全性(如数据加密、防止...

    民族古籍数字化保护系统中ORACLE数据库的并发控制研究.pdf

    综上所述,文章详细阐述了在民族古籍数字化保护系统中,如何利用ORACLE数据库的并发控制策略,尤其是封锁机制,来保障数据的完整性和一致性,为类似系统的开发提供了理论指导和技术参考。同时,文章也提醒开发者注意...

    J2EE架构下数据库

    对于数据库的访问,代码层面的优化还包括合理的事务处理,比如合理的事务大小和并发控制,以确保系统的稳定性和数据的一致性。 综上所述,在J2EE架构下,数据库访问的性能优化是一个系统工程,涉及数据库设计、连接...

    J2EE 经典实例详解

    9. **性能优化**:J2EE应用往往需要处理高并发和大数据量。实例将展示如何通过调整配置、缓存策略和负载均衡来提升应用性能。 10. **安全性**:J2EE提供了多种安全机制,如角色基的访问控制(RBAC)、SSL/TLS加密和...

    J2ee个人博客系统

    - **JTA**:Java Transaction API,提供事务管理功能,保证数据的一致性。 3. **数据库设计** - **数据表设计**:通常包含用户表(User)、文章表(Post)、评论表(Comment)等,涉及到用户注册、登录、文章发布...

    J2EE 新闻发布系统

    一个完整的J2EE新闻发布系统还需要考虑到用户体验、性能优化、并发处理、缓存策略、搜索引擎优化(SEO)等多个方面。开发者需要根据具体需求进行设计,实现一个高效、稳定且易于维护的新闻信息发布平台。

    J2EE的好书收集(英文版)

    10. **最佳实践**:书籍通常会总结开发J2EE应用的最佳实践,如代码组织、测试策略和持续集成,以提升团队效率和软件质量。 通过阅读这些书籍,开发者可以全面掌握J2EE平台的各个方面,并具备构建高效、可扩展的企业...

    J2EE进销存系统课设

    可以通过缓存策略、数据库索引优化、负载均衡和集群等方式提高系统的响应速度和并发处理能力。 10. **维护与升级**:进销存系统需要随着业务发展不断迭代和升级。良好的模块化设计和版本控制有助于系统的持续维护和...

    j2ee新闻发布系统

    1. **MVC设计模式**:模型-视图-控制器(Model-View-Controller)设计模式是J2EE应用开发中的常用架构。在这个新闻系统中,模型负责处理数据逻辑,视图负责展示数据,而控制器则协调模型和视图之间的交互,实现业务...

    J2EE课件 j2ee课件

    总结,J2EE是一个强大而全面的企业级开发平台,其核心在于提供了一套完整的组件和服务来构建分布式系统。随着技术的演进,J2EE的理念和组件仍在现代Java开发中发挥着重要作用,尽管其具体实现可能已经发生了变化。...

Global site tag (gtag.js) - Google Analytics