JDBC数据库编程.ppt
Java实用教程第11章 JDBC数据库编程第11章 JDBC数据库编程11.1 JDBC概述11.2 JDBC中的常用接口11.3 连接数据库11.4 操作数据库10.5 网 络 聊 天11.5 应用JDBC事务11.2 JDBC中的常用接口JDBC提供了众多的接口和类,通过这些接口和类,可以实现与数据库的通信,本节将详细介绍一些常用的JDBC接口和类。11.2.1 Driver接口 每种数据库的驱动程序都应该提供一个实现java.sql.Driver接口的类,简称Driver类,在加载某一驱动程序的Driver类时,它应该创建自己的实例并向java.sql.DriverManager类注册该实例。通常情况下通过java.lang.Class类的静态方法forName(String className),加载欲连接数据库的Driver类,该方法的入口参数为欲加载Driver类的完整路径。成功加载后,会将Driver类的实例注册到DriverManager类中,如果加载失败,将抛出ClassNotFoundException异常,即未找到指定Driver类的异常。11.2.2 DriverManager类java.sql.DriverManager类负责管理JDBC驱动程序的基本服务,是JDBC的管理层,作用于用户和驱动程序之间,负责跟踪可用的驱动程序,并在数据库和驱动程序之间建立连接;另外,DriverManager类也处理诸如驱动程序登录时间限制及登录和跟踪消息的显示等工作。成功加载Driver类并在DriverManager类中注册后,DriverManager类即可用来建立数据库连接。当调用DriverManager类的getConnection()方法请求建立数据库连接时,DriverManager类将试图定位一个适当的Driver类,并检查定位到的Driver类是否可以建立连接,如果可以则建立连接并返回,如果不可以则抛出SQLException异常。DriverManager类DriverManager类提供的常用静态方法如下表所示:方 法 名 称功 能 描 述getConnection(String url,String user,String password)用来获得数据库连接,3个入口参数依次为要连接数据库的URL、用户名和密码,返回值的类型为java.sql.ConnectionsetLoginTimeout(int seconds)用来设置每次等待建立数据库连接的最长时间setLogWriter(java.io.PrintWriter out)用来设置日志的输出对象println(String message)用来输出指定消息到当前的JDBC日志流11.2.3 Connection接口 java.sql.Connection接口代表与特定数据库的连接,在连接的上下文中可以执行SQL语句并返回结果,还可以通过getMetaData()方法获得由数据库提供的相关信息,例如数据表、存储过程和连接功能等信息。Connection接口提供的常用方法如下表所示。方 法 名 称功 能 描 述createStatement()创建并返回一个Statement实例,通常在执行无参的SQL语句时创建该实例prepareStatement()创建并返回一个PreparedStatement实例,通常在执行包含参数的SQL语句时创建该实例,并对SQL语句进行了预编译处理prepareCall()创建并返回一个CallableStatement实例,通常在调用数据库存储过程时创建该实例setAutoCommit()设置当前Connection实例的自动提交模式。默认为true,即自动将更改同步到数据库中;如果设为false,需要通过执行commit()或rollback()方法手动将更改同步到数据库中getAutoCommit()查看当前的Connection实例是否处于自动提交模式,如果是则返回true,否则返回falsesetSavepoint()在当前事务中创建并返回一个Savepoint实例,前提条件是当前的Connection实例不能处于自动提交模式,否则将抛出异常releaseSavepoint()从当前事务中移除指定的Savepoint实例setReadOnly()设置当前Connection实例的读取模式,默认为非只读模式。不能在事务当中执行该操作,否则将抛出异常。有一个boolean型的入口参数,设为true表示开启只读模式,设为false表示关闭只读模式isReadOnly()查看当前的Connection实例是否为只读模式,如果是则返回true,否则返回falseisClosed()查看当前的Connection实例是否被关闭,如果被关闭则返回true,否则返回falsecommit()将从上一次提交或回滚以来进行的所有更改同步到数据库,并释放Connection实例当前拥有的所有数据库锁定rollback()取消当前事务中的所有更改,并释放当前Connection实例拥有的所有数据库锁定。该方法只能在非自动提交模式下使用,如果在自动提交模式下执行该方法,将抛出异常。有一个参数为Savepoint实例的重载方法,用来取消Savepoint实例之后的所有更改,并释放对应的数据库锁定close()立即释放Connection实例占用的数据库和JDBC资源,即关闭数据库连接11.2.4 Statement接口 java.sql.Statement接口用来执行静态的SQL语句,并返回执行结果。例如,对于INSERT、UPDATE和DELETE语句,调用executeUpdate(String sql)方法;对于SELECT语句,则调用executeQuery(String sql)方法,并返回一个永远不能为null的ResultSet实例。Statement接口提供的常用方法如下表所示。方 法 名 称功 能 描 述executeQuery(String sql)执行指定的静态SELECT语句,并返回一个永远不能为null的ResultSet实例executeUpdate(String sql)执行指定的静态INSERT、UPDATE或DELETE语句,并返回一个int型数值,此数为同步更新记录的条数clearBatch()清除位于Batch中的所有SQL语句。如果驱动程序不支持批量处理将抛出异常addBatch(String sql)将指定的SQL命令添加到Batch中。String型入口参数通常为静态的INSERT或UPDATE语句。如果驱动程序不支持批量处理将抛出异常executeBatch()执行Batch中的所有SQL语句,如果全部执行成功,则返回由更新计数组成的数组,数组元素的排序与SQL语句的添加顺序对应。数组元素有以下几种情况:大于或等于零的数:说明SQL语句执行成功,此数为影响数据库中行数的更新计数;:说明SQL语句执行成功,但未得到受影响的行数:说明SQL语句执行失败,仅当执行失败后继续执行后面的SQL语句时出现。如果驱动程序不支持批量、或者未能成功执行Batch中的SQL语句之一,将抛出异常close()立即释放Statement实例占用的数据库和JDBC资源11.2.5 PreparedStatement接口 java.sql.PreparedStatement接口继承并扩展了Statement接口,用来执行动态的SQL语句,即包含参数的SQL语句。通过PreparedStatement实例执行的动态SQL语句将被预编译并保存到PreparedStatement实例中,从而可以反复并且高效地执行该SQL语句。需要注意的是,在通过setXxx()方法为SQL语句中的参数赋值时,建议利用与参数类型匹配的方法,也可以利用setObject()方法为各种类型的参数赋值。PreparedStatement接口的使用方法如下:PreparedStatement接口提供的常用方法如下表所示。PreparedStatement ps=connection .prepareStatement(select*from table_name where id?and(name=?or name=?);ps.setInt(1,6);ps.setString(2,马先生);ps.setObject(3,李先生);ResultSet rs=ps.executeQuery();方 法 名 称功 能 描 述executeQuery()执行前面定义的动态SELECT语句,并返回一个永远不能为null的ResultSet实例executeUpdate()执行前面定义的动态INSERT、UPDATE或DELETE语句,并返回一个int型数值,为同步更新记录的条数SetInt(int i,int x)为指定参数设置int型值,对应参数的SQL类型为INTEGERsetLong(int i,long x)为指定参数设置long型值,对应参数的SQL类型为BIGINTsetFloat(int i,float x)为指定参数设置float型值,对应参数的SQL类型为FLOATsetDouble(int i,double x)为指定参数设置double型值,对应参数的SQL类型为DOUBLEsetString(int i,String x)为指定参数设置String型值,对应参数的SQL类型为VARCHAR或LONGVARCHARsetBoolean(int i,boolean x)为指定参数设置boolean型值,对应参数的SQL类型为BITsetDate(int i,Date x)为指定参数设置java.sql.Date型值,对应参数的SQL类型为DATEsetObject(int i,Object x)用来设置各种类型的参数,JDBC规范定义了从Object类型到SQL类型的标准映射关系,在向数据库发送时将被转换为相应的SQL类型setNull(int i,int sqlType)将指定参数设置为SQL中的NULL。该方法的第二个参数用来设置参数的SQL类型,具体值从java.sql.Types类中定义的静态常量中选择clearParameters()清除当前所有参数的值11.2.6 CallableStatement接口 java.sql.CallableStatement接口继承并扩展了PreparedStatement接口,用来执行SQL的存储过程。JDBC API定义了一套存储过程SQL转义语法,该语法允许对所有RDBMS通过标准方式调用存储过程。该语法定义了两种形式,分别是包含结果参数和不包含结果参数的形式,如果使用结果参数,则必须将其注册为OUT型参数,参数是根据定义位置按顺序引用的,第一个参数的索引为1。为参数赋值的方法使用从PreparedStatement类中继承来的setXXX()方法。在执行存储过程之前,必须注册所有OUT参数的类型,它们的值是在执行后通过getXxx()方法获得的。CallableStatement接口可以返回一个或多个ResultSet对象。处理多个ResultSet对象的方法是从Statement中继承来的。11.2.7 ResultSet接口 java.sql.ResultSet接口类似于一个数据表,通过该接口的实例可以获得检索结果集,以及对应数据表的相关信息,例如列名和类型等,ResultSet实例通过执行查询数据库的语句生成。ResultSet实例具有指向当前数据行的指针,最初,指针指向第一行记录,通过next()方法可以将指针移动到下一行,如果存在下一行该方法则返回true,否则返回false,所以可以通过while循环来迭代ResultSet结果集。默认情况下ResultSet实例不可以更新,只能移动指针,所以只能迭代一次,并且只能按从前向后的顺序。如果需要,可以生成可滚动和可更新的ResultSet实例。ResultSet接口 ResultSet接口提供了从当前行检索不同类型列值的getXxx()方法,均有两个重载方法,分别根据列的索引编号和列的名称检索列值,其中以列的索引编号较为高效,编号从1开始。对于不同的getXxx()方法,JDBC驱动程序尝试将基础数据转换为与getXxx()方法相应的Java类型并返回。在JDBC 2.0 API之后,为该接口添加了一组更新方法updateXxx(),均有两个重载方法,分别根据列的索引编号和列的名称指定列。可以用来更新当前行的指定列,也可以用来初始化要插入行的指定列,但是该方法并未将操作同步到数据库,需要执行updateRow()或insertRow()方法完成同步操作。ResultSet接口提供的常用方法如下表所示方 法 名 称功 能 描 述first()移动指针到第一行。如果结果集为空则返回false,否则返回true。如果结果集类型为TYPE_FORWARD_ONLY 将抛出异常last()移动指针到最后一行。如果结果集为空则返回false,否则返回true。如果结果集类型为TYPE_FORWARD_ONLY 将抛出异常previous()移动指针到上一行。如果存在上一行则返回true,否则返回false。如果结果集类型为TYPE_FORWARD_ONLY 将抛出异常next()移动指针到下一行。指针最初位于第一行之前,第一次调用该方法将移动到第一行。如果存在下一行则返回true,否则返回falsebeforeFirst()移动指针到ResultSet实例的开头,即第一行之前。如果结果集类型为TYPE_FORWARD_ONLY 将抛出异常afterLast()移动指针到ResultSet实例的末尾,即最后一行之后。如果结果集类型为TYPE_FORWARD_ONLY 将抛出异常absolute()移动指针到指定行。有一个int型参数,正数表示从前向后编号,负数表示从后向前编号,编号均从1开始。如果存在指定行则返回true,否则返回false。如果结果集类型为TYPE_FORWARD_ONLY 将抛出异常relative()移动指针到相对于当前行的指定行。有一个int型入口参数,正数表示向后移动,负数表示向前移动,视当前行为0。如果存在指定行则返回true,否则返回false。如果结果集类型为TYPE_FORWARD_ONLY 将抛出异常getRow()查看当前行的索引编号。索引编号从1开始,如果位于有效记录行上则返回一个int型索引编号,否则返回0findColumn()查看指定列名的索引编号。该方法有一个String型参数,为要查看列的名称,如果包含指定列,则返回int型索引编号,否则将抛出异常isBeforeFirst()查看指针是否位于ResultSet实例的开头,即第一行之前。如果是则返回true,否则返回falseisAfterLast()查看指针是否位于ResultSet实例的末尾,即最后一行之后。如果是则返回true,否则返回falseisFirst()查看指针是否位于ResultSet实例的第一行。如果是则返回true,否则返回falseisLast()查看指针是否位于ResultSet实例的最后一行。如果是则返回true,否则返回falseclose()立即释放ResultSet实例占用的数据库和JDBC资源,当关闭所属的Statement实例时也将执行此操作getInt()以int型获取指定列对应SQL类型的值。如果列值为NULL,则返回值0getLong()以long型获取指定列对应SQL类型的值。如果列值为NULL,则返回值0getFloat()以float型获取指定列对应SQL类型的值。如果列值为NULL,则返回值0方 法 名 称功 能 描 述getDouble()以double型获取指定列对应SQL类型的值。如果列值为NULL,则返回值0getString()以String型获取指定列对应SQL类型的值。如果列值为NULL,则返回值nullgetBoolean()以boolean型获取指定列对应SQL类型的值。如果列值为NULL,则返回值falsegetDate()以java.sql.Date型获取指定列对应SQL类型的值。如果列值为NULL,则返回值nullgetObject()以Object型获取指定列对应SQL类型的值。如果列值为NULL,则返回值nullgetMetaData()获取ResultSet实例的相关信息,并返回ResultSetMetaData类型的实例。updateNull()将指定列更改为NULL。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateInt()更改SQL类型对应int型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateLong()更改SQL类型对应long型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateFloat()更改SQL类型对应float型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateDouble()更改SQL类型对应double型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateString()更改SQL类型对应String型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateBoolean()更改SQL类型对应boolean型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateDate()更改SQL类型对应Date型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步updateObject()可更改所有SQL类型的指定列。用于插入和更新,但并不会同步到数据库,需要执行updateRow()或insertRow()方法完成同步moveToInsertRow()移动指针到插入行,并记住当前行的位置。插入行实际上是一个缓冲区,在插入行可以插入记录,此时,仅能调用更新方法和insertRow()方法,通过更新方法为指定列赋值,通过insertRow()方法同步到数据库,在调用insertRow()方法之前,必须为不允许为空的列赋值moveToCurrentRow()移动指针到记住的位置,即调用moveToInsertRow()方法之前所在的行insertRow()将插入行的内容同步到数据库。如果指针不在插入行上,或者有不允许为空的列的值为空,将抛出异常updateRow()将当前行的更新内容同步到数据库。更新当前行的列值后,必须调用该方法,否则不会将更新内容同步到数据库deleteRow()删除当前行。执行该方法后,并不会立即同步到数据库,而是在执行close()方法后才同步到数据库11.3 连接数据库 在访问数据库时,首先要加载数据库的驱动程序,不过只需在第一次访问数据库时加载一次;然后在每次访问数据库时创建一个Connection实例;紧接着执行操作数据库的SQL语句,并处理返回结果;最后在完成此次操作时销毁前面创建的Connection实例,释放与数据库的连接。11.3.1 加载JDBC驱动程序 在与数据库建立连接之前,必须先加载欲连接数据库的驱动程序到JVM(Java虚拟机)中,加载方法为通过java.lang.Class类的静态方法forName(String className);成功加载后,会将加载的驱动类注册给DriverManager类;如果加载失败,将抛出ClassNotFoundException异常,即未找到指定的驱动类,所以需要在加载数据库驱动类时捕捉可能抛出的异常。通常情况下将负责加载数据库驱动的代码放在static块中,因为static块的特点是只在其所在类第一次被加载时执行,即第一次访问数据库时执行,这样就可以避免反复加载数据库驱动,减少对资源的浪费,同时提高了访问数据库的速度。【例11-1】11.3.2 创建数据库连接 java.sql.DriverManager(驱动程序管理器)类是JDBC的管理层,负责建立和管理数据库连接。通过DriverManager类的静态方法getConnection(String url,String user,String password)可以建立数据库连接,3个参数依次为欲连接数据库的路径、用户名和密码,该方法的返回值类型为java.sql.Connection。【例11-2】11.3.3 执行SQL语句 建立数据库连接(Connection)的目的是与数据库进行通信,实现方法为执行SQL语句,但是通过Connection实例并不能执行SQL语句,还需要通过Connection实例创建Statement实例,Statement实例又分为3种类型:(1)Statement实例:该类型的实例只能用来执行静态的SQL语句;(2)PreparedStatement实例:该类型的实例增加了执行动态SQL语句的功能;(3)CallableStatement实例:该类型的实例增加了执行数据库存储过程的功能。上面给出三种不同的类型中Statement是最基础的;PreparedStatement继承Statement,并做了相应的扩展;而CallableStatement继承了PreparedStatement,又做了相应的扩展。在11.4节将详细介绍各种类型实例的使用方法。11.3.4 获得查询结果 通过Statement接口的executeUpdate()或executeQuery()方法,可以执行SQL语句,同时将返回执行结果,如果执行的是executeUpdate()方法,将返回一个int型数值,代表影响数据库记录的条数,即插入、修改或删除记录的条数;如果执行的是executeQuery()方法,将返回一个ResultSet型的结果集,其中不仅包含所有满足查询条件的记录,还包含相应数据表的相关信息,例如每一列的名称、类型和列的数量等。11.3.5 关闭连接 在建立Connection、Statement和ResultSet实例时,均需占用一定的数据库和JDBC资源,所以每次访问数据库结束后,应该及时销毁这些实例,释放它们占用的所有资源,方法是通过各个实例的close()方法,执行close()方法时建议按照如下的顺序:resultSet.close();statement.close();connection.close();建议按上面的顺序关闭的原因在于Connection是一个接口,close()方法的实现方式可能多种多样。关闭连接 如果是通过DriverManager类的getConnection()方法得到的Connection实例,在调用close()方法关闭Connection实例时会同时关闭Statement实例和ResultSet实例。但是通常情况下需要采用数据库连接池,在调用通过连接池得到的Connection实例的close()方法时,Connection实例可能并没有被释放,而是被放回到了连接池中,又被其他连接调用,在这种情况下如果不手动关闭Statement实例和ResultSet实例,它们在Connection中可能会越来越多,虽然JVM的垃圾回收机制会定时清理缓存,但是如果清理得不及时,当数据库连接达到一定数量时,将严重影响数据库和计算机的运行速度,甚至导致软件或系统瘫痪。11.4 操作数据库 访问数据库的目的是操作数据库,包括向数据库插入记录或修改、删除数据库中的记录,或者是从数据库中查询符合一定条件的记录,这些操作既可以通过静态的SQL语句实现,也可以通过动态的SQL语句实现,还可以通过存储过程实现,具体采用的实现方式要根据实际情况而定。在增、删、改数据库中的记录时,分为单条操作和批量操作,单条操作又分为一次只操作一条记录和一次只执行一条SQL语句,批量操作又分为通过一条SQL语句(只能是UPDATE和DELETE语句)操作多条记录和一次执行多条SQL语句。11.4.1 添加数据 在添加记录时,一条INSERT语句只能添加一条记录。如果只需要添加一条记录,通常情况下通过Statement实例完成。【例11-3】【例11-4】【例11-5】【例11-6】【例11-7】11.4.2 查询数据 在查询数据时,既可以利用Statement实例通过执行静态SELECT语句完成,也可以利用PreparedStatement实例通过执行动态SELECT语句完成,还可以利用CallableStatement实例通过执行存储过程完成。(1)利用Statement实例通过执行静态SELECT语句查询数据的典型代码如下:(2)利用PreparedStatement实例通过执行动态SELECT语句查询数据的典型代码如下:ResultSet rs=statement.executeQuery(select*from tb_record where sex=男);String sql=select*from tb_record where sex=?;PreparedStatement prpdStmt=connection.prepareStatement(sql);prpdStmt.setString(1,男);ResultSet rs=prpdStmt.executeQuery();查询数据(3)利用CallableStatement实例通过执行存储过程查询数据的典型代码如下:无论利用哪个实例查询数据,都需要执行executeQuery()方法,这时才真正执行SELECT语句,从数据库中查询符合条件的记录,该方法将返回一个ResultSet型的结果集,在该结果集中不仅包含所有满足查询条件的记录,还包含相应数据表的相关信息,例如每一列的名称、类型和列的数量等。【例11-8】【例11-9】String call=call pro_record_select_by_sex(?);CallableStatement cablStmt=connection.prepareCall(call);cablStmt.setString(1,男);ResultSet rs=cablStmt.executeQuery();11.4.3 修改数据 在修改数据时,即可以利用Statement实例通过执行静态UPDATE语句完成,也可以利用PreparedStatement实例通过执行动态UPDATE语句完成,还可以利用CallableStatement实例通过执行存储过程完成。(1)利用Statement实例通过执行静态UPDATE语句修改数据的典型代码如下:(2)利用PreparedStatement实例通过执行动态UPDATE语句修改数据的典型代码如下:String sql=update tb_record set salary=3000 where duty=部门经理;statement.executeUpdate(sql);String sql=update tb_record set salary=?where duty=?;PreparedStatement prpdStmt=connection.prepareStatement(sql);prpdStmt.setInt(1,3000);prpdStmt.setString(2,部门经理);prpdStmt.executeUpdate();修改数据(3)利用CallableStatement实例通过执行存储过程修改数据的典型代码如下:无论利用哪个实例修改数据,都需要执行executeUpdate()方法,这时才真正执行UPDATE语句,修改数据库中符合条件的记录,该方法将返回一个int型数,为被修改记录的条数。【例11-10】【例11-11】String call=call pro_record_update_salary_by_duty(?,?);CallableStatement cablStmt=connection.prepareCall(call);cablStmt.setInt(1,3000);cablStmt.setString(2,部门经理);cablStmt.executeUpdate();11.4.4 删除数据 在删除数据时,既可以利用Statement实例通过执行静态DELETE语句完成,也可以利用PreparedStatement实例通过执行动态DELETE语句完成,还可以利用CallableStatement实例通过执行存储过程完成。(1)利用Statement实例通过执行静态DELETE语句删除数据的典型代码如下:String sql=delete from tb_merchandise where date2008-2-14;statement.executeUpdate(sql);删除数据(2)利用PreparedStatement实例通过执行动态DELETE语句删除数据的典型代码如下:注意:当需要为日期型参数赋值时,如果已经存在java.sql.Date型对象,可以通过setDate(int parameterIndex,java.sql.Date date)方法为日期型参数赋值;如果不存在java、sql、Date型对象,也可以通过setString(int parameterIndex,String x)方法为日期型参数赋值。String sql=delete from tb_merchandise where date?;PreparedStatement prpdStmt=connection.prepareStatement(sql);prpdStmt.setString(1,2008-2-14);/为日期型参数赋值prpdStmt.executeUpdate();删除数据(3)利用CallableStatement实例通过执行存储过程删除数据的典型代码如下:无论利用哪个实例删除数据,都需要执行executeUpdate()方法,这时才真正执行DELETE语句,删除数据库中符合条件的记录,该方法将返回一个int型数,为被删除记录的条数。【例11-12】【例11-13】String call=call pro_merchandise_delete_by_date(?);CallableStatement cablStmt=connection.prepareCall(call);cablStmt.setString(1,2008-2-14);/为日期型参数赋值cablStmt.executeUpdate();11.5 应用JDBC事务 所谓事务,是指一组相互依赖的操作单元的集合,用来保证对数据库的正确修改,保持数据的完整性,如果一个事务的某个单元操作失败,将取消本次事务的全部操作。例如银行交易、股票交易和网上购物等,都需要利用事务来控制数据的完整性,比如将A账户的资金转入B账户,在A中扣除成功,在B中添加失败,导致数据失去平衡,事务将回滚到原始状态,即A中没少,B中没多。应用JDBC事务 数据库事务必须具备以下特征(简称ACID):(1)原子性(Atomic):每个事务是一个不可分割的整体,只有所有的操作单元执行成功,整个事务才成功;否则此次事务就失败,所有执行成功的操作单元必须撤销,数据库回到此次事务之前的状态;(2)一致性(Consistency):在执行一次事务后,关系数据的完整性和业务逻辑的一致性不能被破坏。例如A与B转账结束后,他们的资金总额是不能改变的;应用JDBC事务(3)隔离性(Isolation):在并发环境中,一个事务所做的修改必须与其他事务所做的修改相隔离。例如一个事务查看的数据必须是其他并发事务修改之前或修改完毕的数据,不能是修改中的数据;(4)持久性(Durability):事务结束后,对数据的修改是永久保存的,即使系统故障导致重启数据库系统,数据依然是修改后的状态。数据库管理系统采用锁的机制来管理事务。当多个事务同时修改同一数据时,只允许持有锁的事务修改该数据,其他事务只能“排队等待”,直到前一个事务释放其拥有的锁。【例11-14】