zoukankan      html  css  js  c++  java
  • Hibernate_事务管理详解

    事务(transaction是数据库管理系统的执行单位,可以是一个数据库操作(如Select操作)或者是一组操作序列。事务ACID属性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

    原子性:保证事务中的所有操作全部执行或全部不执行。例如执行转账事务,要么转账成功,要么失败。成功,则金额从转出帐户转入到目的帐户,并且两个帐户金额将发生相应的变化;失败,则两个账户的金额都不变。不会出现转出帐户扣了钱,而目的帐户没有收到钱的情况。

    一致性:保证数据库始终保持数据的一致性——事务操作之前是一致的,事务操作之后也是一致的,不管事务成功与否。如上面的例子,转账之前和之后数据库都保持数据上的一致性。

    隔离性:多个事务并发执行的话,结果应该与多个事务串行执行效果是一样的。显然最简单的隔离就是将所有事务都串行执行:先来先执行,一个事务执行完了才允许执行下一个。但这样数据库的效率低下,如:两个不同的事务只是读取同一批数据,这样完全可以并发进行。为了控制并发执行的效果就有了不同的隔离级别。下面将详细介绍。

    持久性:持久性表示事物操作完成之后,对数据库的影响是持久的,即使数据库因故障而受到破坏,数据库也应该能够恢复。通常的实现方式是采用日志。

     

    事务隔离级别(transaction isolation levels:隔离级别就是对对事务并发控制的等级。ANSIISO SQL将其分为串行化(SERIALIZABLE)、可重复读(REPEATABLE READ)、读已提交(READ COMMITED)、读未提交(READ UNCOMMITED)四个等级。为了实现隔离级别通常数据库采用锁(Lock)。一般在编程的时候只需要设置隔离等级,至于具体采用什么锁则由数据库来设置。

    串行化(SERIALIZABLE):所有事务都一个接一个地串行执行,这样可以避免幻读(phantom reads)。对于基于锁来实现并发控制的数据库来说,串行化要求在执行范围查询(如选取年龄在10到30之间的用户)的时候,需要获取范围锁(range lock)。如果不是基于锁实现并发控制的数据库,则检查到有违反串行操作的事务时,需要滚回该事务。

    可重复读(REPEATABLE READ:所有被Select获取的数据都不能被修改,这样就可以避免一个事务前后读取数据不一致的情况。但是却没有办法控制幻读,因为这个时候其他事务不能更改所选的数据,但是可以增加数据,因为前一个事务没有范围锁。

    读已提交(READ COMMITED):被读取的数据可以被其他事务修改。这样就可能导致不可重复读。也就是说,事务的读取数据的时候获取读锁,但是读完之后立即释放(不需要等到事务结束),而写锁则是事务提交之后才释放。释放读锁之后,就可能被其他事物修改数据。该等级也是SQL Server默认的隔离等级。

    读未提交(READ UNCOMMITED):这是最低的隔离等级,允许其他事务看到没有提交的数据。这种等级会导致脏读(Dirty Read)。

    不同隔离级别可能发生的并发问题:

    隔离级别

    更新丢失

    脏读

    不可重复读

    幻读

    Read Uncommitted

    NO

    YES

    YES

    YES

    Read

    Committed

    NO

    NO

    YES

    YES

    Repeatable Read

    NO

    NO

    NO

    YES

    Serializable

    NO

    NO

    NO

    NO

     

     

     

     

     

     

     

     

     

    MYSQL中设置事务隔离的命令:

    1.查看当前会话隔离级别

      select @@tx_isolation;

    2.查看系统当前隔离级别

      select @@global.tx_isolation;

    3.设置当前会话隔离级别

      set session transaction isolatin level repeatable read;

    4.设置系统当前隔离级别

      set global transaction isolation level repeatable read;

    5.设置自动提交

      set autocommit = 0/1; (0为不自动提交,1为自动提交)

     

     

    Hibernate中的锁定模式:

    Hibernate中使用LockMode类表示锁定,该类定义了6个常量表示6中不同的锁定模式:

    1. LockMode.NONE:无锁机制(默认)。表示读取数据时首先从缓存中读取,如果缓存中没有要读取的数据,再对数据库进行操作。
    2. LockMode.WRITE:在数据库插入数据或者更新数据时锁定,Hibernate会在写入数据时自动使用这种锁定模式。
    3. LockMode.READ:Hibernate不管缓存中是否存在要读取的数据,总跳过缓存直接读取数据库中的数据。当底层数据库设置事务隔离级别为Repeatable Read或者Serializable是,Hibernate读取数据,自动使用这种锁定模式。
    4. LockMode.UPGRADE:使用悲观锁,在Hibernate底层利用SQL用的for update字句锁定,加锁后其他事务(无论读取,还是写入)均无法并发访问。
    5. LockMode.UPGRADE_NOWAIT:类似LockMode.UPGRADE,不同的是使用for update nowait字句锁定,该子句是Oracle数据库所特有的。
    6. LockMode.FORCE:使用版本号方式实现乐观锁时可强迫指定的持久化对象递增版本号。

    在Hibernate中使用LockMode.UPGRADE或者LockMode.UPGRADE_NOWAIT模式表示使用悲观锁,这种悲观锁完全基于数据库的锁机制实现。

     

    使用Query/Criteria对象的setLockMode()方法设置悲观锁

     1 Session session = HibernateUtil.getSession();
     2         Transaction ts = session.beginTransaction();
     3 
     4         try {
     5             Query query = session.createQuery("from Family as f");
     6             query.setLockMode("f", LockMode.UPGRADE);
     7             List<Family> list = query.list();
     8             ts.commit();
     9         }
    10         catch (Exception e) {
    11             if(ts != null && ts.isActive())
    12                 ts.rollback();
    13             e.printStackTrace();
    14         }
    15         finally {
    16             HibernateUtil.closeSession(session);
    17         }

    第6行中,"f"是Family持久化类的别名,对其返回的所有Family对象加锁. (只有在调用Query对象的list()方法之前设置加锁,才会真正通过数据库的锁机制执行加锁处理;否则无法加锁).

     

    使用Session对象的lock()方法设置悲观锁

     1 Session session = HibernateUtil.getSession();
     2         Transaction ts = session.beginTransaction();
     3 
     4         try {
     5             Family family = (Family) session.get(Family.class, "321");
     6             session.lock(family, LockMode.UPGRADE);
     7             ts.commit();
     8         }
     9         catch (Exception e) {
    10             if (ts != null && ts.isActive())
    11                 ts.rollback();
    12             e.printStackTrace();
    13         }
    14         finally {
    15             HibernateUtil.closeSession(session);
    16         }

    在这里会出现org.hibernate.ObjectDeletedException: attempted to lock a deleted instance 错误提示,不知道什么原因。

     

     

    版本号实现乐观锁:

    乐观锁采取了比较温柔的加锁机制,它乐观地认为数据库中的数据很少发生同时操作的问题,并且大多通过数据的版本号(Version)比较机制实现。

    修改持久化类Family:

    public class Family {
        private String id;
        private String username;
        private String address;
        private double cost;
        private Integer version;
            //getter,setter方法
    }

    其中version属性是用来区别数据的版本,更新数据时版本号会加1,这个version不用设置getter,setter方法,因为下面的配置文件中设置了直接通过数据库version字段获取版本号.

    编写Family.hbm.xml文件:

     1 <hibernate-mapping>
     2     <class name="com.sunflower.yuan.pojo.Family" table="cost" optimistic-lock="version">
     3         <id name="id" column="id">
     4             <generator class="uuid"></generator>
     5         </id>
     6         
     7         <version name="version" column="version" access="field"></version>
     8 
     9         <property name="username" column="username" type="string"></property>
    10         <property name="address" column="address" type="string"></property>
    11         <property name="cost" column="cost" type="double"></property>
    12     </class>
    13 </hibernate-mapping>

    第2行中optimistic-lock属性用来设置乐观锁的实现方式,其值为version.第7行中access属性设置获取数据的方法,这里设置为field直接从数据库字段获取。

    optimistic-lock属性的取值:

    属性值

    说明

    none

    无乐观锁

    version

    通过版本号比较机制实现乐观锁

    dirty

    通过检查持久化对象发生变更过的属性值实现乐观锁

    all

    通过检查持久化对象的所有属性实现乐观锁

     

     

     

     

     

     

     

     

    编写Test.java文件:

     1 public class Test {
     2     public static void main(String[] args) {
     3         Session session = HibernateUtil.getSession();
     4         Session session2 = HibernateUtil.getSession();
     5         
     6         Transaction ts = session.beginTransaction();
     7         Transaction ts2 = session2.beginTransaction();
     8 
     9         try {
    10             Family family = (Family) session.get(Family.class, "8a85f4eb39759e910139759e92cb0001");
    11             System.out.println("version1:" + family.getVersion());
    12             family.setUsername("第一次修改3");
    13             
    14             Family family2 = (Family) session2.get(Family.class, "8a85f4eb39759e910139759e92cb0001");
    15             System.out.println("version2:" + family.getVersion());
    16             family2.setAddress("第二次新修改3");
    17             
    18             ts.commit();
    19             ts2.commit();
    20         }
    21         catch (Exception e) {
    22             if (ts != null && ts.isActive())
    23                 ts.rollback();
    24             e.printStackTrace();
    25         }
    26         finally {
    27             HibernateUtil.closeSession(session);
    28         }
    29     }
    30 }

    第6和第7行中同时开启了两个事务,第10到19行两个事务同时读取和修改同一条数据,乐观锁根据版本号比较拒绝了第2个事务的数据更新,同时抛出StaleObjectStateException.

     

    时间戳实现乐观锁:

    时间戳(timestamp)比较实现乐观锁在数据库表中专门有一个timestamp类型的字段,用来记录该条数据最后的更新时间,通过比较这个时间戳实现乐观锁。其原理和用版本号来实现乐观锁是一样的。

    修改持久化类Family:

    public class Family {
        private String id;
        private String username;
        private String address;
        private double cost;
        private Timestamp version;
       //getter,setter
    }

    增加一个java.sql.TimeStamp类型属性,也可以是java.util.Date属性,不需要为该属性提供getter,setter方法.

    编写Family.hbm.xml文件:

     1 <hibernate-mapping>
     2     <class name="com.sunflower.yuan.pojo.Family" table="cost" optimistic-lock="version">
     3         <id name="id" column="id">
     4             <generator class="uuid"></generator>
     5         </id>
     6         
     7         <timestamp name="version" column="version" access="field"></timestamp>
     8         
     9         <property name="username" column="username" type="string"></property>
    10         <property name="address" column="address" type="string"></property>
    11         <property name="cost" column="cost" type="double"></property>
    12     </class>
    13 </hibernate-mapping>
    一颗平常心,踏踏实实,平静对待一切
  • 相关阅读:
    Android&Java面试题大全—金九银十面试必备
    android招聘啦,美图秀秀欢迎你加入!
    android经典源码,很不错的开源框架
    MongoDB、Hbase、Redis等NoSQL优劣势、应用场景
    体验go语言的风骚式编程
    金九银十中,看看这31道Android面试题
    android高级页面效果集锦
    flask中的request
    flask笔记(三)Flask 添加登陆验证装饰器报错,及解析
    flask笔记(二)
  • 原文地址:https://www.cnblogs.com/hanyuan/p/2663094.html
Copyright © 2011-2022 走看看