首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

10.4  连接到外部数据库

Ruby可连接到各种数据库——从Oracle等大型系统到小型的MySQL,这要感谢很多人的开发工作。出于完整性考虑,这里也包括CSV。

这些包提供的功能将不断变化,务必参阅包含最新信息的在线参考手册,Ruby Application Archive是个不错的起点。

10.4.1  连接到SQLite

对于欣赏零配置软件的人来说,SQLite是个受欢迎的数据库。它是一个自包含的小型可执行文件,是用C语言编写的,能够在单个文件中处理整个数据库。虽然它通常用于小型数据库,但也能够处理几个TB(terabyte)的数据。

Ruby的SQLite绑定比较简单。SQLite::API类封装了C API,由于封装基本上是一对一的且面向对象程度不高,因此仅当绝对必要时才应直接使用该API。

在大多数情况下,SQLite::Database类就能满足要求,下面是一段简短的示例代码:

require 'sqlite'

db = SQLite::Database.new("library.db")

db.execute("select title,author from books") do |row|

  p row

end

db.close

# Output:

# ["The Case for Mars", "Robert Zubrin"]

# ["Democracy in America", "Alexis de Tocqueville"]

# ...

如果没有指定代码块,execute将返回一个ResultSet实例(实际上是一个可迭代的游标)。

rs = db.execute("select title,author from books")

rs.each {|row| p row }    # Same results as before

rs.close

如果返回一个ResultSet,将由客户端负责关闭它(如上面的示例代码所示)。如果需要遍历记录列表多次,可调用reset方法以重新开始(这项功能还处于试验阶段,可能发生变化)。另外,如果愿意,也可使用next和eof?执行生成器风格的迭代(外部迭代)。

rs = db.execute("select title,author from books")

while ! rs.eof?

  rec = rs.next

  p rec                      # Same results as before

end

rs.close

这个库的方法可能引发大量异常,所有这些异常都是SQLite::Exception的子类,因此很容易捕捉任何异常或所有异常。

有必要简单提一下的是,这个库能够同Ara Howard开发的ArrayFields库(这里没有介绍)进行互操作。它让数组元素可以通过名称和整数进行索引或访问。如果在sqlite库前请求(require)了arrayfields库,则可通过名称或数字来索引ResultSet对象(然而,也将其配置为返回散列)。

虽然sqlite库的功能相当完善,但它可能没有提供程序员所需的所有功能,因为SQLite本身还没完全实现SQL92标准。有关SQLite或其Ruby绑定的更详细信息,请在网上搜索。

10.4.2  连接到MySQL

MySQL接口是Ruby中最稳定、功能最齐全的数据库接口之一,它是个扩展,必须在Ruby和MySQL都已安装并正常运行后安装。

安装它后,使用该模块包含三个步骤:首先在脚本中加载该模块,然后连接数据库,最后处理表。连接时需要提供主机、用户名、密码和数据库等常见参数。

require 'mysql'

m = Mysql.new("localhost","ruby","secret","maillist")

r = m.query("SELECT * FROM people ORDER BY name")

r.each_hash do |f|

  print "#{f['name']} - #{f['email']}"

end

# Output looks like:

# John Doe - jdoe@rubynewbie.com

# Fred Smith - smithf@rubyexpert.com

类方法Mysql.new和MysqlRes.each_hash及实例方法query都很有用。

正如README文件描述的,该模块由4个类(Mysql、MysqlRes、MysqlField和MysqlError)组成。这里将总结一些有用的方法,但读者可通过文档找到更详细的信息。

类方法Mysql.new接受多个字符串作为参数(它们的默认值都是nil),并返回一个连接对象。参数分别是host、user、passwd、db、port、sock和flag。new的别名包括real_connect和connect。

方法create_db、select_db和drop_db都接受一个数据库名作为参数,它们的用法如下所示。方法close关闭到服务器的连接。

m=Mysql.new("localhost","ruby","secret")

m.create_db("rtest")     # Create a new database

