SQL 提供了一种强大的声明式查询语言。用 SQL 编写查询通常比用通用程序设计语言同样的查询进行编码要简单得多。然而,基于至少两种原因数据库程序员必须能够访问通用程序设计语言:
1.因为 SQL 并没有提供通用语言的全部表达能力,所以 SQL 并不能表达所有的查询。也就是说,有可能存在这样的查询,它们可以用诸如 C 、 Java 或 Python 那样的语言来编写,但不能用 SQL 来表达。要编写这样的査询,我们可以将 SQL 嵌人一种更强大的语言中。2.非声明式动作(诸如打印一份报告、和用户交互,或者把查询结果发送到图形化用户界面中)都不能在 SQL 中实现。应用程序通常具有几个组件,并且查询或更新数据只是其中一个组件,其他组件则用通用程序设计语言来实现。对于集成应用来说,必须用某种方式把 SQL 与通用程序设计语言结合起来。
可以通过以下两种方式从通用程序设语言中访问 SQL 。
1.动态 SQL ( dynamic SQL ):通用程序可以通过一组函数(对于过程式语言)或者方法(对于面向对象的语言)来连接到数据库服务器并与之通信。动态 SQL 允许程序在运行时以字符串形式来构建 SQL 查询,提交查询,然后以每次一个元组的方式把结果存入程序变量中。 SQL 的动态 SQL 组件允许程序在运行时构建和提交 SQL 查询。我们将介绍两种用于连接到 SQL 数据库并执行查询和更新的标准。一种是用于 Java 语言的应用程序接口 JDBC 。另一种是 ODBC ,它最初是为 C 语言开发的应用程序接口,后来扩展到诸如 C ++、 C #、 Ruby 、 Go 、 PHP 和 Visual Basic 等其他语言。我们还将说明用 Python 编写的程序如何使用 Python Database API 来连接到数据库。
为 Visual Basic.NET 和 C#语言设计的 ADO.NET API 提供了访问数据的函数,它们在高级别上类似于 JDBC 函数,尽管细节不同。AD0.NET API 也可以用于某些类型的非关系型数据源。 ADO.NET 的详细信息可以在联机手册中找到。2.嵌入式 SQL (embedded SQL):与动态 SQL 类似,嵌入式SQL 提供了一种用于程序与数据库服务器进行交互的方式。然而,在嵌入式SQL中, SQL 语句在编译时采用预处理器来进行识别,预处理器将用嵌入式SQL表达的请求转换为函数调用。在运行时,这些函数调用使用提供动态SQL设施的API连接到数据库,但是这些 API 可能只适用于正在使用的数据库。
把 SQL 与通用语言相结合的主要挑战是SQL与这些语言 *** 纵数据的方式不匹配。在 SQL 中,数据的主要类型是关系。SQL 语句在关系上进行 *** 作,并返回关系作为结果。程序设计语言通常一次 *** 作一个变量,并且这些变量大致相当于关系中一个元组的一个属性的值。因此,为了在单个应用中整合这两种类型的语言,需要提供一种机制,以程序可以处理的方式返回查询的结果。
本节中的示例假设在运行数据库系统的服务器上访问一个数据库。
JDBC
JDBC标准定义了用于将java程序连接到数据库服务器的应用程序接口(Application Program Interface,API)(JDBC这个词原本是java数据库连接(Java Database Connectivity)的缩写,但此全称已经不再使用了)。
下图给出了使用JDBC接口的java代码示例。Java程序必须加载java.sql*,它包含了JDBC所提供函数的接口定义。
从Java程序访问数据库的第一步是打开一个与数据库的连接。这一步需要选择使用哪个数据库,比如运行在你的机器上的一个 Oracle 实例,或者运行在另一台机器上的一个 PostgreSQL 数据库。只有在打开一个连接以后, Java 程序才能执行 SQL 语句。
通过使用DriverManager类(在java . sql中)的**getConnection ()**方法打开一个连接。该方法有三个参数。
1.调用 getConnection ()的第一个参数是一个字符串,它指定了 URL 或服务器所运行的主机名称(在我们的示例中是db.yale.edu),以及某些可能的其他信息,例如,与数据库通信所用的协议(在我们的示例中是jdbc:oracle:thin:,我们马上就会看到为什么需要它)、数据库系统用来通信的端口号(在我们的示例中是2000),还有服务器端使用的特定数据库(在我们的示例中是univdb)。请注意 JDBC 只指定 API 而不指定通信协议。一个 JDBC 驱动器可能支持多种协议,我们必须指定一种数据库和驱动器都支持的协议。协议的详细内容是由厂商设定的。2.getConnection ()的第二个参数是一个数据库用户的标识,它是一个字符串。3.第三个参数是密码,它也是一个字符串。(请注意,如果未经授权的人访问你的 Java 代码,在 JDBC 代码中指定密码会带来安全性风险。)在我们在图中的示例中,已经创建了一个 Connection 对象,其句柄是 conn 。
支持 JDBC (所有主流的数据库厂商都支持)的每个数据库产品都会提供一个 JDBC 驱动程序,该驱动程序必须被动态加载才能实现 Java 对数据库的访问。事实上,必须在连接到数据库之前,首先完成该驱动程序的加载。如果已从厂商的网站下载了合适的驱动程序而且该驱动程序在类路径中,那么 getconnection()方法将定位所需的驱动程序。驱动程序提供了从独立于产品的 JDBC 调用到面向产品的调用的转换,而面向产品的调用是正在使用的特定数据库管理系统所需要的。用于与数据库交换信息的实际协议取决于所使用的驱动程序,并且不由 JDBC 标准来定义。一些驱动程序支持不止一种协议,必须根据定数据库产品支持何种协议来选择一种合适的协议。在我们的示例中,当打开与数据库的连接时,字符串jdbc:oracle:thin:指定了 Oracle 所文持的具体协议。 MySQL 的等价表示是jdbc:mysql:。
向数据库系统中传递SQL语句
一旦打开一个数据库连接,程序就可以利用该连接来向数据库发送 SQL 语句用于执行。这是通过 Statement 类的一个实例来完成的。一个 Statement 对象本身并不是 SQL 语句,而是允许 Java 程序调用方法的一个对象,这些方法将给定的 SQL 语句作为参数发送给数据库系统来执行。我们的示例在 conn 连接上创建了一个 Statement 句柄(stmt)。
我们既可以调用 executeQuery ()方法又可以调用 executeUpdate ()方法来执行一条语句,这取决于这条 SQL 语句是查询语句(如果是查询语句,则会返回一个结果集),还是诸如更新( update )、插入( insert )、删除( delete )或创建表( create table )这样的非查询语句。在我们的示例中, stmt.executeUpdate ()执行一条向 instructor 关系中进行插人的更新语句。它返回一个整数,给出插入、更新或者删除元组的数量。对于 DDL 语句,其返回值为零。
异常与资源管理
执行任何 SQL 方法都可能导致引发异常。 try … catch …结构允许我们捕获在进行 JDBC 调用时出现的任何异常(错误情况),并采取适当的 *** 作。在 JDBC 编程中,它可能有助于区分 SQLException (这是面向SQL的异常)和 Exception 的一般情况(它可以是任何 Java 异常,比如空指针异常或数组下标越界异常)。实际上,人们编写的异常处理程序应该比我们在示例代码中编写的(为了简洁起见)更为完整。
打开连接、语句和其他 JDBC 对象都是消耗系统资源的 *** 作。程序员必须注意确保程序关闭所有这样的资源。否则可能会导致数据库系统的资源池被耗尽,使得系统在超时期限到期之前无法访问或无法 *** 作。关闭资源的一种方式是编写显式调用来关闭连接和语句。如果代码由于异常而退出,则此方式将失效,因为带关闭调用的 Java 语句没有被使用。出于这样的原因,首选方式是使用 Java 中的try-with-resources 结构。在示例中,连接和语句对象的打开是在括号内完成的,而不是在花括号内 try 的主体中完成的。在括号内代码中打开的资源在 try 块结尾处自动关闭。这样可以防止我们让连接或语句处于未关闭状态。由于关闭一条语句会隐式关闭为该语句打开的对象,因此这种编码方式可以防止我们让资源处于未关闭状态。在示例中,我们可以用conn.close ()语句来显式地关闭连接,并用stmt.close ()来显式地关闭语句,尽管在我们的示例中是不必这样做的。
获取查询结果
示例代码通过使用stmt.executeQuery ()来执行一次查询。它把结果中的元组集提取到ResultSet对象rset 中,并每次取出一个元组进行处理。结果集上的next ()方法用来测试在结果集中是否还存在至少一个尚未提取的元组,如果存在就取出该元组**。 next ()方法的返回值是一个布尔变量,它表示该方法是否提取了一个元组**。通过使用名称以get打头的各种方法可以检索来自所提取元组的各个属性。 getString ()方法可以检索任何基本的SQL数据类型(并将值转换成 Java String对象),当然也可以使用像getFloat ()那样的约束性更强的方法。各种 get 方法的参数既可以是被指定为一个字符串的属性名,又可以是一个整数,它表明所需属性在元组中的位置。示例给出了在元组中提取属性值的两种方式:利用属性名(dept_name )提取以及利用属性位置(2,代表第二个属性)提取。
预备语句
我们可以创建一条预备语句,其中用“?”来代替某些值,以此指明以后会提供实际的值。数据库系统在预备查询的时候对其进行编译。在每次执行该查询时(用新值去替换那些“?”),数据库系统可以重用此前编译的查询形式,将新的值作为参数来应用。下图代码片段展示了如何使用预备语句。
Connection类的prepareStatement()方法定义了一个查询,该查询可以包含参数值。一些JDBC驱动程序可以将查询作为方法的一部分提交到数据库进行编译,但其他驱动程序此时并未与数据库联系。此方法返回一个PrepareaStatement类的对象。此时未执行任何 SOL 语句。执行需要PreparedStatement类的两个方法 executeQuery()和executeUpdate ()。但是在它们被调用之前,我们必须使用PreparedStatement 类的方法来为“?”参数赋值。 setString ()方法及诸如 setlnt ()那样的用于其他基本SQL类型的类似方法允许我们为参数指定值。第一个变量指明我们为哪个"?参数赋值(第一个参数是1,区别于大多数其他的 Java 结构,这此结构是从0开始的)。第二个变量指定要赋予的值。
在示例中,我们预备了一条 insert 语句,设定了?参数,并且随后调用executeUpdate ()。该示例的最后两行表明:直到我们特别地进行重新设定为止,参数赋值保持不变。这样,最后的语句调用 executeUpdate (),插人元组(88878," Perry ”“ Finance ”,125000)。
在同一查询编译一次然后带不同参数值运行多次的情况下,预备语句便得执行更加高效。然而,只要查询使用用户输入的值,即使该查询只运行一次,预备语句也有一个更为重要的优势使得它们成为执行 SQL 査询的首选方法。假设我们读取一个用户输入的值,然后使用 Java 的字符串 *** 作来构造 SQL 语句。如果用户输入了某些特殊子符,例如一个单引号。除非我们格外小心地检查用户输人,否则生成的 SQL 语句会出现语法错误。SetString()方法为我们自动完成这样的检查,并插入所需的转义字符以确保语法的正确性。
在我们的示例中,假设用户已经输入了对于 ID 、 name 、 dept_name 和 salary 这些变量的值,并且相应的行将被插入 instructor 关系中。假设不使用预备语句,而是使用如下的 Java 表达式把字符串拼接起来以构造查询:
"insert into instructor values('"+ID+"','"+name+"',"+"'"+dept_name+"',"+salary+")"
并且使用 Statement 对象的 executeQuery ()方法来直接执行查询。请注意字符串中单引号的使用,单引号将在生成的 SQL 查询中包围 ID 、name 和 dept_name 的值。
现在,如果用户在 ID 或者姓名字段中敲入了一个单引号,查询字符串就会出现语法错误。一位教师的姓名中很有可能带有引号(例如“ O’Henry ”)。
也许以上示例会被认为是令人讨厌的,但情况可能会糟得多。一种叫作 SQL 注入( SQLinjection )的技术可以被恶意黑客用来窃取数据或损坏数据库。
假设一段 Java 程序输人一个字符串 name ,并且构建査询:
"select *from instructor where name='"+name+"'"
如果用户输入的不是一个姓名,而是:
X’ or ‘Y’='Y
那么,所产生的语句就变成:
"select *from instructor where name='"+"X' or 'Y'='Y"+"'"
即为
select * from instructor where name='X' or 'Y'='Y'
在生成的查询中, where 子句总是为真,并且返回整个教师关系。
更诡计多端的恶意用户可能安排输出甚至更多的数据,包括诸如密码之类的资质、这此资质允许用户连接到数据库并执行其想要的任何 *** 作。可以使用更新( update )语句上的SQL注入攻击来更改在更新列中存储的值。实际上在现实世界中已经发生了许多使用 SQL注入的攻击。通过使用 SQL 注入,对多个金融网站的攻击已导致大量资金被盗窃。
使用预备语句就可以避免这类问题,因为输入的字符串将被插人转义字符,因此所产生的查询变为:
"select *from instructor where name='X' or 'Y'='Y'
这是无害的语句,并返回空的关系。
程序员必须仅通过预备语句的参数将用户输入的字符串传递给数据库;通过使用用户输入值串接起来的字符串来创建 SQL 查询存在极其严重的安全风险,并且在任何程序中都不应该这样做。
有些数据库系统允许在单个 JDBC 的 execute 方法中执行多条 SQL 语句,语句之间用分号分隔。由于 JDBC 驱动程序允许恶意的黑客使用 SQL 注入来插入整条 SQL 语句,因此该特性在某些 JDBC 驱动程序上被默认关闭了。例如,在我们前面的 SQL 注入示例中,一个恶意的用户可以输人:
X';drop table instructor;--;
这将导致向数据库提交一个查询字符串,其中包含两条用分号分隔的语句。由于这些语句以 JDBC 连接所使用的数据库用户标识的权限运行,因此可以执行破坏性的 SQL 语句,例如删除表( drop table )或对用户所选择的任何表进行更新。但是,某些数据库仍然允许执行如上所述的多条语句。因此,正确使用预备语句以避免 SQL 注入的风险是非常重要的。
可调用语句
JDBC 还提供了 CallableStatement 接口,它允许调用 SQL 的存储过程和函数。此接口对函数和过程所扮演的角色跟 prepareStatement 对查询所扮演的角色一样。
CallableStatement cStmt1=conn.prepareCall("{?=call some_function(?)}"); CallableStatement cStmt2=conn.prepareCall("{call some_procedure(?,?)}");
函数返回值和过程输出参数的数据类型必须先用registerOutParameter()方法注册,并可以用与结果集类似的get方法来检索。
元数据特性
正如我们此前提到的, Java 应用程序不包含对数据库中所存储数据的声明。这些声明是 SQL DDL 的一部分。因此,使用 JDBC 的 Java 程序必须要么将关于数据库模式的获取硬编到程序中,要么在运行时直接从数据库系统得到那些信息。后一种方法通常更可取,因为它使得应用程序可以更健壮地应对数据库模式的变化。
请回想一下:当我们使用 executeQuery()方法提交一个查询时,查询结果被封装在一个 ResultSet 对象中。 ResultSet 接口有一个 getmetaData ()方法,它返回一个包含关于结果集的元数据的ResultSetmetaData 对象。 ResultSetmetaData 又进一步具有查找元数据信息的方法,例如结果中的列数、指定列的名称或者指定列的类型。通过这样的方式,即使我们事先不知道结果的模式,也可以编写代码来执行査询。
下面的 Java 代码片段使用 JDBC 来打印出一个结果集的所有列的名称和类型。假定代码中的变量 rs 指代通过执行查询而获得的一个 ResultSet 实例。
ResultSetmetaData rsmd=rs.getmetaData(); for(int i=1;i<=rsmd.getColumnCount();i++) { System.out.println(rsmd.getColumnName(i)); System.out.println(rsmd.getColumnTypeName(i)); }
getColumnCount ()方法返回结果关系的元数(属性个数)。这使得我们能够遍历每个属性(请注意,与 JDBC 的惯例一致,我们从1开始)。对于每个属性,我们来用 getColumnName()和getColumnTypeName ()两种方法来分别检索它的名称和数据类型。
DatabasemetaData 接口提供了查找关于数据库的元数据的方式。 Connection 接口具有一个 getmetaData ()方法,它返回一个 DatabasemetaData 对象。 DatabasemetaData 接口又进一步具有大量的方法来获取关于程序所连接的数据库和数据库系统的元数据。
例如,有些方法可以返回数据库系统的产品名称和版本号。另外一些方法允许应用程序来查询数据库系统所支持的特性。
还有其他方法返回有关数据库本身的信息。下图中的代码展示了如何查找数据库中有关关系的列(属性)信息。假定 conn 变量是一个已打开的数据库连接的句柄。 getColumns ()方法有四个参数:一个目录名称(为空表示目录名称将被忽略)、一个模式名式样、一个表名式样以及一个列名式样。模式名、表名和列名的式样可以用于指定一个名称或式样。式样可以使用 SQL 字符串匹配的特殊字符“%”和“-”,例如,式样“%”匹配所有的名称。只有满足特定名称或式样的模式的表的列被检索出来。结果集中的每行包含有关一个列的信息。这些行具有若干列,比如目录名、模式名、表名、列名、列的类型等。
getTables ()方法允许获取数据库中所有表的列表。 getTables ()的前三个参数与 getColumns()是相同的。第四个参数可用于限制所返回的表的类型;如果被设置为空,则返回所有表,包括系统内部表,但该参数可以设置为仅返回由用户创建的表。
DatabasemetaData 还提供了其他方法来获取与数据库相关的信息,包括获取主码的方法(getPrimaryKeys ())、获取外码引用的方法(getCrossReference())、获取授权的方法、获取诸如最大连接数那样的数据库限制的方法等。
元数据接口可以用于各种任务。例如,它们可以用于编写数据库浏览器,该浏览器允许用户查找数据库中的表,检查它们的模式,检查表中的行,应用选择来查看所需的行等。元数据信息可以使得用于这些任务的代码更为通用,例如,可以以能够在无论何种模式下的所有可能的关系上都有效的方式,来编写代码展示一个关系中的行。类似地,可以编写代码来接受查询字符串、执行该查询并将结果打印为一个格式化的表;无论实际提交的查询是什么,这段代码都可以工作。
其他特性
JDBC 提供了许多其他特性,比如可更新的结果集( updatable result set )。它可以根据在数据库关系上执行选择或投影的查询来创建出可更新的结果集。对结果集中元组的更新将导致对数据库关系的相应元组的更新。
事务允许将多个 *** 作视为一个可以被提交或者回滚的单个原子性单元。在缺省情况下,每条 SQL 语句都被视为一个自动提交的独立事务。 JDBC 的 Connection 接口中的 setAutocommit ()方法允许打开或关闭这种自动提交的行为。因此,如果 conn 是一个打开的连接,则 conn . setAutocommit ( false )将关闭自动提交。然后事务必须要么使用 conn . commit ()来显式提交,要么使用 conn . rollback ()来显式回滚。自动提交可以用 conn . setAutocommit ( true )来打开。
JDBC 提供了处理大对象的接口,而不要求在内存中创建整个大对象。为了获取大对象, ResultSet接口提供了 getBlob ()和 getClob ()方法,它们与getString()方法相似,但是分别返回类型为 Blob 和 Clob 的对象。这些对象并不存储整个大对象,而是存储这些大对象的“定位器”,也就是指向数据库中实际的大对象的逻辑指针。从这些对象中获取数据非常类似于从文件或输入流中获取数据,并且可以采用诸如 getBytes ()和getSubString()那样的方法来实现。
为了在数据库中存储大对象,可以用 PreparedStatement 类的 setBlob (intparameterlndex , InputStream inputStream )方法把一个类型为二进制大对象( blob )的数据库列与一条输入流(例如已被打开的文件)关联起来。当执行预备语句时,数据从输入流被读取,然后被写入数据库的二进制大对象中。相似地,使用以参数索引和字符串流为参数的setClob()方法可以设置字符大对象( clob )的列。
JDBC 还提供了行集( row set )特性,它允许收集结果集并将其发送给其他应用程序。行集既可以向后又可以向前扫描,并且可以被修改。
从Python访问数据库
从 Python 访问数据库可以通过如下图所示的方法来完成。包含 insert 査询的语句显示了如何使用与 JDBC 预备语句等价的 Python 语句,其中的参数在 SQL 查询中由“%s”来标识,并且参数值是作为列表提供的。更新不会自动提交到数据库,需要调用 commit ()方法来提交更新。
try :, except …:块显示了如何捕获异常并打印有关异常的信息。 for 循环说明了如何在查询执行的结果上进行循环,以及如何访问特定行的各个属性。
该程序使用psycopg2驱动程序,它允许连接到 PostgreSQL 数据库,并在程序的第一行导人。驱动程序通常是面向数据库的,使用 MySQLdb 驱动程序连接到 MySQL ,使用 cx_Oracle 连接到 Oracle;但是pyodbc驱动程序可以连接到支持 ODBC 的大多数数据库。程序中使用的 Python 数据库 API 是通过许多数据库的驱动程序来实现的,但是与 JDBC 不同,在跨不同驱动程序的 API 中存在细微的差别,特别是在 connect ()函数的参数方面。
ODBC
开放数据库连接(Open Database Connectivity,ODBC)标准定义了一个 API ,应用程序可以用它来打开与一个数据库的连接、发送查询和更新并获取返回结果。诸如图形化用户界面、统计程序包以及电子表格那样的应用程序可以使用相同的ODBC API来连接到支持ODBC的任何数据库服务器。
每个支持 ODBC 的数据库系统都提供一个必须和客户端程序相链接的库。当客户端程序调用一个ODBC API时,库中的代码就和服务器进行通信以执行被请求的动作并获取结果。
上图给出了一段使用 ODBC API 的C语言代码示例。利用 ODBC 与服务器通信的第一步是建立与服务器的连接。为此,程序先分配一个SQL环境变量,然后分配一个数据库连接句柄。 ODBC 定义了 HENV 、 HDBC 和 RETCODE 类型。程序随后通过使用 SQLConnect 打开数据库的连接,此调用需要几个参数,包括连接句柄、要连接的服务器、用户标识和用于数据库的密码。常量SQL_NTS表示前面的参数是一个以null结尾的字符串。
一旦建立起连接,程序就可以通过使用SQLExecDirect向数据库发送 SQL 命令。因为 C 语言的变量可以和查询结果的属性绑定,所以当使用SQLFetch来获取结果元组时,其属性值被存储在相应的C变量中。SQLBindCol函数执行此项任务。第二个参数标识属性在查询结果中的位置,第三个参数指明从 SQL 到 C 所需的类型转换。下一个参数给出变量的地址。对于像字符串数组那样的变长类型,最后两个参数还要给出变量的最大长度以及当获取元组时要存储实际长度的位置。长度字段返回负值表示该值为空( null )。对于诸如整型或浮点型那样的定长类型,最大长度字段被忽略,然而长度字段返回负值则表示一个空值。
SQLFetch语句在 while 循环中一直执行,直到SQLFetch返回一个非 SQL_SUCCESS 的值。在每次取值时,程序把值存放在由SQLBindCol调用所指定的C变量中,并输出这此值。
在会话结束的时候,程序释放语句的句柄,断升与数据库的连接,同时释放连接句柄和 SQL 环境句柄。好的编程风格要求必须检查每一个函数调用的结果,以确保没有出现错误;为了简洁起见,我们省略了大部分这样的检查。
可以创建一条带有参数的 SQL 语句,例如,请考虑语句 insert into department values (?,?,?)。问号是为后面将提供的值而设置的占位符。上面的语句可以被“预备”,也就是说,在数据库中先编译,并通过为占位符提供实际值来反复执行——在本例中,是通过为 department 关系提供系名、教学楼和预算。
ODBC 为各种任务都定义了函数,例如查找数据库中的所有关系,以及查找查询结果或数据库中关系的列的名称和类型。
在缺省情况下,每条 SQL 语句都被认为是一个自动提交的单独事务。 SQLSetConnect-Option(conn , SQL_AUTOCOMMIT ,0)关闭 conn 连接上的自动提交,然后事务必须通过 SQLTransact ( conn , SQL_COMMIT )来显式提交或通过 SQLTransact ( conn , SQL_ROLLBACK )来显式回滚。
ODBC标准定义了适应性级别( conformance level ),用于指定由标准定义的函数子集。一个ODBC实现可以仅提供核心级特性,也可以提供更高级(级别1或级别2)的特性。级别1要求支持获取目录的有关信息,例如有关当前关系及其属性类型的信息。级別2需要更多的特性,例如发送和检索参数值数组以及检索更详细的目录信息的能力。
SQL 标准定义了一个与ODBC接口类似的调用层接口( Cal Level Interface , CLD 。
嵌入式SQL
SQL 标准定义了将 SQL 嵌入各种程序设计语言(比如 C、C++、Cobol 、Pascal 、 Java、PL/I和Fortran)中的方法。嵌入了 SQL 查询的语言被称为宿主(host)语言,并且在宿主语言中允许使用的SQL结构构成了嵌入式SQL 。
用宿主语言编写的程序可以使用嵌入式 SQL 的语法来访问和更新存储在数据库中的数据。嵌入式SQL程序在编译之前必须先由特殊的预处理器进行处理。该预处理器将嵌入的 SQL 请求替换为宿主语言的声明以及允许运行时执行数据库访问的过程调用。然后,所产生的程序由宿主语言编译器进行编译。这是嵌入式 SQL 与JDBC或ODBC之间的主要区别。
为使预处理器识别出嵌入式 SQL 请求,我们使用 EXEC SQL 语句,它具有如下格式:
EXEC SQL <嵌入式 SQL 语句>;
在执行任何 SQL 语句之前,程序必须首先连接到数据库。在嵌入式 SQL 语句中可以使用宿主语言的变量,不过它们的前面必须加上冒号(:)以将它们与 SQL 变量区分开来。
要遍历一个嵌入式 SQL 查询的结果,我们必须声明一个游标( cursor )变量,它可以随后被打开,并在宿主语言循环中发出获取( fetch )命令来获取査询结果的连续行。行的属性可以提取到宿主语言变量中。数据库更新也可以通过以下方式实现:使用关系上的游标来遍历关系的行,或使用 where 子句来仅遍历所选的行。嵌入式 SQL 命令可用于更新游标所指向的当前行。
嵌入式 SQL 请求的确切语法取决于嵌入 SQL 的语言。可以参考你所使用的特定语言的嵌入语法的手册以获取更多详细信息。
在 JDBC 中, SQL 语句在运行时进行解释(即便它们是使用预备语句功能来创建的)。当使用嵌入式 SQL 时,在预处理时有可能会捕获一些与 SQL 有关的错误(包括数据类型错误)。与在程序中使用动态 SQL 相比,嵌入式 SQL 程序中的 SQL 查询也更容易理解。但是,嵌入式 SQL 也存在一些缺点:
预处理器会创建新的宿主语言代码,这可能会使程序的调试复杂化。预处理器用于标识 SQL 语句的结构可能在语法上与宿主语言在后续版本中所引入的宿主语言语法发生冲突。
其结果是,当前大多数系统使用动态 SQL ,而不是嵌入式 SQL 。微软语言集成査询( Microsoft Language Integrated Query , LINQ )工具是一种例外,它扩展了宿主语言以包括对查询的支持,而不是使用预处理器将嵌入式 SQL 查询转换为宿主语言。
嵌入式数据库
JDBC 和 ODBC 都假设服务器在托管数据库的数据库系统上运行。某些应用程序使用完全存在于应用程序中的数据库。此类应用程序仅为内部使用而维护数据库,并且除非通过应用程序本身,否则无法访问到数据库。在这种情况下,可以使用嵌入式数据库( embedded database )以及使用实现了可从程序设计语言中访问的 SQL 数据库的几种包中的一种。热门的选择包括 Java DB 、 SQLite 、 HSQLBD。还有 MySQL 的嵌入式版本。
嵌入式数据库系统缺少完全基于服务器的数据库系统的许多特性,但是它们为那些可以从数据库抽象中受益而无须支持超大型数据库或大规模事务处理的应用程序提供了优势。
请不要将嵌入式数据库与嵌入式 SQL 相混淆,后者是连接到/服务器上运行的数据库/的一种方式。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)