`
dreamer_0924
  • 浏览: 179936 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

数据库事务及Java中的处理

阅读更多

事 务是指一组相互依赖的操作行为,举一个举得不能再被举的例子就是银行交易,当然还有其他像网上购物,电子货币交易等等,事务的成功取决于这些相互依赖的操 作行为是否都能执行成功,只要有一个操作行为失败,就意味着整个事务失败。例如:bill和tom的QQ账户的QQ币点数都是500点,现在bill把 100点QQ币转到tom的QQ币账号上,这个事务就包含以下操作行为:
     ---- bill的QQ币账号减少100点
     ---- tom的QQ币账号增加100点

     这两个操作作为一个不可分割的工作单元,假如仅仅第一步操作执行成功,但是第二步执行失败,那么整个事务失败,回滚到事务开始前的状态,bill和tom 的QQ账户的QQ币点数还依然是500点。显然如果没有事务的概念,那么就会造成bill的100点QQ币神秘的消失了...
     数据库的事务就是上面提到的事务在RDB中的实现,它由一组在业务逻辑上相互依赖的SQL语句组成,假设以上QQ账户在DB中的qq_account表结构如下:
        ------------------------------------   
             id       name      balance
        ------------------------------------
             1         bill           500
             2         tom          500
        ------------------------------------

     以上事务的SQL可以表示为:
        UPDATE qq_account SET balance=400 WHERE id=1;
        UPDATE qq_account SET balance=600 WHERE id=2;

    只要两条SQL语句有一个执行失败,整个事务就失败,qq_account表中的数据就必须回退到最初的状态,而不会被更新。
    数据库事务必须具备ACID特性,具体的含义如下:

     ----- Atomic(原子性):只整个数据库事务是不可分割的工作单元。只有事务中所有的操作执行成功,才算整个事务成功;事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。通过上面的例子我们已经看得很清楚了。

     ----- Consistency(一致性):指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如,不管上面的例子里面QQ币转账的事务成功还是失败,都应该保证事务结束后qq_account表中bill和tom的QQ币总额为1000点。

     ----- Isolation(隔离性):指的是在并发的环境之中,当不同的事务同时操作相同的数据时,每个事务都有各自的完整数据空间,这里涉及的事情就多了,我想在后面单独总结成一篇文章。

     ----- Durability(持久性):指的是只有事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库后,数据库还能恢复到事务成功结束时的状态。           


事务的ACID只是一个抽象的概念,具体是由RDBMS来实现的。数据库管理系统用日志来保证事务的原子性、一致性和持久性。日志记录了事务 对数据库所做的更新,如果某个事务在执行过程中发生了错误,就可以根据日志,撤销事务对数据库已经做的更新,使数据库回退到执行事务前的初始状态。所以不 论讲到什么数据库,都会有专门的一章来讲日志,呵呵,终于从本质上明白了日志的作用。

     至于事务的隔离性,RDBMS则是采用锁机制来实现的。当多个事务同时更新数据库中的临界数据时,只允许持有锁的事务才能更新该数据,其他事务必须等待, 直到前一个数据释放了锁,其他事务才可能有机会来进行更新,这和我们在OS中学的进程的并发时所谈到的锁机制原理差不多。

    上面提到了数据库事务的ACID特性,那么谁来保证数据库事务具有ACID呢?其实,只要向数据库系统声明一个事务,数据库系统就会自动保证事务的ACID特性的。那么下面从抽象的概念上来看看怎么声明事务:
     -- BEGIN 事务的开始边界
     -- COMMIT 事务的正常结束边界,提交事务,永久保存被事务更新后的数据库状态。
     -- ROLLBACK 事务的异常结束边界,撤销事务,使数据退回到执行事务前的初始状态。

     数据库系统支持以下两种事务模式:
     -- 自动提交模式:每个SQL语句都是一个独立的事务,当数据库系统执行完一个SQL语句后,会自动提交事务。
     -- 手工提交事务:必须由数据库的客户端显式指定事务开始边界和结束边界。

     以MySQL为例,如果我们启动一个MySQL客户端,就会得到一个独立的数据库连接。每个数据库连接都有一个全局变量@@autocommit,表示当前的事务模式,它有两个可选值:

     -- 0:表示手工提交模式。
     -- 1:默认值,表示自动提交模式。

     可以通过SQL命令 SELECT @@autocommit 来查看当前的事务模式。如果想要把当前的事务模式改为手工提交模式,可以使用以下SQL命令 SET autocommit=0 来实现。

     还要多说几句,在MySQL中,数据库表分为三种类型:INNODB、BDB、MyISAM。其中前两种类型的表支持数据库事务,而MyISAM类型的表 不支持事务。在使用CREATE TABLE命令创建表时默认的类型为MyISAM,如果希望更改的话可以通过如下DLL来进行:
    
     ALTER TABLE table_name TYPE=INNODB;
     如果希望在创建时就指定表的类型,可以通过以下DLL来进行:

     CREATE TABLE table_name(
         .....
         .....
     ) TYPE=INNODB;

    好了,下面我们来看几个在实践应用程序中声明事务的模板。
  
   1.在MySQL.exe客户端程序中声明事务
     我们以一条插入语句为例,当然如果是自动提交模式的话,每一个SQL语句都是一个单独的事务,我们直接运行以下语句就可以了:
       mysql>INSERT INTO qq_account VALUES('leon', 1000);

    下面我们看看在手动模式下的命令:
       mysql>SET autocommit=0;
       mysql>BEGIN;
       mysql>INSERT INTO qq_account VALUES('leon', 1000);
       mysql>COMMIT;

     这样一个事务就完成了,下面我们看一看如何撤销一个事务,很简单也许你已经猜到了吧
       mysql>BEGIN;
       mysql>INSERT INTO qq_account VALUES('leon', 1000);
       mysql>ROLLBACK;

   2.在JDBC API中声明事务
    没有什么难度,我们直接来看看代码:
      Connection con = null;
      Statement stmt = null;
      try {
         con = DriverManager.getConnection(dbUrl,dbUser,dbPwd);
         //设置手工提交事务模式
         con.setAutoCommit(false);
         stmt = con.createStatement();
         stmt.executeUpdate("......");
         //提交事务
         con.commit();
      } catch(SQLException e) {
         if(con != null) {
            try {
               //操作不成功则撤销事务
               con.rollback();     
            } catch(SQLException e) {
               ......
            }
         }
         ......
      } finally {
         if(con != null) {
            try{
               if(stmt != null) {
                  try {
                     stmt.close();
                  } catch(SQLException e) {
                     ......
                  }
               }
               con.close();
            } catch(SQLException e) {
               ......
            }
         }
      }
     没有什么可再说的了,代码详细得已经像是一篇技术文章了,需要注意的是对于新建的Connection实例来说,在默认的情况下采用自动提交的事务模式, 通过方法 con.setAutoCommit(false)来设置手动提交模式。

   3.在Hibernate API中声明事务
     Hibernate封装了JDBC API和JTA API,尽管应用程序可以绕过Hibernate API,直接通过JDBC API和JTA API来声明事务,但是这不利于跨平台的开发,下面我们只考虑Hibernate API的实现:
      Configuration config = new Configuration().configure();
      SessionFactory factory = config.buildSessionFactory();
      Session session = factory.openSession();
      Transation tx = null;
      try {
           //开始一个事务
           tx = session.beginTransaction();
           Account account = new Account("leon",1000);
           session.save(account);
           //提交事务
           tx.commit();
      } catch(HibernateException e) {
            if(tx != null) {
                try {
                   //操作不成功则撤销事务
                   tx.rollback();
                } catch(HibernateException e) {
                   ......
                }
             }
             ......
      } finally {
             if(session != null) {
                try {
                   session.close();
                } catch(HibernateException e) {
                   ......
                }
             }
      }
     上面的代码和JDBC API很相像,值得注意的是,在任何时候,一个Session只允许有一个未提交的事务。以下代码对于一个Session同时声明了两个未提交的事务,这是不允许的:
        tx1 = session.beginTransaction();
        tx2 = session.beginTransaction();
           ......
        tx1.commit();
        tx2.commit();
    

       关于事务并发问题,当初在学习DB2的时候,就一直想弄明白,可是当时是为了应试,老师也没有讲得太明白,所以一直决心要总结一下,后来呢,就一直拖到了今天...闲话少讲了,开始正题吧!

     在并发的环境之中,一个数据库系统会同时为各种各样的客户端提供服务,对于同时运行的多个事务,当这些事务访问数据库中相同的数据时如果没有采取必要的隔离机制,就会出现各种并发问题,可以把这些问题归纳为一下五种:

      ---- 第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。
      ---- 脏读:一个事务读到了另一个事务未提交的更新数据。
      ---- 虚读:一个事务读到了另一个事务已经提交的新插入的数据。
      ---- 不可重复读:一个事务读到另一个事务已提交的更新数据。
      ---- 第二类丢失更新:一个事务覆盖另一个事务已提交的更新数据,这是不可重复读的一个特例。

     这样的定义实在太过死板,而且让人也很难理解,还是通过一个例子来仔细的分析一下每一种并发问题,以下引用孙卫琴老师的《精通Hibernate:JAVA对象持久化技术详解》里面的例子,个人认为这个例子很不错。

     假设现在一所银行有一个取款事务和一个支票转账事务操作同一个银行账户的情形,首先我们假设两个事务顺序执行,而不是并发执行,那麽整个的步骤如下:
      ---- 银行客户在银行前台请求取款100元,出纳员先查询账户信息,得知存款余额为1000元。
      ---- 出纳员判断存款余额大于取款额,支付给客户100元,并将账户上的存款余额改为900元。
      ---- 出纳员处理一张转账支票,该支票向此账户汇入100元,出纳员先查询账户信息,得知存款余额为900元。
      ---- 出纳员将存款余额改为1000元。

     可以很清楚的看到,如果顺序的执行这两个事务,不会出现任何问题,可是现实之中并非如此简单,如果两个事务分别由两个出纳员同时执行那麽可能就会出现并发的问题,下面我们分别来介绍:

1.第一类丢失更新
            
  时 间   取款事务                                  转账事务
 --------------------------------------------------------------------------------------------
     T1     开始事务
     T2                                                      开始事务
     T3     查询账户余额为1000元                               
     T4                                                      查询账户余额为1000元
     T5                                                      汇入100元把余额改为1100元
     T6                                                      提交事务
     T7     取出100元把余额改为900元
     T8     撤销事务余额恢复为1000元
 ----------------------------------------------------------------------------------------------
     如果按照以上的时间次序并发执行的话,由于支票转账事务对存款余额所做的更新被取款事务的撤销所覆盖,所以客户会损失100元。

2.脏 读

            
   时 间    取款事务                                  转账事务
 ----------------------------------------------------------------------------------------------
      T1      开始事务
      T2                                                      开始事务
      T3     查询账户余额为1000元                               
      T4                                  
      T5     取出100元把余额改为900元                             
      T6                                                      查询账户余额为900元(脏读)
      T7     撤销事务余额恢复为1000元
      T8                                                      汇入100元把余额改为1000元
      T9                                                      提交事务
 ----------------------------------------------------------------------------------------------
     由于支票转账事务查询了取款事务未提交的更新数据,并且在这个查询结果的基础上进行了更新操作,如果取款事务最后被撤销 ,会导致银行客户损失100元。

3.虚 读
  下面我们另举一个例子来看看什么是虚读。
  时 间    注册事务                       统计事务
 ----------------------------------------------------------------------------------------------
     T1     开始事务
     T2                                          开始事务
     T3                                          统计注册客户总数为10000人 

     T4     注册一个新用户                            
     T5     提交事务                          
     T6                                          统计注册客户总数为10001人
     T7                                          到底哪一个统计数据有效?
-----------------------------------------------------------------------------------------------

     统计事务无法相信查询的结果,因为查询结果是不确定的,随时可能被其他事务改变。      
     对于实际应用,在一个事务中不会对相同的数据查询两次,假定统计事务在T3时刻统计注册客户的总数,执行SELECT语句,在T6时刻不再查询数据库,而 是直接打印统计结果为10000,这个统计结果与数据库当中数据是有所出入的,确切的说,它反映的是T3时刻的数据状态,而不是当前的数据状态。应该根据 实际需要来决定是否允许虚读。以上面的统计事务为例,如果仅仅想大致了解一下注册客户总数,那麽可以允许虚读;如果在一个事务中,会依据查询结果来做出精 确的决策,那麽就必须采取必要的事务隔离措施,避免虚读。

4.不可重复读

   时 间    取款事务                                   转账事务
----------------------------------------------------------------------------------------------
      T1      开始事务
      T2                                                      开始事务
      T3     查询账户余额为1000元                                                            
      T4                                                      查询账户余额为1000元
      T5     取出100元把余额改为900元                             
      T6     提交事务                           
      T7                                                     查询账户余额为900元
      T8                                                     余额到底是1000元还是900元?
-----------------------------------------------------------------------------------------------     

如上面所示,如果支票转账事务两次查询账户的存款余额,但得到了不同的查询结果,这使得银行出纳员无法相信查询结果,因为查询结果是不确定的,随时可能被其他事务改变。

5.第二类丢失更新
            
   时 间    取款事务                                  转账事务
 ----------------------------------------------------------------------------------------------
      T1      开始事务
      T2                                                      开始事务
      T3     查询账户余额为1000元                               
      T4                                                      查询账户余额为1000元
      T5     取出100元把余额改为900元                             
      T6     提交事务                           
      T7     
      T8                                                      汇入100元把余额改为1100元
      T9                                                      提交事务
 ----------------------------------------------------------------------------------------------
     每个事务都不知道其他事务的存在,最后一个事务对记录所做的更新将覆盖由其他事务对记录所做的已提交的更新。上面的例子里由于支票转账事务覆盖了取款事务 对存款余额所做的更新,导致银行最后损失了100元,哈哈,你是不是开始喜欢第二类丢失更新了呢?
     怎样才能消除这些并发问题呢?请听下回.......  

分享到:
评论

相关推荐

    java代码-使用java解决数据库事务处理的源代码

    java代码-使用java解决数据库事务处理的源代码 ——学习参考资料:仅用于个人学习使用!

    java模拟数据库事务

    Java模拟数据库事务主要涉及到几个关键知识点,这些知识点在软件开发中尤其在处理数据一致性与并发控制时至关重要。首先,我们来逐一深入理解这些技术。 1) **Socket编程**:Socket是网络通信的基础,它提供了进程...

    java中数据库事务的处理

    在Java中,JDBC(Java Database Connectivity)提供了一种直接与数据库交互的方式,同时也支持事务处理。例如,通过设置Connection对象的autoCommit属性为false,可以手动控制事务的开始、提交和回滚。下面是一个...

    java多线程处理数据库数据

    通过以上方法,我们可以在Java中有效地利用多线程处理数据库数据,提高程序的并发能力和效率。记得在设计时充分考虑线程间的协作与同步,以及数据库连接的管理和优化,以确保程序的稳定性和性能。

    java 数据库操作,事务回滚

    java 应用程序操作数据库实例,若出现sql异常则事务回滚。

    java数据库驱动jar包

    在Java中,JDBC驱动程序提供了一个标准的API,使得开发人员能够使用SQL语句与各种数据库进行交互。这个压缩包包含了用于连接到SQL Server 2005的Java驱动以及JDTS驱动,它们都是实现JDBC规范的不同类型。 首先,让...

    oracle 根据数据库自动生成JAVA代码

    "Oracle根据数据库自动生成JAVA代码"这一技术就是将数据库中的表结构映射到Java编程语言中的实体类、DAO(Data Access Object)、Service、Controller以及前端展示层的HTML模板,从而减少手动编写这些基础代码的工作...

    java数据库驱动包

    在Java中,JDBC(Java Database Connectivity)是一个标准API,允许程序员用Java语言来访问和处理存储在各种数据库中的数据。这个压缩包包含了对MySQL、Oracle和DB2这三种流行数据库的JDBC驱动,它们分别是Java与...

    java连接数据库 java 数据库连接 数据库 java连接

    在Java编程语言中,连接数据库是一项基础且至关重要的任务,特别是在构建服务器端应用程序时。Java提供了多种方式来实现这一目标,主要通过Java Database Connectivity (JDBC) API。JDBC是Java平台的一个标准接口,...

    神通数据库java连接驱动.zip

    在实际开发中,我们还需要考虑异常处理、连接池的使用、事务管理等高级话题,以确保程序的健壮性和性能。例如,使用像C3P0或HikariCP这样的连接池库,可以在多线程环境中更有效地管理和重用数据库连接。同时,对于...

    图书管理系统(数据库课程设计java)

    在处理数据库操作时,Java可能使用了JDBC(Java Database Connectivity)接口,它允许Java程序连接到各种类型的数据库,并执行SQL语句。 此外,为了实现数据持久化,系统可能还采用了ORM(对象关系映射)框架,如...

    java 多线程操作数据库

    在当今高度并发的应用环境中,Java多线程技术被广泛应用于处理数据库操作,以提升系统的响应速度和处理能力。本文将基于一个具体的Java多线程操作数据库的应用程序,深入探讨其背后的原理、实现细节以及潜在的挑战。...

    Java中数据库事务处理的代码清单.pdf

    以下是一份简化的Java中数据库事务处理的代码清单,以及相关的知识点解析。 1. **连接数据库**: - 在`ConnectAccess.java`类中,通过`Class.forName()`方法加载数据库驱动,这里是JDBC的Access驱动。`url`属性...

    java多个数据库实现数据同步

    在Java中,实现多数据库同步的方法有很多,以下是一些常用的技术和策略: 1. **触发器和存储过程**:在每个数据库中设置触发器,当数据发生变化时,触发器会调用存储过程,将更改发送到其他数据库。然而,这种方法...

    SQLlite数据库查看工具和操作数据库代码(JAVA)

    接下来,关于Java中操作SQLite数据库,主要使用SQLite JDBC驱动。以下是关键步骤和概念: 1. **添加依赖**:在项目中引入SQLite JDBC驱动库,通常通过Maven或Gradle进行管理。 2. **建立连接**:使用`java.sql....

    java各种数据库jar包

    通过这个驱动,开发者可以在Java应用中创建数据库连接,执行SQL语句,处理结果集等。确保将这个驱动添加到项目的类路径中,以便Java程序可以找到并使用它。 3. **sqljdbc4.jar** 是针对Microsoft SQL Server的JDBC...

    数据库加java实现的超市管理系统

    在这个系统中,数据库用于存储超市的各种数据,如商品信息、库存、销售记录等,而Java作为后端开发语言,负责处理业务逻辑、与数据库交互以及提供接口给前端应用。 **数据库部分** 数据库在系统中扮演着核心角色,...

    java事务处理总结

    本文将从多个角度对Java中的事务处理进行深入解析,帮助开发者更好地理解和运用这一技术。 #### 一、事务的基本概念 事务(Transaction)是指一系列的操作作为一个完整的单元来执行。这些操作要么全部成功完成,要么...

    事务处理java案例代码

    在Java中,我们通常使用JDBC(Java Database Connectivity)来处理数据库事务。以下是一些关键步骤: 1. 连接数据库:通过DriverManager.getConnection()方法建立数据库连接。 2. 创建Statement或...

    oracle 在java中的事务处理和异常回滚。

    在Java编程中,Oracle...总的来说,Oracle在Java中的事务处理和异常回滚是通过JDBC接口实现的,它保证了数据库操作的原子性、一致性、隔离性和持久性。理解并熟练掌握这些概念对于开发健壮的Java应用程序至关重要。

Global site tag (gtag.js) - Google Analytics