- 浏览: 612695 次
- 性别:
- 来自: 太原
文章分类
- 全部博客 (240)
- 程序员数学/线性代数(Linear Algebra) (2)
- 程序员数学/微积分(Calculus) (6)
- 机器学习(Machine Learning) (5)
- JAVA SE (63)
- JAVA EE (14)
- 数据库技术 (26)
- struts (4)
- 软件设计/设计模式 (0)
- ibatis (2)
- XML (4)
- 领域建模 (0)
- 数据资源共享 (1)
- 软件工程 (11)
- 技术以外 (6)
- 面向对象 (2)
- 科学数据共享 (1)
- 资源 (7)
- WEB2.0 (11)
- 电子商务 (10)
- 算法、数据结构、数学 (10)
- LAMP (1)
- 杂谈 (12)
- C语言 (7)
- 程序设计思想 (3)
- 读书和笔记 (1)
- 生活 健身 养生 (5)
- WEB UI (2)
- eclipse (2)
- 项目管理 (7)
- oracle (5)
- linux (1)
- webGIS (6)
最新评论
-
TimePower:
OK~终于明白了~~
参数(parameter)和属性(Attribute)的区别 -
OnTheRoad_lee:
不错,正式我想要的东西,一直不明白序列化是什么?有什么用?至此 ...
我对Java Serializable(序列化)的理解和总结 -
EchoZhouYou:
好久不上这,找这本书时发现这一篇,特意登录来赞一下
《程序设计语言——实践之路》读后感 -
yong7356:
学习一下Serializable
我对Java Serializable(序列化)的理解和总结 -
dengjm_2012:
写得不错!
我对Java Serializable(序列化)的理解和总结
1. JDBC 的用途是什么?
简单地说,JDBC 可做三件事:
与数据库建立连接,
发送SQL 语句,
处理结果。
下列代码段给出了以上三步的基本示例:
Connection con = DriverManager.getConnection ("jdbc:odbc:wombat", "login", "password");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next())
System.out.println(rs.getString("a") + " " + rs.getString("b") + " " + rs.getString("c"));
2. JDBC 是一种低级API ,是高级API 的基础
JDBC 是个“低级”接口,也就是说,它用于直接调用SQL 命令。在这方面它的功能极佳,并
比其它的数据库连接API 易于使用,但它同时也被设计为一种基础接口,在它之上可以建立高级接口和工具。高级接口是“对用户友好的”接口,它使用的是一种更易理解和更为方便的API,这种API 在幕后被转换为诸如JDBC 这样的低级接口。
3. JDBC 与ODBC 和其它API 的比较
目前,Microsoft 的ODBC(开放式数据库连接)API 可能是使用最广的、用于访问关系数据库
的编程接口。它能在几乎所有平台上连接几乎所有的数据库。为什么Java 不使用ODBC?
对 这个问题的回答是:Java 可以使用ODBC,但最好是在JDBC 的帮助下以JDBC-ODBC 桥的形式使用,这一点我们稍后再说。现在的问题已变成:“为什么需要JDBC”? 回答如下:ODBC 不适合直接在Java 中使用,因为它使用C 语言接口。从Java 调用本地C 代码在安全性、实现、坚固性和程序的自动移植性方面都有许多缺点。
启用“纯Java ”机制需要象JDBC 这样的Java API。如果使用ODBC,就必须手动地将ODBC 驱动程序管理器和驱动程序安装在每台客户机上。如果完全用Java 编写JDBC 驱动程序则JDBC 代码在所有Java 平台上(从网络计算机到大型机)都可以自动安装、移植并保证安全性。
4 两层模型和三层模型
JDBC API 既支持数据库访问的两层模型,同时也支持三层模型。
在两层模型中,Java applet 或应用程序将直接与数据库进行对话。这将需要一个JDBC 驱动
程 序来与所访问的特定数据库管理系统进行通讯。用户的SQL 语句被送往数据库中,而其结果将被送回给用户。数据库可以位于另一台计算机上,用户通过网络连接到上面。这就叫做客户机/服务器配置,其中用户的计算机为 客户机,提供数据库的计算机为服务器。网络可以是Intranet(它可将公司职员连接起来),也可以是Internet。
在三层模型中,命令先是被发送到服务的“中间层”,然后由它将SQL 语句发送给数据库。数
据 库对SQL 语句进行处理并将结果送回到中间层,中间层再将结果送回给用户。MIS 主管们都发现三层模型很吸引人,因为可用中间层来控制对公司数据的访问和可作的的更新的种类。中间层的另一个好处是,用户可以利用易于使用的高级API, 而中间层将把它转换为相应的低级调用。最后,许多情况下三层结构可提供一些性能上的好处。
到目前为止,中间层通常都用C 或C++ 这类语言来编写,这些语言执行速度较快。然而,随
着 最优化编译器(它把Java 字节代码转换为高效的特定于机器的代码)的引入,用Java 来实现中间层将变得越来越实际。这将是一个很大的进步,它使人们可以充分利用Java 的诸多优点(如坚固、多线程和安全等特征)。JDBC 对于从Java 的中间层来访问数据库非常重要。
5 SQL 的一致性
结构化查询语言(SQL) 是访问关系数据库的标准语言。困难之处在于:虽然大多数的DBMS (数
据 库管理系统)对其基本功能都使用了标准形式的SQL,但它们却不符合最近为更高级的功能定义的标准SQL 语法或语义。例如,并非所有的数据库都支持储存程序或外部连接,那些支持这一功能的数据库又相互不一致。人们希望SQL 中真正标准的那部份能够进行扩展以包括越来越多的功能。但同时JDBC API 又必须支持现有的SQL。JDBC API 解决这个问题的一种方法是允许将任何查询字符串一直传到所涉及的DBMS 驱动程序上。这意味着应用程序可以使用任意多的SQL 功能,但它必须冒这样的风险:有可能在某些DBMS 上出错。事实上,应用程序查询甚至不一定要是SQL,或者说它可以是个为特定的DBMS 设计的SQL 的专用派生物(例如,文档或图象查)。
JDBC 处理SQL 一致性问题的第二种方法是提供ODBC 风格的转义子句。这将在4.1.5 节“语
句对象中的SQL 转义语法”中讨论。转义语法为几个常见的SQL 分歧提供了一种标准的JDBC 语法。例如,对日期文字和已储存过程的调用都有转义语法。
对于复杂的应用程序,JDBC 用第三种方法来处理SQL 的一致性问题。它利用DatabaseMetaData接口来提供关于DBMS 的描述性信息,从而使应用程序能适应每个DBMS 的要求和功能。
由于JDBC API 将用作开发高级数据库访问工具和API 的基础API,因此它还必须注意其所有
上 层建筑的一致性。“符合JDBC 标准TM" 代表用户可依赖的JDBC 功能的标准级别。要使用这一说明,驱动程序至少必须支持ANSI SQL-2 EntryLevel(ANSI SQL-2 代表美国国家标准局1992 年所采用的标准。Entry Level 代表SQL 功能的特定清单)。驱动程序开发人员可用JDBC API 所带的测试工具包来确定他们的驱动程序是否符合这些标准。
“符合JDBC 标准TM” 表示提供者的JDBC 实现已经通过了JavaSoft 提供的一致性测试。
这 些一致性测试将检查JDBCAPI 中定义的所有类和方法是否都存在,并尽可能地检查程序是否具有SQL Entry Level 功能。当然,这些测试并不完全,而且JavaSoft 目前也无意对各提供者的实现进行标级。但这种一致性定义的确可对JDBC 实现提供一定的可信度。随着越来越多的数据库提供者、连接提供者、Internet 提供者和应用程序编程员对JDBC API 的接受,JDBC 也正迅速成为Java数据库访问的标准。
6 JDBC 产品
有关最新的信息,请查阅JDBC 的网站,可通过从以下URL 开始浏览找到:
http://java.sun.com/products/jdbc
7 JavaSoft 框架
JavaSoft 提供三种JDBC 产品组件,它们是Java 开发工具包(JDK) 的组成部份:JDBC 驱动
程序管理器,JDBC 驱动程序测试工具包,和JDBC-ODBC 桥。
JDBC 驱动程序管理器是JDBC 体系结构的支柱。它实际上很小,也很简单;其主要作用是把
Java 应用程序连接到正确的JDBC 驱动程序上,然后即退出。
JDBC 驱动程序测试工具包为使JDBC 驱动程序运行您的程序提供一定的可信度。只有通过
JDBC 驱动程序测试包的驱动程序才被认为是符合JDBC 标准TM 的。
JDBC-ODBC 桥使ODBC 驱动程序可被用作JDBC 驱动程序。它的实现为JDBC 的快速发展提供了一条途径,其长远目标提供一种访问某些不常见的DBMS(如果对这些不常见的DBMS 未实现JDBC) 的方法。
8 JDBC 驱动程序的类型
我们目前所知晓的JDBC 驱动程序可分为以下四个种类:
JDBC-ODBC 桥加ODBC 驱动程序:JavaSoft 桥产品利用ODBC 驱动程序提供JDBC 访问。注意,必须将ODBC 二进制代码(许多情况下还包括数据库客户机代码)加载到使用该驱动程序的每个客户机上。因此,这种类型的驱动程序最适合于企业网(这种网络上客户机的安装 不是主要问题),或者是用Java 编写的三层结构的应用程序服务器代码。
本地API - 部份用Java 来编写的驱动程序: 这种类型的驱动程序把客户机API 上的JDBC
调用转换为Oracle、Sybase、Informix、DB2 或其它DBMS 的调用。注意,象桥驱动程序一样,这种类型的驱动程序要求将某些二进制代码加载到每台客户。
JDBC 网络纯Java 驱动程序:这种驱动程序将JDBC 转换为与DBMS 无关的网络协议,之后这种协议又被某个服务器转换为一种DBMS 协议。这种网络服务器中间件能够将它的纯Java 客户机连接到多种不同的数据库上。所用的具体协议取决于提供者。通常,这是最为灵活的JDBC 驱动程序。有可能所有这种解决方案的提供者都提供适合于Intranet 用的产品。为了使这些产品也支持Internet 访问,它们必须处理Web 所提出的安全性、通过防火墙的访问等方面的额外要求。几家提供者正将JDBC 驱动程序加到他们现有的数据库中间件产品中。
本地协议纯Java 驱动程序:这种类型的驱动程序将JDBC 调用直接转换为DBMS 所使用的网
络 协议。这将允许从客户机机器上直接调用DBMS 服务器,是Intranet 访问的一个很实用的解决方法。由于许多这样的协议都是专用的,因此数据库提供者自己将是主要来源,有几家提供者已在着手做这件事了。最后,我们预计第3、 4 类驱动程序将成为从JDBC 访问数据库的首选方法。第1、2 类驱动程序在直接的纯Java 驱动程序还没有上市前将会作为过渡方案来使用。对第1、2 类驱动程序可能会有一些变种,这些变种要求有连接器,但通常这些是更加不可取的解决方案。第3、4 类驱动程序提供了Java 的所有优点,包括自动安装(例如,通过使用JDBC 驱动程序的applet 来下载该驱动程序)。
9 Connection 对象-----------连接
Connection 对象代表与数据库的连接。连接过程包括所执行的SQL 语句和在该连接上所返回
的结果。一个应用程序可与单个数据库有一个或多个连接,或者可与许多数据库有连接。2.1.1 打
开 连接与数据库建立连接的标准方法是调用DriverManager.getConnection 方法。该方法接受含有某个URL 的字符串。DriverManager 类(即所谓的JDBC管理层)将尝试找到可与那个URL 所代表的数据库进行连接的驱动程序。DriverManager 类存有已注册的Driver 类的清单。当调用方法getConnection 时,它将检查清单中的每个驱动程序,直到找到可与URL 中指定的数据库进行连接的驱动程序为止。Driver 的方法connect 使用这个URL 来建立实际的连接。用户可绕过JDBC 管理层直接调用Driver 方法。这在以下特殊情况下将很有用:当两个驱动器可同时连接到数据库中,而用户需要明确地选用其中特定的驱动器。但一般情况下,让 DriverManager 类处理打开连接这种事将更为简单。
下述代码显示如何打开一个与位于URL "jdbc:odbc:wombat" 的数据库的连接。所用的用户标
识符为"oboy" ,口令为"12Java":
String url = "jdbc:odbc:wombat";
Connection con = DriverManager.getConnection(url, "oboy", "12Java");
10 JDBC URL
JDBC URL 提供了一种标识数据库的方法,可以使相应的驱动程序能识别该数据库并与之建立连接。实际上,驱动程序编程员将决定用什么JDBC URL 来标识特定的驱动程序。用户不必关心如何来形成JDBC URL;他们只须使用与所用的驱动程序一起提供的URL 即可。JDBC 的作用是提供某些约定,驱动程序编程员在构造他们的JDBC URL 时应该遵循这些约定。由于JDBC URL 要与各种不同的驱动程序一起使用,因此这些约定应非常灵活。
首先,它们应允许不同的驱动程序使用不同的方案来命名数据库。例如, odbc 子协议允许(但并不是要求) URL含有属性值。
第二,JDBC URL 应允许驱动程序编程员将一切所需的信息编入其中。这样就可以让要与给定数据库对话的applet 打开数据库连接,而无须要求用户去做任何系统管理工作。
第 三, JDBCURL 应允许某种程度的间接性。也就是说,JDBC URL 可指向逻辑主机或数据库名,而这种逻辑主机或数据库名将由网络命名系统动态地转换为实际的名称。这可以使系统管理员不必将特定主机声明为JDBC 名称的一部份。网络命名服务(例如DNS、NIS 和DCE )有多种,而对于使用哪种命名服务并无限制。
JDBC URL 的标准语法如下所示。它由三部分组成,各部分间用冒号分隔:
jdbc:< 子协议>:< 子名称>
JDBC URL 的三个部分可分解如下:
jdbc ─ 协议。JDBC URL 中的协议总是jdbc。
<子协议> ─ 驱动程序名或数据库连接机制(这种机制可由一个或多个驱动程序支持)的名称。
子协议名的典型示例是"odbc",该名称是为用于指定ODBC 风格的数据资源名称的URL 专门保留的。例如,为了通过JDBC-ODBC 桥来访问某个数据库,可以用如下所示的URL:
jdbc:odbc:fred
本例中,子协议为"odbc",子名称"fred" 是本地ODBC 数据资源。
如果要用网络命名服务(这样JDBC URL 中的数据库名称不必是实际名称),则命名服务可以
作为子协议。例如,可用如下所示的URL :jdbc:dcenaming:accounts-payable 本例中,该URL 指
定了本地DCE 命名服务应该将数据库名称"accounts-payable" 解析为更为具体的可用于连接真
实数据库的名称。
< 子名称> ─ 一种标识数据库的方法。子名称可以依不同的子协议而变化。它还可以有子名称的子名称(含有驱动程序编程员所选的任何内部语法)。使用子名称的目的是为定位 数据库提供足够的信息。前例中,因为ODBC 将提供其余部份的信息,因此用"fred" 就已足够。然而,位于远程服务器上的数据库需要更多的信息。例如,如果数据库是通过Internet 来访问的,则在JDBC URL 中应将网络地址作为子名称的一部份包括进去,且必须遵循如下所示的标准URL 命名约定://主机名:端口/子协议假设"dbnet" 是个用于将某个主机连接到Internet 上的协议,则
JDBC URL 类似:
jdbc:dbnet://wombat:356/fred 2.1.4 "odbc" 子协议子协议odbc 是一种特殊情况。它是为
用于指定ODBC 风格的数据资源名称的URL 而保留的,并具有下列特性:允许在子名称(数据资源名称)后面指定任意多个属性值。odbc 子协议的完整语法为:
jdbc:odbc:< 数据资源名称>[;< 属性名>=< 属性值>]*
因此,以下都是合法的jdbc:odbc 名称:
jdbc:odbc:qeor7jdbc:odbc:wombat
jdbc:odbc:wombat;CacheSize=20;ExtensionCase=LOWER
jdbc:odbc:qeora;UID=kgh;PWD=fooey
11 注册子协议驱动程序编程员可保留某个名称以将之用作JDBC URL 的子协议名。
当DriverManager 类将此名称加到已注册的驱动程序清单中时,为之保留该名称的驱动程序应
能识别该名称并与它所标识的数据库建立连接。例如,odbc 是为JDBC- ODBC 桥而保留的。
示例之二,假设有个Miracle 公司,它可能会将"miracle" 注册为连接到其Miracle DBMS 上
的JDBC 驱动程序的子协议,从而使其他人都无法使用这个名称。JavaSoft 目前作为非正式代理负责注册JDBC 子协议名称。要注册某个子协议名称,请发送电子邮件到下述地址:
jdbc@wombat.eng.sun.com
12 发送
SQL 语句连接一旦建立,就可用来向它所涉及的数据库传送SQL 语句。JDBC 对可被发送的SQL语句类型不加任何限制。这就提供了很大的灵活性,即允许使用特定的数据库语句或甚至于非SQL语句。然而,它要求用户自己负责确保所涉 及的数据库可以处理所发送的SQL 语句,否则将自食其果。例如,如果某个应用程序试图向不支持储存程序的DBMS 发送储存程序调用,就会失败并将抛出异常。JDBC 要求驱动程序应至少能提供ANSI SQL-2 Entry Level 功能才可算是符合JDBC标准TM 的。这意味着用户至少可信赖这一标准级别的功能。JDBC 提供了三个类,用于向数据库发送SQL语句。
Connection 接口中的三个方法可用于创建这些类的实例。下面列出这些类及其创建方法:
Statement ─ 由方法createStatement 所创建。Statement 对象用于发送简单的SQL 语句。
PreparedStatement ─ 由方法prepareStatement 所创建。PreparedStatement 对象用于发送带有一个或多个输入参数( IN 参数)的SQL 语句。PreparedStatement 拥有一组方法,用于设置IN 参数的值。执行语句时,这些IN 参数将被送到数据库中。PreparedStatement 的实例扩展了Statement ,因此它们都包括了Statement 的方法。PreparedStatement 对象有可能比Statement 对象的效率更高,因为它已被预编译过并存放在那以供将来使用。
CallableStatement ─ 由方法prepareCall 所创建。CallableStatement 对象用于执行SQL储存程序─ 一组可通过名称来调用(就象函数的调用那样)的SQL 语句。CallableStatement 对象从PreparedStatement 中继承了用于处理IN 参数的方法,而且还增加了用于处理OUT 参数和INOUT 参数的方法。
以下所列提供的方法可以快速决定应用哪个Connection 方法来创建不同类型的SQL 语句:
createStatement 方法用于:简单的SQL 语句(不带参数)
prepareStatement 方法用于: 带一个或多个IN 参数的SQL 语句经常被执行的简单SQL 语句prepareCall 方法用于: 调用已储存过程
13 事务
事 务由一个或多个这样的语句组成:这些语句已被执行、完成并被提交或还原。当调用方法commit 或rollback 时,当前事务即告就结束,另一个事务随即开始。缺省情况下,新连接将处于自动提交模式。也就是说,当执行完语句后,将自动对那个语句调用commit 方法。这种情况下,由于每个语句都是被单独提交的,因此一个事务只由一个语句组成。如果禁用自动提交模式,事务将要等到commit 或rollback 方法被显式调用时才结束,因此它将包括上一次调用commit 或rollback 方法以来所有执行过的语句。对于第二种情况,事务中的所有语句将作为组来提交或还原。方法commit 使SQL 语句对数据库所做的任何更改成为永久性的,它还将释放事务持有的全部锁。而方法rollback 将弃去那些更改。有时用户在另一个更改生效前不想让此更改生效。这可通过禁用自动提交并将两个更新组合在一个事务中来达到。如果两个更新都是成功,则调用 commit 方法,从而使两个更新结果成为永久性的;如果其中之一或两个更新都失败了,则调用rollback 方法,以将值恢复为进行更新之前的值。大多数JDBC 驱动程序都支持事务。事实上,符合JDBC 的驱动程序必须支持事务。DatabaseMetaData 给出的信息描述DBMS 所提供的事务支持水平。
14 事务隔离级别
如果DBMS 支持事务处理,它必须有某种途径来管理两个事务同时对一个数据库进行操作时可
能 发生的冲突。用户可指定事务隔离级别,以指明DBMS 应该花多大精力来解决潜在冲突。例如,当事务更改了某个值而第二个事务却在该更改被提交或还原前读取该值时该怎么办假设第一个事务被还原后,第二个事务所 读取的更改值将是无效的,那么是否可允许这种冲突? JDBC 用户可用以下代码来指示DBMS 允许在值被提交前读取该值(“dirty 读取”),其中con 是当前连接:
con.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);
事务隔离级别越高,为避免冲突所花的精力也就越多。Connection 接口定义了五级,其中最低
级 别指定了根本就不支持事务,而最高级别则指定当事务在对某个数据库进行操作时,任何其它事务不得对那个事务正在读取的数据进行任何更改。通常,隔离级别越 高,应用程序执行的速度也就越慢(由于用于锁定的资源耗费增加了,而用户间的并发操作减少了)。在决定采用什么隔离级别时,开发人员必须在性能需求和数据 一致性需求之间进行权衡。当然,实际所能支持的级别取决于所涉及的DBMS 的功能。当创建Connection 对象时,其事务隔离级别取决于驱动程序,但通常是所涉及的数据库的缺省值。用户可通过调用setIsolationLevel方法来更改事务隔离级别。新 的级别将在该连接过程的剩余时间内生效。要想只改变一个事务的事务隔离级别,必须在该事务开始前进行设置,并在该事务结束后进行复位。我们不提倡在事务的 中途对事务隔离级别进行更改,因为这将立即触发commit方法的调用,使在此之前所作的任何更改变成永久性的。
15 DriverManager 类-------------驱动设置
DriverManager 类是JDBC 的管理层,作用于用户和驱动程序之间。它跟踪可用的驱动程序,
并 在数据库和相应驱动程序之间建立连接。另外,DriverManager 类也处理诸如驱动程序登录时间限制及登录和跟踪消息的显示等事务。对于简单的应用程序,一般程序员需要在此类中直接使用的唯一方法是 DriverManager.getConnection。正如名称所示,该方法将建立与数据库的连接。JDBC 允许用户调用DriverManager 的方法getDriver、getDrivers 和registerDriver 及Driver 的方法connect。但多数情况下,让DriverManager 类管理建立连接的细节为上策。
1、跟踪可用驱动程序
DriverManager 类包含一列Driver 类,它们已通过调用方法DriverManager.registerDriver
对 自己进行了注册。所有Driver 类都必须包含有一个静态部分。它创建该类的实例,然后在加载该实例时DriverManager 类进行注册。这样,用户正常情况下将不会直接调用DriverManager.registerDriver;而是在加载驱动程序时由驱动程序自动调用。 加载Driver 类,
然后自动在DriverManager 中注册的方式有两种:
通过调用方法Class.forName。这将显式地加载驱动程序类。由于这与外部设置无关,因此推
荐使用这种加载驱动程序的方法。以下代码加载类acme.db.Driver:Class.forName("acme.db.Driver");
如果将acme.db.Driver 编写为加载时创建实例,并调用以该实例为参数的DriverManager.registerDriver(本该如此),则它在DriverManager 的驱动程序列表中,并可用
于创建连接。通过将驱动程序添加到java.lang.System 的属性jdbc.drivers 中。这是一个由
DriverManager 类加载的驱动程序类名的列表,由冒号分隔:初始化DriverManager 类时,它搜索系统属性jdbc.drivers,如果用户已输入了一个或多个驱动程序,则DriverManager 类将试图加载它们。以下代码说明程序员如何在~/.hotjava/properties 中输入三个驱动程序类(启动时,
HotJava 将把它加载到系统属性列表中):
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver;
对DriverManager 方法的第一次调用将自动加载这些驱动程序类。
注意:加载驱动程序的第二种方法需要持久的预设环境。如果对这一点不能保证,则调用方法
Class.forName 显式地加载每个驱动程序就显得更为安全。这也是引入特定驱动程序的方法,因为一旦DriverManager 类被初始化,它将不再检查jdbc.drivers 属性列表。在以上两种情况中,新加载的Driver 类都要通过调用DriverManager.registerDriver 类进行自我注册。如上所述,加载类时将自动执行这一过程。由于安全方面的原因,JDBC 管理层将跟踪哪个类加载器提供哪个驱动程序。这样,当DriverManager 类打开连接时,它仅使用本地文件系统或与发出连接请求的代码相同的类加载器提供的驱动程序。
2、建立连接
加载Driver 类并在DriverManager 类中注册后,它们即可用来与数据库建立连接。当调用
DriverManager.getConnection 方法发出连接请求时,DriverManager 将检查每个驱动程序,查看
它 是否可以建立连接。有时可能有多个JDBC 驱动程序可以与给定的URL 连接。例如,与给定远程数据库连接时,可以使用JDBC-ODBC 桥驱动程序、JDBC 到通用网络协议驱动程序或数据库厂商提供的驱动程序。在这种情况下,测试驱动程序的顺序至关重要,因为DriverManager 将使用它所找到的第一个可以成功连接到给定URL 的驱动程序。
首先DriverManager 试图按注册的顺序使用每个驱动程序(jdbc.drivers 中列出的驱动程序
总 是先注册)。它将跳过代码不可信任的驱动程序,除非加载它们的源与试图打开连接的代码的源相同。它通过轮流在每个驱动程序上调用方法 Driver.connect,并向它们传递用户开始传递给方法DriverManager.getConnection 的URL 来对驱动程序进行测试,然后连接第一个认出该URL 的驱动程序。这种方法初看起来效率不高,但由于不可能同时加载数十个驱动程序,因此每次连接实际只需几个过程调用和字符串比较。
以下代码是通常情况下用驱动程序(例如JDBC-ODBC 桥驱动程序)建立连接所需所有步骤的示例:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //加载驱动程序
String url = "jdbc:odbc:fred";
DriverManager.getConnection(url, "userID", "passwd");
16 Statement 对象-----------语句
Statement 对象用于将SQL 语句发送到数据库中。实际上有三种Statement 对象,它们都作
为在给定连接上执行SQL 语句的包容器:Statement、PreparedStatement(它从Statement 继承
而来)和CallableStatement(它从PreparedStatement 继承而来)。它们都专用于发送特定类型
的SQL 语句: Statement 对象用于执行不带参数的简单SQL 语句;PreparedStatement 对象用于执行带或不带IN 参数的预编译SQL 语句;CallableStatement 对象用于执行对数据库已存储过程的调用。Statement 接口提供了执行语句和获取结果的基本方法。PreparedStatement 接口添加了处理IN 参数的方法;而CallableStatement 添加了处理OUT 参数的方法。
1、创建Statement 对象
建立了到特定数据库的连接之后,就可用该连接发送SQL 语句。Statement 对象用Connection
的方法createStatement 创建,如下列代码段中所示:
Connection con = DriverManager.getConnection(url, "sunny", "");
Statement stmt = con.createStatement();
为了执行Statement 对象,被发送到数据库的SQL 语句将被作为参数提供给Statement 的方
法:ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table2");
2、使用Statement 对象执行语句
Statement 接口提供了三种执行SQL 语句的方法:executeQuery、executeUpdate 和execute。
使用哪一个方法由SQL 语句所产生的内容决定。
方法executeQuery 用于产生单个结果集的语句,例如SELECT 语句。
方 法executeUpdate 用于执行INSERT、UPDATE 或DELETE 语句以及SQL DDL(数据定义语言)语句,例如CREATE TABLE 和DROP TABLE。INSERT、UPDATE 或DELETE 语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新计数)。对于CREATE TABLE 或DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。
方法execute 用于执行返回多个结果集、多个更新计数或二者组合的语句。因为多数程序员不
会 需要该高级功能,所以本概述后面将在单独一节中对其进行介绍。执行语句的所有方法都将关闭所调用的Statement 对象的当前打开结果集(如果存在)。这意味着在重新执行Statement 对象之前,需要完成对当前ResultSet 对象的处理。应注意,继承了Statement 接口中所有方法的PreparedStatement 接口都有自己的executeQuery、executeUpdate 和execute 方法。Statement 对象本身不包含SQL 语句,因而必须给Statement.execute 方法提供SQL 语句作为参数。PreparedStatement 对象并不将SQL 语句
作为参数提供给这些方法,因为它们已经包含预编译SQL 语句。CallableStatement 对象继承这些方法的PreparedStatement 形式。对于这些方法的PreparedStatement 或CallableStatement 版
本,使用查询参数将抛出SQLException。
3、语句完成
当连接处于自动提交模式时,其中所执行的语句在完成时将自动提交或还原。语句在已执行且
所有结果返回时,即认为已完成。对于返回一个结果集的executeQuery 方法,在检索完ResultSet
对象的所有行时该语句完成。对于方法executeUpdate,当它执行时语句即完成。但在少数调用方法execute 的情况中,在检索所有结果集或它生成的更新计数之后语句才完成。
有些DBMS 将已存储过程中的每条语句视为独立的语句;而另外一些则将整个过程视为一个复
合语句。在启用自动提交时,这种差别就变得非常重要,因为它影响什么时候调用commit 方法。在前一种情况中,每条语句单独提交;在后一种情况中,所有语句同时提交。
4、关闭Statement 对象
Statement 对象将由Java 垃圾收集程序自动关闭。而作为一种好的编程风格,应在不需要Statement 对象时显式地关闭它们。这将立即释放DBMS 资源,有助于避免潜在的内存问题。
5、Statement 对象中的SQL 转义语法
Statement 可包含使用SQL 转义语法的SQL 语句。转义语法告诉驱动程序其中的代码应该以
不同方式处理。驱动程序将扫描任何转义语法,并将它转换成特定数据库可理解的代码。这使得转义语法与DBMS 无关,并允许程序员使用在没有转义语法时不可用的功能。转义子句由花括号和关键字界定:
{keyword . . . parameters . . . }
该关键字指示转义子句的类型,如下所示。
escape 表示LIKE 转义字符
字符“%”和“_”类似于SQL LIKE 子句中的通配符(“%”匹配零个或多个字符,而“_”则匹
配一个字符)。为了正确解释它们,应在其前面加上反斜杠(“\”),它是字符串中的特殊转义字符。在查询末尾包括如下语法即可指定用作转义字符的字符:
{escape 'escape-character'}
例如,下列查询使用反斜杠字符作为转义字符,查找以下划线开头的标识符名:
stmt.executeQuery("SELECT name FROM Identifiers
WHERE Id LIKE `\_%' {escape `\'};
fn 表示标量函数
几乎所有DBMS 都具有标量值的数值、字符串、时间、日期、系统和转换函数。要使用这些函
数,可使用如下转义语法:关键字fn 后跟所需的函数名及其参数。例如,下列代码调用函数concat将两个参数连接在一起:
{fn concat("Hot", "Java")};
可用下列语法获得当前数据库用户名:{fn user()};
标量函数可能由语法稍有不同的DBMS 支持,而它们可能不被所有驱动程序支持。各种
DatabaseMetaData 方法将列出所支持的函数。例如,方法getNumericFunctions 返回用逗号分隔
的数值函数列表,而方法getStringFunctions 将返回字符串函数,等等。
驱动程序将转义函数调用映射为相应的语法,或直接实现该函数。
d、t 和ts 表示日期和时间文字
DBMS 用于日期、时间和时间标记文字的语法各不相同。JDBC 使用转义子句支持这些文字的语
法的ISO 标准格式。驱动程序必须将转义子句转换成DBMS 表示。
例如,可用下列语法在JDBC SQL 语句中指定日期:
{d `yyyy-mm-dd'}
在该语法中,yyyy 为年代,mm 为月份,而dd 则为日期。驱动程序将用等价的特定于DBMS 的
表示替换这个转义子句。例如,如果'28- FEB-99' 符合基本数据库的格式,则驱动程序将用它替
换{d 1999-02-28}。
对于TIME 和TIMESTAMP 也有类似的转义子句:
{t `hh:mm:ss'}
{ts `yyyy-mm-dd hh:mm:ss.f . . .'}
TIMESTAMP 中的小数点后的秒(.f . . .)部分可忽略。
call 或? = call 表示已存储过程
如果数据库支持已存储过程,则可从JDBC 中调用它们,语法为:
{call procedure_name[(?, ?, . . .)]}
或(其中过程返回结果参数):
{? = call procedure_name[(?, ?, . . .)]}
方括号指示其中的内容是可选的。它们不是语法的必要部分。
输入参数可以为文字或参数。有关详细信息,参见JDBC 指南中第7 节,“CallableStatement”。
可通过调用方法DatabaseMetaData.supportsStoredProcedures 检查数据库是否支持已存储
过程。
oj 表示外部连接
外部连接的语法为
{oj outer-join}
其中outer-join 形式为
table LEFT OUTER JOIN {table / outer-join} ON search-condition
外部连接属于高级功能。有关它们的解释可参见SQL 语法。JDBC 提供了三种
DatabaseMetaData 方法用于确定驱动程序支持哪些外部连接类型:supportsOuterJoins、
supportsFullOuterJoins 和supportsLimitedOuterJoins。
方法Statement.setEscapeProcessing 可打开或关闭转义处理;缺省状态为打开。当性能极为
重要时,程序员可能想关闭它以减少处理时间。但通常它将出于打开状态。应注意:
setEscapeProcessing 不适用于PreparedStatement 对象,因为在调用该语句前它就可能已被发送
到数据库。有关预编译的信息,参见PreparedStatement。
6、使用方法execute
execute 方法应该仅在语句能返回多个ResultSet 对象、多个更新计数或ResultSet 对象与
更 新计数的组合时使用。当执行某个已存储过程或动态执行未知SQL 字符串(即应用程序程序员在编译时未知)时,有可能出现多个结果的情况,尽管这种情况很少见。例如,用户可能执行一个已存储过程(使用 CallableStatement 对象- 参见第135 页的CallableStatement),并且该已存储过程可执行更新,然后执行选择,再进行更新,再进行选择,等等。通常使用已存储过程的人应知道它 所返回的内容。
因为方法execute 处理非常规情况,所以获取其结果需要一些特殊处理并不足为怪。例如,假
定已知某个过程返回两个结果集,则在使用方法execute 执行该过程后,必须调用方法
getResultSet 获得第一个结果集,然后调用适当的getXXX 方法获取其中的值。要获得第二个结果集,需要先调用getMoreResults 方法,然后再调用getResultSet 方法。如果已知某个过程返回两个更新计数,则首先调用方法getUpdateCount,然后调用getMoreResults,并再次调用
getUpdateCount。 对于不知道返回内容,则情况更为复杂。如果结果是ResultSet 对象,则方法execute 返回true;如果结果是Java int,则返回false。如果返回int,则意味着结果是更新计数或执行的语句是DDL 命令。在调用方法execute 之后要做的第一件事情是调用getResultSet 或
getUpdateCount。调用方法getResultSet 可以获得两个或多个ResultSet 对象中第一个对象;或
调用方法getUpdateCount 可以获得两个或多个更新计数中第一个更新计数的内容。
当SQL 语句的结果不是结果集时,则方法getResultSet 将返回null。这可能意味着结果是
一个更新计数或没有其它结果。在这种情况下,判断null 真正含义的唯一方法是调用方法
getUpdateCount, 它将返回一个整数。这个整数为调用语句所影响的行数;如果为-1 则表示结果是结果集或没有结果。如果方法getResultSet 已返回null(表示结果不是ResultSet 对象),则返回值-1 表示没有其它结果。也就是说,当下列条件为真时表示没有结果(或没有其它结果):
((stmt.getResultSet() == null) && (stmt.getUpdateCount() == -1))
如果已经调用方法getResultSet 并处理了它返回的ResultSet 对象,则有必要调用方法
getMoreResults 以确定是否有其它结果集或更新计数。如果getMoreResults 返回true,则需要
再次调用getResultSet 来检索下一个结果集。如上所述,如果getResultSet 返回null,则需要
调用getUpdateCount 来检查null 是表示结果为更新计数还是表示没有其它结果。
当getMoreResults 返回false 时,它表示该SQL 语句返回一个更新计数或没有其它结果。
因此需要调用方法getUpdateCount 来检查它是哪一种情况。在这种情况下,当下列条件为真时表示没有其它结果:
((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
下面的代码演示了一种方法用来确认已访问调用方法execute 所产生的全部结果集和更新计
数:
stmt.execute(queryStringWithUnknownResults);
while (true) {
int rowCount = stmt.getUpdateCount();
if (rowCount > 0) { // 它是更新计数
System.out.println("Rows changed = " + count);
stmt.getMoreResults();
continue;
}
if (rowCount == 0) { // DDL 命令或0 个更新
System.out.println(" No rows changed or statement was DDL command");
stmt.getMoreResults();
continue;
}
// 执行到这里,证明有一个结果集
// 或没有其它结果
ResultSet rs = stmt.getResultSet;
if (rs != null) {
. . . // 使用元数据获得关于结果集列的信息
while ( rs
break; // 没有其它结果
17 ResultSet 对象
ResultSet 包含符合SQL 语句中条件的所有行,并且它通过一套get 方法(这些get 方法可
以访问当前行中的不同列)提供了对这些行中数据的访问。ResultSet.next 方法用于移动到
ResultSet 中的下一行,使下一行成为当前行。
结果集一般是一个表,其中有查询所返回的列标题及相应的值。例如,如果查询为SELECT a, b,
c FROM Table1,则结果集将具有如下形式:
a b c
-------- --------- --------
12345 Cupertino CA
83472 Redmond WA
83492 Boston MA
下面的代码段是执行SQL 语句的示例。该SQL 语句将返回行集合,其中列1 为int,列2 为
String,而列3 则为字节数组:
java.sql.Statement stmt = conn.createStatement();
ResultSet r = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (r.next())
{
// 打印当前行的值。
int i = r.getInt("a");
String s = r.getString("b");
float f = r.getFloat("c");
System.out.println("ROW = " + i + " " + s + " " + f);
}
1、行和光标
ResultSet 维护指向其当前数据行的光标。每调用一次next 方法,光标向下移动一行。最初
它位于第一行之前,因此第一次调用next 将把光标置于第一行上,使它成为当前行。随着每次调用next 导致光标向下移动一行,按照从上至下的次序获取ResultSet 行。
在ResultSet 对象或其父辈Statement 对象关闭之前,光标一直保持有效。
在SQL 中,结果表的光标是有名字的。如果数据库允许定位更新或定位删除,则需要将光标的
名字作为参数提供给更新或删除命令。可通过调用方法getCursorName 获得光标名。
注意:不是所有的DBMS 都支持定位更新和删除。可使用
DatabaseMetaData.supportsPositionedDelete 和supportsPositionedUpdate 方法来检查特定连
接是否支持这些操作。当支持这些操作时,DBMS/驱动程序必须确保适当锁定选定行,以使定位更新不会导致更新异常或其它并发问题。
2、列
方法getXXX 提供了获取当前行中某列值的途径。在每一行内,可按任何次序获取列值。但为
了保证可移植性,应该从左至右获取列值,并且一次性地读取列值。列名或列号可用于标识要从中获取数据的列。例如,如果ResultSet 对象rs 的第二列名为“title”,并将值存储为字符串,
则下列任一代码将获取存储在该列中的值:
String s = rs.getString("title");
String s = rs.getString(2);
注意列是从左至右编号的,并且从列1 开始。同时,用作getXXX 方法的输入的列名不区分大
小写。提供使用列名这个选项的目的是为了让在查询中指定列名的用户可使用相同的名字作为
getXXX 方法的参数。另一方面,如果select 语句未指定列名(例如在“select * from table1”
中或列是导出的时),则应该使用列号。这些情况下,户将无法确切知道列名。
有 些情况下,SQL 查询返回的结果集中可能有多个列具有相同的名字。如果列名用作getXXX 方法的参数,则getXXX 将返回第一个匹配列名的值。因而,如果多个列具有相同的名字,则需要使用列索引来确保检索了正确的列值。这时,使用列号效率要稍微高一些。关于 ResultSet 中列的信息,可通过调用方法ResultSet.getMetaData 得到。返回的ResultSetMetaData 对象将给出其ResultSet 对象各列的编号、类型和属性。如果列名已知,但不知其索引,则可用方法findColumn 得到其列号。
3、数据类型和转换
对于getXXX 方法,JDBC 驱动程序试图将基本数据转换成指定Java 类型,然后返回适合的
Java 值。例如,如果getXXX 方法为getString,而基本数据库中数据类型为VARCHAR,则JDBC 驱动程序将把VARCHAR 转换成Java String。getString 的返回值将为Java String 对象。下表显示了允许用getXXX 获取的JDBC 类型及推荐用它获取的JDBC 类型(通用SQL 类型)。小写的x 表示允许getXXX 方法获取该数据类型;大写的X 表示对该数据类型推荐使用getXXX
方法。例如,除了getBytes 和getBinaryStream 之外的任何getXXX 方法都可用来获取LONGVARCHAR 值,但是推荐根据返回的数据类型使用getAsciiStream 或getUnicodeStream 方法。方法getObject 将任何数据类型返回为Java Object。当基本数据类型是特定于数据库的抽象类型或当通用应用程序需要接受任何数据类型时,它是非常有用的。可使用 ResultSet.getXXX 方法获取常见的JDBC 数据类型。
“x”表示该getXXX 方法可合法地用于获取给定JDBC 类型。
“X”表示推荐使用该getXXX 方法来获取给定JDBC 类型。
getByte X x x x x x x x x x x x x
getShort x X x x x x x x x x x x x
getInt x x X x x x x x x x x x x
getLong x x x X x x x x x x x x x
getFloat x x x x X x x x x x x x x
getDouble x x x x x X X x x x x x x
getBigDecimal x x x x x x x X X x x x x
getBoolean x x x x x x x x x X x x x
getString x x x x x x x x x x X X x x x x x x x
getBytes X X x
getDate x x x X x
getTime x x x X x
getTimestamp x x x x X
getAsciiStream x x X x x x
getUnicodeStream x x X x x x
getBinaryStream x x X
getObject x x x x x x x x x x x x x x x x x x x
4、对非常大的行值使用流
ResultSet 可以获取任意大的LONGVARBINARY 或LONGVARCHAR 数据。方法getBytes 和
getString 将数据返回为大的块(最大为Statement.getMaxFieldSize 的返回值)。但是,以较小
的固定块获取非常大的数据可能会更方便,而这可通过让ResultSet 类返回java.io.Input 流来
完成。从该流中可分块读取数据。注意:必须立即访问这些流,因为在下一次对ResultSet 调用
getXXX 时它们将自动关闭(这是由于基本实现对大块数据访问有限制)。
JDBC API 具有三个获取流的方法,分别具有不同的返回值:
getBinaryStream 返回只提供数据库原字节而不进行任何转换的流。
getAsciiStream 返回提供单字节ASCII 字符的流。
getUnicodeStream 返回提供双字节Unicode 字符的流。
注意:它不同于Java 流,后者返回无类型字节并可(例如)通用于ASCII 和Unicode 字符。
下列代码演示了getAsciiStream 的用法:
java.sql.Statement stmt = con.createStatement();
ResultSet r = stmt.executeQuery("SELECT x FROM Table2");
// 现在以4K 块大小获取列1 结果:
byte buff = new byte[4096];
while (r
// 将新填充的缓冲区发送到ASCII 输出流:
output.write(buff, 0, size);
}
}
5、NULL 结果值
要确定给定结果值是否是JDBC NULL,必须先读取该列,然后使用ResultSet.wasNull 方法检
查该次读取是否返回JDBC NULL。
当使用ResultSet.getXXX 方法读取JDBC NULL 时,方法wasNull 将返回下列值之一:
Java null 值:对于返回Java 对象的getXXX 方法(例如getString、getBigDecimal、
getBytes、getDate、getTime、getTimestamp、getAsciiStream、getUnicodeStream、getBinaryStream、
getObject 等)。
零值:对于getByte、getShort、getInt、getLong、getFloat 和getDouble。
false 值:对于getBoolean。
6、可选结果集或多结果集
通常使用executeQuery(它返回单个ResultSet)或executeUpdate(它可用于任何数据库修
改语句,并返回更新行数)可执行SQL 语句。但有些情况下,应用程序在执行语句之前不知道该语句是否返回结果集。此外,有些已存储过程可能返回几个不同的结果集和/或更新计数。
为 了适应这些情况,JDBC 提供了一种机制,允许应用程序执行语句,然后处理由结果集和更新计数组成的任意集合。这种机制的原理是首先调用一个完全通用的execute 方法,然后调用另外三个方法,getResultSet、getUpdateCount 和getMoreResults。这些方法允许应用程序一次一个地研究语句结果,并确定给定结果是ResultSet 还是更新计数。
用户不必关闭ResultSet;当产生它的Statement 关闭、重新执行或用于从多结果序列中获取
下一个结果时,该ResultSet 将被Statement 自动关闭。
18 PreparedStatement 接口
该PreparedStatement 接口继承Statement,并与之在两方面有所不同:
PreparedStatement 实例包含已编译的SQL 语句。这就是使语句“准备好”。包含于
PreparedStatement 对象中的SQL 语句可具有一个或多个IN 参数。IN 参数的值在SQL 语句创建时未被指定。相反的,该语句为每个IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX 方法来提供。
由于PreparedStatement 对象已预编译过,所以其执行速度要快于Statement 对象。因此,
多次执行的SQL 语句经常创建为PreparedStatement 对象,以提高效率。
作为Statement 的子类,PreparedStatement 继承了Statement 的所有功能。另外它还添加
了一整套方法,用于设置发送给数据库以取代IN 参数占位符的值。同时,三种方法execute、
executeQuery 和executeUpdate 已被更改以使之不再需要参数。这些方法的Statement 形式(接
受SQL 语句参数的形式)不应该用于PreparedStatement 对象。
1、创建PreparedStatement 对象
以下的代码段(其中con 是Connection 对象)创建包含带两个IN 参数占位符的SQL 语句
的PreparedStatement 对象:
PreparedStatement pstmt = con.prepareStatement("UPDATE table4 SET m = ? WHERE x = ?");
pstmt 对象包含语句"UPDATE table4 SET m = ? WHERE x = ?",它已发送给DBMS,并为执行
作好了准备。
2、传递IN 参数
在执行PreparedStatement 对象之前,必须设置每个? 参数的值。这可通过调用setXXX 方
法 来完成,其中XXX 是与该参数相应的类型。例如,如果参数具有Java 类型long,则使用的方法就是setLong。setXXX 方法的第一个参数是要设置的参数的序数位置,第二个参数是设置给该参数的值。例如,以下代码将第一个参数设为123456789,第二个参数设为 100000000:
pstmt.setLong(1, 123456789);
pstmt.setLong(2, 100000000);
一旦设置了给定语句的参数值,就可用它多次执行该语句,直到调用clearParameters 方法清
除它为止。在连接的缺省模式下(启用自动提交),当语句完成时将自动提交或还原该语句。
如果基本数据库和驱动程序在语句提交之后仍保持这些语句的打开状态,则同一个
PreparedStatement 可执行多次。如果这一点不成立,那么试图通过使用PreparedStatement 对象
代替Statement 对象来提高性能是没有意义的。
利用pstmt(前面创建的PreparedStatement 对象),以下代码例示了如何设置两个参数占位
符的值并执行pstmt 10 次。如上所述,为做到这一点,数据库不能关闭pstmt。在该示例中,第一个参数被设置为"Hi"并保持为常数。在for 循环中,每次都将第二个参数设置为不同的值:从0开始,到9 结束。
pstmt.setString(1, "Hi");
for (int i = 0; i < 10; i++) {
pstmt.setInt(2, i);
int rowCount = pstmt.executeUpdate();
}
3、IN 参数中数据类型的一致性
setXXX 方法中的XXX 是Java 类型。它是一种隐含的JDBC 类型(一般SQL 类型),因为驱
动程序将把Java 类型映射为相应的JDBC 类型(遵循该JDBCGuide 中§8.6.2 “映射Java 和
JDBC 类型”表中所指定的映射),并将该JDBC 类型发送给数据库。例如,以下代码段将
PreparedStatement 对象pstmt 的第二个参数设置为44,Java 类型为short:
pstmt.setShort(2, 44);
驱动程序将44 作为JDBC SMALLINT 发送给数据库,它是Java short 类型的标准映射。
程序员的责任是确保将每个IN 参数的Java 类型映射为与数据库所需的JDBC 数据类型兼容
的JDBC 类型。不妨考虑数据库需要JDBC SMALLINT 的情况。如果使用方法setByte ,则驱动程序将JDBC TINYINT 发送给数据库。这是可行的,因为许多数据库可从一种相关的类型转换为另一种类型,并且通常TINYINT 可用于SMALLINT 适用的任何地方
19 CallableStatement 对象
CallableStatement 对象为所有的DBMS 提供了一种以标准形式调用已储存过程的方法。已储
存 过程储存在数据库中。对已储存过程的调用是CallableStatement 对象所含的内容。这种调用是用一种换码语法来写的,有两种形式:一种形式带结果参,另一种形式不带结果参数。结果参数是一种输出(OUT) 参数,是已储存过程的返回值。两种形式都可带有数量可变的输入(IN 参数)、输出(OUT 参数)或输入和输出(INOUT 参数)的参数。问号将用作参数的占位符。
在JDBC 中调用已储存过程的语法如下所示。注意,方括号表示其间的内容是可选项;方括号本身并不是语法的组成部份。
{call 过程名[(?, ?, ...)]}
返回结果参数的过程的语法为:
{? = call 过程名[(?, ?, ...)]}
不带参数的已储存过程的语法类似:
{call 过程名}
通常,创建CallableStatement 对象的人应当知道所用的DBMS 是支持已储存过程的,并且知
道这些过程都是些什么。然而,如果需要检查,多种DatabaseMetaData 方法都可以提供这样的信息。例如,如果DBMS 支持已储存过程的调用,则supportsStoredProcedures 方法将返回true,
而getProcedures 方法将返回对已储存过程的描述。CallableStatement 继承Statement 的方法
(它们用于处理一般的SQL 语句),还继承了PreparedStatement 的方法(它们用于处理IN 参)。
CallableStatement 中定义的所有方法都用于处理OUT 参数或INOUT 参数的输出部分:注册
OUT 参数的JDBC 类型(一般SQL 类型)、从这些参数中检索结果,或者检查所返回的值是否为JDBC NULL。
1、创建CallableStatement 对象
CallableStatement 对象是用Connection 方法prepareCall 创建的。下例创建
CallableStatement 的实例,其中含有对已储存过程getTestData 调用。该过程有两个变量,但不
含结果参数:
CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
其中?占位符为IN、OUT 还是INOUT 参数,取决于已储存过程getTestData。
2、IN 和OUT 参数
将IN 参数传给CallableStatement 对象是通过setXXX 方法完成的。该方法继承自
PreparedStatement。所传入参数的类型决定了所用的setXXX 方法(例如,用setFloat 来传入
float 值等)。如果已储存过程返回OUT 参数,则在执行CallableStatement 对象以前必须先注册每个OUT参数的JDBC 类型(这是必需的,因为某些DBMS 要求JDBC 类型)。注册JDBC 类型是用registerOutParameter 方法来完成的。语句执行完后,CallableStatement 的getXXX 方法将取回参数值。正确的getXXX 方法是为各参数所注册的JDBC 类型所对应的Java 类型。换言之,registerOutParameter 使用的是JDBC 类型(因此它与数据库返回的JDBC 类型匹配),而getXXX将之转换为Java 类型。
作为示例,下述代码先注册OUT 参数,执行由cstmt 所调用的已储存过程,然后检索在OUT 参
数中返回的值。方法getByte 从第一个OUT 参数中取出一个Java 字节,而getBigDecimal 从第二个OUT 参数中取出一个BigDecimal 对象(小数点后面带三位数):
CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3);
cstmt.executeQuery();
byte x = cstmt.getByte(1);
java.math.BigDecimal n = cstmt.getBigDecimal(2, 3);
CallableStatement 与ResultSet 不同,它不提供用增量方式检索大OUT 值的特殊机制。
3、INOUT 参数
既支持输入又接受输出的参数(INOUT 参数)除了调用registerOutParameter 方法外,还要
求调用适当的setXXX 方法(该方法是从PreparedStatement 继承来的)。setXXX 方法将参数值
设 置为输入参数,而registerOutParameter 方法将它的JDBC 类型注册为输出参数。setXXX 方法提供一个Java 值,而驱动程序先把这个值转换为JDBC 值,然后将它送到数据库中。这种IN 值的JDBC 类型和提供给registerOutParameter 方法的JDBC 类型应该相同。然后,要检索输出值,就要用对应的getXXX 方法。例如,Java 类型为byte 的参数应该使用方法setByte 来赋输入值。应该给registerOutParameter 提供类型为TINYINT 的JDBC 类型,同时应使用getByte 来检索输出值。
下例假设有一个已储存过程reviseTotal,其唯一参数是INOUT 参数。方法setByte 把此参数设为25,驱动程序将把它作为JDBC TINYINT 类型送到数据库中。接着,registerOutParameter
将该参数注册为JDBC TINYINT。执行完该已储存过程后,将返回一个新的JDBC TINYINT 值。方法
getByte 将把这个新值作为Java byte 类型检索。
CallableStatement cstmt = con.prepareCall("{call reviseTotal(?)}");
cstmt.setByte(1, 25);
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.executeUpdate();
byte x = cstmt.getByte(1);
4、先检索结果,再检索OUT 参数
由于某些DBMS 的限制,为了实现最大的可移植性,建议先检索由执行CallableStatement 对
象所产生的结果,然后再用CallableStatement.getXXX 方法来检索OUT 参数。如果
CallableStatement 对象返回多个ResultSet 对象(通过调用execute 方法),在检索OUT 参数
前应先检索所有的结果。这种情况下,为确保对所有的结果都进行了访问,必须对Statement 方法getResultSet、getUpdateCount 和getMoreResults 进行调用,直到不再有结果为止。
检索完所有的结果后,就可用CallableStatement.getXXX 方法来检索OUT 参数中的值。
5、检索作为OUT 参数的NULL 值
返 回到OUT 参数中的值可能会是JDBC NULL。当出现这种情形时,将对JDBC NULL 值进行转换以使getXXX 方法所返回的值为null、0 或false,这取决于getXXX 方法类型。对于ResultSet对象,要知道0 或false 是否源于JDBCNULL 的唯一方法,是用方法wasNull进行检测。如果getXXX方法读取的最后一个值是JDBC NULL,则该方法返回true,否则返回flase。
简单地说,JDBC 可做三件事:
与数据库建立连接,
发送SQL 语句,
处理结果。
下列代码段给出了以上三步的基本示例:
Connection con = DriverManager.getConnection ("jdbc:odbc:wombat", "login", "password");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next())
System.out.println(rs.getString("a") + " " + rs.getString("b") + " " + rs.getString("c"));
2. JDBC 是一种低级API ,是高级API 的基础
JDBC 是个“低级”接口,也就是说,它用于直接调用SQL 命令。在这方面它的功能极佳,并
比其它的数据库连接API 易于使用,但它同时也被设计为一种基础接口,在它之上可以建立高级接口和工具。高级接口是“对用户友好的”接口,它使用的是一种更易理解和更为方便的API,这种API 在幕后被转换为诸如JDBC 这样的低级接口。
3. JDBC 与ODBC 和其它API 的比较
目前,Microsoft 的ODBC(开放式数据库连接)API 可能是使用最广的、用于访问关系数据库
的编程接口。它能在几乎所有平台上连接几乎所有的数据库。为什么Java 不使用ODBC?
对 这个问题的回答是:Java 可以使用ODBC,但最好是在JDBC 的帮助下以JDBC-ODBC 桥的形式使用,这一点我们稍后再说。现在的问题已变成:“为什么需要JDBC”? 回答如下:ODBC 不适合直接在Java 中使用,因为它使用C 语言接口。从Java 调用本地C 代码在安全性、实现、坚固性和程序的自动移植性方面都有许多缺点。
启用“纯Java ”机制需要象JDBC 这样的Java API。如果使用ODBC,就必须手动地将ODBC 驱动程序管理器和驱动程序安装在每台客户机上。如果完全用Java 编写JDBC 驱动程序则JDBC 代码在所有Java 平台上(从网络计算机到大型机)都可以自动安装、移植并保证安全性。
4 两层模型和三层模型
JDBC API 既支持数据库访问的两层模型,同时也支持三层模型。
在两层模型中,Java applet 或应用程序将直接与数据库进行对话。这将需要一个JDBC 驱动
程 序来与所访问的特定数据库管理系统进行通讯。用户的SQL 语句被送往数据库中,而其结果将被送回给用户。数据库可以位于另一台计算机上,用户通过网络连接到上面。这就叫做客户机/服务器配置,其中用户的计算机为 客户机,提供数据库的计算机为服务器。网络可以是Intranet(它可将公司职员连接起来),也可以是Internet。
在三层模型中,命令先是被发送到服务的“中间层”,然后由它将SQL 语句发送给数据库。数
据 库对SQL 语句进行处理并将结果送回到中间层,中间层再将结果送回给用户。MIS 主管们都发现三层模型很吸引人,因为可用中间层来控制对公司数据的访问和可作的的更新的种类。中间层的另一个好处是,用户可以利用易于使用的高级API, 而中间层将把它转换为相应的低级调用。最后,许多情况下三层结构可提供一些性能上的好处。
到目前为止,中间层通常都用C 或C++ 这类语言来编写,这些语言执行速度较快。然而,随
着 最优化编译器(它把Java 字节代码转换为高效的特定于机器的代码)的引入,用Java 来实现中间层将变得越来越实际。这将是一个很大的进步,它使人们可以充分利用Java 的诸多优点(如坚固、多线程和安全等特征)。JDBC 对于从Java 的中间层来访问数据库非常重要。
5 SQL 的一致性
结构化查询语言(SQL) 是访问关系数据库的标准语言。困难之处在于:虽然大多数的DBMS (数
据 库管理系统)对其基本功能都使用了标准形式的SQL,但它们却不符合最近为更高级的功能定义的标准SQL 语法或语义。例如,并非所有的数据库都支持储存程序或外部连接,那些支持这一功能的数据库又相互不一致。人们希望SQL 中真正标准的那部份能够进行扩展以包括越来越多的功能。但同时JDBC API 又必须支持现有的SQL。JDBC API 解决这个问题的一种方法是允许将任何查询字符串一直传到所涉及的DBMS 驱动程序上。这意味着应用程序可以使用任意多的SQL 功能,但它必须冒这样的风险:有可能在某些DBMS 上出错。事实上,应用程序查询甚至不一定要是SQL,或者说它可以是个为特定的DBMS 设计的SQL 的专用派生物(例如,文档或图象查)。
JDBC 处理SQL 一致性问题的第二种方法是提供ODBC 风格的转义子句。这将在4.1.5 节“语
句对象中的SQL 转义语法”中讨论。转义语法为几个常见的SQL 分歧提供了一种标准的JDBC 语法。例如,对日期文字和已储存过程的调用都有转义语法。
对于复杂的应用程序,JDBC 用第三种方法来处理SQL 的一致性问题。它利用DatabaseMetaData接口来提供关于DBMS 的描述性信息,从而使应用程序能适应每个DBMS 的要求和功能。
由于JDBC API 将用作开发高级数据库访问工具和API 的基础API,因此它还必须注意其所有
上 层建筑的一致性。“符合JDBC 标准TM" 代表用户可依赖的JDBC 功能的标准级别。要使用这一说明,驱动程序至少必须支持ANSI SQL-2 EntryLevel(ANSI SQL-2 代表美国国家标准局1992 年所采用的标准。Entry Level 代表SQL 功能的特定清单)。驱动程序开发人员可用JDBC API 所带的测试工具包来确定他们的驱动程序是否符合这些标准。
“符合JDBC 标准TM” 表示提供者的JDBC 实现已经通过了JavaSoft 提供的一致性测试。
这 些一致性测试将检查JDBCAPI 中定义的所有类和方法是否都存在,并尽可能地检查程序是否具有SQL Entry Level 功能。当然,这些测试并不完全,而且JavaSoft 目前也无意对各提供者的实现进行标级。但这种一致性定义的确可对JDBC 实现提供一定的可信度。随着越来越多的数据库提供者、连接提供者、Internet 提供者和应用程序编程员对JDBC API 的接受,JDBC 也正迅速成为Java数据库访问的标准。
6 JDBC 产品
有关最新的信息,请查阅JDBC 的网站,可通过从以下URL 开始浏览找到:
http://java.sun.com/products/jdbc
7 JavaSoft 框架
JavaSoft 提供三种JDBC 产品组件,它们是Java 开发工具包(JDK) 的组成部份:JDBC 驱动
程序管理器,JDBC 驱动程序测试工具包,和JDBC-ODBC 桥。
JDBC 驱动程序管理器是JDBC 体系结构的支柱。它实际上很小,也很简单;其主要作用是把
Java 应用程序连接到正确的JDBC 驱动程序上,然后即退出。
JDBC 驱动程序测试工具包为使JDBC 驱动程序运行您的程序提供一定的可信度。只有通过
JDBC 驱动程序测试包的驱动程序才被认为是符合JDBC 标准TM 的。
JDBC-ODBC 桥使ODBC 驱动程序可被用作JDBC 驱动程序。它的实现为JDBC 的快速发展提供了一条途径,其长远目标提供一种访问某些不常见的DBMS(如果对这些不常见的DBMS 未实现JDBC) 的方法。
8 JDBC 驱动程序的类型
我们目前所知晓的JDBC 驱动程序可分为以下四个种类:
JDBC-ODBC 桥加ODBC 驱动程序:JavaSoft 桥产品利用ODBC 驱动程序提供JDBC 访问。注意,必须将ODBC 二进制代码(许多情况下还包括数据库客户机代码)加载到使用该驱动程序的每个客户机上。因此,这种类型的驱动程序最适合于企业网(这种网络上客户机的安装 不是主要问题),或者是用Java 编写的三层结构的应用程序服务器代码。
本地API - 部份用Java 来编写的驱动程序: 这种类型的驱动程序把客户机API 上的JDBC
调用转换为Oracle、Sybase、Informix、DB2 或其它DBMS 的调用。注意,象桥驱动程序一样,这种类型的驱动程序要求将某些二进制代码加载到每台客户。
JDBC 网络纯Java 驱动程序:这种驱动程序将JDBC 转换为与DBMS 无关的网络协议,之后这种协议又被某个服务器转换为一种DBMS 协议。这种网络服务器中间件能够将它的纯Java 客户机连接到多种不同的数据库上。所用的具体协议取决于提供者。通常,这是最为灵活的JDBC 驱动程序。有可能所有这种解决方案的提供者都提供适合于Intranet 用的产品。为了使这些产品也支持Internet 访问,它们必须处理Web 所提出的安全性、通过防火墙的访问等方面的额外要求。几家提供者正将JDBC 驱动程序加到他们现有的数据库中间件产品中。
本地协议纯Java 驱动程序:这种类型的驱动程序将JDBC 调用直接转换为DBMS 所使用的网
络 协议。这将允许从客户机机器上直接调用DBMS 服务器,是Intranet 访问的一个很实用的解决方法。由于许多这样的协议都是专用的,因此数据库提供者自己将是主要来源,有几家提供者已在着手做这件事了。最后,我们预计第3、 4 类驱动程序将成为从JDBC 访问数据库的首选方法。第1、2 类驱动程序在直接的纯Java 驱动程序还没有上市前将会作为过渡方案来使用。对第1、2 类驱动程序可能会有一些变种,这些变种要求有连接器,但通常这些是更加不可取的解决方案。第3、4 类驱动程序提供了Java 的所有优点,包括自动安装(例如,通过使用JDBC 驱动程序的applet 来下载该驱动程序)。
9 Connection 对象-----------连接
Connection 对象代表与数据库的连接。连接过程包括所执行的SQL 语句和在该连接上所返回
的结果。一个应用程序可与单个数据库有一个或多个连接,或者可与许多数据库有连接。2.1.1 打
开 连接与数据库建立连接的标准方法是调用DriverManager.getConnection 方法。该方法接受含有某个URL 的字符串。DriverManager 类(即所谓的JDBC管理层)将尝试找到可与那个URL 所代表的数据库进行连接的驱动程序。DriverManager 类存有已注册的Driver 类的清单。当调用方法getConnection 时,它将检查清单中的每个驱动程序,直到找到可与URL 中指定的数据库进行连接的驱动程序为止。Driver 的方法connect 使用这个URL 来建立实际的连接。用户可绕过JDBC 管理层直接调用Driver 方法。这在以下特殊情况下将很有用:当两个驱动器可同时连接到数据库中,而用户需要明确地选用其中特定的驱动器。但一般情况下,让 DriverManager 类处理打开连接这种事将更为简单。
下述代码显示如何打开一个与位于URL "jdbc:odbc:wombat" 的数据库的连接。所用的用户标
识符为"oboy" ,口令为"12Java":
String url = "jdbc:odbc:wombat";
Connection con = DriverManager.getConnection(url, "oboy", "12Java");
10 JDBC URL
JDBC URL 提供了一种标识数据库的方法,可以使相应的驱动程序能识别该数据库并与之建立连接。实际上,驱动程序编程员将决定用什么JDBC URL 来标识特定的驱动程序。用户不必关心如何来形成JDBC URL;他们只须使用与所用的驱动程序一起提供的URL 即可。JDBC 的作用是提供某些约定,驱动程序编程员在构造他们的JDBC URL 时应该遵循这些约定。由于JDBC URL 要与各种不同的驱动程序一起使用,因此这些约定应非常灵活。
首先,它们应允许不同的驱动程序使用不同的方案来命名数据库。例如, odbc 子协议允许(但并不是要求) URL含有属性值。
第二,JDBC URL 应允许驱动程序编程员将一切所需的信息编入其中。这样就可以让要与给定数据库对话的applet 打开数据库连接,而无须要求用户去做任何系统管理工作。
第 三, JDBCURL 应允许某种程度的间接性。也就是说,JDBC URL 可指向逻辑主机或数据库名,而这种逻辑主机或数据库名将由网络命名系统动态地转换为实际的名称。这可以使系统管理员不必将特定主机声明为JDBC 名称的一部份。网络命名服务(例如DNS、NIS 和DCE )有多种,而对于使用哪种命名服务并无限制。
JDBC URL 的标准语法如下所示。它由三部分组成,各部分间用冒号分隔:
jdbc:< 子协议>:< 子名称>
JDBC URL 的三个部分可分解如下:
jdbc ─ 协议。JDBC URL 中的协议总是jdbc。
<子协议> ─ 驱动程序名或数据库连接机制(这种机制可由一个或多个驱动程序支持)的名称。
子协议名的典型示例是"odbc",该名称是为用于指定ODBC 风格的数据资源名称的URL 专门保留的。例如,为了通过JDBC-ODBC 桥来访问某个数据库,可以用如下所示的URL:
jdbc:odbc:fred
本例中,子协议为"odbc",子名称"fred" 是本地ODBC 数据资源。
如果要用网络命名服务(这样JDBC URL 中的数据库名称不必是实际名称),则命名服务可以
作为子协议。例如,可用如下所示的URL :jdbc:dcenaming:accounts-payable 本例中,该URL 指
定了本地DCE 命名服务应该将数据库名称"accounts-payable" 解析为更为具体的可用于连接真
实数据库的名称。
< 子名称> ─ 一种标识数据库的方法。子名称可以依不同的子协议而变化。它还可以有子名称的子名称(含有驱动程序编程员所选的任何内部语法)。使用子名称的目的是为定位 数据库提供足够的信息。前例中,因为ODBC 将提供其余部份的信息,因此用"fred" 就已足够。然而,位于远程服务器上的数据库需要更多的信息。例如,如果数据库是通过Internet 来访问的,则在JDBC URL 中应将网络地址作为子名称的一部份包括进去,且必须遵循如下所示的标准URL 命名约定://主机名:端口/子协议假设"dbnet" 是个用于将某个主机连接到Internet 上的协议,则
JDBC URL 类似:
jdbc:dbnet://wombat:356/fred 2.1.4 "odbc" 子协议子协议odbc 是一种特殊情况。它是为
用于指定ODBC 风格的数据资源名称的URL 而保留的,并具有下列特性:允许在子名称(数据资源名称)后面指定任意多个属性值。odbc 子协议的完整语法为:
jdbc:odbc:< 数据资源名称>[;< 属性名>=< 属性值>]*
因此,以下都是合法的jdbc:odbc 名称:
jdbc:odbc:qeor7jdbc:odbc:wombat
jdbc:odbc:wombat;CacheSize=20;ExtensionCase=LOWER
jdbc:odbc:qeora;UID=kgh;PWD=fooey
11 注册子协议驱动程序编程员可保留某个名称以将之用作JDBC URL 的子协议名。
当DriverManager 类将此名称加到已注册的驱动程序清单中时,为之保留该名称的驱动程序应
能识别该名称并与它所标识的数据库建立连接。例如,odbc 是为JDBC- ODBC 桥而保留的。
示例之二,假设有个Miracle 公司,它可能会将"miracle" 注册为连接到其Miracle DBMS 上
的JDBC 驱动程序的子协议,从而使其他人都无法使用这个名称。JavaSoft 目前作为非正式代理负责注册JDBC 子协议名称。要注册某个子协议名称,请发送电子邮件到下述地址:
jdbc@wombat.eng.sun.com
12 发送
SQL 语句连接一旦建立,就可用来向它所涉及的数据库传送SQL 语句。JDBC 对可被发送的SQL语句类型不加任何限制。这就提供了很大的灵活性,即允许使用特定的数据库语句或甚至于非SQL语句。然而,它要求用户自己负责确保所涉 及的数据库可以处理所发送的SQL 语句,否则将自食其果。例如,如果某个应用程序试图向不支持储存程序的DBMS 发送储存程序调用,就会失败并将抛出异常。JDBC 要求驱动程序应至少能提供ANSI SQL-2 Entry Level 功能才可算是符合JDBC标准TM 的。这意味着用户至少可信赖这一标准级别的功能。JDBC 提供了三个类,用于向数据库发送SQL语句。
Connection 接口中的三个方法可用于创建这些类的实例。下面列出这些类及其创建方法:
Statement ─ 由方法createStatement 所创建。Statement 对象用于发送简单的SQL 语句。
PreparedStatement ─ 由方法prepareStatement 所创建。PreparedStatement 对象用于发送带有一个或多个输入参数( IN 参数)的SQL 语句。PreparedStatement 拥有一组方法,用于设置IN 参数的值。执行语句时,这些IN 参数将被送到数据库中。PreparedStatement 的实例扩展了Statement ,因此它们都包括了Statement 的方法。PreparedStatement 对象有可能比Statement 对象的效率更高,因为它已被预编译过并存放在那以供将来使用。
CallableStatement ─ 由方法prepareCall 所创建。CallableStatement 对象用于执行SQL储存程序─ 一组可通过名称来调用(就象函数的调用那样)的SQL 语句。CallableStatement 对象从PreparedStatement 中继承了用于处理IN 参数的方法,而且还增加了用于处理OUT 参数和INOUT 参数的方法。
以下所列提供的方法可以快速决定应用哪个Connection 方法来创建不同类型的SQL 语句:
createStatement 方法用于:简单的SQL 语句(不带参数)
prepareStatement 方法用于: 带一个或多个IN 参数的SQL 语句经常被执行的简单SQL 语句prepareCall 方法用于: 调用已储存过程
13 事务
事 务由一个或多个这样的语句组成:这些语句已被执行、完成并被提交或还原。当调用方法commit 或rollback 时,当前事务即告就结束,另一个事务随即开始。缺省情况下,新连接将处于自动提交模式。也就是说,当执行完语句后,将自动对那个语句调用commit 方法。这种情况下,由于每个语句都是被单独提交的,因此一个事务只由一个语句组成。如果禁用自动提交模式,事务将要等到commit 或rollback 方法被显式调用时才结束,因此它将包括上一次调用commit 或rollback 方法以来所有执行过的语句。对于第二种情况,事务中的所有语句将作为组来提交或还原。方法commit 使SQL 语句对数据库所做的任何更改成为永久性的,它还将释放事务持有的全部锁。而方法rollback 将弃去那些更改。有时用户在另一个更改生效前不想让此更改生效。这可通过禁用自动提交并将两个更新组合在一个事务中来达到。如果两个更新都是成功,则调用 commit 方法,从而使两个更新结果成为永久性的;如果其中之一或两个更新都失败了,则调用rollback 方法,以将值恢复为进行更新之前的值。大多数JDBC 驱动程序都支持事务。事实上,符合JDBC 的驱动程序必须支持事务。DatabaseMetaData 给出的信息描述DBMS 所提供的事务支持水平。
14 事务隔离级别
如果DBMS 支持事务处理,它必须有某种途径来管理两个事务同时对一个数据库进行操作时可
能 发生的冲突。用户可指定事务隔离级别,以指明DBMS 应该花多大精力来解决潜在冲突。例如,当事务更改了某个值而第二个事务却在该更改被提交或还原前读取该值时该怎么办假设第一个事务被还原后,第二个事务所 读取的更改值将是无效的,那么是否可允许这种冲突? JDBC 用户可用以下代码来指示DBMS 允许在值被提交前读取该值(“dirty 读取”),其中con 是当前连接:
con.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);
事务隔离级别越高,为避免冲突所花的精力也就越多。Connection 接口定义了五级,其中最低
级 别指定了根本就不支持事务,而最高级别则指定当事务在对某个数据库进行操作时,任何其它事务不得对那个事务正在读取的数据进行任何更改。通常,隔离级别越 高,应用程序执行的速度也就越慢(由于用于锁定的资源耗费增加了,而用户间的并发操作减少了)。在决定采用什么隔离级别时,开发人员必须在性能需求和数据 一致性需求之间进行权衡。当然,实际所能支持的级别取决于所涉及的DBMS 的功能。当创建Connection 对象时,其事务隔离级别取决于驱动程序,但通常是所涉及的数据库的缺省值。用户可通过调用setIsolationLevel方法来更改事务隔离级别。新 的级别将在该连接过程的剩余时间内生效。要想只改变一个事务的事务隔离级别,必须在该事务开始前进行设置,并在该事务结束后进行复位。我们不提倡在事务的 中途对事务隔离级别进行更改,因为这将立即触发commit方法的调用,使在此之前所作的任何更改变成永久性的。
15 DriverManager 类-------------驱动设置
DriverManager 类是JDBC 的管理层,作用于用户和驱动程序之间。它跟踪可用的驱动程序,
并 在数据库和相应驱动程序之间建立连接。另外,DriverManager 类也处理诸如驱动程序登录时间限制及登录和跟踪消息的显示等事务。对于简单的应用程序,一般程序员需要在此类中直接使用的唯一方法是 DriverManager.getConnection。正如名称所示,该方法将建立与数据库的连接。JDBC 允许用户调用DriverManager 的方法getDriver、getDrivers 和registerDriver 及Driver 的方法connect。但多数情况下,让DriverManager 类管理建立连接的细节为上策。
1、跟踪可用驱动程序
DriverManager 类包含一列Driver 类,它们已通过调用方法DriverManager.registerDriver
对 自己进行了注册。所有Driver 类都必须包含有一个静态部分。它创建该类的实例,然后在加载该实例时DriverManager 类进行注册。这样,用户正常情况下将不会直接调用DriverManager.registerDriver;而是在加载驱动程序时由驱动程序自动调用。 加载Driver 类,
然后自动在DriverManager 中注册的方式有两种:
通过调用方法Class.forName。这将显式地加载驱动程序类。由于这与外部设置无关,因此推
荐使用这种加载驱动程序的方法。以下代码加载类acme.db.Driver:Class.forName("acme.db.Driver");
如果将acme.db.Driver 编写为加载时创建实例,并调用以该实例为参数的DriverManager.registerDriver(本该如此),则它在DriverManager 的驱动程序列表中,并可用
于创建连接。通过将驱动程序添加到java.lang.System 的属性jdbc.drivers 中。这是一个由
DriverManager 类加载的驱动程序类名的列表,由冒号分隔:初始化DriverManager 类时,它搜索系统属性jdbc.drivers,如果用户已输入了一个或多个驱动程序,则DriverManager 类将试图加载它们。以下代码说明程序员如何在~/.hotjava/properties 中输入三个驱动程序类(启动时,
HotJava 将把它加载到系统属性列表中):
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver;
对DriverManager 方法的第一次调用将自动加载这些驱动程序类。
注意:加载驱动程序的第二种方法需要持久的预设环境。如果对这一点不能保证,则调用方法
Class.forName 显式地加载每个驱动程序就显得更为安全。这也是引入特定驱动程序的方法,因为一旦DriverManager 类被初始化,它将不再检查jdbc.drivers 属性列表。在以上两种情况中,新加载的Driver 类都要通过调用DriverManager.registerDriver 类进行自我注册。如上所述,加载类时将自动执行这一过程。由于安全方面的原因,JDBC 管理层将跟踪哪个类加载器提供哪个驱动程序。这样,当DriverManager 类打开连接时,它仅使用本地文件系统或与发出连接请求的代码相同的类加载器提供的驱动程序。
2、建立连接
加载Driver 类并在DriverManager 类中注册后,它们即可用来与数据库建立连接。当调用
DriverManager.getConnection 方法发出连接请求时,DriverManager 将检查每个驱动程序,查看
它 是否可以建立连接。有时可能有多个JDBC 驱动程序可以与给定的URL 连接。例如,与给定远程数据库连接时,可以使用JDBC-ODBC 桥驱动程序、JDBC 到通用网络协议驱动程序或数据库厂商提供的驱动程序。在这种情况下,测试驱动程序的顺序至关重要,因为DriverManager 将使用它所找到的第一个可以成功连接到给定URL 的驱动程序。
首先DriverManager 试图按注册的顺序使用每个驱动程序(jdbc.drivers 中列出的驱动程序
总 是先注册)。它将跳过代码不可信任的驱动程序,除非加载它们的源与试图打开连接的代码的源相同。它通过轮流在每个驱动程序上调用方法 Driver.connect,并向它们传递用户开始传递给方法DriverManager.getConnection 的URL 来对驱动程序进行测试,然后连接第一个认出该URL 的驱动程序。这种方法初看起来效率不高,但由于不可能同时加载数十个驱动程序,因此每次连接实际只需几个过程调用和字符串比较。
以下代码是通常情况下用驱动程序(例如JDBC-ODBC 桥驱动程序)建立连接所需所有步骤的示例:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //加载驱动程序
String url = "jdbc:odbc:fred";
DriverManager.getConnection(url, "userID", "passwd");
16 Statement 对象-----------语句
Statement 对象用于将SQL 语句发送到数据库中。实际上有三种Statement 对象,它们都作
为在给定连接上执行SQL 语句的包容器:Statement、PreparedStatement(它从Statement 继承
而来)和CallableStatement(它从PreparedStatement 继承而来)。它们都专用于发送特定类型
的SQL 语句: Statement 对象用于执行不带参数的简单SQL 语句;PreparedStatement 对象用于执行带或不带IN 参数的预编译SQL 语句;CallableStatement 对象用于执行对数据库已存储过程的调用。Statement 接口提供了执行语句和获取结果的基本方法。PreparedStatement 接口添加了处理IN 参数的方法;而CallableStatement 添加了处理OUT 参数的方法。
1、创建Statement 对象
建立了到特定数据库的连接之后,就可用该连接发送SQL 语句。Statement 对象用Connection
的方法createStatement 创建,如下列代码段中所示:
Connection con = DriverManager.getConnection(url, "sunny", "");
Statement stmt = con.createStatement();
为了执行Statement 对象,被发送到数据库的SQL 语句将被作为参数提供给Statement 的方
法:ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table2");
2、使用Statement 对象执行语句
Statement 接口提供了三种执行SQL 语句的方法:executeQuery、executeUpdate 和execute。
使用哪一个方法由SQL 语句所产生的内容决定。
方法executeQuery 用于产生单个结果集的语句,例如SELECT 语句。
方 法executeUpdate 用于执行INSERT、UPDATE 或DELETE 语句以及SQL DDL(数据定义语言)语句,例如CREATE TABLE 和DROP TABLE。INSERT、UPDATE 或DELETE 语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新计数)。对于CREATE TABLE 或DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。
方法execute 用于执行返回多个结果集、多个更新计数或二者组合的语句。因为多数程序员不
会 需要该高级功能,所以本概述后面将在单独一节中对其进行介绍。执行语句的所有方法都将关闭所调用的Statement 对象的当前打开结果集(如果存在)。这意味着在重新执行Statement 对象之前,需要完成对当前ResultSet 对象的处理。应注意,继承了Statement 接口中所有方法的PreparedStatement 接口都有自己的executeQuery、executeUpdate 和execute 方法。Statement 对象本身不包含SQL 语句,因而必须给Statement.execute 方法提供SQL 语句作为参数。PreparedStatement 对象并不将SQL 语句
作为参数提供给这些方法,因为它们已经包含预编译SQL 语句。CallableStatement 对象继承这些方法的PreparedStatement 形式。对于这些方法的PreparedStatement 或CallableStatement 版
本,使用查询参数将抛出SQLException。
3、语句完成
当连接处于自动提交模式时,其中所执行的语句在完成时将自动提交或还原。语句在已执行且
所有结果返回时,即认为已完成。对于返回一个结果集的executeQuery 方法,在检索完ResultSet
对象的所有行时该语句完成。对于方法executeUpdate,当它执行时语句即完成。但在少数调用方法execute 的情况中,在检索所有结果集或它生成的更新计数之后语句才完成。
有些DBMS 将已存储过程中的每条语句视为独立的语句;而另外一些则将整个过程视为一个复
合语句。在启用自动提交时,这种差别就变得非常重要,因为它影响什么时候调用commit 方法。在前一种情况中,每条语句单独提交;在后一种情况中,所有语句同时提交。
4、关闭Statement 对象
Statement 对象将由Java 垃圾收集程序自动关闭。而作为一种好的编程风格,应在不需要Statement 对象时显式地关闭它们。这将立即释放DBMS 资源,有助于避免潜在的内存问题。
5、Statement 对象中的SQL 转义语法
Statement 可包含使用SQL 转义语法的SQL 语句。转义语法告诉驱动程序其中的代码应该以
不同方式处理。驱动程序将扫描任何转义语法,并将它转换成特定数据库可理解的代码。这使得转义语法与DBMS 无关,并允许程序员使用在没有转义语法时不可用的功能。转义子句由花括号和关键字界定:
{keyword . . . parameters . . . }
该关键字指示转义子句的类型,如下所示。
escape 表示LIKE 转义字符
字符“%”和“_”类似于SQL LIKE 子句中的通配符(“%”匹配零个或多个字符,而“_”则匹
配一个字符)。为了正确解释它们,应在其前面加上反斜杠(“\”),它是字符串中的特殊转义字符。在查询末尾包括如下语法即可指定用作转义字符的字符:
{escape 'escape-character'}
例如,下列查询使用反斜杠字符作为转义字符,查找以下划线开头的标识符名:
stmt.executeQuery("SELECT name FROM Identifiers
WHERE Id LIKE `\_%' {escape `\'};
fn 表示标量函数
几乎所有DBMS 都具有标量值的数值、字符串、时间、日期、系统和转换函数。要使用这些函
数,可使用如下转义语法:关键字fn 后跟所需的函数名及其参数。例如,下列代码调用函数concat将两个参数连接在一起:
{fn concat("Hot", "Java")};
可用下列语法获得当前数据库用户名:{fn user()};
标量函数可能由语法稍有不同的DBMS 支持,而它们可能不被所有驱动程序支持。各种
DatabaseMetaData 方法将列出所支持的函数。例如,方法getNumericFunctions 返回用逗号分隔
的数值函数列表,而方法getStringFunctions 将返回字符串函数,等等。
驱动程序将转义函数调用映射为相应的语法,或直接实现该函数。
d、t 和ts 表示日期和时间文字
DBMS 用于日期、时间和时间标记文字的语法各不相同。JDBC 使用转义子句支持这些文字的语
法的ISO 标准格式。驱动程序必须将转义子句转换成DBMS 表示。
例如,可用下列语法在JDBC SQL 语句中指定日期:
{d `yyyy-mm-dd'}
在该语法中,yyyy 为年代,mm 为月份,而dd 则为日期。驱动程序将用等价的特定于DBMS 的
表示替换这个转义子句。例如,如果'28- FEB-99' 符合基本数据库的格式,则驱动程序将用它替
换{d 1999-02-28}。
对于TIME 和TIMESTAMP 也有类似的转义子句:
{t `hh:mm:ss'}
{ts `yyyy-mm-dd hh:mm:ss.f . . .'}
TIMESTAMP 中的小数点后的秒(.f . . .)部分可忽略。
call 或? = call 表示已存储过程
如果数据库支持已存储过程,则可从JDBC 中调用它们,语法为:
{call procedure_name[(?, ?, . . .)]}
或(其中过程返回结果参数):
{? = call procedure_name[(?, ?, . . .)]}
方括号指示其中的内容是可选的。它们不是语法的必要部分。
输入参数可以为文字或参数。有关详细信息,参见JDBC 指南中第7 节,“CallableStatement”。
可通过调用方法DatabaseMetaData.supportsStoredProcedures 检查数据库是否支持已存储
过程。
oj 表示外部连接
外部连接的语法为
{oj outer-join}
其中outer-join 形式为
table LEFT OUTER JOIN {table / outer-join} ON search-condition
外部连接属于高级功能。有关它们的解释可参见SQL 语法。JDBC 提供了三种
DatabaseMetaData 方法用于确定驱动程序支持哪些外部连接类型:supportsOuterJoins、
supportsFullOuterJoins 和supportsLimitedOuterJoins。
方法Statement.setEscapeProcessing 可打开或关闭转义处理;缺省状态为打开。当性能极为
重要时,程序员可能想关闭它以减少处理时间。但通常它将出于打开状态。应注意:
setEscapeProcessing 不适用于PreparedStatement 对象,因为在调用该语句前它就可能已被发送
到数据库。有关预编译的信息,参见PreparedStatement。
6、使用方法execute
execute 方法应该仅在语句能返回多个ResultSet 对象、多个更新计数或ResultSet 对象与
更 新计数的组合时使用。当执行某个已存储过程或动态执行未知SQL 字符串(即应用程序程序员在编译时未知)时,有可能出现多个结果的情况,尽管这种情况很少见。例如,用户可能执行一个已存储过程(使用 CallableStatement 对象- 参见第135 页的CallableStatement),并且该已存储过程可执行更新,然后执行选择,再进行更新,再进行选择,等等。通常使用已存储过程的人应知道它 所返回的内容。
因为方法execute 处理非常规情况,所以获取其结果需要一些特殊处理并不足为怪。例如,假
定已知某个过程返回两个结果集,则在使用方法execute 执行该过程后,必须调用方法
getResultSet 获得第一个结果集,然后调用适当的getXXX 方法获取其中的值。要获得第二个结果集,需要先调用getMoreResults 方法,然后再调用getResultSet 方法。如果已知某个过程返回两个更新计数,则首先调用方法getUpdateCount,然后调用getMoreResults,并再次调用
getUpdateCount。 对于不知道返回内容,则情况更为复杂。如果结果是ResultSet 对象,则方法execute 返回true;如果结果是Java int,则返回false。如果返回int,则意味着结果是更新计数或执行的语句是DDL 命令。在调用方法execute 之后要做的第一件事情是调用getResultSet 或
getUpdateCount。调用方法getResultSet 可以获得两个或多个ResultSet 对象中第一个对象;或
调用方法getUpdateCount 可以获得两个或多个更新计数中第一个更新计数的内容。
当SQL 语句的结果不是结果集时,则方法getResultSet 将返回null。这可能意味着结果是
一个更新计数或没有其它结果。在这种情况下,判断null 真正含义的唯一方法是调用方法
getUpdateCount, 它将返回一个整数。这个整数为调用语句所影响的行数;如果为-1 则表示结果是结果集或没有结果。如果方法getResultSet 已返回null(表示结果不是ResultSet 对象),则返回值-1 表示没有其它结果。也就是说,当下列条件为真时表示没有结果(或没有其它结果):
((stmt.getResultSet() == null) && (stmt.getUpdateCount() == -1))
如果已经调用方法getResultSet 并处理了它返回的ResultSet 对象,则有必要调用方法
getMoreResults 以确定是否有其它结果集或更新计数。如果getMoreResults 返回true,则需要
再次调用getResultSet 来检索下一个结果集。如上所述,如果getResultSet 返回null,则需要
调用getUpdateCount 来检查null 是表示结果为更新计数还是表示没有其它结果。
当getMoreResults 返回false 时,它表示该SQL 语句返回一个更新计数或没有其它结果。
因此需要调用方法getUpdateCount 来检查它是哪一种情况。在这种情况下,当下列条件为真时表示没有其它结果:
((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
下面的代码演示了一种方法用来确认已访问调用方法execute 所产生的全部结果集和更新计
数:
stmt.execute(queryStringWithUnknownResults);
while (true) {
int rowCount = stmt.getUpdateCount();
if (rowCount > 0) { // 它是更新计数
System.out.println("Rows changed = " + count);
stmt.getMoreResults();
continue;
}
if (rowCount == 0) { // DDL 命令或0 个更新
System.out.println(" No rows changed or statement was DDL command");
stmt.getMoreResults();
continue;
}
// 执行到这里,证明有一个结果集
// 或没有其它结果
ResultSet rs = stmt.getResultSet;
if (rs != null) {
. . . // 使用元数据获得关于结果集列的信息
while ( rs
break; // 没有其它结果
17 ResultSet 对象
ResultSet 包含符合SQL 语句中条件的所有行,并且它通过一套get 方法(这些get 方法可
以访问当前行中的不同列)提供了对这些行中数据的访问。ResultSet.next 方法用于移动到
ResultSet 中的下一行,使下一行成为当前行。
结果集一般是一个表,其中有查询所返回的列标题及相应的值。例如,如果查询为SELECT a, b,
c FROM Table1,则结果集将具有如下形式:
a b c
-------- --------- --------
12345 Cupertino CA
83472 Redmond WA
83492 Boston MA
下面的代码段是执行SQL 语句的示例。该SQL 语句将返回行集合,其中列1 为int,列2 为
String,而列3 则为字节数组:
java.sql.Statement stmt = conn.createStatement();
ResultSet r = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (r.next())
{
// 打印当前行的值。
int i = r.getInt("a");
String s = r.getString("b");
float f = r.getFloat("c");
System.out.println("ROW = " + i + " " + s + " " + f);
}
1、行和光标
ResultSet 维护指向其当前数据行的光标。每调用一次next 方法,光标向下移动一行。最初
它位于第一行之前,因此第一次调用next 将把光标置于第一行上,使它成为当前行。随着每次调用next 导致光标向下移动一行,按照从上至下的次序获取ResultSet 行。
在ResultSet 对象或其父辈Statement 对象关闭之前,光标一直保持有效。
在SQL 中,结果表的光标是有名字的。如果数据库允许定位更新或定位删除,则需要将光标的
名字作为参数提供给更新或删除命令。可通过调用方法getCursorName 获得光标名。
注意:不是所有的DBMS 都支持定位更新和删除。可使用
DatabaseMetaData.supportsPositionedDelete 和supportsPositionedUpdate 方法来检查特定连
接是否支持这些操作。当支持这些操作时,DBMS/驱动程序必须确保适当锁定选定行,以使定位更新不会导致更新异常或其它并发问题。
2、列
方法getXXX 提供了获取当前行中某列值的途径。在每一行内,可按任何次序获取列值。但为
了保证可移植性,应该从左至右获取列值,并且一次性地读取列值。列名或列号可用于标识要从中获取数据的列。例如,如果ResultSet 对象rs 的第二列名为“title”,并将值存储为字符串,
则下列任一代码将获取存储在该列中的值:
String s = rs.getString("title");
String s = rs.getString(2);
注意列是从左至右编号的,并且从列1 开始。同时,用作getXXX 方法的输入的列名不区分大
小写。提供使用列名这个选项的目的是为了让在查询中指定列名的用户可使用相同的名字作为
getXXX 方法的参数。另一方面,如果select 语句未指定列名(例如在“select * from table1”
中或列是导出的时),则应该使用列号。这些情况下,户将无法确切知道列名。
有 些情况下,SQL 查询返回的结果集中可能有多个列具有相同的名字。如果列名用作getXXX 方法的参数,则getXXX 将返回第一个匹配列名的值。因而,如果多个列具有相同的名字,则需要使用列索引来确保检索了正确的列值。这时,使用列号效率要稍微高一些。关于 ResultSet 中列的信息,可通过调用方法ResultSet.getMetaData 得到。返回的ResultSetMetaData 对象将给出其ResultSet 对象各列的编号、类型和属性。如果列名已知,但不知其索引,则可用方法findColumn 得到其列号。
3、数据类型和转换
对于getXXX 方法,JDBC 驱动程序试图将基本数据转换成指定Java 类型,然后返回适合的
Java 值。例如,如果getXXX 方法为getString,而基本数据库中数据类型为VARCHAR,则JDBC 驱动程序将把VARCHAR 转换成Java String。getString 的返回值将为Java String 对象。下表显示了允许用getXXX 获取的JDBC 类型及推荐用它获取的JDBC 类型(通用SQL 类型)。小写的x 表示允许getXXX 方法获取该数据类型;大写的X 表示对该数据类型推荐使用getXXX
方法。例如,除了getBytes 和getBinaryStream 之外的任何getXXX 方法都可用来获取LONGVARCHAR 值,但是推荐根据返回的数据类型使用getAsciiStream 或getUnicodeStream 方法。方法getObject 将任何数据类型返回为Java Object。当基本数据类型是特定于数据库的抽象类型或当通用应用程序需要接受任何数据类型时,它是非常有用的。可使用 ResultSet.getXXX 方法获取常见的JDBC 数据类型。
“x”表示该getXXX 方法可合法地用于获取给定JDBC 类型。
“X”表示推荐使用该getXXX 方法来获取给定JDBC 类型。
getByte X x x x x x x x x x x x x
getShort x X x x x x x x x x x x x
getInt x x X x x x x x x x x x x
getLong x x x X x x x x x x x x x
getFloat x x x x X x x x x x x x x
getDouble x x x x x X X x x x x x x
getBigDecimal x x x x x x x X X x x x x
getBoolean x x x x x x x x x X x x x
getString x x x x x x x x x x X X x x x x x x x
getBytes X X x
getDate x x x X x
getTime x x x X x
getTimestamp x x x x X
getAsciiStream x x X x x x
getUnicodeStream x x X x x x
getBinaryStream x x X
getObject x x x x x x x x x x x x x x x x x x x
4、对非常大的行值使用流
ResultSet 可以获取任意大的LONGVARBINARY 或LONGVARCHAR 数据。方法getBytes 和
getString 将数据返回为大的块(最大为Statement.getMaxFieldSize 的返回值)。但是,以较小
的固定块获取非常大的数据可能会更方便,而这可通过让ResultSet 类返回java.io.Input 流来
完成。从该流中可分块读取数据。注意:必须立即访问这些流,因为在下一次对ResultSet 调用
getXXX 时它们将自动关闭(这是由于基本实现对大块数据访问有限制)。
JDBC API 具有三个获取流的方法,分别具有不同的返回值:
getBinaryStream 返回只提供数据库原字节而不进行任何转换的流。
getAsciiStream 返回提供单字节ASCII 字符的流。
getUnicodeStream 返回提供双字节Unicode 字符的流。
注意:它不同于Java 流,后者返回无类型字节并可(例如)通用于ASCII 和Unicode 字符。
下列代码演示了getAsciiStream 的用法:
java.sql.Statement stmt = con.createStatement();
ResultSet r = stmt.executeQuery("SELECT x FROM Table2");
// 现在以4K 块大小获取列1 结果:
byte buff = new byte[4096];
while (r
// 将新填充的缓冲区发送到ASCII 输出流:
output.write(buff, 0, size);
}
}
5、NULL 结果值
要确定给定结果值是否是JDBC NULL,必须先读取该列,然后使用ResultSet.wasNull 方法检
查该次读取是否返回JDBC NULL。
当使用ResultSet.getXXX 方法读取JDBC NULL 时,方法wasNull 将返回下列值之一:
Java null 值:对于返回Java 对象的getXXX 方法(例如getString、getBigDecimal、
getBytes、getDate、getTime、getTimestamp、getAsciiStream、getUnicodeStream、getBinaryStream、
getObject 等)。
零值:对于getByte、getShort、getInt、getLong、getFloat 和getDouble。
false 值:对于getBoolean。
6、可选结果集或多结果集
通常使用executeQuery(它返回单个ResultSet)或executeUpdate(它可用于任何数据库修
改语句,并返回更新行数)可执行SQL 语句。但有些情况下,应用程序在执行语句之前不知道该语句是否返回结果集。此外,有些已存储过程可能返回几个不同的结果集和/或更新计数。
为 了适应这些情况,JDBC 提供了一种机制,允许应用程序执行语句,然后处理由结果集和更新计数组成的任意集合。这种机制的原理是首先调用一个完全通用的execute 方法,然后调用另外三个方法,getResultSet、getUpdateCount 和getMoreResults。这些方法允许应用程序一次一个地研究语句结果,并确定给定结果是ResultSet 还是更新计数。
用户不必关闭ResultSet;当产生它的Statement 关闭、重新执行或用于从多结果序列中获取
下一个结果时,该ResultSet 将被Statement 自动关闭。
18 PreparedStatement 接口
该PreparedStatement 接口继承Statement,并与之在两方面有所不同:
PreparedStatement 实例包含已编译的SQL 语句。这就是使语句“准备好”。包含于
PreparedStatement 对象中的SQL 语句可具有一个或多个IN 参数。IN 参数的值在SQL 语句创建时未被指定。相反的,该语句为每个IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX 方法来提供。
由于PreparedStatement 对象已预编译过,所以其执行速度要快于Statement 对象。因此,
多次执行的SQL 语句经常创建为PreparedStatement 对象,以提高效率。
作为Statement 的子类,PreparedStatement 继承了Statement 的所有功能。另外它还添加
了一整套方法,用于设置发送给数据库以取代IN 参数占位符的值。同时,三种方法execute、
executeQuery 和executeUpdate 已被更改以使之不再需要参数。这些方法的Statement 形式(接
受SQL 语句参数的形式)不应该用于PreparedStatement 对象。
1、创建PreparedStatement 对象
以下的代码段(其中con 是Connection 对象)创建包含带两个IN 参数占位符的SQL 语句
的PreparedStatement 对象:
PreparedStatement pstmt = con.prepareStatement("UPDATE table4 SET m = ? WHERE x = ?");
pstmt 对象包含语句"UPDATE table4 SET m = ? WHERE x = ?",它已发送给DBMS,并为执行
作好了准备。
2、传递IN 参数
在执行PreparedStatement 对象之前,必须设置每个? 参数的值。这可通过调用setXXX 方
法 来完成,其中XXX 是与该参数相应的类型。例如,如果参数具有Java 类型long,则使用的方法就是setLong。setXXX 方法的第一个参数是要设置的参数的序数位置,第二个参数是设置给该参数的值。例如,以下代码将第一个参数设为123456789,第二个参数设为 100000000:
pstmt.setLong(1, 123456789);
pstmt.setLong(2, 100000000);
一旦设置了给定语句的参数值,就可用它多次执行该语句,直到调用clearParameters 方法清
除它为止。在连接的缺省模式下(启用自动提交),当语句完成时将自动提交或还原该语句。
如果基本数据库和驱动程序在语句提交之后仍保持这些语句的打开状态,则同一个
PreparedStatement 可执行多次。如果这一点不成立,那么试图通过使用PreparedStatement 对象
代替Statement 对象来提高性能是没有意义的。
利用pstmt(前面创建的PreparedStatement 对象),以下代码例示了如何设置两个参数占位
符的值并执行pstmt 10 次。如上所述,为做到这一点,数据库不能关闭pstmt。在该示例中,第一个参数被设置为"Hi"并保持为常数。在for 循环中,每次都将第二个参数设置为不同的值:从0开始,到9 结束。
pstmt.setString(1, "Hi");
for (int i = 0; i < 10; i++) {
pstmt.setInt(2, i);
int rowCount = pstmt.executeUpdate();
}
3、IN 参数中数据类型的一致性
setXXX 方法中的XXX 是Java 类型。它是一种隐含的JDBC 类型(一般SQL 类型),因为驱
动程序将把Java 类型映射为相应的JDBC 类型(遵循该JDBCGuide 中§8.6.2 “映射Java 和
JDBC 类型”表中所指定的映射),并将该JDBC 类型发送给数据库。例如,以下代码段将
PreparedStatement 对象pstmt 的第二个参数设置为44,Java 类型为short:
pstmt.setShort(2, 44);
驱动程序将44 作为JDBC SMALLINT 发送给数据库,它是Java short 类型的标准映射。
程序员的责任是确保将每个IN 参数的Java 类型映射为与数据库所需的JDBC 数据类型兼容
的JDBC 类型。不妨考虑数据库需要JDBC SMALLINT 的情况。如果使用方法setByte ,则驱动程序将JDBC TINYINT 发送给数据库。这是可行的,因为许多数据库可从一种相关的类型转换为另一种类型,并且通常TINYINT 可用于SMALLINT 适用的任何地方
19 CallableStatement 对象
CallableStatement 对象为所有的DBMS 提供了一种以标准形式调用已储存过程的方法。已储
存 过程储存在数据库中。对已储存过程的调用是CallableStatement 对象所含的内容。这种调用是用一种换码语法来写的,有两种形式:一种形式带结果参,另一种形式不带结果参数。结果参数是一种输出(OUT) 参数,是已储存过程的返回值。两种形式都可带有数量可变的输入(IN 参数)、输出(OUT 参数)或输入和输出(INOUT 参数)的参数。问号将用作参数的占位符。
在JDBC 中调用已储存过程的语法如下所示。注意,方括号表示其间的内容是可选项;方括号本身并不是语法的组成部份。
{call 过程名[(?, ?, ...)]}
返回结果参数的过程的语法为:
{? = call 过程名[(?, ?, ...)]}
不带参数的已储存过程的语法类似:
{call 过程名}
通常,创建CallableStatement 对象的人应当知道所用的DBMS 是支持已储存过程的,并且知
道这些过程都是些什么。然而,如果需要检查,多种DatabaseMetaData 方法都可以提供这样的信息。例如,如果DBMS 支持已储存过程的调用,则supportsStoredProcedures 方法将返回true,
而getProcedures 方法将返回对已储存过程的描述。CallableStatement 继承Statement 的方法
(它们用于处理一般的SQL 语句),还继承了PreparedStatement 的方法(它们用于处理IN 参)。
CallableStatement 中定义的所有方法都用于处理OUT 参数或INOUT 参数的输出部分:注册
OUT 参数的JDBC 类型(一般SQL 类型)、从这些参数中检索结果,或者检查所返回的值是否为JDBC NULL。
1、创建CallableStatement 对象
CallableStatement 对象是用Connection 方法prepareCall 创建的。下例创建
CallableStatement 的实例,其中含有对已储存过程getTestData 调用。该过程有两个变量,但不
含结果参数:
CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
其中?占位符为IN、OUT 还是INOUT 参数,取决于已储存过程getTestData。
2、IN 和OUT 参数
将IN 参数传给CallableStatement 对象是通过setXXX 方法完成的。该方法继承自
PreparedStatement。所传入参数的类型决定了所用的setXXX 方法(例如,用setFloat 来传入
float 值等)。如果已储存过程返回OUT 参数,则在执行CallableStatement 对象以前必须先注册每个OUT参数的JDBC 类型(这是必需的,因为某些DBMS 要求JDBC 类型)。注册JDBC 类型是用registerOutParameter 方法来完成的。语句执行完后,CallableStatement 的getXXX 方法将取回参数值。正确的getXXX 方法是为各参数所注册的JDBC 类型所对应的Java 类型。换言之,registerOutParameter 使用的是JDBC 类型(因此它与数据库返回的JDBC 类型匹配),而getXXX将之转换为Java 类型。
作为示例,下述代码先注册OUT 参数,执行由cstmt 所调用的已储存过程,然后检索在OUT 参
数中返回的值。方法getByte 从第一个OUT 参数中取出一个Java 字节,而getBigDecimal 从第二个OUT 参数中取出一个BigDecimal 对象(小数点后面带三位数):
CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3);
cstmt.executeQuery();
byte x = cstmt.getByte(1);
java.math.BigDecimal n = cstmt.getBigDecimal(2, 3);
CallableStatement 与ResultSet 不同,它不提供用增量方式检索大OUT 值的特殊机制。
3、INOUT 参数
既支持输入又接受输出的参数(INOUT 参数)除了调用registerOutParameter 方法外,还要
求调用适当的setXXX 方法(该方法是从PreparedStatement 继承来的)。setXXX 方法将参数值
设 置为输入参数,而registerOutParameter 方法将它的JDBC 类型注册为输出参数。setXXX 方法提供一个Java 值,而驱动程序先把这个值转换为JDBC 值,然后将它送到数据库中。这种IN 值的JDBC 类型和提供给registerOutParameter 方法的JDBC 类型应该相同。然后,要检索输出值,就要用对应的getXXX 方法。例如,Java 类型为byte 的参数应该使用方法setByte 来赋输入值。应该给registerOutParameter 提供类型为TINYINT 的JDBC 类型,同时应使用getByte 来检索输出值。
下例假设有一个已储存过程reviseTotal,其唯一参数是INOUT 参数。方法setByte 把此参数设为25,驱动程序将把它作为JDBC TINYINT 类型送到数据库中。接着,registerOutParameter
将该参数注册为JDBC TINYINT。执行完该已储存过程后,将返回一个新的JDBC TINYINT 值。方法
getByte 将把这个新值作为Java byte 类型检索。
CallableStatement cstmt = con.prepareCall("{call reviseTotal(?)}");
cstmt.setByte(1, 25);
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.executeUpdate();
byte x = cstmt.getByte(1);
4、先检索结果,再检索OUT 参数
由于某些DBMS 的限制,为了实现最大的可移植性,建议先检索由执行CallableStatement 对
象所产生的结果,然后再用CallableStatement.getXXX 方法来检索OUT 参数。如果
CallableStatement 对象返回多个ResultSet 对象(通过调用execute 方法),在检索OUT 参数
前应先检索所有的结果。这种情况下,为确保对所有的结果都进行了访问,必须对Statement 方法getResultSet、getUpdateCount 和getMoreResults 进行调用,直到不再有结果为止。
检索完所有的结果后,就可用CallableStatement.getXXX 方法来检索OUT 参数中的值。
5、检索作为OUT 参数的NULL 值
返 回到OUT 参数中的值可能会是JDBC NULL。当出现这种情形时,将对JDBC NULL 值进行转换以使getXXX 方法所返回的值为null、0 或false,这取决于getXXX 方法类型。对于ResultSet对象,要知道0 或false 是否源于JDBCNULL 的唯一方法,是用方法wasNull进行检测。如果getXXX方法读取的最后一个值是JDBC NULL,则该方法返回true,否则返回flase。
发表评论
-
Drools JBPM区别
2011-03-21 18:02 1793参考资料: 1、http://www.simpleframew ... -
推荐web开发教材
2009-11-25 13:00 1413大家能否给推荐一本 用JAVA语言相关的WEB开发方面的教材。 ... -
JAVA 线程编程 ----两个线程程序
2009-11-20 13:08 2114Java的内置线程支持 关 ... -
JAVA 线程编程 ----什么时候多线程可能不好(When Multiple Threads Might Not Be Good)
2009-11-13 12:59 1241设计程序时候创建更多的线程不总是一个好主意。线程不是免费的;它 ... -
JAVA 线程编程 ----为什么要用多线程(Why Use Multiple Thread)
2009-11-11 15:51 3365许多情况下,在一个程 ... -
JAVA 线程编程 ----线程介绍
2009-11-10 14:59 1252概述 浏览网页时,当 ... -
DataSource data source JNDI之间的关系
2009-09-16 17:01 2367data source :所谓data source就 ... -
字符,字节和编码三者的关系
2009-06-03 23:17 0http://www.regexlab.com/zh/enco ... -
jasperreport
2009-05-04 14:17 0http://www.docstoc.com/docs/213 ... -
iReport设计报表以Excel格式导出时注意的问题
2009-04-23 15:08 7210刚开始使用iReport工具,没想到就遇到了问题: 在以Exc ... -
到主机 的 TCP/IP 连接失败,达到最大连接数
2009-04-23 14:35 4429问题描述: _______________ ... -
Acegi框架介绍
2009-03-30 23:57 1499转自:这里 灰色字体 ... -
对语言的一些理解
2008-05-06 17:54 1457语言的本质 包 ... -
HOW TO DESIGN PROGRAMS
2008-04-21 09:55 1699http://www.htdp.org ·前言 向儿童传 ... -
AOP 入门好文章
2008-04-18 18:42 1445http://dev.rdxx.com/Java/2005-0 ... -
Head First JSP &Servlet读后感------chapt01
2008-01-02 22:50 2045Web程序和普通的GUI程序 ... -
JNDI资料整理
2007-12-26 16:38 1308******************************* ... -
JDBC初级应用实例(二) 转
2007-12-25 09:35 1232转自:http://blog.csdn.net/axman/a ... -
JDBC初级应用实例(一) 转
2007-12-25 09:25 1437转自:http://blog.csdn.net/a ... -
JDBC基础(四)转
2007-12-25 09:07 1279转自:http://blog.csdn.net/axman/a ...
相关推荐
在IT行业中,数据库访问是应用程序开发中的核心部分。...同时,随着ORM(对象关系映射)框架如Hibernate和MyBatis的普及,虽然直接使用JDBC编写DAO的方式逐渐减少,但理解JDBC DAO仍然是每个Java开发者必备的基础知识。
总之,"Java编程实例JDBC+MySQL+Oracle+SQLServer"这个主题涵盖了Java与数据库交互的基础知识,包括JDBC的使用、不同数据库系统的特性,以及连接池和高级框架的应用。对于学习JavaEE开发的人来说,这些都是不可或缺...
地理信息系统(GIS)是一门集合了计算机科学、地理学、遥感技术、测绘学、城市规划、土地管理以及市政建设等多学科知识的边缘技术。它对于经济发展和社会的可持续发展扮演了极为重要的角色。 随着计算机技术、空间...
在当今社会,随着汽车的普及,驾驶技能成为了许多人必备的生活技能。为了提高驾驶员的安全意识和驾驶水平,基于JSP的驾驶知识学习与评测系统应运而生。这个系统为驾驶学员提供了一个便捷、高效的学习平台,并通过...
这表明Java编程在当前互联网技术领域的重要性,以及其在教育体系中的普及和应用。 3. 知识点的广度与深度:文档指出在Java程序设计教学中,老师和学生会遇到知识点太多、涉及面太广的问题。这导致学生难以将各个...
4. **数据库技术**:熟练使用JDBC API,并熟悉至少一种持久化/ORM框架(如Hibernate、JDO、Cocobase、TopLink、iBatis)来处理数据库操作。 5. **IDE与开发工具**:熟悉至少一种Java IDE(如SunOne、NetBeans、...
本文详细介绍了JSP前台与数据库后台连接技术的相关知识点,包括JDBC技术的不同连接方式及其特点。通过JDBC接口直接连接数据库、通过JDBC-ODBC桥连接数据库以及通过JavaBean连接数据库这三种方法各有优缺点。开发人员...
【标题】"j2EE知识用MyEclipse编写的简单的BBS论坛"涉及的主要知识点是基于Java企业版(Java 2 Platform, Enterprise Edition,简称J2EE)的Web应用程序开发,利用MyEclipse集成开发环境(Integrated Development ...
以下将详细阐述标题和描述中涉及到的关键知识点,包括Oracle、Mysql、Sqlserver数据库连接、JSON处理和Struts框架。 1. **Oracle数据库连接**: Oracle JDBC驱动程序(如ojdbc.jar)是Java应用程序与Oracle数据库...
同时,随着Spring框架的普及,Spring JDBC和JPA(Java Persistence API)提供了一种更高级且简化的方式来处理数据库操作,让开发者可以专注于业务逻辑而不是底层的数据库操作。无论选择哪种方式,理解和掌握Java...
### Java连接各种数据库知识点详解 #### 一、概述 在软件开发过程中,数据库连接是必不可少的一个环节。Java作为一门广泛使用的编程语言,在处理与不同类型的数据库进行交互时有着丰富的工具和方法。本文将详细...
随着多核处理器的普及,Java编程面临新的挑战和机遇。多核环境下,需要特别注意以下几点: - **并发控制**:多线程编程时需要关注同步问题,确保共享资源的正确访问。 - **线程安全**:使用线程安全的数据结构,如`...
总的来说,这本书是JSP和J2EE开发者的宝贵资源,涵盖了从基本概念到高级技术,以及XML和JDBC的实用知识,适合初学者和有经验的开发者进行深入学习。书中提供的实例源码和详细解释能帮助读者更好地理解和应用这些技术...
随着互联网的普及,越来越多的消费者选择在线购物,因此网上商城的设计与实现对于促进电子商务的发展具有重要意义。它不仅提高了商家的销售效率,还为消费者提供了便捷的购物体验。此外,通过网上商城,企业可以更...
随着电子信息行业的不断发展,网络通信以及信息技术在人类生活中的普及,利用计算机技术、网络通信技术和Internet实现商务活动的国际化、信息化,已成为各国商务发展的一大趋势。小型电子商务网站的设计与实现是当前...
* 需求背景:随着汽车消费大众化和各种机动车辆的普及,停车场收费管理系统的需求日益迫切。 * 需求意义:该系统旨在解决当前停车场管理中存在的问题,提高停车场的管理效率和收费透明度。 知识点2:系统概要设计 ...
它允许开发者自定义SQL、存储过程以及高级映射,避免了JDBC的繁琐操作,提高了开发效率。 在系统设计上,可能包括以下几个主要模块: 1. 用户管理模块:用户注册、登录、个人信息管理等功能,确保用户信息的安全与...
3. 注解配置:随着Java注解的普及,Spring也支持使用注解来配置Bean,如@Service、@Component、@Repository和@Controller等,以及依赖注入用的@Autowired。 再者,Spring的面向切面编程(AOP)模块提供了强大的横切...
EDI在早期的电子商务中扮演了重要的角色,而Internet的普及使得EDI变得更加普及,能够通过互联网实现数据交换。网上商城在接入EDI系统后,可以更加高效地处理与供货商、零售商和客户的交易。 接下来,从技术架构的...