m.select_db("rtest2")    # Select a different database

m.drop_db("rtest")       # Delete a database

m.close                    # Close the connection

在最新的版本中,create_db和drop_db已被摒弃,但通过如下方式定义它们可使其仍管用:

class Mysql

  def create_db(db)

    query("CREATE DATABASE #{db}")

  end

  def drop_db(db)

    query("DROP DATABASE #{db}")

  end

end

方法list_dbs以数组形式返回一个可用数据库名列表。

dbs = m.list_dbs       # ["people","places","things"]

query接受一个字符串作为参数,并默认返回一个MysqlRes对象。它也可能返回一个Mysql对象,这取决于query_with_result是如何设置的。

发现错误时,可通过errno获取错误号;而error返回实际的错误消息。

begin

  r=m.query("create table rtable

   (

      id int not null auto_increment,

      name varchar(35) not null,

      desc varchar(128) not null,

      unique id(id)

   )")

# exception happens...

rescue

  puts m.error

  # Prints: You have an error in your SQL syntax

  # near 'desc varchar(128) not null ,

  #   unique id(id)

  # )' at line 5"

  puts m.errno

  # Prints 1064

  # ('desc' is reserved for descending order)

end

下面总结了MysqlRes的一些有用的实例方法:

·    fetch_fields:以MysqlField对象数组的方式返回下一行。

·    fetch_row:以字段值数组的方式返回下一行。

·    fetch_hash(with_table=false):返回一个散列,其中包含下一行的字段名和值。

·    num_rows:返回结果集中的行数。

·    each:顺序返回字段值数组的迭代器。

·    each_hash(with_table=false):顺序返回散列{fieldname => fieldvalue}的迭代器(使用x[‘field name’]可获取字段值)。

MysqlField也有一些实例方法:

·    name:返回指定字段的名称。

·    table:返回指定字段所属表的名称。

·    length:返回字段的定义长度。

·    max_length:返回结果集中最长字段的长度。

·    hash:返回一个散列,其中包含name、table、def、type、length、max_length、flags和decimals的名称和值。

请以在线文档为准,更详细的信息,请访问MySQL网站(http://www.mysql.com)和Ruby Application Archive。

10.4.3  连接到PostgreSQL

RAA提供了一个扩展,可用于访问PostgreSQL(支持PostgreSQL 6.5/7.0)。

假如已安装和配置好PostgreSQL(并创建了一个名为testdb的表),则只需遵循在Ruby中使用其他数据库接口的步骤:加载模块,连接数据库,然后处理表。可能需要执行查询、获取选定的结果以及处理事务。

require 'postgres'

conn = PGconn.connect("",5432, "", "", "testdb")

conn.exec("create table rtest ( number integer default 0 );")

conn.exec("insert into rtest values ( 99 )")

res = conn.query("select * from rtest")

# res id [["99"]]

PGconn类包含connect方法,该方法接受典型的数据库连接参数,如主机、端口、数据库、用户名和登录密码,但它还通过第三和第四参数接受选项和tty参数。在上面的例子中,已经以特权用户的身份连接到了UNIX套接字,因此不需要用户名和密码,且主机、选项和tty都为空。端口必须为整数,其他参数都是字符串。它的一个别名是方法new。

接下来需要处理表,这需要采取某种方式执行查询,为此可使用实例方法PGconn#exec和PGconn#query。

exec方法将其字符串参数作为SQL查询请求发送给PostgreSQL,如果成功则返回一个PGresult实例,否则引发PGError异常。

query方法也将其字符串参数作为SQL查询请求发送给PostgreSQL。然而,它在成功时返回一个数组。返回的数组实际上是个元组数组。如果失败,它返回nil,而错误细节可以通过调用方法error获取。

有个特殊方法可用于将值插入到指定表中,这就是insert_table。虽然名为insert_table,但它的实际意思是“插入到表中”(insert into table)。该方法返回一个PGconn对象。

conn.insert_table("rtest",[[34]])

res = conn.query("select * from rtest")

# res is [["99"], ["34"]]

上述代码将一行值插入到rtest表中。在这个简单的例子中,表开始只有一列。注意,PGResult对象res表明,更新后的结果包含两个元组。PGresult的方法将稍后讨论。

在PGconn类中,其他可能很有用的方法包括:

·    db:返回连接的数据库名。

·    host:返回连接的服务器名。

·    user:返回通过认证的用户名。

·    error:返回有关连接的错误消息。

·    finish、close:关闭后端连接。

·    loimport(file):将文件导入到大型对象中。如果成功,返回PGlarge实例;如果失败,则引发PGError异常。

·    loexport(oid, file):将大型对象oid保存到文件中。

·    locreate([mode]):如果成功则返回PGlarge实例,如果失败将引发PGError异常。

·    loopen(oid, [mode]):打开大型对象oid,如果成功则返回PGlarge实例。mode参数指定打开大型对象的模式,取值为INV_READ或INV_WRITE(如果省略,默认为INV_READ)。

·    lounlink(oid):将Postgres大型对象oid解除链接(删除)。

注意,最后5个方法(loimport、loexport、locreate、loopen和lounlink)涉及PGlarge类的对象。PGlarge类有专用于访问和修改其对象的方法(这些对象是PGconn的实例方法loimport、locreate和loopen等创建的)。

下面是PGlarge的方法:

·    open([mode]):打开一个大型对象,mode参数指定模式(与前面介绍的PGconn#loopen相同)。

·    close:关闭大型对象(当成无用单元被收集时也将关闭)。

·    read([length]):从大型对象中读取length个字节,如果没有指定length,则读取所有数据。

·    write(str):将字符串写入大型对象,并返回写入的字节数。

·    tell:返回指针的当前位置。

·    seek(offset, whence):将指针移到offset指定的位置,参数whence的可能取值为SEEK_SET、SEEK_CUR或SEEK_END(0、1、2)。

·    unlink:删除大型对象。

·    oid:返回大型对象的oid。

·    size:返回大型对象的大小。

·    export(file):将大型对象oid保存到文件中。

我们最感兴趣的是PGresult的实例方法(如下面的列表所示),PGresult对象是由查询创建的(使用完这些对象后,使用PGresult#clear释放它们占用的内容)。

·    result:返回数组中的查询结果元组。

·    each:迭代器。

·    []:存取器。

·    fields:返回由查询结果中的字段组成的数组。

·    num_tuples:返回查询结果中的元组数。

·    fieldnum(name):返回命名字段的索引。

·    type(index):返回与字段类型对应的整数。

·    size(index):返回字段的大小,以字节为单位;–1表示字段的长度可变。

·    getvalue(tup_num, field_num):返回参数指定的字段的值,其中tup_num相当于行号。

·    getlength(tup_num, field_num):返回字段长度,单位为字节。

·    cmdstatus:返回最后一条查询命令的状态字符串。

·    clear:删除PGresult对象。

10.4.4  连接到LDAP

至少有三个用于Ruby的LDAP库。Takaaki Tateishi开发的Ruby/LDAP库是个相当“瘦”的封装类。如果读者是LDAP专家,则这个库足以满足需求;如果不是专家,可能觉得它有点复杂。下面是一个例子:

conn = LDAP::Conn.new("rsads02.foo.com")

conn.bind("CN=username,CN=Users,DC=foo,DC=com","password") do |bound|

  bound.search("DC=foo,DC=com", LDAP::LDAP_SCOPE_SUBTREE,

        "(&(name=*) (objectCategory=person))", ['name','ipPhone']) do |user|

    puts "#{user['name']} #{user['ipPhone']}"

  end

end

ActiveLDAP遵循了ActiveRecord的设计模式,下面是一个使用示例,摘自其主页:

require 'activeldap'

require 'examples/objects/user'

require 'password'

# Instantiate Ruby/ActiveLDAP connection, etc

ActiveLDAP::Base.connect(:password_block => Proc.new { Password.get('Password: ') },

                    :allow_anonymous => false)

# Load a user record (Class defined in the examples)

wad = User.new('wad')

# Print the common name

p wad.cn

# Change the common name

wad.cn = "Will"

# Save it back to LDAP

wad.write

还有一个更新的由Francis Cianfrocca开发的库,有些人更喜欢这个库。下面是其用法示例:

require 'net/ldap'

ldap = Net::LDAP.new :host => server_ip_address,

      :port => 389,

      :auth => {

                  :method => :simple,

                  :username => "cn=manager,dc=example,dc=com",

                 :password => "opensesame"

      }

filter = Net::LDAP::Filter.eq( "cn", "George*" )

treebase = "dc=example,dc=com"

ldap.search( :base => treebase, :filter => filter ) do |entry|

  puts "DN: #{entry.dn}"

  entry.each do |attribute, values|

    puts "   #{attribute}:"

    values.each do |value|

      puts "      --->#{value}"

    end

  end

end

p ldap.get_operation_result

这些库哪个更好见仁见智,建议读者自己研究它们以形成自己的观点。

10.4.5  连接到Oracle

Oracle是世界上功能最强大、最流行的数据库系统之一,自然有很多人想用Ruby连接该数据库。当前最好的库是Kubo Takehiro开发的OCI8。

虽然名为OCI8,它实际上也可用于Oracle 8以后的版本。然而,由于它还不是很成熟,可能不支持较新版本的有些新特性。

OCI8的API被划分为一个“瘦”封装类(严格遵守Oracle Call Interface API的低级API)和一个高级API。在大多数情况下,程序员只需使用高级API,将来可能不再提供有关低级API的文档。

OCI8模块包含Cursor和Blob类。OCIException类是在数据库操作中可能引发的三种异常类——OCIError、OCIBreak和OCIInvalidHandle的超类。

使用OCI8.new连接到数据库,至少要传递用户名和密码,该方法将返回一个可用于执行查询的句柄。下面是一个例子:

require 'oci8'

session = OCI8.new('user', 'password')

query = "SELECT TO_CHAR(SYSDATE, 'YYYY/MM/DD') FROM DUAL"

cursor = session.exec(query)

result = cursor.fetch         # Only one iteration in this case

cursor.close

session.logoff

上面的例子对一个游标进行操作,这里只对游标执行了一次fetch,然后关闭它。当然,也可检索多行:

query = 'select * from some_table'

cursor = session.exec(query)

while row = cursor.fetch

  puts row.join(",")

end

cursor.close

# Or with a block:

nrows = session.exec(query) do |row|

  puts row.join(",")

end

查询中绑定变量看起来“像”符号。要将绑定变量和实际值相关联,有多种方式。

session = OCI8.new("user","password")

query = "select * from people where name = :name"

# One way...

session.exec(query,'John Smith')

# Another...

cursor = session.parse(query)

cursor.exec('John Smith')

# And another...

cursor = session.parse(query)

cursor.bind_param(':name','John Smith')  # bind by name

cursor.exec

# And another.

cursor = session.parse(query)

cursor.bind_param(1,'John Smith')         # bind by position

cursor.exec

对于喜欢使用DBI的人来说,有一个DBI适配器,更详细的信息请参阅OCI8的文档。

10.4.6  使用DBI封装类

从理论上说,DBI使得能够以独立于数据库的方式访问数据库,即不管底层的数据库是Oracle、MySQL、PostgreSQL还是其他数据库,都可使用相同的代码来访问。通常只需要修改一行代码,即指定要使用哪种适配器的代码。虽然有时候DBI无法执行复杂的或数据库特定的操作,但对大多数日常工作来说,这是一个很方便的工具。

假设有一个Oracle数据库,并要使用OCI8自带的驱动程序或适配器来访问它,只需将足够的信息传递给connect方法,使其能够成功地连接到数据库,以后的所有操作都很简单。

require "dbi"

db = DBI.connect("dbi:OCI8:mydb", "user", "password")

query = "select * from people"

stmt = db.prepare(query)

stmt.execute

while row = stmt.fetch do

  puts row.join(",")

end

stmt.finish

db.disconnect

在上面的例子中,可认为prepare方法编译或解析查询,然后查询被执行。fetch方法从结果集中检索一行,如果没有更多的行则返回nil(因此在上面的代码中使用了while循环)。可将finish方法视为关闭或释放连接的操作。

有关所有DBI特性的完整介绍,请参阅参考手册。有关完整的数据库驱动程序列表,请参阅RubyForge和RAA等。

10.4.7  对象关系映射器(Object-Relational Mapper,ORM)

传统的关系数据库擅长处理关系型数据,它以高效方式处理查询,而不需要预先知道即席查询的特征,但这种模型的面向对象程度不高。

这两种模型(RDBMS和OOP)都无处不在,但它们之间存在阻抗不匹配(impedance mismatch),很多人试图在这种差异之间搭建桥梁,实现这种目标的软件称为对象关系映射器(ORM)。

这种问题的解决方式很多,每种方式都有其优点和缺点。下面简要地介绍两种流行的ORM:ActiveRecord和Og(后者表示object graph(对象图))。

用于Ruby的ActiveRecord库是以Martin Fowler开发的设计模式ActiveRecord命名的。从本质上说,它将数据库表和类管理起来,这样无序使用SQL就能够直观地对数据进行操作。具体地说,它“封装数据库表或视图中的行,封装数据库访问,并添加有关这些数据的域逻辑”(参见Martin Fowler编著的《Patterns of Enterprise Application Architecture》,Addison Wesley,2003)。

通过继承ActiveRecord::Base,并对类进行定制,以描述每个表。和DBI一样,连接时必须提供足够的信息,以便能够识别并连接到数据库。下面这个简单的例子说明了其工作原理:

require 'active_record'

ActiveRecord::Base.establish_connection(:adapter => "oci8",

                                                :username => "username",

                                                :password => "password",

                                                :database => "mydb",

                                                :host => "myhost")

class SomeTable < ActiveRecord::Base

  set_table_name "test_table"

  set_primary_key "some_id"

end

SomeTable.find(:all).each do |rec|

  # process rec as needed...

end

item = SomeTable.new

item.id = 1001

item.some_column = "test"

item.save

该API功能丰富而复杂,建议读者研究网上或书籍中的教程。这个库是Ruby on Rails不可分割的一部分,介绍Ruby on Rails的资料都将介绍到它。

Og不同于ActiveRecord的地方是,后者的面向数据库程度高,而前者的面向对象程度高。Og能够根据Ruby类的定义生成数据库架构(但不能反过来)。

Og的思维方式不同,其应用没有ActiveRecord广泛。但作者认为,它包含一些有趣的特性,是一个功能强大的ORM,尤其是在根据对象设计数据库时。

要定义要存储的类,可使用property方法,它与attr_accessor类似,但需要指定相关联的类型(类)。

class SomeClass

  property :alpha, String

  property :beta, String

  property :gamma, String

end

其他数据类型包括Integer、Float、Time和Date等,属性可以是任何Ruby对象。

连接到数据库的方式与使用ActiveRecord或DBI时相同。

db = Og::Database.new(:destroy  => false,

                          :name => 'mydb',

                          :store  => :mysql,

                          :user     => 'hal9000',

                          :password => 'chandra')

每个对象都有save方法,它实际执行将对象插入到数据库中的操作:

obj = SomeClass.new

obj.alpha  = "Poole"

obj.beta   = "Whitehead"

obj.gamma  = "Kaminski"

obj.save

还有一些用传统数据库描述对象关系的方法:

class Dog

  has_one :house

  belongs_to :owner

  has_many :fleas

end

这些方法及其他方法(如many_to_many和refers_to)可帮助创建复杂的对象—表关系。

Og很大,这里无法完整介绍,更详细的信息请参阅其他在线资源,如http://oxyliquit.de

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论