zoukankan      html  css  js  c++  java
  • Hibernate 事务 详解 实例

    1.数据库事务的概念

    数据库事务是指由一个或多个SQL语句组成的工作单元,这个工作单元中的SQL语句相互依赖,如果有一个SQL语句执行失败,就必须撤销整个工作单元。

       在并发环境中,多个事务同时访问相同的数据资源时,可能会造成各种并发问题,可通过设定数据库的事务隔离级别来避免,还可采用悲观锁和乐观锁来解决丢失更新这一并发问题。

       数据库事务必须具备ACID特征:

      A: Atomic原子性,整个事务不可分割,要么都成功,要么都撤销。  

      C: Consistency一致性,事务不能破坏关系数据的完整性和业务逻辑的一致性,例如转账,应保证事务结束后两个账户的存款总额不变。

      I:  Isolation隔离性,多个事务同时操纵相同数据时,每个事务都有各自的完整数据空间  D: Durability持久性,只要事务成功结束,对数据库的更新就必须永久保存下来,即使系统发生崩溃,重启数据库后,数据库还能恢复到事务成功结束时的状态。


    2.事务边界声明

    只要声明了一个事务,数据库系统就会自动保证事务的ACID特性。

         

          声明事务包含:

          事务的开始边界

          事务的正常结束边界(commit):提交事务,永久保存

          事务的异常结束边界(rollback):撤销事务,数据库回退到执行事务前的状态

         

         数据库支持两种事务模式:

          自动提交模式:每个SQL语句都是一个独立的事务,数据库执行完一条SQL语句后,会自动提交事务。

          手工提交模式:必须由数据库的客户程序显式指定事务的开始和结束边界

         

          JDBC Connection类的事务控制方法:

          setAutoCommit(boolean autoCommit) 设置是否自动提交事务,默认自动

          commit() 提交事务

          rollback() 撤销事务
    Hibernate
    控制事务的方法:

         1. 调用sessionFactory不带参数的openSession方法,从连接池获得连接,Session自动把连接设为手工提交事务模式。

          Session   session = sessionFactory.openSession();

          若调用带connection参数的openSession,则需要自己设置手工提交:

          connection.setAutoCommit(false);

          Session   session = sessionFactory.openSession(connection);
          2. 
    声明事务的开始边界

          Transaction tx = session.beginTransaction();

          3. 提交事务

           tx.commit();

          4. 撤销事务:

           tx.rollback(); 

          一个session可以对应多个事务,但是应优先考虑让一个session只对应一个事务,当一个事务结束或撤销后,就关闭session.

         不管事务成功与否,最后都应调用sessionclose关闭session

          任何时候一个session只允许有一个未提交的事务,不能同时开始两个事务



    3.多事务并发问题

    同时运行多个事务访问相同数据时,可能会导致5类并发问题:

          1. 第一类丢失更新:撤销一个事务时,把其他事务已提交的更新覆盖

          2. 脏读:一个事务读到另一事务未提交的更新数据

          3. 虚读:一个事务读到另一事务已提交的新插入的数据

          4. 不可重复读:一个事务读到另一事务已提交的更新数据

          5. 第二类丢失更新:一个事务覆盖另一事务已提交的更新数据,不可重复读的特例

         

           1. 第一类丢失更新:

           t1 开始事务

           t2 开始事务

           t3 查询存款余额为1000

           t4 查询存款余额为1000

           t5汇入100元,把存款余额改为1100

           t6提交事务

           t7取出100元,把存款余额改为900

           t8撤销事务,账户的存款余额恢复为1000

           2. 脏读:

           t1开始事务

           t2开始事务
           t3
    查询存款余额为1000

           t4

           t5取出100元,把存款余额改为900

           t6查询账户余额为900(脏读)

           t7撤销事务,账户的存款余额恢复为1000

           t8汇入100元,存款余额改为1000

           t9提交事务

           3. 虚读:

           t1开始事务

           t2开始事务

       t3统计网站注册人数为1000

           t4注册一个新用户

           t5提交事务

           t6统计网站注册人数为1000(虚读)

           t7到底哪个统计数据有效?

           4. 不可重复读:

           t1开始事务

           t2开始事务
           t3
    查询账户余额为1000                                                               

           t4查询账户余额为1000

           t5取出100元,余额改为900

           t6提交事务

           t7查询账户余额为900

           t8到底余额是1100元还是900+100元?

           5. 第二类丢失更新:

           t1开始事务

           t2开始事务

           t3查询账户余额为1000                                                              

           t4查询账户余额为1000

           t5取出100元,余额改为900

           t6提交事务

           t7汇入100元,把存款余额改为1100

           t8提交事务


    4.
    数据库锁

    数据库必须具有隔离并发运行的各个事务的能力,数据库采用锁来实现事务的隔

    离性。

    根据数据库能够锁定的资源,可分为以下锁:

    数据库级锁

    表级锁

    区域级锁

    页面级锁

    键值锁: 锁定数据库表中带有索引的一行数据

    行级锁:锁定数据库表中的单行记录

    锁的封锁粒度越大,隔离性越高,并发性越低

    锁升级指调整锁的粒度,将多个低粒度锁替换成更高粒度的锁,以降低系统负荷。

    按照封锁程度,锁可分为:

    共享锁:用于读数据,非独占,允许其他事务同时读取锁定的资源,但不允许其 

    他事务更新。

     加锁:执行select语句时,数据库会为事务分配共享锁,锁定被查询的数据。

     解锁:数据被读取后,立即解除

    兼容性:还可再放置共享锁和更新锁 

    独占锁:

    也叫排他锁,用于修改数据,锁定的资源不能被其他事务读取和修改。

    加锁的条件:一个事务执行insert  update  delete语句时,自动使用独占锁,若已有其他锁存在,无法再独占锁

    解锁的条件:一直事务结束才能被解除

    兼容性:不能和其他锁兼容,不能再放置其他任何锁。

    并发性能:较差,只允许有一个事务访问锁定的数据,其他事务要访问,必读等待,直到前一个事务结束,解除了独占锁。

    更新锁:

    在更新操作的初始化阶段用来锁定可能要修改的资源,避免使用共享锁造成的死

    锁现象。

    如果使用共享锁,更新数据的操作分为两步:

    1.获得一个共享锁,读取一条记录

    2.将共享锁升级为独占锁,再执行更新操作

    如果同时多个事务同时更新该数据,每个事务都先获得一把共享锁,在更新数据

    的时候,这些事务都要先将共享锁升级为独占锁。由于独占锁不能与其他锁并存,

    因此每个事务都进入等待,等待其他事务释放共享锁,造成了死锁。

    如果使用更新锁,更新数据的操作分为以下两步:

    1.获得一个更新锁,读取一条记录

    2.将更新锁升级为独占锁,再执行更新操作

    更新锁的特征:

    加锁的条件:一个事务执行update语句时,数据库先为事务分配一把更新锁

    解锁的条件:读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。

    兼容性:更新锁与共享锁是兼容的,可以同时放置,但是最多只能放一把更新锁,以确保多个事务更新数据时,只有一个事务能获得更新锁,再把更新锁升级为独占锁,其他事务必须等到前一个事务结束后,才能获得更新锁,这就避免了死锁。

    并发性能允许多个事务同时读锁定的资源,但不允许其他事务修改它。

    死锁及其预防:

    死锁是指多个事务分别锁定了一个资源,又试图请求锁定对方已经锁定的资源,这就产生了

    一个锁定请求环,导致多个事务都处于等待对方释放锁定资源的状态。

    预防:

    合理安排表访问顺序

    使用短事务

    允许脏读

    错开执行时间

    使用尽可能低的事务隔离级别



    5.事务隔离级别

    锁机制能解决各种并发问题,但是会影响并发性能,为了能让用户根据实际应用的需要,在

    事务的隔离性和并发性之间做出合理权衡,数据库系统提供了四种事务隔离级别供用户选择:

    8 Serializable:串行化  完全看不到其他事务的更新,串行等待

    4 Repeatable Read:可重复读  事务可看到其他事务已提交的新插入记录,但是不能看到其他事务对已有记录的更新

    2 Read Commited:读已提交数据  事务可看到其他事务已提交的新插入记录,还能看到其他事务已经提交的对已有记录的更新

    1 Read  Uncommited: 读未提交数据  事务可看到其他事务没有提交的新插入记录,还能看到其他事务没有提交的对已有记录的更新

    优先考虑隔离级别为Read Commited,能避免脏读,有较好的并发性能,可能会导致不可重复读、虚读和第二类丢失更新,但是可以采用悲观锁或乐观锁来控制。

     

    Hibernate中设置事务隔离级别:

     hibernate.connection.isolation = 2          (Read Commited)



    6.悲观锁和乐观锁

    悲观锁:先锁定资源,能防止丢失更新和不可重复读问题,但是影响并发性能

    乐观锁:完全依靠数据库的隔离级别来自动管理锁的工作,采用版本控制可避免并发问题。

    悲观锁的实现:

    显式指定独占锁来锁定资源

        select语句默认采用共享锁

        可采用 select  .. for update  来显式指定采用独占锁来锁定查询的记录

        Hibernate采用LockMode来实现锁定模式

    2. 增加一个LOCK 字段

        Hibernate 采用<version><timestamp>来实现版本控制

    乐观锁的实现:

    <class name=“”  table=“”  optimistic-lock=“all” dynamic-update=“true”>

  • 相关阅读:
    Java--环境变量(jdk、jre)配置
    Jsoup爬取数据设置代理IP
    数据库连接的莫名问题:create connection error
    springBoot使用过程的小感悟
    Activiti工作流引擎数据库表结构
    IDEA创建Activiti工作流开发
    Java开源爬虫框架WebCollector设置代理
    使用OkHttpClient爬取json数据
    雪中的杭州
    StringBuffer中的append方法
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6314275.html
Copyright © 2011-2022 走看看