`
choelea
  • 浏览: 74470 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

乐观锁与悲观锁-结合hibernate的简介

阅读更多

Web应用中并发控制的实现

业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在 金融 系统的日终结算处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,就是所谓的“锁”,即给选定的目标数据上锁,使其无法被其他程序修改。有两种锁机制:即通常所说的“乐观锁(Optimistic Locking)” 和“悲观锁(Pessimistic Locking)”。

1.乐观锁(Optimistic Locking)

乐观锁(optimistic locking)则乐观的认为资料的存取很少发生同时存取的问题,因而不作数据库层次上的锁定,为了维护正确的数据,乐观锁定使用应用程序上的逻辑实现版本控制来解决。实际上乐观锁并不会锁定任何记录。

并发控制时,数据不一致的情况一旦发生,有几个解决的 方法 ,一种是先更新为主,一种是后更新的为主,比较复杂的就是检查发生变动的数据来实现,或是检查所有属性来实现乐观锁定。

Hibernate通过版本号检查来实现后更新为主,这也是Hibernate所推荐的方式,在数据库中加入一个VERSON栏记录,在读取数据时连同版本号一同读取,并在更新数据时递增版本号,然后比对版本号与数据库中的版本号,如果大于数据库中的版本号则予以更新,否则就回报错误。

以Hibernate实现版本号控制锁定的话,我们的对象中增加一个version属性,例如:

public class MyAccount {
private int version;
....
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
....
}

而在映像文件中,我们使用optimistic-lock属性设定version控制,属性栏之后增加一个标签,例如:

 

optimistic-lock="version"

设定好版本控制之后,在上例中如果B客户试图更新数据,将会引发StableObjectStateException例外,我们可以捕捉这个例外,在处理中重新读取数据库中的数据,同时将B客户目前的数据与数据库中的数据读出来,让B客户有机会比对不一致的数据,以决定要变更的部份,或者您可以设计程式自动读取新的资料,并重复扣款业务流程,直到数据可以更新为止,这一切可以在后台执行,而不用让您的客户知道。在其它架构中也可通过这种思路来实现乐观锁,但版本控制和冲突的检测要在自己程序的程序中实现和维护。

Comment:(几种实现策略)

       版本(Version)字段:在我们的实体中增加一个版本控制字段,每次事务更新后就将版本字段的值加1. 
       时间戳(timestamps):采取这种策略后,每次程序修改数据,时间戳也会更新。当每次要提交更新的时候就会将当前时间(数据库中时间)和实体加载时的时间进行比较,如果不一致,那么就报告乐观锁失败,从而回滚事务或者重新尝试提交。采用时间戳有一些不足,比如在集群环境下,每个节点的时间同步也许会成问题,并且如果并发事务间隔时间小于当前平台最小的时钟单位,那么就会发生覆盖前一个事务结果的问题。因此一般采用版本字段比较好。  
       基于所有属性进行检测:采用这种策略的时候,需要比较每个字段在读取以后有没有被修改过,所以这种策略实现起来比较麻烦,要求对每个属性都进行比较,如果采用hiernate的话,因为Hibernate在一级缓存中可以进行脏检测,那么可以判断哪些字段被修改过,从而动态的生成sql语句进行更新。

 

2.悲观锁(Pessimistic Locking)

虽然乐观锁能够提高系统的性能,但它是对发生冲突的访问进行事后的补救,应用在用户输入数据量很少的场合比较适合,但如果在 企业 ERP,用户与系统交互涉及大量数据在页面表单上录入,如果事后提交失败后才提示用户要重新录入是很不现实的,所以有必要进行事前控制,这就要采用悲观锁。

在多个客户端可能读取同一笔数据或同时更新一笔数据的情况下,防止同一个数据被修改而造成混乱,最简单的手段就是在读取时对数据进行锁定,其它客户端不能对同一笔数据进行更新的读取动作。

悲观锁定(Pessimistic Locking)一如其名称所示,悲观的认定每次资料存取时,其它的客户端也会存取同一笔数据,因此对该笔数据进行事先锁定,直到自己操作完成后解除锁定。

悲观锁定通常透过系统或数据库本身的功能来实现,依赖系统或数据库本身提供的锁定机制,Hibernate即是如此,我们可以利用Query或Criteria的setLockMode()方法来设定要锁定的表或列(row)及其锁定模式,锁定模式有以下的几个:

LockMode.WRITE:在insert或update时进行锁定,Hibernate会在save()方法时自动获得锁定。

LockMode.UPGRADE:利用SELECT … FOR UPDATE进行锁定。

LockMode.UPGRADE_NOWAIT:利用SELECT … FOR UPDATE NOWAIT进行锁定,在Oracle环境下使用。

LockMode.READ:在读取记录时Hibernate会自动获得锁定。

LockMode.NONE:没有锁定。

也可以在使用Session的load()或是lock()时指定锁定模式以进行锁定。

如果数据库不支持所指定的锁定模式,Hibernate会选择一个合适的锁定替换,而不是丢出一个例外。

3.其它构架中悲观锁的实现

Hibernate的悲观锁,也是基于数据库的锁机制实现。下面的代码实现了对“用户”查询记录的加锁:

 

String sqlStr = "from userInfo as user where user.userId=’admin’";
Query query = session.createQuery(sqlStr);
query.setLockMode("user",LockMode.UPGRADE); //加锁
List userList = query.list();//执行查询,获取数据

query.setLockMode对查询语句中,特定别名所对应的记录进行加锁(我们为userInfo类指定了一个别名“user”),这里也就是对返回的所有user记录进行加锁:

 

select tuser0_.id as id, tuser0_.userId as userId, tuser0_.group_id as group_id, 
tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where 
(tuser0_.userId =’admin’ ) for update

通过上述转换后的sql语句可知,Hibernate的加锁其实是利用了数据库的for update语句,在读取阶段对某条记录的锁定,而在更新阶段提交,释放锁。

其实其它架构也可以采取该思路,不过,数据库的for update语句的锁定和释放一定要在数据的同一个连接中,如果读取阶段和更新阶段不是统一连接,即读取之后断开了与数据库的连接,则for update语句的锁定立即失效,为此,如果其它架构中要采取这种方式则要做相应的调整。

首先,由于Web应用是无状态的,也就是说数据库的for update语句的锁定和释放不一定是数据的同一个连接,为此,采用痕迹跟踪法,在读取数据时生成唯一的序列号(serialId),建立与数据连接的映射,并放置一个map数据结构中;在更新时,通过该serialId在连接池中重新获取该连接,用该连接去更新数据。

如果系统是采用dao读取数据,实体bean去更新数据,则只要在更新数据之前断开读取数据时的连接,则可以通过其它途径更新数据,如下代码所示:

 

public void update (AbstractEntityData data, String[] selTeamName ,String serialId) 
throws Exception {
dao.closeConnect(serialId);
bo.update(data);
}

其中,dao.closeConnect(serialId)是断开数据连接,bo.update(data)是通过EJB更新数据库

4.序列号(serialId)的创建和维护

由于不同用户可能同时建立连接或同一用户先后建立连接,故创建序列号可以在读取数据时通过sessionId和时间戳组合而成。而在操作的过程中,为了保持序列号不会丢失和唯一性,它不能放在session或application中,而是放在页面的request对象里,通过它向其它页面传递。

5.关联表的锁定

其实,Hibernate的悲观锁方式只能对单个表的记录进行锁定,但现实中,存在关联更新的情况,即在更新主表的时候有可能会更新到与之相关的子表,与此同时,其它用户也可能通过其它主表更新相应的子表同一条记录。

有两种方式处理,一是在读取数据通过sql语句关联子表相应记录,因为for update对所有关联表中符合条件的记录都会加锁;二是为子表找一个入口表,在更新子表的同时,必须更新子表的入口表。

6.例外操作的处理

采用这种方式,有一些例外情况必须小心处理,一是页面的关闭,如果调用相应的方法,如onbeforeunload()等,释放对应的数据库连接;二是用户非正常关机退出系统,必须有数据库周期清除无用的连接,如间隔二十分钟等,来释放读取时对数据的锁定,否则,该数据会长时间被锁定,直至应用服务器重启。

结论

软件系统的并发控制一般是通过加锁来实现,同样,Web应用也是采用乐观锁和悲观锁来实现,乐观锁是一种事后补救措施,是通过程序的逻辑控制版本来实现的,而悲观锁是事前的一种预防措施,它利用数据库的锁机制来实现,Hibernate对它做了一层封装,使应用更加方便,为了让其它架构都能适用,本文还原了Hibernate的实现原理,提出一般的实现思路和注意实现。

Comment:对于一般的系统而言,当一某一特定用户登录后。 其数据往往有类似UserID这些主键来关联,所以和其他用户的数据冲突不大。 因此事后补救的情况并不多, 这种情况下乐观锁就有了更好的performance。

分享到:
评论

相关推荐

    Hibernate悲观锁和乐观锁的实现

    在进行Hibernate的测试时,可以创建一个名为`hibernate_test`的项目,编写对应的实体类、映射文件以及测试用例,模拟并发场景,来深入理解并对比悲观锁和乐观锁的差异和效果。 总之,理解并合理运用悲观锁和乐观锁...

    Hibernate悲观锁与乐观锁案例

    在Java的持久化框架Hibernate中,悲观锁和乐观锁是两种重要的并发控制策略,它们用于管理数据库中的数据在多线程环境下的访问安全。本文将深入探讨这两种锁机制的原理、应用场景及其区别。 首先,我们来理解悲观锁...

    Hibernate 乐观和悲观锁

    6. **文件关联**:压缩包中的文件名称列表看似与主题“Hibernate 乐观和悲观锁”不直接相关,"走出软件作坊:三五个人十来条枪 如何成为开发正规军.chm、走出软件作坊.doc"可能是一些关于软件开发团队建设和成长的...

    Hibernate乐观锁

    `hibernate-mapping`配置文件中,`<version>`元素定义了这个版本字段,它的`optimistic-lock`属性默认设置为`version`,表示使用该字段进行乐观锁控制。 除了`version`字段,代码还展示了其他属性如`fullname`、`...

    数据库事务、hibernate悲观锁和乐观锁

    在《hibernate_3200_Concurrency_Pessimistic_Lock》这个压缩包中,很可能包含了关于Hibernate框架如何实现悲观锁和乐观锁的详细资料,包括源代码示例、配置方法和使用场景。通过学习这些资料,你可以深入了解这两种...

    Hibernate性能(缓存 延迟加载 事务 悲观 乐观锁).ppt

    - **乐观锁**:在读取时不加锁,假设不会有并发冲突,只有在更新数据时检查是否被其他事务修改过。通常通过版本号或时间戳实现,如果冲突则事务失败。乐观锁适用于读多写少的情况,减少锁的使用,提高并发性能。 在...

    hibernate锁实验,以及解决方案

    1. **悲观锁与乐观锁** - 悲观锁:在读取数据时就假设会发生并发冲突,因此在读取时会立即加锁,防止其他事务修改数据。在Hibernate中,悲观锁主要通过`LockMode.UPGRADE`实现,即使用`SELECT ... FOR UPDATE`语句...

    HIbernate与oracle数据库应用例子

    - 乐观锁/悲观锁:处理并发控制,防止数据冲突。 通过上述步骤,开发者可以在Java应用中高效地使用Hibernate与Oracle数据库进行数据操作。实践过程中,应不断优化和调整,以适应不同场景的需求。在实际项目中,还...

    hibernate5--2.数据持久化及事务

    Hibernate通过`@Lock`注解配合`LockModeType`实现悲观锁。 **5. 事务的回滚规则** - 任何未捕获的`HibernateException`或`JDBCException`都会导致事务自动回滚。 - 使用`Session.flush()`显式触发脏检查,如果发现...

    数据库与Hibernate教案

    - 数据库事务和并发控制:理解ACID属性,以及乐观锁和悲观锁的概念。 2. **Hibernate ORM框架**: - Hibernate架构:介绍Session、SessionFactory和Transaction的概念,理解它们在ORM中的角色。 - 映射配置:...

    Hibernate开发指南

    - 将Hibernate与Spring框架结合使用,实现更加灵活的开发模式。 - **要点**: - **配置DataSource**。 - **创建SessionFactory**。 - **定义TransactionManager**。 #### 结论 本指南全面介绍了Hibernate的基础...

    hibernate中文参考文档

    - **乐观锁与悲观锁**: 提供了两种不同级别的并发控制机制,以应对多用户环境下的数据冲突问题。 #### 八、错误处理与调试 - **异常处理**: Hibernate提供了多种异常类型,用于指示不同的错误情况,如`...

    最新hibernate 4.1.8.Final版本(2012年11-01最新发布)

    - **并发控制策略**:提供了更灵活的并发控制策略,如.optimistic-locking(乐观锁)和.pessimistic-locking(悲观锁),可以根据应用需求进行选择。 4. **连接池集成** - Hibernate支持多种连接池实现,如C3P0、...

    Hibernate教程26_事务并发处理

    在实际项目中,需要根据业务需求和并发量来灵活选择悲观锁或乐观锁,或者结合两者以达到最佳效果。同时,还要注意合理配置事务隔离级别,如读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读...

    hibernate学习笔记,学习大纲【吐血推荐】

    - **乐观锁与悲观锁**:控制并发更新,防止数据不一致。 ### 8. HQL与Criteria查询 - **HQL**:Hibernate Query Language,面向对象的查询语言,类似于SQL。 - **Criteria查询**:更面向对象的查询方式,可动态...

    Java.Persistence.with.Hibernate.pdf

    - **并发控制**:采用乐观锁或悲观锁等机制来处理并发访问问题。 #### 高级特性 除了基本的持久化功能外,Hibernate还提供了一些高级特性,如继承映射、多租户支持和跨库数据同步等,以满足更复杂的应用需求。 - ...

    Hibernate中,利用版本管理机制来控制事务并发

    1. 乐观锁与悲观锁:乐观锁假设冲突很少发生,只在提交时检查冲突;而悲观锁则在读取数据时就加锁,防止其他事务修改。Hibernate的版本管理机制采用的是乐观锁。 2. 事务隔离级别:Hibernate的版本管理机制与数据库...

    Hibernate 开发学习指导书

    - **乐观锁** (Optimistic Locking): 通过版本号等方式检测数据是否被其他事务修改,从而决定是否执行更新操作。 #### 十一、Hibernate分页 - 实现了分页查询的功能,可以通过设置查询参数来获取指定页的数据。 ##...

Global site tag (gtag.js) - Google Analytics