当前位置: 移动技术网 > IT编程>开发语言>Java > Java的Hibernate框架数据库操作中锁的使用和查询类型

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

2019年07月22日  | 移动技术网IT编程  | 我要评论

 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注解来定义版本号字段。

201612285333338.png (351×120)

将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的数据了,所以
抛出了异常。

201612285414974.png (353×131)

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语句的控制,具体选择
哪种查询方式就要看具体应用了。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网