技术开发 频道

使用Ruby配备Oracle数据库

【IT168 技术文档】{$PageTitle=Ruby:软件开发 Gem}
由于动态脚本编写语言提供了强大的特性并支持快速开发,因此使用它们的人越来越多。最近,人们对 Ruby on Rails 框架的兴趣将 Ruby 推到了软件开发社区的前列。然而,Ruby 不仅可用于 Web 开发,它还为数据库专业人员提供了强大、简洁、灵活的脚本编写语言。本文深入研究了 Ruby 的某些功能以及如何通过 Oracle 数据库利用这些功能。

Ruby:软件开发 Gem

尽管整个软件开发领域对 Ruby on Rails Web 开发框架的兴趣与日俱增,但 Ruby 才诞生了 10 多年。它是一种面向对象的动态脚本编写语言,具有许多内置特性、丰富的库以及公认的跟踪记录。

Ruby 共享了其他动态脚本编写语言(如 Perl 和 Python)的许多优点,包括对正则表达式以及动态创建变量和方法的强大支持。对于那些以前主要使用静态类型语言(如 Java、C++ 和 C#)的人们,Ruby 提供了一个了解如何使用动态语言编写代码的好方法。Ruby 远比 Perl 更易于读写。

我的同事 Cameron Hendricks 说,“Perl 是编程语言中的拉斯维加斯。”动态语言有时会在运行时给您带来意外。这些意外有时是优点,您感觉像中了大奖;有时是缺点,其中某些意外甚至很难调试。Ruby 提供了 Perl 的许多潜在优点,但没有 Perl 那么多的缺点。对我而言,Ruby 的可预见性更强,不太像代码开发赌博。

本文介绍如何交互地使用 Ruby 以及与以前编写的脚本协同使用,以支持与数据库相关的常用函数。本文中示例的设计初衷不仅仅是介绍 Ruby 如何支持与 Oracle 数据库有关的简单脚本编写,还要演示 Ruby 的某些简单但强大的特性。

获得 Ruby。 针对基于 Microsoft Windows 的平台,特别容易下载和安装 Ruby。RubyGems 是一个工具,用于获取和安装更新、扩展以及额外的 Ruby 库。

运行 Ruby。 下载并安装了 Ruby 之后,可通过两种主要的方法开始。第一种方法是编写脚本文件(扩展名通常为 .rb)并使用 ruby 命令运行这些文件。第二种方法是使用交互式 Ruby(使用 irb 命令)从命令行交互地运行 Ruby 命令。

如果不想安装 Ruby,但仍想体验某些基本的 Ruby 语法,请参见交互式 Ruby 主页。虽然您不能在该站点上运行本文中与数据库相关的示例,但可以使用 Ruby 任何基本的内置函数和语法。

你好,Ruby!

Ruby 随取随用并且没有任何特殊的语法,它充当了一个不错的计算器,尤其是在您使用交互式 Ruby (irb) 时,如图 1 所示。图 1 和图 2 演示了如何通过两种不同的提示风格(默认的和简单的)运行交互式 Ruby。图 1 演示了 Ruby 的某些数学运算符,图 2 演示了 Ruby 的某些字符操作符以及字符串和数值格式之间的转换。您还可以在 Ruby 主页上试用图 1 和图 2 中 irb 执行的操作。

图1:Ruby 解释器和 Ruby 数学运算
图2:Ruby 解释器和 Ruby 字符串操作

Ruby 提供了一些对于编写和维护脚本非常有用的全局常数。图 3 显示了其中某些正在运行的预定义全局常数。这三个全局常数指明了这个特定 Ruby 解释器的底层平台、版本和发布日期。此处并未显示,但您还可以利用另一个预定义的全局常数 ENV 查看操作系统中设置的环境变量。在本文后面,您还将了解如何使用散列版本的 ENV。

图3:有用的 Ruby 常数

图 4 显示了交互式 Ruby 的另一个非常有用的特性。您在该图中看不到单击 TAB 键的操作,因此只能相信我的话:在 IRB 提示符处输入 0,后面跟一个句点,然后单击 TAB 键两次。然后,系统会显示针对 0 或其他数值的方法。可将这看作与 Java 集成开发环境 (IDE)(如 JDeveloper)中的方法名称完成类似。您会看到针对数字的 ceiling (ceil)、floor 以及 round 方法以及转换方法,如 to_s(转换成字符串)和 to_a(转换成数组)。

图4:Ruby IRB tab 完成


Ruby 与 Oracle 数据库交互

为了使 Ruby 能够与我们的 Oracle 数据库通信,需要使用 Ruby/OCI8。可从 http://rubyforge.org/projects/ruby-oci8/ 获得。尽管我们可以用交互式 IRB 编写自己的 Ruby 代码来访问数据库,但这种方法比较费事。较好的方法是:将我们的数据库访问代码编写成一个 Ruby 脚本文件,然后用 ruby 解释器命令运行该 Ruby 脚本文件。

Ruby 与 Oracle DDL SQL。 我们首先创建一个用于创建新表的 Ruby 脚本文件。我们需要声明与 Ruby/DBI 结合使用的是一个 require 语句,然后使用 Ruby/DBI API。清单 1 显示以上内容和 CREATE 语句。

清单1:createStatesTable.rb

            

require 'dbi'

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

dbh.do("CREATE TABLE states (

id CHAR(2) PRIMARY KEY,

name VARCHAR2(15) NOT NULL,

capital VARCHAR2(25) NOT NULL)")

dbh.disconnect

清单 1 中的大多数代码都是您在 SQL*Plus 或 SQL Developer 中用于创建一个新数据库表的 SQL 代码。数据库连接字符串包括 DBI:OCI8:ORCL 字符串以及用户名和口令(本示例中为 hr/hr)。DBI 部分是标准内容,OCI8 部分指的是 Ruby/OCI8 驱动程序,ORCL 部分指的是数据库服务。可以使用简单的 createStatesTable.rb 命令在命令行执行这个 createStatesTable.rb 文件。如果一切顺利,则不会提供任何反馈,但该表将处于 HR 模式中。

继续介绍 Ruby 的其他数据库操作之前,需要指出 ruby 解释器命令允许您检查脚本的语法而无需实际运行它。例如,如果要测试 createStatesTable.rb 脚本中的语法而不实际创建该表,我可以使用 -c 选项,如下: ruby -c createStatesTable.rb.

清单 1 中与 Ruby 有关的另一件有趣的事情是未定义变量 dbh 的数据类型。与其他动态脚本编写语言一样,Ruby 不需要声明变量的数据类型,而是根据变量的上下文来假定所需的数据类型。在其余的 Ruby 代码清单中,我们将看到更多这样的“动态类型 (duck typing)”— 如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。

Ruby 与 Oracle DML SQL。 创建了一个 STATES 表之后,我们来填充它。我们将向该表中插入几个代表性的州名,如清单 2 所示。

清单2:populateStatesTable.rb

            

require 'dbi'

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

sqlInsert = "INSERT INTO states (id, name, capital)

VALUES (?, ?, ?)"

dbh.do(sqlInsert, "AL", "Alabama", "Birmingham")

dbh.do(sqlInsert, "AZ", "Arizona", "Phoenix")

dbh.do(sqlInsert, "CO", "Colorado", "Denver")

dbh.do(sqlInsert, "FL", "Florida", "Tallahassee")

dbh.do(sqlInsert, "MA", "Maine", "Augusta")

dbh.do(sqlInsert, "PA", "Pennsylvania", "Philadelphia")

dbh.do(sqlInsert, "UT", "Utah", "Salt Lake City")

dbh.do(sqlInsert, "WA", "Washington", "Seattle")

dbh.do(sqlInsert, "WY", "Wyoming", "Cheyenne")

dbh.commit

dbh.disconnect

清单 2 中有一些需要了解的 Ruby/DBI 新特性。首先,注意问号在 INSERT 语句中用作占位符。这很有用,因为它允许创建该语句一次,然后针对插入到表中的每个代表性州名进行填充。由于 INSERT 语句是一个 DML 语句(与清单 1 中的 DDL CREATE 语句不同),因此在该清单中还需要一个 COMMIT 调用。

执行了这两个 Ruby 脚本之后,您就具有了一个名为 STATES 的数据库表,其中填充了几个州。现在,假设您在一些州的首府上犯了一些典型错误,甚至弄混了一个州的缩写(用 MA 代表 Maine,实际上 MA 代表 Massachusetts)。

清单 3 显示了用以修复这些问题的简单 UPDATE 脚本。清单 3 用占位符更新州首府并直接更新 Maine 的错误缩写,而不使用任何占位符或任何类型的语句准备。

清单3:updateStatesTable.rb

            

require 'dbi'

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

sqlCapitalsUpdate = "UPDATE states SET capital = ? WHERE id = ?"

dbh.do(sqlCapitalsUpdate, "Montgomery", "AL")

dbh.do(sqlCapitalsUpdate, "Harrisburg", "PA")

dbh.do(sqlCapitalsUpdate, "Olympia", "WA")

dbh.do("UPDATE states SET id = 'ME' WHERE name = 'Maine'")

dbh.commit

dbh.disconnect

正如您希望的那样,DELETE 的执行方式与其同等的 DML 语句 INSERT 和 UPDATE 的执行方式非常类似。因此,您现在将转到一条 SELECT 语句,因为该语句添加了一个新技巧:数据库查询的结果集。

清单 4 演示如何在我们新填充的 STATES 表上执行一个简单查询。内置的 p 函数打印出返回的行,如图 5 所示。您可能已经使用内置的 puts 方法或 pp(“优化打印程序”)库来显示查询结果。清单 4 显示使用 # 指示有关打印查询结果的这两种方式的 Ruby 内联注释。

清单4:queryStatesTable.rb

            

require 'dbi'

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

rs = dbh.prepare('SELECT * FROM states')

rs.execute

while rsRow = rs.fetch do

p rsRow

#Alternative output: puts rsRow

#Alternative output: pp rsRow

end

rs.finish

dbh.disconnect

图5:查询结果

Ruby DBI 提供了更好的输出格式。清单 5 和 6 显示使用 DBI 分别以表格和 XML 格式输出相同的查询结果。清单 5 和 6 中的代码不仅显示了 DBI::Utils 输出格式功能的用法,还显示了与清单 4 略微不同的获取和操作结果集的方法。

清单5:queryStatesTableFormatter.rb

            

require 'dbi'

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

rs = dbh.execute('SELECT * FROM states')

rows = rs.fetch_all

column_names = rs.column_names

rs.finish

DBI::Utils::TableFormatter.ascii(column_names, rows)

dbh.disconnect

Listing 6: queryStatesTableXML.rb

require 'dbi'

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

rs = dbh.execute('SELECT * FROM states')

states_rows = rs.fetch_all

rs.finish

DBI::Utils::XMLFormatter.table(states_rows)

dbh.disconnect

图 6 和 7 分别显示了这些脚本产生的表格式输出和 XML 输出。

图6:查询结果的表格式输出
图7:XML 格式的查询结果

您现在已经利用 Ruby 和 Oracle 执行了基本的 CRUD(CREATE、RETRIEVE、UPDATE 和 DELETE)操作。现在该介绍结合使用 Ruby 和 Oracle 存储过程的示例了。

Ruby 与 Oracle 存储过程。 PL/SQL 是许多基于 Oracle 数据库的应用程序频繁使用的语言。由于用户已经对 Oracle 存储过程投入了大量的资源、时间以及从实际经验中吸取的教训,因此您使用的任何语言可以访问大量已证明的功能是很重要的。

清单 7 显示用于访问 PL/SQL 内置存储过程 DBMS_UTILITY.DB_VERSION(接受两个 OUT 参数)的代码,您的代码显示通过这两个 OUT 参数传回的结果。

清单7:builtInDBVersionCompat.rb

            

require 'dbi'

db_read_str = 'BEGIN DBMS_UTILITY.DB_VERSION(?, ?); END;'

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

sth_db = dbh.prepare(db_read_str)

sth_db.bind_param(1, ' ' * 50) # allow for up to 50 chars

sth_db.bind_param(2, ' ' * 50) # allow for up to 50 chars

sth_db.execute

version = sth_db.func(:bind_value, 1)

puts "Oracle DB Version: " + version

compatibility = sth_db.func(:bind_value, 2)

puts "Oracle DB Compatibility: " + compatibility

dbh.disconnect

图 8 显示了运行该 Ruby 代码的显示结果。

图8:Ruby 访问内置 DBMS_UTILITY.DB_VERSION 的结果

清单 7 还有另一个需要注意的有趣地方。该清单演示了 Ruby 符号(本例中为 :bind_value)的使用,还显示了使用 DBI::Handle(本例中名为 sth_db)函数方法调用特定于该 Ruby DBI 数据库驱动程序的功能。

清单 8 中的代码演示了使用 Ruby DBI 运行内置的存储函数 DBMS_METADATA.GET_DDL。由于多方面的原因,这个内置函数很有用,下面有关如何获取 Ruby DBI 中某个函数的返回值的示例就是其中一个原因。该函数需要将对象类型和对象名与几个可选参数一起传入。在这种情况下,您只传入两个必需的参数,并使用名称批注传递它们。

清单8:builtInGetDDL.rb

            

require 'dbi'

db_read_str = 'BEGIN :out1 := DBMS_METADATA.GET_DDL(object_type=>:in1, '

db_read_str += 'name=>:in2); END;'

puts db_read_str

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

sth_db = dbh.prepare(db_read_str)

sth_db.bind_param("out1", ' ' * 20000 )

sth_db.bind_param("in1", "TABLE")

sth_db.bind_param("in2", "STATES")

sth_db.execute

puts sth_db.func("bind_value", "out1")

dbh.disconnect

DBMS_METADATA.GET_DDL 函数可返回创建我们指定的表所需的 DDL。注意,这是有用的信息,因为它为我们提供了我们重新创建表时将需要的 DDL。返回的 DDL 明显比我们的 CREATE TABLE 语句更具体,因为它显式说明了我们假定的设置。查看这些结果可以更深入地了解 Oracle 数据库的工作,帮助数据库专业人员决定要更改和调整的参数。

除了显示如何访问 Oracle 存储函数的返回值之外,清单 8 还显示了与我们先前使用的不同的绑定变量的方法。在之前具有绑定变量的清单(清单 7)中,我们使用的是位置绑定,这意味着您将问号 (?) 放在数据库执行字符串中,并使用从 1 开始的连续整数来引用值。在清单 8 中,您使用的是名称绑定,这意味着我们放入的是 Ruby 符号(如本例中的 :out1、:in1 和 :in2),稍后通过名称引用这些绑定变量以访问其值。Ruby 对位置绑定与名称绑定的支持与 PL/SQL 对位置参数批注和命名参数批注的支持类似。图 9 显示了运行清单 8(用于创建我们的 STATES 表的 DDL)的结果。

图9:DBMS_METADATA.GET_DDL 中的 DDL create 语句


Ruby 与 Oracle:并非所有一切都包括在数据库中

Oracle 专业人员的兴趣并不仅限于 Oracle 数据库;值得关注的还包括配置文件、环境设置,甚至其他 Oracle 产品(如 Oracle 应用服务器)。我们来看几个将 Ruby 用于这些领域的示例。

Ruby 与环境。 使用 Ruby 可以轻松访问操作系统的环境变量。清单 9 显示了一个为用户提供他们定义的环境变量列表的简单的 Ruby 程序。

清单9:displayOracleENV.rb

            

ENV.each {|key ,value| print key, "=", value, "\n"}

这个单行代码使用每种方法对 ENV 散列进行迭代,每行打印一个环境变量及其值。这个单行示例进一步证明了 Ruby 语言的强大和简洁。

Ruby 与操作系统文件。 Oracle 数据库专业人员访问各种配置和其他文件(如 init.ora 文件)的内容是很有用的。Ruby 使得查看该文件中的键值设置非常简单,如清单 10 所示。

清单10:displayInitOra.rb

            

=begin

This Ruby script demonstrates several Ruby features while performing the

functionality of returning values and their setting from the init.ora file.

This multiline comment block is demarcated by the =begin and =end.

Normal, single-line comments are also shown in this code (pound sign).

=end

fileName = ARGV.to_s

# example of if statement

if fileName.size < 1

print "Enter init.ora path and name: "

fileName = gets.chomp!

end

#example of unless statement

unless fileName.size > 1

fileName = "C:\\oracle\\product\\10.1.0\\admin\\orcl\\init.ora"

end

print "File Name: " + fileName + "!"

theFile = File.new(fileName, "r") # read the file

text = theFile.read # copy file's contents into string

theFile.close # string holds file contents; close file

regExp = Regexp.new('^.+=.+$')

puts text.scan(regExp)

清单 10 演示了 Ruby 的若干特性。可能该清单中演示的最重要的 Ruby 特性是使用 Regexp 处理正则表达式。通过所需模式 (^.+=.+$) 实例化 Regexp 实例以匹配字符串。该正则表达式应用于字符串的 scan 方法,返回与所提供的正则表达式匹配的字符串中的子字符串。与 Perl 一样,Ruby 提供对正则表达式的强大支持。

清单 10 还演示了从命令行和基于控制台的用户提示符获得输入。代码首先在命令行使用预定义的 Ruby 变量 ARGV,尝试获取文件路径和文件名。如果不奏效,代码然后提示用户输入路径。如果用户不提供路径和文件名,则使用硬编码的路径(包括文件名)。这些条件语句还显示 if 语句以及与 Perl 类似的 unless 语句。

清单 10 中的代码显示正在运行的 chomp! 方法。String 方法用于删除从用户控制台输入中检索到的基于字符串的文件名末尾的记录分隔符。如果该操作失败,尝试根据该文件名打开一个 File 对象时,gets 输入中的新行将引发一些麻烦。Ruby 方法上的惊叹号表明该方法正在执行有可能对对象危险的操作。同样,Ruby 支持以问号 (?) 结束的方法,指示“疑问”方法返回真或假结果。

本节的最后一个示例是关于一个简单的 Ruby 脚本,该脚本比较两个 init.ora 文件并报告它们之间的差异。清单 11 使用许多与清单 10 中相同的方法,但是读入作为命令行参数传入的两个 init.ora 文件的内容。

清单11:diffInitOra.rb

            

=begin

This script compares two files and returns from each file what it has that

the other provided file does not have. Original order of either file does

not matter.

=end

require 'pp'

unless ARGV.size == 2

print "Usage: ruby arrayMagic.rb firstFile secondFile"

exit

end

firstFile = File.new(ARGV[0], "r")

secondFile = File.new(ARGV[1], "r")

regExp = Regexp.new('^.+=.+$')

text1 = firstFile.read.scan(regExp)

text2 = secondFile.read.scan(regExp)

firstFile.close

pp firstFile

puts text1-text2, "\n"

pp secondFile

puts text2-text1

secondFile.close

本示例显示使用 pp 库打印出文件的名称,如果其中一个文件未打开,则显示该文件关闭的事实。最重要的是,该代码清单显示了 Ruby 正在运行的强大的减法运算符。只需在从文件中读取的两个字符串之间使用一个减号,就可以轻松确定两个文件之间的差异。

对与清单 11 有关的示例而言,我将一个已有的 init.ora 文件复制到另一个副本中,然后更改该副本中的参数 (open_cursors, db_name, job_queue_processes)。为使事情更有趣,我还重新排列了 init.ora 文件的第二个副本中参数的顺序。图 10 显示了对这两个 init.ora 文件运行清单 11 中的代码的结果。这段简单的代码执行了所有繁重的任务,准确返回了我们所知道的文件中的差异,甚至对第二个文件进行了重新排列。

图10:两个 init.ora 文件之间的差异

正如这些示例所显示的那样,Ruby 简化壮大了文件操作。Ruby 同样可以与脱机输出以及其他对 Oracle 专业人员有用的文件类型一起使用。


Ruby 异常处理

Ruby 具有一些可用于脚本和面向对象的应用程序的强大特性。其中一个特性是 Ruby 的异常处理。清单 12 包含使用 Ruby 的异常处理机制的示例。

清单12:rubyExceptions.rb

            

=begin

This script is similar to Listing 10, but demonstrates Ruby exception handling

using functionality originally demonstrated in Listing 10.

=end

fileName = ARGV.to_s

#obtain file name if not provided on command-line

if fileName.size < 1

print "Enter init.ora path and name: "

fileName = gets.chomp!

end

#ensure something has been provided for file name

begin

if fileName.size < 1

raise ArgumentError, "No valid file name provided" + "\n"

end

rescue ArgumentError => argErr

print argErr

raise # re-raise this exception and force script termination

end

#get file contents

begin #Begin exception handling block for file I/O

theFile = File.new(fileName, "r")

text = theFile.read

theFile.close

rescue IOError

print "I/O Error: Problem accessing file " + fileName + "\n"

exit

rescue Errno::ENOENT

print "ENOENT: Cannot find file " + fileName + "\n"

exit

rescue Errno::EPERM

print "EPERM: Insufficient rights to open " + fileName + "\n"

raise

rescue Exception # Catch-all: More exceptions captured than with "rescue" alone

print "Generic error rescued (captured) during file I/O attempt." + "\n"

raise

else

print "Good news! There was no problem with file I/O for " + fileName + "\n"

ensure

print "Good or bad, file handling attempt is now complete!\n"

end #End exception handling block for file I/O

#obtain text string for regular expression

print "Enter regular expression pattern: "

pattern = gets.chomp!

begin #Begin exception handling block for regular expression

regExp = Regexp.new(pattern)

rescue RegexpError

print "Problem with regular expression " + regExp.to_s + "\n"

exit # Nothing to be done with a bad regular expression

ensure

print "Regular expression evaluated was: " + regExp.to_s + "\n"

end #End exception handling block for regular expression

puts text.scan(regExp)

清单 12 演示了使用 begin 关键字标记可以引发(抛出)异常的代码块的起始位置。该清单中使用关键字 raise 供脚本引发(抛出)自己的异常。关键字 rescue 多次出现,与 Java 的 catch 关键字类似。Ruby 的 ensure 关键字与 Java 的 finally 关键字类似,在该代码清单中用于执行功能,无论是否遇到异常。

如清单 12 所示,Ruby 还支持 else 关键字涵盖以下情况下应该执行的功能:如果未针对由 begin 和 end 指示的特定代码块抛出任何异常。代码清单的注释进一步解释了 Ruby 的异常处理功能。

图 11 显示了运行清单 12 中的脚本三次的结果。第一次运行该脚本时,故意提供了一个未知的文件名以调用文件处理异常处理。第二次运行该脚本时用正确的文件名演示 else 块的执行,并允许执行转至与正则表达式处理相关的代码块。第二次执行该脚本时,提供了一个错误的表达式以演示 RegexpError 的处理。第三次运行该脚本时演示了脚本的完整运行,不引发异常。所有这三次运行都演示了 ensure 块的执行,因为无论是否引发(抛出)异常,所有异常处理过程中始终调用该块。

图11:正在运行的 Ruby 异常

Ruby DBI 异常处理。 最后一部分介绍了 Ruby 异常处理。Ruby/DBI 提供了自己的异常处理层次结构,如清单 13 所示。实际上,较常见的是只捕获“database catch all”异常 DBI::DatabaseError 并访问清单 13 中所显示的属性,以打印出数据库错误代码和错误消息。

清单13:rubyDbExceptions.rb

            

=begin

Listing13-rubyDbExceptions.rb

This script demonstrates the use of exceptions with DBI::DatabaseError

and database-related exception classes that inherit from DBI::DatabaseError.

Often, it is enough to capture DatabaseError, but this example shows all

of the inherited exception classes. DatabaseError is rescued last so that

a more particular database-related exception might be rescued first.

This "try" block of this code includes an "infinite" while loop that will

open connections without closing them until a DBI::DatabaseError is forced.

=end

require 'dbi'

begin

counter = 0

while 0 # "infinite" loop because 0 resolves to "true" for Ruby conditional

dbh = DBI.connect('DBI:OCI8:ORCL', 'hr', 'hr')

counter += 1

puts "DB Connection #" + counter.to_s + "\n"

#Intentionally NOT closing with dbh.close to force DatabaseError.

end

rescue DBI::DataError => dataErr

dbh.rollback

puts "DB error due to problem with data"

puts "Error Code: #{dataErr.err}"

puts "Error Message: #{dataErr.errstr}"

puts "DB rollback.\n"

rescue DBI::IntegrityError => integErr

# Example: Trying to insert same value for unique column twice.

dbh.rollback

puts "DB error due to integrity problem."

puts "Error Code: #{integErr.err}"

puts "Error Message: #{integErr.errstr}"

puts "DB rollback.\n"

rescue DBI::InternalError => internErr

dbh.rollback

puts "DB error database internal error."

puts "Error Code: #{internErr.err}"

puts "Error Message: #{internErr.errstr}"

puts "DB rollback.\n"

rescue DBI::NotSupportedError => notSuppErr

dbh.rollback

puts "DB feature not supported."

puts "Error Code: #{notSuppErr.err}"

puts "Error Message: #{notSuppErr.errstr}"

puts "DB rollback.\n"

rescue DBI::OperationalError => opErr

dbh.rollback

puts "DB error due to problems with operation of database."

puts "Error Code: #{opErr.err}"

puts "Error Message: #{opErr.errstr}"

puts "DB rollback.\n"

rescue DBI::ProgrammingError => dbProgErr

# Example: Bad column name in SQL statement.

dbh.rollback

puts "DB error due to programming problem.\n"

puts "Error Code: #{dbProgErr.err}"

puts "Error Message: #{dbProgErr.errstr}"

puts "DB rollback.\n"

rescue DBI::DatabaseError => dbErr

# Catch-all for all database exceptions.

dbh.rollback

puts "Database exception encountered."

puts "Error Code: #{dbErr.err}"

puts "Error Message: #{dbErr.errstr}"

puts "DB rollback."

rescue DBI::InterfaceError => ifError

dbh.rollback

puts "Problem with DBI interface encountered."

rescue RuntimeError

dbh.rollback

puts "Unknown error (not DB or DBI) encountered."

else

puts "DB commit.\n"

dbh.commit

ensure

puts "Disconnecting database handler."

dbh.disconnect

end

清单 13 中显示的其他 DBI 异常包括那些派生自 DBI::DatabaseError 的异常,以及用于捕获与 DBI 接口而非数据库有关的异常的 DBI::InterfaceError 异常。

清单 13 中的代码将导致抛出一个异常,因为存在一个“while 0”循环(Ruby 中 0 的值为“true”),该循环将重复执行,直至抛出异常。由于在抛出 DBI::DatabaseError 之前重复调用 DBI.connect 而没有正确关闭打开的连接,因此的确会抛出该异常。在这种情况下,显示的错误代码为 12520(特定于 Oracle 的数据库错误代码),错误字符串为“ORA-12520:TNS:listener could not find available handler for requested type of server.”这些输出代码和字符串值是使用适当的 DBI::DatabaseError 属性(err 和 errstr)输出的。

如果用一个数据库操作(如 SELECT 语句)替换清单 13 中的 while 循环(故意强制数据库错误),rescue、else 和 ensure 块将和清单 12 中的对应块一样被激活。如果仍出现任何异常(如完整性约束),将调用适当的“rescue”块。如果没有遇到指定了 rescue 块的异常,“else”块中的代码将执行并提交事务。在任何情况下,无论是否抛出异常,都将执行“ensure”代码块,并相应地断开与处理程序的连接。

编写 Ruby 脚本时,不一定需要捕获 (rescue) 异常(考虑 Java 的非强制异常)。清单 12 之前的所有未进行异常处理的代码清单都对此进行了阐释。然而,如果您知道可能出现某些异常并希望您的脚本针对这些情况进行某些处理(如立即退出或者打印出某些与异常相关的详细信息),Ruby 可以简化异常处理。如果您不想将 rescue-else-ensure 块与抛出异常的代码块相关联,该脚本将突然停止该代码块的执行,将显示常规异常信息,执行下一个代码块。

图 12 显示了 Ruby 的一些主要异常。图 13 显示了 Ruby DBI 异常。除了这些标准异常之外,您始终可以创建和使用自己的 Ruby 异常。

图12:所选的 Ruby 异常
图13:Ruby/DBI 异常

由于 Ruby(与 Java 一样)是一种面向对象的语言,因此我能够用 Oracle JDeveloper 10g 的 UML 建模工具创建创建图 12 和 13 中的类图。由于 DatabaseError 属性用在代码清单中,因此在图 13 中专门调用这些属性。

0
相关文章