`
txf2004
  • 浏览: 6999364 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Hibernate与数据库锁

 
阅读更多

一、为什么要使用锁?

要想弄清楚锁机制存在的原因,首先要了解事务的概念。
事务是对数据库一系列相关的操作,它必须具备ACID特征:

A(原子性):要么全部成功,要么全部撤销。
C(一致性):要保持数据库的一致性。
I(隔离性):不同事务操作相同数据时,要有各自的数据空间。
D(持久性):一旦事务成功结束,它对数据库所做的更新必须永久保持。

我们常用的关系型数据库RDBMS实现了事务的这些特性。其中,原子性、
一致性和持久性都是采用日志来保证的。而隔离性就是由今天我们关注的
锁机制来实现的,这就是为什么我们需要锁机制。

如果没有锁,对隔离性不加控制,可能会造成哪些后果呢?

1.更新丢失:事务1提交的数据被事务2覆盖。
2.脏读:事务2查询到了事务1未提交的数据。
3.虚读:事务2查询到了事务1提交的新建数据。
4.不可重复读:事务2查询到了事务1提交的更新数据。

下面来看Hibernate的例子,两个线程分别开启两个事务操作tb_account表中
的同一行数据col_id=1。
package com.cdai.orm.hibernate.annotation;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "tb_account")
public class Account implements Serializable {

	private static final long serialVersionUID = 5018821760412231859L;

	@Id
	@Column(name = "col_id")
	private long id;
	
	@Column(name = "col_balance")
	private long balance;

	public Account() {
	}
	
	public Account(long id, long balance) {
		this.id = id;
		this.balance = balance;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public long getBalance() {
		return balance;
	}

	public void setBalance(long balance) {
		this.balance = balance;
	}

	@Override
	public String toString() {
		return "Account [id=" + id + ", balance=" + balance + "]";
	}
	
}

package com.cdai.orm.hibernate.transaction;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;

import com.cdai.orm.hibernate.annotation.Account;

public class DirtyRead {

	public static void main(String[] args) {

		final SessionFactory sessionFactory = new AnnotationConfiguration().
				addFile("hibernate/hibernate.cfg.xml").				
				configure().
				addPackage("com.cdai.orm.hibernate.annotation").
				addAnnotatedClass(Account.class).
				buildSessionFactory();
		
		Thread t1 = new Thread() {
			
			@Override
			public void run() {
				Session session1 = sessionFactory.openSession();
				Transaction tx1 = null;
				try {
					tx1 = session1.beginTransaction();
					System.out.println("T1 - Begin trasaction");
					Thread.sleep(500);
					
					Account account = (Account) 
							session1.get(Account.class, new Long(1));
					System.out.println("T1 - balance=" + account.getBalance());
					Thread.sleep(500);
					
					account.setBalance(account.getBalance() + 100);
					System.out.println("T1 - Change balance:" + account.getBalance());
					
					tx1.commit();
					System.out.println("T1 - Commit transaction");
					Thread.sleep(500);
				}
				catch (Exception e) {
					e.printStackTrace();
					if (tx1 != null)
						tx1.rollback();
				} 
				finally {
					session1.close();
				}
			}
			
		};
		
		// 3.Run transaction 2
		Thread t2 = new Thread() {
			
			@Override
			public void run() {
				Session session2 = sessionFactory.openSession();
				Transaction tx2 = null;
				try {
					tx2 = session2.beginTransaction();
					System.out.println("T2 - Begin trasaction");
					Thread.sleep(500);
					
					Account account = (Account) 
							session2.get(Account.class, new Long(1));
					System.out.println("T2 - balance=" + account.getBalance());
					Thread.sleep(500);
					
					account.setBalance(account.getBalance() - 100);
					System.out.println("T2 - Change balance:" + account.getBalance());
					
					tx2.commit();
					System.out.println("T2 - Commit transaction");
					Thread.sleep(500);
				}
				catch (Exception e) {
					e.printStackTrace();
					if (tx2 != null)
						tx2.rollback();
				} 
				finally {
					session2.close();
				}
			}
			
		};
		
		t1.start();
		t2.start();
		
		while (t1.isAlive() || t2.isAlive()) {
			try {
				Thread.sleep(2000L);
			} catch (InterruptedException e) {
			}
		}
		
		System.out.println("Both T1 and T2 are dead.");
		sessionFactory.close();
		
	}

}
事务1将col_balance减小100,而事务2将其减少100,最终结果可能是0,也
可能是200,事务1或2的更新可能会丢失。log输出也印证了这一点,事务1和2
的log交叉打印。

T1 - Begin trasaction
T2 - Begin trasaction
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=?
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ where account0_.col_id=?
T1 - balance=100
T2 - balance=100
T2 - Change balance:0
T1 - Change balance:200
Hibernate: update tb_account set col_balance=? where col_id=?
Hibernate: update tb_account set col_balance=? where col_id=?
T1 - Commit transaction
T2 - Commit transaction
Both T1 and T2 are dead.



由此可见,隔离性是一个需要慎重考虑的问题,理解锁很有必要。


二、有多少种锁?

常见的有共享锁、更新锁和独占锁。

1.共享锁:用于读数据操作,允许其他事务同时读取。当事务执行select语句时,
数据库自动为事务分配一把共享锁来锁定读取的数据。
2.独占锁:用于修改数据,其他事务不能读取也不能修改。当事务执行insert、
update和delete时,数据库会自动分配。
3.更新锁:用于避免更新操作时共享锁造成的死锁,比如事务1和2同时持有
共享锁并等待获得独占锁。当执行update时,事务先获得更新锁,然后将
更新锁升级成独占锁,这样就避免了死锁。

此外,这些锁都可以施加到数据库中不同的对象上,即这些锁可以有不同的粒度。
如数据库级锁、表级锁、页面级锁、键级锁和行级锁。

所以锁是有很多种的,这么多锁要想完全掌握灵活使用太难了,我们又不是DBA。
怎么办?还好,锁机制对于我们一般用户来说是透明的,数据库会自动添加合适的
锁,并在适当的时机自动升级、降级各种锁,真是太周到了!我们只需要做的就是
学会根据不同的业务需求,设置好隔离级别就可以了。


三、怎样设置隔离级别?

一般来说,数据库系统会提供四种事务隔离级别供用户选择:

1.Serializable(串行化):当两个事务同时操纵相同数据时,事务2只能停下来等。

2.Repeatable Read(可重复读):事务1能看到事务2新插入的数据,不能看到对
已有数据的更新。

3.Read Commited(读已提交数据):事务1能看到事务2新插入和更新的数据。

4.Read Uncommited(读未提交数据):事务1能看到事务2没有提交的插入和更新
数据。


四、应用程序中的锁

当数据库采用Read Commited隔离级别时,可以在应用程序中采用悲观锁或乐观锁。

1.悲观锁:假定当前事务操作的数据肯定还会有其他事务访问,因此悲观地在应用
程序中显式指定采用独占锁来锁定数据资源。在MySQL、Oracle中支持以下形式:

select ... for update

显式地让select采用独占锁锁定查询的记录,其他事务要查询、更新或删除这些被
锁定的数据,都要等到该事务结束后才行。

在Hibernate中,可以在load时传入LockMode.UPGRADE来采用悲观锁。修改前面的例子,
在事务1和2的get方法调用处,多传入一个LockMode参数。从log中可以看出,事务1和2
不再是交叉运行,事务2等待事务1结束后才可以读取数据,所以最终col_balance值是正确
的100。
package com.cdai.orm.hibernate.transaction;

import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.cdai.orm.hibernate.annotation.Account;
import com.cdai.orm.hibernate.annotation.AnnotationHibernate;

public class UpgradeLock {

	@SuppressWarnings("deprecation")
	public static void main(String[] args) {

		final SessionFactory sessionFactory = AnnotationHibernate.createSessionFactory(); 

		// Run transaction 1
		Thread t1 = new Thread() {
			
			@Override
			public void run() {
				Session session1 = sessionFactory.openSession();
				Transaction tx1 = null;
				try {
					tx1 = session1.beginTransaction();
					System.out.println("T1 - Begin trasaction");
					Thread.sleep(500);
					
					Account account = (Account) 
							session1.get(Account.class, new Long(1), LockMode.UPGRADE);
					System.out.println("T1 - balance=" + account.getBalance());
					Thread.sleep(500);
					
					account.setBalance(account.getBalance() + 100);
					System.out.println("T1 - Change balance:" + account.getBalance());
					
					tx1.commit();
					System.out.println("T1 - Commit transaction");
					Thread.sleep(500);
				}
				catch (Exception e) {
					e.printStackTrace();
					if (tx1 != null)
						tx1.rollback();
				} 
				finally {
					session1.close();
				}
			}
			
		};
		
		// Run transaction 2
		Thread t2 = new Thread() {
			
			@Override
			public void run() {
				Session session2 = sessionFactory.openSession();
				Transaction tx2 = null;
				try {
					tx2 = session2.beginTransaction();
					System.out.println("T2 - Begin trasaction");
					Thread.sleep(500);
					
					Account account = (Account) 
							session2.get(Account.class, new Long(1), LockMode.UPGRADE);
					System.out.println("T2 - balance=" + account.getBalance());
					Thread.sleep(500);
					
					account.setBalance(account.getBalance() - 100);
					System.out.println("T2 - Change balance:" + account.getBalance());
					
					tx2.commit();
					System.out.println("T2 - Commit transaction");
					Thread.sleep(500);
				}
				catch (Exception e) {
					e.printStackTrace();
					if (tx2 != null)
						tx2.rollback();
				} 
				finally {
					session2.close();
				}
			}
			
		};
		
		t1.start();
		t2.start();
		
		while (t1.isAlive() || t2.isAlive()) {
			try {
				Thread.sleep(2000L);
			} catch (InterruptedException e) {
			}
		}
		
		System.out.println("Both T1 and T2 are dead.");
		sessionFactory.close();

	}

}
T1 - Begin trasaction
T2 - Begin trasaction
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?
Hibernate: select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?
T2 - balance=100
T2 - Change balance:0
Hibernate: update tb_account set col_balance=? where col_id=?
T2 - Commit transaction
T1 - balance=0
T1 - Change balance:100
Hibernate: update tb_account set col_balance=? where col_id=?
T1 - Commit transaction
Both T1 and T2 are dead.

Hibernate对于SQLServer 2005会执行SQL:

select account0_.col_id as col1_0_0_, account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock, rowlock) where account0_.col_id=?

为选定的col_id为1的数据行加上行锁和更新锁。

2.乐观锁:假定当前事务操作的数据不会有其他事务同时访问,因此完全依靠数据库
的隔离级别来自动管理锁的工作。在应用程序中采用版本控制来避免可能低概率出现
的并发问题。

在Hibernate中,使用Version注解来定义版本号字段。




将DirtyLock中的Account对象替换成AccountVersion,其他代码不变,执行出现异常。
package com.cdai.orm.hibernate.transaction;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
@Table(name = "tb_account_version")
public class AccountVersion {

	@Id
	@Column(name = "col_id")
	private long id;
	
	@Column(name = "col_balance")
	private long balance;
	
	@Version
	@Column(name = "col_version")
	private int version;

	public AccountVersion() {
	}

	public AccountVersion(long id, long balance) {
		this.id = id;
		this.balance = balance;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public long getBalance() {
		return balance;
	}

	public void setBalance(long balance) {
		this.balance = balance;
	}

	public int getVersion() {
		return version;
	}

	public void setVersion(int version) {
		this.version = version;
	}
	
}
log如下:

T1 - Begin trasaction
T2 - Begin trasaction
Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=?
Hibernate: select accountver0_.col_id as col1_0_0_, accountver0_.col_balance as col2_0_0_, accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id=?
T1 - balance=1000
T2 - balance=1000
T1 - Change balance:900
T2 - Change balance:1100
Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=?
Hibernate: update tb_account_version set col_balance=?, col_version=? where col_id=? and col_version=?
T1 - Commit transaction
2264 [Thread-2] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.cdai.orm.hibernate.transaction.AccountVersion#1]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1934)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2578)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2478)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2805)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:114)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:260)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:180)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1206)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:375)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at com.cdai.orm.hibernate.transaction.VersionLock$2.run(VersionLock.java:93)
Both T1 and T2 are dead.

由于乐观锁完全将事务隔离交给数据库来控制,所以事务1和2交叉运行了,事务1提交
成功并将col_version改为1,然而事务2提交时已经找不到col_version为0的数据了,所以
抛出了异常。




分享到:
评论

相关推荐

    Hibernate悲观锁和乐观锁的实现

    在Hibernate源码中,乐观锁的实现主要依赖于`AbstractEntityPersister`类的`checkOptimisticLocking()`方法,它会比较当前对象的版本信息与数据库中的版本信息,如果不同则抛出`StaleObjectStateException`异常。...

    Hibernate的乐观锁与悲观锁

    ### Hibernate的乐观锁与悲观锁 #### 一、引言 在并发环境下,尤其是在金融、电商等业务场景中,确保数据的一致性和完整性至关重要。**Hibernate**作为一种流行的Java持久层框架,提供了多种机制来处理并发控制...

    Java的Hibernate框架数据库操作中锁的使用和查询类型

    在Java的Hibernate框架中,数据库操作的锁和查询类型对于保证数据一致性至关重要。Hibernate作为SSH三大Web开发框架之一,提供了一种便捷的方式来处理数据库交互。本文将深入探讨Hibernate中的锁机制以及不同类型的...

    HIbernate与oracle数据库应用例子

    在IT行业中,Hibernate是一个强大的Java持久化框架,它简化了Java应用程序与数据库之间的交互。而Oracle则是一款全球广泛使用的大型关系型数据库管理系统。本篇将深入探讨如何在实际项目中结合Hibernate与Oracle...

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

    在Hibernate中,可以使用`@Version`注解来实现乐观锁,该注解会在实体类的一个属性上添加版本字段,每次更新时,Hibernate会比较当前版本号和数据库中的版本号,如果不同,则认为有并发冲突,更新失败。乐观锁的优点...

    hibernate的乐观锁和悲观锁

    ### Hibernate的乐观锁和悲观锁 #### 一、引言 在软件开发中,尤其是在涉及大量并发操作的应用场景下,确保数据的一致性和完整性是非常重要的。对于基于Java Web的应用而言,Hibernate作为一款流行的ORM框架,提供...

    Hibernate乐观锁和悲观锁分析

    【Hibernate乐观锁与悲观锁详解】 在开发过程中,尤其是在并发环境下,确保数据的一致性和完整性至关重要。Hibernate,作为Java领域广泛使用的ORM框架,提供了一种处理并发数据访问冲突的手段,那就是锁机制。主要...

    Hibernate注释方法描述数据库映射

    Hibernate 是一款强大的对象关系映射(ORM)框架,它简化了 Java 开发者与数据库交互的过程。在 Hibernate 中,可以利用注解来描述实体类与数据库表之间的映射关系。这种基于注解的方式大大减少了配置的工作量,并且...

    改AHibernate 实现数据库 自动新增表参数

    Hibernate允许开发者用面向对象的方式来处理数据库操作,通过XML或注解定义对象与数据库表的映射关系。当实体类发生变化时,传统的做法是手动修改SQL脚本来更新数据库表结构。然而,为实现自动新增表参数,我们需要...

    Hibernate锁机制_悲观锁和乐观锁

    Hibernate 会在数据库中加入一个 VERSION 栏记录,在读取数据时连同版本号一同读取,并在更新数据时递增版本号,然后比对版本号与数据库中的版本号,如果大于数据库中的版本号则予以更新,否则就回报错误。...

    Hibernate乐观锁

    使用Hibernate的乐观锁,开发者可以避免长时间持有数据库锁,从而提高系统性能,尤其在并发访问量大的场景下。但是,它并不适用于所有情况。当并发冲突频繁发生时,由于频繁的更新失败,可能会导致应用程序的效率...

    hibernate乐观锁和悲观锁学习

    在Hibernate中,通常通过在实体类的映射文件中设置`optimistic-lock`属性来实现乐观锁,比如设置为`version`,这将利用数据库的版本字段来检测并发冲突。当尝试更新时,如果发现版本号已变,说明有其他事务进行了...

    Hibernate version 乐观锁 (xml方式)

    当多个事务同时尝试更新同一数据时,只有版本号与数据库中的版本号一致的事务才能成功更新,否则会被视为冲突并回滚。 接下来,我们将在XML映射文件中配置乐观锁。假设我们有一个名为`User`的实体类,其中包含一个...

    数据库与Hibernate教案

    【数据库与Hibernate教案】 在IT领域,数据库是存储和管理数据的核心工具,而Hibernate作为一款优秀的对象关系映射(ORM)框架,极大地简化了Java开发者与数据库之间的交互。本教案将深入探讨这两个重要概念及其...

    Hibernate悲观锁与乐观锁

    《Hibernate 悲观锁与乐观锁详解》 在多用户并发访问的环境中,数据库管理系统必须具备有效的数据访问控制机制,以确保数据的一致性和完整性。Hibernate,作为一款流行的Java持久化框架,提供了两种主要的锁定策略...

    Hibernate悲观锁与乐观锁案例

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

Global site tag (gtag.js) - Google Analytics