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

这篇文章主要介绍了Java的Hibernate框架数据库操作中锁的使用和查询类型,Hibernate是Java的SSH三大web开发框架之一,需要的朋友可以参考下

 Hibernate与数据库

一、为什么要使用锁?

要想弄清楚锁机制存在的原因,首先要了解事务的概念。

事务是对数据库一系列相关的操作,它必须具备ACID特征:

A(原子性):要么全部成功,要么全部撤销。

C(一致性):要保持数据库的一致性。

I(隔离性):不同事务操作相同数据时,要有各自的数据空间。

D(持久性):一旦事务成功结束,它对数据库所做的更新必须永久保持。

我们常用的关系型数据库RDBMS实现了事务的这些特性。其中,原子性、

一致性和持久性都是采用日志来保证的。而隔离性就是由今天我们关注的

锁机制来实现的,这就是为什么我们需要锁机制。

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

更新丢失:事务1提交的数据被事务2覆盖。

脏读:事务2查询到了事务1未提交的数据。

虚读:事务2查询到了事务1提交的新建数据。

不可重复读:事务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主要有三种查询方法

1.HQL (Hibernate Query Language)

sql很类似,支持分页、连接、分组、聚集函数和子查询等特性,

但HQL是面向对象的,而不是面向关系数据库中的表。正因查询语句

是面向Domain对象的,所以使用HQL可以获得跨平台的好处,Hibernate

自动帮我们根据不同的数据库翻译成不同的sql语句。这在需要支持

多种数据库或者数据库迁移的应用中是十分方便的。

但得到方便的同时,由于sql语句是由Hibernate自动生成的,所以这不

利于sql语句的效率优化和调试,当数据量很大时可能会有效率问题,

出了问题也不便于排查解决

2.QBC/QBE (Query by Criteria/Example)

QBC/QBE是通过组装查询条件或者模板对象来执行查询的。这在需要

灵活地支持许多查询条件自由组合的应用中是比较方便的。同样的问题

是由于查询语句是自由组装的,创建一条语句的代码可能很长,并且

包含许多分支条件,很不便于优化和调试。

3.sql

Hibernate也支持直接执行sql查询方式。这种方式牺牲了Hibernate跨

数据库的优点,手工地编写底层sql语句,从而获得最好的执行效率,

相对前两种方法,优化和调试方便了一些。

下面来看一组简单的例子。

package com.cdai.orm.hibernate.query; import java.util.Arrays; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Example; import org.hibernate.criterion.Expression; import com.cdai.orm.hibernate.annotation.Account; public class BasicQuery { public static void main(String[] args) { SessionFactory sessionFactory = new AnnotationConfiguration(). addFile("hibernate/hibernate.cfg.xml"). configure(). addPackage("com.cdai.orm.hibernate.annotation"). addAnnotatedClass(Account.class). buildSessionFactory(); Session session = sessionFactory.openSession(); // 1.HQL Query query = session.createquery("from Account as a where a.id=:id"); query.setLong("id", 1); List result = query.list(); for (Object row : result) { System.out.println(row); } // 2.QBC Criteria criteria = session.createCriteria(Account.class); criteria.add(Expression.eq("id", new Long(2))); result = criteria.list(); for (Object row : result) { System.out.println(row); } // 3.QBE Account example= new Account(); example.setBalance(100); result = session.createCriteria(Account.class). add(Example.create(example)). list(); for (Object row : result) { System.out.println(row); } // 4.sql query = session.createsqlQuery( " select top 10 * from tb_account order by col_id desc "); result = query.list(); for (Object row : result) { System.out.println(Arrays.toString((Object[]) row)); } session.close(); } }

Hibernate: select account0_.col_id as col1_0_, account0_.col_balance as col2_0_ from tb_account account0_ where account0_.col_id=? Account [id=1, balance=100] Hibernate: select this_.col_id as col1_0_0_, this_.col_balance as col2_0_0_ from tb_account this_ where this_.col_id=? Account [id=2, balance=100] Hibernate: select this_.col_id as col1_0_0_, this_.col_balance as col2_0_0_ from tb_account this_ where (this_.col_balance=?) Account [id=1, balance=100] Account [id=2, balance=100] Hibernate: select top 10 * from tb_account order by col_id desc [2, 100] [1, 100]

从log中可以清楚的看到Hibernate对于生成sql语句的控制,具体选择

哪种查询方式就要看具体应用了。

相关文章

Java中的String是不可变对象 在面向对象及函数编程语言中,不...
String, StringBuffer 和 StringBuilder 可变性 String不可变...
序列化:把对象转换为字节序列的过程称为对象的序列化. 反序...
先说结论,是对象!可以继续往下看 数组是不是对象 什么是对...
为什么浮点数 float 或 double 运算的时候会有精度丢失的风险...
面试题引入 这里引申出一个经典问题,看下面代码 Integer a ...