Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 293|回复: 0

[Java框架学习]Hibernate中的事务和并发控制

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-11-6 00:01:03 | 显示全部楼层 |阅读模式
    1.    事务介绍:

    1.1.       事务的定义:


    事务就是指作为单个逻辑工作单元执行的一组数据操作,这些操作要么必须全部成功,要么必须全部失败,以保证数据的一致性和完整性。


    1.2.       事务具有ACID属性:


    o
    原子性(Atomic):事务由一个或多个行为绑在一起组成,好像是一个单独的工作单元。原子性确保在事务中的所有操作要么都发生,要么都不发生。


    o
    一致性(Consistent):一旦一个事务结束了(不管成功与否),系统所处的状态和它的业务规则是一致的。即数据应当不会被破坏。


    o
    隔离性(Isolated):事务应该允许多个用户操作同一个数据,一个用户的操作不会和其他用户的操作相混淆。


    o
    持久性(Durable):一旦事务完成,事务的结果应该持久化。





          事务的ACID特性是由关系数据库管理系统(RDBMS)来实现的。


    o 数据库管理系统采用日志来保证事务的原子性、一致性和持久性。日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志,撤销事务对数据库已做的更新,使数据库退回到执行事务前的初始状态。


    o 数据库管理系统采用锁机制来实现事务的隔离性。当多个事务同时更新数据库相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。


    2.    数据库事务声明:


    数据库系统的客户程序只要向数据库系统声明了一个事务,数据库系统就会自动保证事务的ACID特性。在JDBC API中,java.sql.Connection类代表一个数据库连接。它提供了以下方法控制事务:


    1.         setAutoCommit(Boolean autoCommit):设置是否自动提交事务。


    2.         commit():提交事务。


    3.         rollback():撤销事务。


    2.1.       JDBC API声明事务的示例代码如下:


      
       
       
       
         Connection = null;
         
       
         PreparedStatement pstmt = null;
         
       
         try{
         
       
         con = DriverManager.getConnection(dbUrl, username, password);
         
       
         //
         设置手工提交事务模式
         
       
         con.setAutoCommit(false);
         
       
         pstmt = ……;
         
       
         pstmt.executeUpdate();
         
       
         //
         提交事务
         
       
         con.commit();
         
       
         }catch(Exception e){
         
       
         //
         事务回滚
         
       
         con.rollback();
         
       
         …..
         
       
         } finally{
         
       
             …….
         
       
         }
         
       
      


           Hibernate

    JDBC
    进行了轻量级的对象封装,
    Hibernate
    本身在设计时并不具备事务处理功能,平时所用的
    Hibernate
    的事务,只是将底层的
    JDBCTransaction
    或者
    JTATransaction
    进行了一下封装,在外面套上
    Transaction

    Session
    的外壳,其实底层都是通过委托底层的
    JDBC

    JTA
    来实现事务的调度功能。


    2.2.       Hibernate中使用JDBC事务:


    要在
    Hibernate
    中使用事务,可以配置
    Hibernate
    事务为
    JDBCTransaction
    或者
    JTATransaction
    ,这两种事务的生命周期不一样,可以在
    hibernate.cfg.xml
    中指定使用的是哪一种事务。以下配置为使用
    JDBC
    事务。注:如果不进行配置,
    Hibernate
    也会默认使用
    JDBC
    事务。


      
       
       
       
         
         
       
         <session-factory>
         
       
         ……
         
       
         <property name="hibernate.transaction.factory_class">
         
       
         org.hibernate.transaction.JDBCTransactionFactory
         
       
         </property>
         
       
         ……
         
       
         </session-factory>
         
       
      


    Hibernate 使用JDBC transaction处理方式如下所示:


      
       
       
       
         Transaction tx = null;
         
       
         try {
         
       
             tx = sess.beginTransaction();
         
       
          
         
       
             // do some work
         
       
             ...
         
       
          
         
       
             tx.commit();
         
       
         }
         
       
         catch (RuntimeException e) {
         
       
             if (tx != null) tx.rollback();
         
       
             throw e; // or display error message
         
       
         }
         
       
         finally {
         
       
             sess.close();
         
       
         }
         
       
      





    2.3.       Hibernate中使用JTA事务:


    JTA(java Transaction API)
    是事务服务的
    javaEE
    解决方案。本质上,它是描述事务接口的
    JavaEE
    模型的一部分。


    JTA
    具有的
    3
    个接口:
    UserTransaction
    接口、
    TransactionManager
    接口和
    Transaction
    接口,这些接口共享公共的事务操作。
    UserTransaction
    能够执行事务划分和基本的事务操作,
    TransactionManager
    能够执行上下文管理。


    在一个具有多个数据库的系统中,可能一个程序将会调用几个数据库中的数据,需要一种分布事务,或者准备用
    JTA
    来管理
    Session
    的长事务,那么就需要使用
    JTATransaction




    hibernate.cfg.xml
    中配置
    JTA
    事务管理:


      
       
       
       
         <session-factory>
         
       
         ……
         
       
         <property name="hibernate.transaction.factory_class">
         
       
         org.hibernate.transaction.JTATransactionFactory
         
       
         </property>
         
       
         ……
         
       
         </session-factory>
         
       
      


    下面是一个实际应用的
    JTA
    示例:


      
       
       
       
         // BMT(bean管理事务) idiom with getCurrentSession()
         
       
         try {
         
       
             UserTransaction tx = (UserTransaction)new InitialContext()
         
       
                                     .lookup("java:comp/UserTransaction");
         
       
          
         
       
             tx.begin();
         
       
          
         
       
             // Do some work on Session bound to transaction
         
       
             factory.getCurrentSession().load(...);
         
       
             factory.getCurrentSession().persist(...);
         
       
          
         
       
             tx.commit();
         
       
         }
         
       
         catch (RuntimeException e) {
         
       
             tx.rollback();
         
       
             throw e; // or display error message
         
       
         }
         
       
      


    在CMT方式下,事务声明是在session bean的部署描述符中,而不需要编程。 因此,代码被简化为:


      
       
       
       
         // CMT idiom
         
       
         Session sess = factory.getCurrentSession();
         
       
         
         
       
         // do some work
         
       
         ...
         
       
      





    3.    多个事务并发引起的问题:


    3.1.        
    第一类丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。


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


    3.3.        
    幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。


    3.4.        
    不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。


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


    4.    事务隔离级别:


    为了解决多个事务并发会引发的问题。数据库系统提供了四种事务隔离级别供用户选择。


    o Serializable:串行化。隔离级别最高


    o Repeatable Read:可重复读。


    o Read Committed:读已提交数据。


    o Read Uncommitted:读未提交数据。隔离级别最差。


    数据库系统采用不同的锁类型来实现以上四种隔离级别,具体的实现过程对用户是透明的。用户应该关心的是如何选择合适的隔离级别。


    对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读,而且具有较好的并发性能。


    每个数据库连接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别。
    JDBC
    数据库连接使用数据库系统默认的隔离级别。在Hibernate的配置文件中可以显示地设置隔离级别。每一种隔离级别对应着一个正整数。


    Read Uncommitted: 1


    Read Committed: 2


    Repeatable Read: 4


    Serializable: 8


    在hibernate.cfg.xml中设置隔离级别如下:


      
       
       
       
             <session-factory>
         
       
         <!-- 设置JDBC的隔离级别 -->
         
       
         <property name="hibernate.connection.isolation">2</property>
         
       
         </session-factory>
         
       
      


    设置之后,在开始一个事务之前,Hibernate将为从连接池中获得的JDBC连接设置级别。需要注意的是,在受管理环境中,
    如果
    Hibernate
    使用的数据库连接来自于应用服务器提供的数据源,Hibernate
    不会改变这些连接的事务隔离级别。在这种情况下,应该通过修改应用服务器的数据源配置来修改隔离级别。


    5.    并发控制:


    当数据库系统采用Red Committed隔离级别时,会导致不可重复读和第二类丢失更新的并发问题,在可能出现这种问题的场合。可以
    在应用程序中采用悲观锁或乐观锁来避免这类问题。


    5.1.       乐观锁(Optimistic Locking)


    乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此不作数据库层次上的锁定。为了维护正确的数据,乐观锁使用应用程序上的版本控制(由程序逻辑来实现的)来避免可能出现的并发问题。


    唯一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、 或者时间戳来检测更新冲突(并且防止更新丢失)。








    5.1.1.      
    使用版本检查(<version>)



    Hibernate中通过版本号检查来实现后更新为主,这也是Hibernate推荐的方式。在数据库表中加入一个version(版本)字段,在读取数据时连同版本号一起读取,并在更新数据时比较版本号与数据库表中的版本号,如果等于数据库表中的版本号则予以更新,并递增版本号,如果小于数据库表中的版本号就抛出异常。


    使用<version>进行版本控制的步骤:


    1)      在持久化类中定义一个代表版本号的属性:


      
       
       
       
         package org.qiujy.domain.versionchecking;
         
       
          
         
       
         import java.util.Date;
         
       
          
         
       
         public class Product implements java.io.Serializable{
         
       
                private Long id ;
         
       
                /** 版本号 */
         
       
                private int version;
         
       
                private String name; //产品名
         
       
                private String description; //描述--简介
         
       
                private Double unitCost; //单价
         
       
                private Date pubTime; //生产日期
         
       
                
         
       
                public Product(){}
         
       
          
         
       
                //以下为getter()和setter()方法
         
       
         }
         
       
      


      


    2)      在Product.hbm.xml文件中用<version>元素来建立Product类的version属性与表中version字段的映射。


    3)      Hibernate在其数据库访问引擎中内置了乐观锁定实现,默认也是选择version方式作为Hibernate乐观锁定实现机制。所以,在配置文件及程序中可以不作其它设置。按往常一样写操作代码。


      
       
       
       
         package org.qiujy.domain.versionchecking;
         
       
          
         
       
         import java.util.Date;
         
       
          
         
       
         import org.hibernate.HibernateException;
         
       
         import org.hibernate.Session;
         
       
         import org.hibernate.Transaction;
         
       
         import org.qiujy.common.HibernateSessionFactory;
         
       
          
         
       
         public class TestVersionChecking {
         
       
                
         
       
                public static void main(String[] args) {
         
       
                       Product prod = new Product();
         
       
                       prod.setName("IBM thinkPad T60");
         
       
                       prod.setUnitCost(new Double(26000.00));
         
       
                       prod.setDescription("笔记本电脑");
         
       
                       prod.setPubTime(new Date());
         
       
                      
         
       
                       //test start.......
         
       
                       Session session = HibernateSessionFactory.getSession();
         
       
                       Transaction tx =null;
         
       
                       try{
         
       
                              tx = session.beginTransaction();
         
       
                              
         
       
                              session.save(prod);
         
       
                         
         
       
                             tx.commit();
         
       
                       }catch(HibernateException e){
         
       
                              if(tx != null){
         
       
                                     tx.rollback();
         
       
                              }
         
       
                              e.printStackTrace();
         
       
                       }finally{
         
       
                              HibernateSessionFactory.closeSession();
         
       
                       }
         
       
                
         
       
         //进行更新 测试..
         
       
                       prod.setDescription("新款的");
         
       
                      
         
       
                       Session session2 = HibernateSessionFactory.getSession();
         
       
                       Transaction tx2 =null;
         
       
                       try{
         
       
                              tx2 = session2.beginTransaction();
         
       
                              
         
       
                              session2.update(prod);
         
       
                        
         
       
                             tx2.commit();
         
       
                       }catch(HibernateException e){
         
       
                              if(tx2 != null){
         
       
                                     tx2.rollback();
         
       
                              }
         
       
                              e.printStackTrace();
         
       
                       }finally{
         
       
                              HibernateSessionFactory.closeSession();
         
       
                       }
         
       
                }
         
       
         }
         
       
      


    新增数据时产生的SQL是:


      
       
       
       
         insert into products (version, name, description, unitCost, pubTime)
         
       
             values(?, ?, ?, ?, ?)
         
       
      




    程序无需为Product对象的version属性显示赋值,当持久化一个Product对象时,Hibernate会自动为它赋初始值为0。


    更新数据时产生的SQL是:




      
       
       
       
             update
         
       
                 products
         
       
             set
         
       
                 version=?,
         
       
                 name=?,
         
       
                 description=?,
         
       
                 unitCost=?,
         
       
                 pubTime=?
         
       
             where
         
       
                 id=?
         
       
                 and version=?
         
       
      


    当Hibernate更新一个Product对象,会根据它的id和version属性到相应的数据库表中定位匹配的记录,如果存在这条匹配的记录,就更新记录,并且把version字段的值加1。若找不到匹配的记录,此时Hibernate会抛出StaleObjectStateException。


      


    需要注意的是,由于乐观锁定是使用系统中的程序来控制,而不是使用数据库中的锁定机制,因而如果有人故意自行更新版本信息来超过检查,则锁定机制就无效。所以建议把持久化类中的version的get方法设置为private的。


    5.1.2.      
    使用时间戳(<timestamp>)



    跟版本检查的用法相似。不再多说。


    5.2.       悲观锁(Pessimistic Locking)


    悲观锁假定当前事务操纵数据资源时,肯定还会有其他事务同时访问该数据资源,为了避免当前事务的操作受到干扰,先锁定资源。尽管悲观锁能够防止丢失更新和不可重复读这类并发问题,但是它影响并发性能,因此应该很谨慎地使用悲观锁。


    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1874599

      
      
       
       

         
       

         
       
      
    复制代码
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-25 11:51 , Processed in 0.316736 second(s), 36 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表