Hibernate5笔记8--Hibernate事务相关内容

Hibernate事务相关内容:

  (1) 事务四大特性(简称ACID):

    (1)原子性(Atomicity)
      事务中的全部操作在数据库中是不可分割的,要么全部完成,要么均不执行。
    (2)一致性(Consistency)
      几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致。

    (3)隔离性(Isolation)
      事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
    (4)持久性(Durability)
      对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。

  (2) 事务并发执行时会出现的四大问题:

    (1) 更新丢失,又分为两类:

      (1)回滚更新丢失:

        A、B事务同时读取某数据,并均做修改。A事务先做了提交,而B事务又做回滚。此时,A事务提交的更新数据丢失。

        对应实例:回滚更新丢失在一般的数据库中不会存在,因为数据库最低的事务隔离级别已经解决了这个问题。

      (2)提交更新丢失:

        A、B事务同时读取某数据,并均做修改。A事务先做了提交,然后B事务也做提交。此时,A事务提交的更新数据会被B事务的提交给覆盖。

        对应实例:

         1. 假设当当网上用户下单买了本书,这时数据库中有条订单号为 001的订单,其中有个status字段是’有效’,表示该订单是有效的;
           2. 后台管理人员查询到这条001的订单,并且看到状态是有效的
           3. 用户发现下单的时候下错了,于是撤销订单,假设运行这样一条 SQL: update order_table set status = ‘取消’ where order_id = 001;
           4. 后台管理人员由于在2这步看到状态有效的,这时,虽然用户在3 这步已经撤销了订单,可是管理人员并未刷新界面,看到的订单状态还是有效的,于是点击”发货”按钮,将该订单发到物流部门,同时运行类似如下SQL,将订单状态改成已发货:update order_table set status = ‘已发货’ where order_id = 001
         

         如果当当的系统这样实现,显然不对了,肯定要挨骂了,明明已经取消了订单,为什么还会发货呢?而且确实取消订单的操作发生在发货操作之前啊。 因为在这样的实现下,后台管理人员无论怎么做都有可能会出错,因为他打开系统看到有效的订单和他点发货之间肯定有个时间差,在这个时间差的时候总是存在用户取消订单的可能。

    (2)脏读

      即读取到不正确的数据,因为A事务可能还没提交最终数据,B事务就读取了中途的数据,一旦A事务回滚了,这个数据就是是不正确的。

      对应示例: 公司发工资了,领导把5000元打到singo的账号上,但是该事务并未提交,而singo正好去查看账户,发现工资已经到账,是5000元整,非常高兴。可是不幸的是,领导发现发给singo的工资金额不对,是2000元,于是迅速回滚了事务,修改金额后,将事务提交,最后singo实际的工资只有2000元,singo空欢喜一场。

    (3)不可重复读

      A事务读取某一数据后,B事务对其做了修改,当A事务再次读该数据时得到与前一次不同的值。

      对应示例: singo拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转账,把singo工资卡的2000元转到另一账户,并在singo之前提交了事务,当singo扣款时,系统检查到singo的工资卡已经没有钱,扣款失败,singo十分纳闷,明明卡里有钱,为何......

    (4)幻读

      A事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据。这是因为在两次查询过程中有B事务插入数据造成的。

      对应示例:singo的老婆工作在银行部门,她时常通过银行内部系统查看singo的信用卡消费记录。有一天,她正在查询到singo当月信用卡的总消费金额(select sum(amount) from transaction where month = 本月)为80元,而singo此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录(insert transaction ... ),并提交了事务,随后singo的老婆将singo当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,singo的老婆很诧异,以为出现了幻觉,幻读就这样产生了。

  (3)事务隔离级别及对应会出现的问题:

    (1)Read uncommitted(未授权读取、读未提交):

      写事务阻止其他写事务,但是没有阻止其他读事务。避免了回滚更新丢失,却可能出现脏读。

    (2)Read committed(授权读取、读提交):

      写事务会阻止其他读写事务。读事务不会阻止其他任何事务。避免了脏读,但是却可能出现不可重复读。

    (3)Repeatable read(可重复读取):

      读事务会阻止其他写事务,但是不会阻止其他读事务。避免了不可重复读和脏读,但是有时可能出现幻读

      可重复读阻止的写事务包括update和delete(只给存在的表加上了锁),但是不包括insert(新行不存在,所以没有办法加锁),所以一个事务第一次读取可能读取到了10条记录,但是第二次可能读取到11条,这就是幻读。

    (4)Serializable(序列化):

      提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

      序列化是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读。

    大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。
    MYSQL的默认隔离级别就是Repeatable read。

   (4)提交更新丢失问题的解决:

     方法一,使用乐观锁(含义:这样的问题是小概率的最后一步做更新的时候再锁住,免得锁住时间太长影响其他人做有关操作):

       乐观锁使用方法一:很简单,就是使用前面所说的这样一条SQL,这其实是所谓使用”前镜像”的方式来保证需要更新的数据是符合要求的,
        update order_table set status = ‘已发货’ where order_id = 001 and status = ‘有效’
      乐观锁使用方法二:使用版本列[比如时间戳或版本号]
        时间戳:这个方法比较简单,也最常用,就是在数据库表格中加一列last_modified_date,就是最后更新的时间,每次用户端更新的时候都将这列设成 systimestamp,当前系统时间;
        然后每次服务端更新的时候,就改成这样 update order_table set status = ‘已发货’ where order_id = 001 and last_modified_date = old (上次查看时的系统时间),last_modified_date这样,就可以检验出数据库的值是否在上次查看和这次更新的时候发生了变化,如果发生了变化,那么last_modified_date就变化了,以后的更新就会返回更新了0行,系统就可以通知用户数据发生了变化,然后选择刷新数据或者其他流程。

        版本号:用户端和服务事务从数据库中读取数据时同时会读出一个数据版本号。当用户端事务将修改过的数据写入到数据库中时,会使版本号增1。当服务端事务发生回滚或覆盖时,会首先对比自己数据的版本号与数据库中数据的版本号。若它们相等,则说明数据库中数据没有发生变化,服务端事务可以将数据将修改写入到数据中。若小于数据库中的版本号,则说明用户端事务已经修改过该数据,将抛出异常。
    方法二,使用悲观锁(含义:这样的问题是高概率的最好一开始就锁住免得像使用乐观锁的时候,更新老是失败):

      服务端查询一条记录,并试图后续要做更新的时候,那么在查询的时候使用select *** for update nowait 语句,通过添加for update nowait语句,将这条记录锁住,避免其他用户更新,从而保证后续的更新是在正确的状态下更新的。

      但是存在一个问题:如果打开页面做查询的时候就将记录锁住,并且保持这个连接,那对连接的占用太长了,整个系统能承受的并发量就很小了。以oracle 10g为例,默认情况下,最大连接数是150,也就是说最多只能承受150个用户同时访问了。

    方法选择:乐观锁会导致更新失败频繁,悲观锁会降低并发性能。所以,一般在更新频繁的状况中使用悲观锁,其他情况用乐观锁较好。

  (5)Hibernate代码:

    (1)设置Hibernate事务隔离级别:

      在Hibernate的配置文件中可以显示的配置数据库事务隔离级别。每一个隔离级别用一个整数表示:

        8 - Serializable 串行化
        4 - Repeatable Read 可重复读
        2 - Read Commited 可读已提交
        1 - Read Uncommited 可读未提交

      在hibernate.cfg.xml中使用hibernate.connection.isolation参数配置数据库事务隔离级别。

    (2)乐观锁代码:

      在Hibernate映射文件的<class/>标签中,有一个子标签<version/>,其name属性用于指定作为版本的属性名称。其还有一个子标签<timestamp/>用于指定作为时间戳的属性名称。

      这两个子标签的用法相同,不同的是,作为版本的属性要求其类型为int,而作为时间戳的属性,要求为其类型为java.sql.timestamp。

      例如映射文件中是<version name="version" column="tversion"/>,则实体类中定义一个属性private int version;

      其他的事情由系统自己维护

    (3)悲观锁代码:

      在测试代码中锁定,不需要修改实体类:

 1 @Test
 2 public void test01_SQL() {
 3     //1. 获取Session
 4     Session session = HbnUtils.getSession();
 5     try {
 6         //2. 开启事务
 7         session.beginTransaction();
 8         //3. 操作
 9         //悲观锁中的写锁
10         //Student student = session.get(Student.class, 2, LockMode.PESSIMISTIC_WRITE);
11         //悲观锁中的读锁
12         Student student = session.get(Student.class, 2, LockMode.PESSIMISTIC_READ);
13         System.out.println(student);
14         //4. 事务提交
15         session.getTransaction().commit();
16     } catch (Exception e) {
17         e.printStackTrace();
18         //5. 事务回滚
19         session.getTransaction().rollback();
20     }
21 }

      session.get方法的第三个参数就是用来设置悲观锁的,使用第三个参数之后,我们每次发送的SQL语句都会加上"for update"用于告诉数据库锁定相关数据。

原文地址:https://www.cnblogs.com/qjjazry/p/6295367.html