原文:
docs.oracle.com/javase/tutorial/reallybigindex.html
长期持久性
原文:
docs.oracle.com/javase/tutorial/javabeans/advanced/longpersistence.html
长期持久性是一种模型,可以将 bean 保存为 XML 格式。
有关 XML 格式和如何为非 bean 实现长期持久性的信息,请参阅XML 模式和使用 XMLEncoder。
编码器和解码器
XMLEncoder
类用于编写Serializable
对象的文本表示的输出文件。以下代码片段是将 Java bean 及其属性以 XML 格式写入的示例:
XMLEncoder encoder = new XMLEncoder(
new BufferedOutputStream(
new FileOutputStream("Beanarchive.xml")));
encoder.writeObject(object);
encoder.close();
XMLDecoder
类读取使用 XMLEncoder 创建的 XML 文档:
XMLDecoder decoder = new XMLDecoder(
new BufferedInputStream(
new FileInputStream("Beanarchive.xml")));
Object object = decoder.readObject();
decoder.close();
XML 中有什么?
XML bean 存档具有自己特定的语法,其中包括以下标签来表示每个 bean 元素:
-
用于描述 XML 版本和编码类型的 XML 前言
-
一个
**<java>**
标签,用于包含 bean 的所有对象元素 -
一个
**<object>**
标签,用于表示从其序列化形式重建对象所需的一组方法调用<object class="javax.swing.JButton" method="new"> <string>Ok</string> </object>
或语句
<object class="javax.swing.JButton"> <void method="setText"> <string>Cancel</string> </void> </object>
-
用于定义适当的基本类型的标签:
-
**<boolean>**
-
**<byte>**
-
**<char>**
-
**<short>**
-
**<int>**
-
**<long>**
-
**<float>**
-
**<double>**
<int>5555</int>
-
-
一个**<
class
>**标签,用于表示 Class 的一个实例。<class>java.swing.JFrame</class>
-
一个**<
array
>**标签用于定义数组<array class="java.lang.String" length="5"> </array>
以下代码表示将为SimpleBean
组件生成的 XML 存档:
<?xml version="1.0" encoding="UTF-8" ?>
<java>
<object class="javax.swing.JFrame">
<void method="add">
<object class="java.awt.BorderLayout" field="CENTER"/>
<object class="SimpleBean"/>
</void>
<void property="defaultCloseOperation">
<object class="javax.swing.WindowConstants" field="DISPOSE_ON_CLOSE"/>
</void>
<void method="pack"/>
<void property="visible">
<boolean>true</boolean>
</void>
</object>
</java>
Bean 自定义
原文:
docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html
自定义提供了一种修改 Bean 外观和行为的方式,使其在应用程序构建器中满足您的特定需求。对于 Bean 开发人员,有几个级别的自定义可用,以允许其他开发人员充分利用 Bean 的潜在功能。
以下链接对学习有关属性编辑器和自定义器很有用:
-
PropertyEditor
接口 -
PropertyEditorSupport
类 -
PropertyEditorManager
类 -
Customizer
接口 -
BeanInfo
接口
在符合 Beans 规范的构建工具中,可以在设计时自定义 Bean 的外观和行为。有两种方式可以自定义 Bean:
-
通过使用属性编辑器。每个 Bean 属性都有自己的属性编辑器。NetBeans GUI Builder 通常在属性窗口中显示 Bean 的属性编辑器。与特定属性类型相关联的属性编辑器编辑该属性类型。
-
通过使用自定义器。自定义器为您提供了完全的 GUI 控制权,用于对 Bean 进行自定义。当属性编辑器不实用或不适用时,将使用自定义器。与属性关联的属性编辑器不同,自定义器与 Bean 关联。
属性编辑器
属性编辑器是用于自定义特定属性类型的工具。属性编辑器在属性窗口中激活。此窗口确定属性的类型,搜索相关的属性编辑器,并以相关方式显示属性的当前值。
属性编辑器必须实现PropertyEditor
接口,该接口提供了指定属性在属性表中如何显示的方法。以下图表示包含myBean1
属性的属性窗口:
您可以通过单击属性条目开始编辑这些属性。单击大多数条目将弹出单独的面板。例如,要设置foreground
或background
,请使用带有颜色选择的选择框,或按下“…”按钮以使用标准 ColorEditor 窗口。单击toolTipText
属性将打开 StringEditor 窗口。
支持类PropertyEditorSupport
提供了PropertyEditor
接口的默认实现。通过从PropertyEditorSupport
子类化您的属性编辑器,您可以简单地重写您需要的方法。
要在属性窗口中显示当前属性值"sample",需要重写isPaintable
以返回true
。然后必须重写paintValue
以在属性表中的矩形中绘制当前属性值。以下是ColorEditor
如何实现paintValue
:
public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
Color oldColor = gfx.getColor();
gfx.setColor(Color.black);
gfx.drawRect(box.x, box.y, box.width-3, box.height-3);
gfx.setColor(color);
gfx.fillRect(box.x+1, box.y+1, box.width-4, box.height-4);
gfx.setColor(oldColor);
}
要支持自定义属性编辑器,需要重写另外两个方法。重写supportsCustomEditor
以返回 true,然后重写getCustomEditor
以返回自定义编辑器实例。ColorEditor.getCustomEditor
返回this
。
此外,PropertyEditorSupport
类维护一个PropertyChangeListener
列表,并在绑定属性更改时向这些侦听器发送属性更改事件通知。
如何将属性编辑器与属性关联
属性编辑器是通过以下方式发现并与给定属性关联的:
-
通过
BeanInfo
对象进行显式关联。标题的编辑器通过以下代码行设置:pd.setPropertyEditorClass(TitleEditor.class);
-
通过
java.beans.PropertyEditorManager.registerEditor
方法进行显式注册。此方法接受两个参数:bean 类类型和要与该类型关联的编辑器类。 -
名称搜索。如果一个类没有明确关联的属性编辑器,那么
PropertyEditorManager
将通过以下方式搜索该类的属性编辑器:-
将"Editor"附加到完全限定的类名。例如,对于
my.package.ComplexNumber
类,属性编辑器管理器将搜索my.package.ComplexNumberEditor
类。 -
将"Editor"附加到类名并搜索类路径。
-
自定义器
您已经了解到构建工具为您创建自己的属性编辑器提供支持。对于复杂的、工业强度的 bean,视觉构建器应满足哪些其他需求?有时,将一个单一根选择关于 bean 类型渲染的一半属性变得无关紧要是不可取的。JavaBeans 规范提供了用户定义的自定义器,通过它们您可以为 bean 属性定义比属性编辑器提供的更高级别的定制。
当您使用一个 bean 自定义器时,您完全控制如何配置或编辑一个 bean。自定义器是专门针对 bean 定制的应用程序。有时属性不足以表示 bean 的可配置属性。自定义器用于需要复杂指令来更改 bean 的地方,以及属性编辑器过于原始无法实现 bean 定制的地方。
所有自定义器必须:
-
扩展
java.awt.Component
或其子类之一。 -
实现
java.beans.Customizer
接口,这意味着实现方法来注册PropertyChangeListener
对象,并在目标 bean 发生更改时向这些侦听器触发属性更改事件。 -
实现一个默认构造函数。
-
通过
BeanInfo.getBeanDescriptor
将自定义器与其目标类关联。
教程:JDBC 数据库访问
原文:
docs.oracle.com/javase/tutorial/jdbc/index.html
JDBC API 的设计初衷是让简单的事情保持简单。这意味着 JDBC 使得日常数据库任务变得容易。这个教程将通过示例引导您使用 JDBC 执行常见的 SQL 语句,并执行数据库应用程序常见的其他目标。
这个教程分为以下几个课程:
列出了 JDBC 的特性,描述了 JDBC 架构,并回顾了 SQL 命令和关系数据库概念。
涵盖了 JDBC API。
在第一课结束时,您将学会如何使用基本的 JDBC API 来创建表,向表中插入值,查询表,检索查询结果,并更新表。在这个过程中,您将学会如何使用简单语句和预编译语句,并看到一个存储过程的示例。您还将学会如何执行事务,以及如何捕获异常和警告。
教程:JDBC 简介
原文:
docs.oracle.com/javase/tutorial/jdbc/overview/index.html
JDBC API 是一个可以访问任何类型表格数据的 Java API,特别是存储在关系数据库中的数据。
JDBC 帮助您编写管理这三种编程活动的 Java 应用程序:
-
连接到数据源,比如数据库
-
向数据库发送查询和更新语句
-
从数据库中检索并处理查询结果
以下简单的代码片段给出了这三个步骤的简单示例:
public void connectToAndQueryDatabase(String username, String password) {
Connection con = DriverManager.getConnection(
"jdbc:myDriver:myDatabase",
username,
password);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next()) {
int x = rs.getInt("a");
String s = rs.getString("b");
float f = rs.getFloat("c");
}
}
这段简短的代码片段实例化了一个DriverManager
对象,用于连接数据库驱动程序并登录数据库,实例化了一个Statement
对象,将您的 SQL 语言查询传递给数据库;实例化了一个ResultSet
对象,检索您的查询结果,并执行一个简单的while
循环,用于检索和显示这些结果。就是这么简单。
JDBC 产品组件
JDBC 包括四个组件:
-
JDBC API — JDBC™ API 提供了从 Java™编程语言对关系数据进行编程访问的方式。使用 JDBC API,应用程序可以执行 SQL 语句,检索结果,并将更改传播回底层数据源。JDBC API 还可以在分布式、异构环境中与多个数据源交互。
JDBC API 是 Java 平台的一部分,包括Java™标准版(Java™ SE)和Java™企业版(Java™ EE)。JDBC 4.0 API 分为两个包:
java.sql
和javax.sql.
这两个包都包含在 Java SE 和 Java EE 平台中。 -
JDBC 驱动管理器 — JDBC
DriverManager
类定义了可以将 Java 应用程序连接到 JDBC 驱动程序的对象。DriverManager
一直是 JDBC 架构的支柱。它非常小而简单。标准扩展包
javax.naming
和javax.sql
允许您使用注册到Java 命名和目录接口™(JNDI)命名服务的DataSource
对象与数据源建立连接。您可以使用任一连接机制,但尽可能使用DataSource
对象是推荐的。 -
JDBC 测试套件 — JDBC 驱动程序测试套件帮助您确定 JDBC 驱动程序是否能运行您的程序。这些测试并不全面或详尽,但它们确实涵盖了 JDBC API 中的许多重要特性。
-
JDBC-ODBC 桥 — Java 软件桥通过 ODBC 驱动程序提供 JDBC 访问。请注意,您需要将 ODBC 二进制代码加载到每台使用此驱动程序的客户端机器上。因此,ODBC 驱动程序最适合用于企业网络,其中客户端安装不是一个主要问题,或者用于在 Java 中编写的应用程序服务器代码的三层架构。
这个教程使用这四个 JDBC 组件中的前两个来连接数据库,然后构建一个使用 SQL 命令与测试关系数据库通信的 Java 程序。最后两个组件用于在专业环境中测试 Web 应用程序,或与支持 ODBC 的数据库管理系统通信。
JDBC 架构
原文:
docs.oracle.com/javase/tutorial/jdbc/overview/architecture.html
JDBC API 支持数据库访问的两层和三层处理模型。
图 1:用于数据访问的两层架构。
在两层模型中,Java 小程序或应用程序直接与数据源通信。这需要一个能够与被访问的特定数据源通信的 JDBC 驱动程序。用户的命令被传递到数据库或其他数据源,并将这些语句的结果发送回用户。数据源可能位于用户通过网络连接的另一台机器上。这被称为客户端/服务器配置,用户的机器是客户端,而托管数据源的机器是服务器。网络可以是一个内部网络,例如连接公司内部员工的网络,也可以是互联网。
在三层模型中,命令被发送到一个“中间层”服务,然后再将命令发送到数据源。数据源处理命令并将结果发送回中间层,然后再发送给用户。MIS 主管发现三层模型非常有吸引力,因为中间层使得可以控制对企业数据的访问和更新类型。另一个优点是简化了应用程序的部署。最后,在许多情况下,三层架构可以提供性能优势。
图 2:用于数据访问的三层架构。
直到最近,中间层通常是用诸如 C 或 C++之类的语言编写的,这些语言提供了快速的性能。然而,随着将 Java 字节码转换为高效的机器特定代码的优化编译器的引入,以及诸如 Enterprise JavaBeans™之类的技术,Java 平台正迅速成为中间层开发的标准平台。这是一个巨大的优势,使得可以利用 Java 的健壮性、多线程和安全功能。
随着企业越来越多地使用 Java 编程语言编写服务器代码,JDBC API 在三层架构的中间层中被越来越多地使用。使 JDBC 成为服务器技术的一些特点包括其支持连接池、分布式事务和断开的行集。JDBC API 也是允许从 Java 中间层访问数据源的途径。
关系数据库概述
原文:
docs.oracle.com/javase/tutorial/jdbc/overview/database.html
数据库是一种以便于检索信息的方式存储信息的手段。简单来说,关系数据库是以行和列的形式呈现信息的数据库。表被称为关系,因为它是相同类型对象(行)的集合。表中的数据可以根据共同的键或概念相关联,从表中检索相关数据的能力是关系数据库这个术语的基础。数据库管理系统(DBMS)处理数据的存储、维护和检索方式。在关系数据库的情况下,关系数据库管理系统(RDBMS)执行这些任务。本书中使用的 DBMS 是一个包括 RDBMS 在内的通用术语。
完整性规则
关系表遵循某些完整性规则,以确保它们包含的数据保持准确并始终可访问。首先,关系表中的行应该都是不同的。如果有重复的行,解决哪一个是正确选择的问题可能会出现问题。对于大多数 DBMS,用户可以指定不允许重复行,如果这样做,DBMS 将阻止添加任何重复现有行的行。
传统关系模型的第二个完整性规则是列值不能是重复组或数组。数据完整性的第三个方面涉及空值的概念。数据库通过使用空值来指示缺失值的情况。它不等同于空白或零。空白被认为等于另一个空白,零等于另一个零,但两个空值不被视为相等。
当表中的每一行都不同时,可以使用一个或多个列来标识特定行。这个唯一的列或列组称为主键。任何作为主键的列都不能为 null;如果是,包含它的主键将不再是完整的标识符。这个规则称为实体完整性。
Employees
表展示了一些关系数据库概念。它有五列和六行,每行代表一个不同的雇员。
Employees
表
员工编号 | 名 | 姓 | 出生日期 | 车牌号 |
---|---|---|---|---|
10001 | 阿克塞尔 | 华盛顿 | 43 年 8 月 28 日 | 5 |
10083 | 阿维德 | 沙玛 | 54 年 11 月 24 日 | null |
10120 | 乔纳斯 | 金斯伯格 | 69 年 1 月 1 日 | null |
10005 | 弗洛伦斯 | 沃约科夫斯基 | 71 年 7 月 4 日 | 12 |
10099 | 肖恩 | 华盛顿 | 66 年 9 月 21 日 | null |
10035 | 伊丽莎白 | 山口 | 59 年 12 月 24 日 | null |
该表的主键通常会是员工编号,因为每个员工编号都保证是不同的。(数字比字符串更有效率用于比较。)也可以使用 First_Name
和 Last_Name
,因为两者的组合在我们的示例数据库中也只标识一行。仅使用姓氏将不起作用,因为有两个姓氏为“Washington”的员工。在这种特殊情况下,名字都是不同的,因此可以想象使用该列作为主键,但最好避免使用可能出现重复的列。如果 Elizabeth Yamaguchi 在这家公司找到工作,而主键是 First_Name
,则关系数据库管理系统将不允许添加她的名字(如果已经指定不允许重复)。因为表中已经有一个 Elizabeth,再添加一个将使主键无法作为标识一行的方法。请注意,虽然使用 First_Name
和 Last_Name
是这个示例的唯一复合键,但在更大的数据库中可能不是唯一的。还要注意,Employees
表假设每个员工只能有一辆车。
SELECT
语句
SQL 是一种设计用于与关系数据库一起使用的语言。有一组被认为是标准的基本 SQL 命令,被所有关系数据库管理系统使用。例如,所有关系数据库管理系统都使用 SELECT
语句。
SELECT
语句,也称为查询,用于从表中获取信息。它指定一个或多个列标题,一个或多个要选择的表,以及一些选择条件。关系数据库管理系统返回满足所述要求的列条目的行。例如,以下 SELECT
语句将获取拥有公司车辆的员工的名字和姓氏:
SELECT First_Name, Last_Name
FROM Employees
WHERE Car_Number IS NOT NULL
符合要求(Car_Number
列中不为 null 的行集)的结果集如下。对于满足要求的每一行,都会打印出名字和姓氏,因为 SELECT
语句(第一行)指定了列 First_Name
和 Last_Name
。FROM
子句(第二行)给出了将从中选择列的表。
FIRST_NAME | LAST_NAME |
---|---|
Axel | Washington |
Florence | Wojokowski |
以下代码生成一个包含整个表的结果集,因为它要求表 Employees 中的所有列没有限制(没有 WHERE
子句)。请注意,SELECT *
意味着“SELECT
所有列”。
SELECT *
FROM Employees
WHERE
子句
SELECT
语句中的 WHERE
子句提供了选择值的条件。例如,在以下代码片段中,只有在列 Last_Name
以字符串 ‘Washington’ 开头的行中才会选择值。
SELECT First_Name, Last_Name
FROM Employees
WHERE Last_Name LIKE 'Washington%'
关键字LIKE
用于比较字符串,并提供了可以使用通配符的功能。例如,在上面的代码片段中,'Washington’末尾有一个百分号(%
),表示任何包含字符串’Washington’加零个或多个额外字符的值都将满足这个选择条件。因此,'Washington’或’Washingtonian’都会匹配,但’Washing’不会。LIKE
子句中使用的另一个通配符是下划线(_
),代表任意一个字符。例如,
WHERE Last_Name LIKE 'Ba_man'
会匹配’Barman’、‘Badman’、‘Balman’、‘Bagman’、'Bamman’等等。
下面的代码片段有一个使用等号(=
)比较数字的WHERE
子句。它选择了被分配车辆 12 的员工的名字和姓氏。
SELECT First_Name, Last_Name
FROM Employees
WHERE Car_Number = 12
下一个代码片段选择了员工编号大于 10005 的员工的名字和姓氏:
SELECT First_Name, Last_Name
FROM Employees
WHERE Employee_Number > 10005
WHERE
子句可能会变得相当复杂,包含多个条件,在一些数据库管理系统中还可能有嵌套条件。本概述不会涵盖复杂的WHERE
子句,但以下代码片段有一个带有两个条件的WHERE
子句;这个查询选择了员工编号小于 10100 且没有公司车的员工的名字和姓氏。
SELECT First_Name, Last_Name
FROM Employees
WHERE Employee_Number < 10100 and Car_Number IS NULL
一种特殊类型的WHERE
子句涉及连接,将在下一节中解释。
连接
关系数据库的一个显著特点是可以通过所谓的连接从多个表中获取数据。假设在检索拥有公司车辆的员工姓名后,想要找出谁拥有哪辆车,包括车辆的品牌、型号和年份。这些信息存储在另一个表Cars
中:
Cars
表
Car_Number | 品牌 | 型号 | 年份 |
---|---|---|---|
5 | 本田 | 思域 DX | 1996 |
12 | 丰田 | 卡罗拉 | 1999 |
为了将两个表关联起来,必须有一列同时出现在两个表中。这一列在一个表中必须是主键,在另一个表中被称为外键。在这种情况下,出现在两个表中的列是Car_Number
,它是表Cars
的主键,也是表Employees
的外键。如果 1996 年本田思域被损坏并从Cars
表中删除,那么Car_Number
为 5 也必须从Employees
表中删除,以保持所谓的参照完整性。否则,Employees
表中的外键列(Car_Number
)将包含一个不指向Cars
表中任何内容的条目。外键必须为空或等于所引用表的现有主键值。这与主键不同,主键不可为空。在表Employees
的Car_Number
列中有几个空值,因为员工可能没有公司车。
以下代码要求输入拥有公司车辆的员工的名字和姓氏,以及这些车辆的制造商、型号和年份。请注意,FROM
子句列出了Employees
和Cars
表,因为请求的数据包含在这两个表中。在列名之前使用表名和点号(.
)表示哪个表包含该列。
SELECT Employees.First_Name, Employees.Last_Name,
Cars.Make, Cars.Model, Cars.Year
FROM Employees, Cars
WHERE Employees.Car_Number = Cars.Car_Number
这将返回一个类似以下的结果集:
FIRST_NAME | LAST_NAME | LICENSE_PLATE | MILEAGE | YEAR |
---|---|---|---|---|
John | Washington | ABC123 | 5000 | 1996 |
Florence | Wojokowski | DEF123 | 7500 | 1999 |
常见的 SQL 命令
SQL 命令分为不同的类别,主要包括数据操作语言(DML)命令和数据定义语言(DDL)命令。DML 命令处理数据,无论是检索数据还是修改数据以保持其最新状态。DDL 命令创建或更改表以及其他数据库对象,如视图和索引。
以下是更常见的 DML 命令列表:
-
SELECT —
用于从数据库中查询和显示数据。SELECT
语句指定要包含在结果集中的列。应用程序中使用的 SQL 命令中绝大多数是SELECT
语句。 -
INSERT —
向表中添加新行。INSERT
用于填充新创建的表或向已存在的表中添加新行(或多行)。 -
DELETE —
从表中删除指定的行或一组行 -
UPDATE —
改变表中某一列或一组列中的现有值
更常见的 DDL 命令如下:
-
CREATE TABLE —
创建一个带有用户提供的列名的表。用户还需要为每个列中的数据指定一个类型。不同的关系型数据库管理系统具有不同的数据类型,因此用户可能需要使用元数据来确定特定数据库使用的数据类型。CREATE TABLE
通常比数据操作命令使用频率低,因为表只创建一次,而添加或删除行或更改单个值通常更频繁发生。 -
DROP TABLE —
删除所有行并从数据库中删除表定义。根据 SQL92,过渡级别的规范,JDBC API 实现需要支持DROP TABLE
命令。但是,对于DROP TABLE
的CASCADE
和RESTRICT
选项的支持是可选的。此外,当存在引用正在被删除的表的视图或完整性约束时,DROP TABLE
的行为是由实现定义的。 -
ALTER TABLE —
向表中添加或删除列。它还添加或删除表约束并更改列属性
结果集和游标
满足查询条件的行被称为结果集。结果集中返回的行数可以是零、一或多个。用户可以逐行访问结果集中的数据,游标提供了这样的功能。游标可以被看作是指向包含结果集行的文件的指针,并且该指针有能力跟踪当前正在访问的行。游标允许用户从顶部到底部处理结果集的每一行,因此可用于迭代处理。大多数数据库管理系统在生成结果集时会自动创建游标。
早期的 JDBC API 版本为结果集的游标增加了新的功能,允许它向前和向后移动,还允许它移动到指定的行或相对于另一行的位置。
更多信息请参见从结果集中检索和修改值。
事务
当一个用户正在访问数据库中的数据时,另一个用户可能同时访问相同的数据。例如,第一个用户正在同时更新表中的某些列,而第二个用户正在从同一表中选择列,这时第二个用户可能会得到部分旧数据和部分更新数据。因此,数据库管理系统使用事务来维护数据的一致状态(数据一致性),同时允许多个用户同时访问数据库(数据并发性)。
事务是由一个或多个 SQL 语句组成的逻辑工作单元。事务以提交或回滚结束,具体取决于数据一致性或数据并发性是否存在问题。提交语句将使事务中 SQL 语句产生的更改永久生效,而回滚语句将撤消事务中 SQL 语句产生的所有更改。
锁是一种机制,阻止两个事务同时操作相同的数据。例如,表锁会阻止在该表上存在未提交事务时删除该表。在某些数据库管理系统中,表锁还会锁定表中的所有行。行锁可以阻止两个事务修改同一行,或者阻止一个事务在另一个事务仍在修改该行时选择该行。
更多信息请参见使用事务。
存储过程
存储过程是一组可以通过名称调用的 SQL 语句。换句话说,它是可执行的代码,一个小型程序,执行特定任务,可以像调用函数或方法一样调用。传统上,存储过程是用特定于数据库管理系统的编程语言编写的。最新一代的数据库产品允许使用 Java 编程语言和 JDBC API 编写存储过程。用 Java 编程语言编写的存储过程在不同数据库管理系统之间是字节码可移植的。一旦编写了存储过程,它就可以被使用和重复使用,因为支持存储过程的数据库管理系统会将其存储在数据库中。查看使用存储过程获取有关编写存储过程的信息。
元数据
数据库存储用户数据,也存储关于数据库本身的信息。大多数数据库管理系统都有一组系统表,列出数据库中的表、每个表中的列名、主键、外键、存储过程等。每个数据库管理系统都有自己的函数来获取有关表布局和数据库功能的信息。JDBC 提供了 DatabaseMetaData
接口,驱动程序编写者必须实现该接口,以便其方法返回有关驱动程序和/或为其编写驱动程序的数据库管理系统的信息。例如,大量的方法返回驱动程序是否支持特定功能。这个接口为用户和工具提供了一种标准化的获取元数据的方式。一般来说,编写工具和驱动程序的开发人员最有可能关注元数据。
课程:JDBC 基础知识
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/index.html
在这节课中,您将学习 JDBC API 的基础知识。
-
入门指南设置了一个基本的数据库开发环境,并展示了如何编译和运行 JDBC 教程示例。
-
使用 JDBC 处理 SQL 语句概述了处理任何 SQL 语句所需的步骤。接下来的页面将更详细地描述这些步骤:
-
建立连接连接到您的数据库。
-
连接到数据源对象展示了如何使用
DataSource
对象连接到数据库,这是获取数据源连接的首选方式。 -
处理 SQLException 展示了如何处理由数据库错误引起的异常。
-
设置表格描述了 JDBC 教程示例中使用的所有数据库表格以及如何使用 JDBC API 和 SQL 脚本创建和填充表格。
-
从结果集中检索和修改值开发了配置数据库、发送查询和从数据库检索数据的过程。
-
使用预编译语句描述了创建数据库查询的更灵活方式。
-
使用事务展示了如何控制数据库查询何时实际执行。
-
-
使用 RowSet 对象介绍了
RowSet
对象;这些对象以一种比结果集更灵活且更易于使用的方式保存表格数据。接下来的页面将描述可用的不同类型的RowSet
对象:-
使用 JdbcRowSet 对象
-
使用 CachedRowSet 对象
-
使用 JoinRowSet 对象
-
使用 FilteredRowSet 对象
-
使用 WebRowSet 对象
-
-
使用高级数据类型介绍了其他数据类型;接下来的页面将更详细地描述这些数据类型:
-
使用大对象
-
使用 SQLXML 对象
-
使用数组对象
-
使用 DISTINCT 数据类型
-
使用结构化对象
-
使用自定义类型映射
-
使用数据链接对象
-
使用行 ID 对象
-
-
使用存储过程展示了如何创建和使用存储过程,这是一组可以像调用 Java 方法一样具有可变输入和输出参数的 SQL 语句组。
-
使用 GUI API 的 JDBC 演示了如何将 JDBC 与 Swing API 集成。
入门指南
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/gettingstarted.html
本教程附带的示例代码创建了一个数据库,供一家名为 The Coffee Break 的小咖啡馆的业主使用,该咖啡馆以磅为单位出售咖啡豆,以杯为单位出售冲泡咖啡。
以下步骤配置了一个 JDBC 开发环境,您可以在其中编译和运行教程示例:
-
在您的计算机上安装最新版本的 Java SE SDK
-
如果需要,安装您的数据库管理系统(DBMS)
-
从您的数据库供应商处安装一个 JDBC 驱动程序
-
安装 Apache Ant
-
安装 Apache Xalan
-
下载示例代码
-
修改
build.xml
文件 -
修改教程属性文件
-
编译和打包示例
-
创建数据库、表和填充表
-
运行示例
在您的计算机上安装最新版本的 Java SE SDK
在您的计算机上安装最新版本的 Java SE SDK。
确保 Java SE SDK bin
目录的完整路径在您的PATH
环境变量中,以便您可以从任何目录运行 Java 编译器和 Java 应用程序启动器。
如果需要,请安装您的数据库管理系统(DBMS)
本教程已在以下数据库管理系统上进行测试:
-
Java DB
注意:Java DB 不再包含在最新版本的 JDK 中。Java DB 是 Apache Derby 的一个重新命名。如果您想使用 Java DB,请从Apache DB 项目下载最新版本。
-
MySQL
请注意,如果您使用其他 DBMS,可能需要修改教程示例的代码。
从您的数据库供应商处安装一个 JDBC 驱动程序
如果您使用 Java DB,则已经带有一个 JDBC 驱动程序。如果您使用 MySQL,请安装最新版本的 MySQL JDBC 驱动程序Connector/J。
联系您的数据库供应商,获取适用于您的 DBMS 的 JDBC 驱动程序。
JDBC 驱动程序有许多可能的实现。这些实现按以下方式分类:
-
类型 1:将 JDBC API 实现为另一个数据访问 API 的映射的驱动程序,例如 ODBC(开放数据库连接)。这种类型的驱动程序通常依赖于本地库,这限制了它们的可移植性。JDBC-ODBC 桥就是类型 1 驱动程序的一个例子。
注意:JDBC-ODBC 桥应被视为一个过渡解决方案。Oracle 不支持它。只有在您的数据库管理系统没有提供仅支持 Java 的 JDBC 驱动程序时才考虑使用它。
-
Type 2: 部分用 Java 编程语言编写,部分用本机代码编写的驱动程序。这些驱动程序使用特定于它们连接的数据源的本机客户端库。由于本机代码的存在,它们的可移植性受到限制。Oracle 的 OCI(Oracle Call Interface)客户端驱动程序是 Type 2 驱动程序的一个示例。
-
Type 3: 使用纯 Java 客户端并使用数据库独立协议与中间件服务器通信的驱动程序。然后中间件服务器将客户端的请求传达给数据源。
-
Type 4: 纯 Java 驱动程序,实现特定数据源的网络协议。客户端直接连接到数据源。
检查你的数据库管理系统中带有哪些驱动程序类型。Java DB 带有两种 Type 4 驱动程序,一个是嵌入式驱动程序,另一个是网络客户端驱动程序。MySQL Connector/J 是一种 Type 4 驱动程序。
安装 JDBC 驱动程序通常包括将驱动程序复制到计算机上,然后将其位置添加到类路径中。此外,除了 Type 4 驱动程序之外的许多 JDBC 驱动程序需要你安装客户端 API。通常不需要其他特殊配置。
安装 Apache Ant
这些步骤使用 Apache Ant,一个基于 Java 的工具,来构建、编译和运行 JDBC 教程示例。前往以下链接下载 Apache Ant:
[
ant.apache.org/](https://ant.apache.org/)
确保 Apache Ant 可执行文件在你的PATH
环境变量中,这样你就可以在任何目录中运行它。
安装 Apache Xalan
示例RSSFeedsTable.java
,在使用 SQLXML 对象中有描述,如果你的数据库管理系统是 Java DB,则需要 Apache Xalan。该示例使用 Apache Xalan-Java。前往以下链接下载:
[
xml.apache.org/xalan-j/](https://xml.apache.org/xalan-j/)
下载示例代码
示例代码JDBCTutorial.zip
包括以下文件:
-
properties
-
javadb-build-properties.xml
-
javadb-sample-properties.xml
-
mysql-build-properties.xml
-
mysql-sample-properties.xml
-
-
sql
-
javadb
-
create-procedures.sql
-
create-tables.sql
-
drop-tables.sql
-
populate-tables.sql
-
-
mysql
-
create-procedures.sql
-
create-tables.sql
-
drop-tables.sql
-
populate-tables.sql
-
-
-
src/com/oracle/tutorial/jdbc
-
CachedRowSetSample.java
-
CityFilter.java
-
ClobSample.java
-
CoffeesFrame.java
-
CoffeesTable.java
-
CoffeesTableModel.java
-
DatalinkSample.java
-
ExampleRowSetListener.java
-
FilteredRowSetSample.java
-
JdbcRowSetSample.java
-
JDBCTutorialUtilities.java
-
JoinSample.java
-
ProductInformationTable.java
-
RSSFeedsTable.java
-
StateFilter.java
-
StoredProcedureJavaDBSample.java
-
StoredProcedureMySQLSample.java
-
SuppliersTable.java
-
WebRowSetSample.java
-
-
txt
colombian-description.txt
-
xml
-
rss-coffee-industry-news.xml
-
rss-the-coffee-break-blog.xml
-
-
build.xml
创建一个目录来包含示例的所有文件。这些步骤将此目录称为 *<JDBC 教程目录>*
。将 JDBCTutorial.zip
的内容解压缩到 *<JDBC 教程目录>*
中。
修改 build.xml 文件
build.xml
文件是 Apache Ant 用于编译和执行 JDBC 示例的构建文件。文件 properties/javadb-build-properties.xml
和 properties/mysql-build-properties.xml
包含 Java DB 和 MySQL 需要的额外 Apache Ant 属性。文件 properties/javadb-sample-properties.xml
和 properties/mysql-sample-properties.xml
包含示例所需的属性。
修改这些 XML 文件如下:
修改 build.xml
在 build.xml
文件中,修改属性 ANTPROPERTIES
,指向 properties/javadb-build-properties.xml
或 properties/mysql-build-properties.xml
,取决于您的 DBMS。例如,如果您正在使用 Java DB,则您的 build.xml
文件将包含以下内容:
<property
name="ANTPROPERTIES"
value="properties/javadb-build-properties.xml"/>
<import file="${ANTPROPERTIES}"/>
同样,如果您正在使用 MySQL,您的 build.xml
文件将包含以下内容:
<property
name="ANTPROPERTIES"
value="properties/mysql-build-properties.xml"/>
<import file="${ANTPROPERTIES}"/>
修改特定于数据库的属性文件
在 properties/javadb-build-properties.xml
或 properties/mysql-build-properties.xml
文件(取决于您的 DBMS),根据以下表格中的描述修改以下属性:
属性 | 描述 |
---|---|
JAVAC | 您的 Java 编译器 javac 的完整路径名。 |
JAVA | 您的 Java 运行时可执行文件 java 的完整路径名。 |
PROPERTIESFILE | 属性文件的名称,可以是 properties/javadb-sample-properties.xml 或 properties/mysql-sample-properties.xml 。 |
MYSQLDRIVER | 你的 MySQL 驱动程序的完整路径名。对于 Connector/J,通常是 *<Connector/J 安装目录>*/mysql-connector-java-*版本号*.jar 。 |
JAVADBDRIVER | 您的 Java DB 驱动程序的完整路径名。通常是 *<Java DB 安装目录>*/lib/derby.jar 。 |
XALANDIRECTORY | 包含 Apache Xalan 的目录的完整路径名。 |
CLASSPATH | JDBC 教程使用的类路径。您无需更改此值。 |
XALAN | 文件 xalan.jar 的完整路径名。 |
DB.VENDOR | 一个值,可以是 derby 或 mysql ,取决于您是使用 Java DB 还是 MySQL。教程使用此值来构建连接到 DBMS 和识别特定于 DBMS 的代码和 SQL 语句所需的 URL。 |
DB.DRIVER | JDBC 驱动程序的完全限定类名。对于 Java DB,这是 org.apache.derby.jdbc.EmbeddedDriver 。对于 MySQL,这是 com.mysql.cj.jdbc.Driver 。 |
DB.HOST | 托管您的 DBMS 的计算机的主机名。 |
DB.PORT | 托管您的 DBMS 的计算机的端口号。 |
DB.SID | 教程创建和使用的数据库名称。 |
DB.URL.NEWDATABASE | 创建新数据库时用于连接到您的 DBMS 的连接 URL。您无需更改此值。 |
DB.URL | 用于连接到您的 DBMS 的连接 URL。您无需更改此值。 |
DB.USER | 具有在 DBMS 中创建数据库权限的用户的名称。 |
DB.PASSWORD | 指定在DB.USER 中的用户的密码。 |
DB.DELIMITER | 用于分隔 SQL 语句的字符。不要更改此值。它应该是分号字符(; )。 |
修改教程属性文件
教程示例使用properties/javadb-sample-properties.xml
文件或properties/mysql-sample-properties.xml
文件中的值(取决于您的 DBMS)来连接到 DBMS 并初始化数据库和表,如下表所述:
属性 | 描述 |
---|---|
dbms | 取值为derby 或mysql ,取决于您是使用 Java DB 还是 MySQL。本教程使用此值来构建连接到 DBMS 所需的 URL,并识别 DBMS 特定的代码和 SQL 语句。 |
jar_file | 包含本教程所有类文件的 JAR 文件的完整路径名。 |
driver | JDBC 驱动程序的完全限定类名。对于 Java DB,这是org.apache.derby.jdbc.EmbeddedDriver 。对于 MySQL,这是com.mysql.cj.jdbc.Driver 。 |
database_name | 教程创建和使用的数据库名称。 |
user_name | 具有在 DBMS 中创建数据库权限的用户的名称。 |
password | 指定在user_name 中的用户的密码。 |
server_name | 托管您的 DBMS 的计算机的主机名。 |
port_number | 托管您的 DBMS 的计算机的端口号。 |
注意:为了简化演示 JDBC API,JDBC 教程示例代码不执行部署系统通常使用的密码管理技术。在生产环境中,您可以遵循 Oracle 数据库密码管理指南并禁用任何示例帐户。请参阅Oracle 数据库安全指南中的应用程序设计中的密码保护部分,了解密码管理指南和其他安全建议。
编译并打包示例
在命令提示符下,将当前目录更改为*<JDBC 教程目录>*
。从该目录运行以下命令编译示例并将其打包到一个 jar 文件中:
ant jar
创建数据库、表格,并填充表格
如果您使用的是 MySQL,则运行以下命令来创建数据库:
ant create-mysql-database
注意:build.xml
文件中不存在用于为 Java DB 创建数据库的相应 Ant 目标。用于建立数据库连接的 Java DB 数据库 URL 包括创建数据库的选项(如果尚不存在)。有关更多信息,请参阅建立连接。
如果您正在使用 Java DB 或 MySQL,则可以从同一目录运行以下命令来删除现有的示例数据库表,重新创建表并填充它们。对于 Java DB,此命令还会在数据库不存在时创建数据库:
ant setup
注意:在运行示例中的 Java 类之前,您应该每次运行ant setup
命令。这些示例中的许多示例都期望示例数据库表的内容中有特定的数据。
运行示例
build.xml
文件中的每个目标对应于 JDBC 示例中的一个 Java 类或 SQL 脚本。以下表列出了build.xml
文件中的目标,每个目标执行的类或脚本,以及每个目标需要的其他类或文件:
Ant 目标 | 类或 SQL 脚本 | 其他必需类或文件 |
---|---|---|
javadb-create-procedure | javadb/create-procedures.sql ;查看build.xml 文件以查看运行的其他 SQL 语句 | 无其他必需文件 |
mysql-create-procedure | mysql/create-procedures.sql | 无其他必需文件 |
run | JDBCTutorialUtilities | 无其他必需类 |
runct | CoffeesTable | JDBCTutorialUtilities |
runst | SuppliersTable | JDBCTutorialUtilities |
runjrs | JdbcRowSetSample | JDBCTutorialUtilities |
runcrs | CachedRowSetSample ,ExampleRowSetListener | JDBCTutorialUtilities |
runjoin | JoinSample | JDBCTutorialUtilities |
runfrs | FilteredRowSetSample | JDBCTutorialUtilities ,CityFilter ,StateFilter |
runwrs | WebRowSetSample | JDBCTutorialUtilities |
runclob | ClobSample | JDBCTutorialUtilities ,txt/colombian-description.txt |
runrss | RSSFeedsTable | JDBCTutorialUtilities ,xml 目录中包含的 XML 文件 |
rundl | DatalinkSample | JDBCTutorialUtilities |
runspjavadb | StoredProcedureJavaDBSample | JDBCTutorialUtilities ,SuppliersTable ,CoffeesTable |
runspmysql | StoredProcedureMySQLSample | JDBCTutorialUtilities ,SuppliersTable ,CoffeesTable |
runframe | CoffeesFrame | JDBCTutorialUtilities ,CoffeesTableModel |
例如,要运行CoffeesTable
类,请将当前目录更改为*<JDBC 教程目录>*
,然后从该目录运行以下命令:
ant runct
使用 JDBC 处理 SQL 语句
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/processingsqlstatements.html
一般来说,要使用 JDBC 处理任何 SQL 语句,您需要按照以下步骤进行:
-
建立连接。
-
创建语句。
-
执行查询。
-
处理
ResultSet
对象。 -
关闭连接。
本页使用教程示例中的方法CoffeesTable.viewTable
来演示这些步骤。此方法输出表COFFEES
的内容。此方法将在本教程的后续部分中更详细地讨论:
public static void viewTable(Connection con) throws SQLException {
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
try (Statement stmt = con.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + ", " + supplierID + ", " + price +
", " + sales + ", " + total);
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
建立连接
首先,与要使用的数据源建立连接。数据源可以是 DBMS、传统文件系统或具有相应 JDBC 驱动程序的其他数据源。此连接由Connection
对象表示。有关更多信息,请参见建立连接。
创建语句
Statement
是表示 SQL 语句的接口。您执行Statement
对象,它们生成ResultSet
对象,这是表示数据库结果集的数据表。您需要一个Connection
对象来创建Statement
对象。
例如,CoffeesTable.viewTable
使用以下代码创建了一个Statement
对象:
stmt = con.createStatement();
有三种不同类型的语句:
-
Statement
:用于执行没有参数的简单 SQL 语句。 -
PreparedStatement
:(扩展Statement
。)用于预编译可能包含输入参数的 SQL 语句。有关更多信息,请参见使用预编译语句。 -
CallableStatement:
(扩展PreparedStatement
。)用于执行可能包含输入和输出参数的存储过程。有关更多信息,请参见存储过程。
执行查询
要执行查询,请调用Statement
中的execute
方法,如下所示:
-
execute
:如果查询返回的第一个对象是ResultSet
对象,则返回true
。如果查询可能返回一个或多个ResultSet
对象,请使用此方法。通过反复调用Statement.getResultSet
检索查询返回的ResultSet
对象。 -
executeQuery
:返回一个ResultSet
对象。 -
executeUpdate
:返回一个整数,表示受 SQL 语句影响的行数。如果您使用INSERT
、DELETE
或UPDATE
SQL 语句,请使用此方法。
例如,CoffeesTable.viewTable
使用以下代码执行了一个Statement
对象:
ResultSet rs = stmt.executeQuery(query);
有关更多信息,请参见从结果集中检索和修改值。
处理 ResultSet 对象
通过游标访问ResultSet
对象中的数据。请注意,此游标不是数据库游标。这个游标是指向ResultSet
对象中一行数据的指针。最初,游标位于第一行之前。您调用ResultSet
对象中定义的各种方法来移动游标。
例如,CoffeesTable.viewTable
重复调用ResultSet.next
方法来将游标向前移动一行。每次调用next
时,该方法会输出游标当前位置的行中的数据:
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + ", " + supplierID + ", " + price +
", " + sales + ", " + total);
}
// ...
查看从结果集中检索和修改值获取更多信息。
关闭连接
当您使用完Connection
、Statement
或ResultSet
对象后,请调用其close
方法立即释放它正在使用的资源。
或者,可以使用try
-with-resources 语句自动关闭Connection
、Statement
和ResultSet
对象,无论是否抛出SQLException
。(当 JDBC 在与数据源交互时遇到错误时,会抛出SQLException
。查看处理 SQL 异常获取更多信息。)自动资源语句由一个try
语句和一个或多个声明的资源组成。例如,CoffeesTable.viewTable
方法会自动关闭其Statement
对象,如下所示:
public static void viewTable(Connection con) throws SQLException {
String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
try (Statement stmt = con.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
String coffeeName = rs.getString("COF_NAME");
int supplierID = rs.getInt("SUP_ID");
float price = rs.getFloat("PRICE");
int sales = rs.getInt("SALES");
int total = rs.getInt("TOTAL");
System.out.println(coffeeName + ", " + supplierID + ", " + price +
", " + sales + ", " + total);
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
以下语句是一个try
-with-resources 语句,声明了一个资源stmt
,当try
块终止时将自动关闭该资源:
try (Statement stmt = con.createStatement()) {
// ...
}
查看 try-with-resources 语句在 Essential Classes 教程中获取更多信息。
建立连接
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html
首先,您需要与要使用的数据源建立连接。数据源可以是 DBMS、传统文件系统或其他具有相应 JDBC 驱动程序的数据源。通常,JDBC 应用程序使用以下两个类之一连接到目标数据源:
-
DriverManager
:此完全实现的类将应用程序连接到由数据库 URL 指定的数据源。当此类首次尝试建立连接时,它会自动加载类路径中找到的任何 JDBC 4.0 驱动程序。请注意,您的应用程序必须在版本 4.0 之前手动加载任何 JDBC 驱动程序。 -
DataSource
:此接口优先于DriverManager
,因为它允许底层数据源的详细信息对您的应用程序透明。设置DataSource
对象的属性,使其表示特定的数据源。有关更多信息,请参阅使用 DataSource 对象进行连接。有关使用DataSource
类开发应用程序的更多信息,请参阅最新版本的*Java EE 教程*。
注意:本教程中的示例使用DriverManager
类而不是DataSource
类,因为它更容易使用,而且示例不需要DataSource
类的功能。
本页涵盖以下主题:
-
使用 DriverManager 类
-
指定数据库连接 URL
使用 DriverManager 类
使用DriverManager
类连接到您的 DBMS 涉及调用方法DriverManager.getConnection
。以下方法,JDBCTutorialUtilities.getConnection
,建立数据库连接:
public Connection getConnection() throws SQLException {
Connection conn = null;
Properties connectionProps = new Properties();
connectionProps.put("user", this.userName);
connectionProps.put("password", this.password);
if (this.dbms.equals("mysql")) {
conn = DriverManager.getConnection(
"jdbc:" + this.dbms + "://" +
this.serverName +
":" + this.portNumber + "/",
connectionProps);
} else if (this.dbms.equals("derby")) {
conn = DriverManager.getConnection(
"jdbc:" + this.dbms + ":" +
this.dbName +
";create=true",
connectionProps);
}
System.out.println("Connected to database");
return conn;
}
方法DriverManager.getConnection
建立数据库连接。此方法需要数据库 URL,具体取决于您的 DBMS。以下是一些数据库 URL 的示例:
-
MySQL:
jdbc:mysql://localhost:3306/
,其中localhost
是托管数据库的服务器名称,3306
是端口号 -
Java DB:
jdbc:derby:*testdb*;create=true
,其中*testdb*
是要连接的数据库的名称,create=true
指示 DBMS 创建数据库。注意:此 URL 与 Java DB 嵌入式驱动程序建立数据库连接。Java DB 还包括使用不同 URL 的网络客户端驱动程序。
此方法使用Properties
对象指定访问 DBMS 所需的用户名和密码。
注意:
-
通常,在数据库 URL 中,你还会指定要连接的现有数据库的名称。例如,URL
jdbc:mysql://localhost:3306/mysql
代表了名为mysql
的 MySQL 数据库的数据库 URL。本教程中的示例使用不指定特定数据库的 URL,因为示例会创建一个新数据库。 -
在以前的 JDBC 版本中,要获取连接,你首先需要通过调用方法
Class.forName
来初始化你的 JDBC 驱动程序。这些方法需要一个类型为java.sql.Driver
的对象。每个 JDBC 驱动程序都包含一个或多个实现接口java.sql.Driver
的类。Java DB 的驱动程序是org.apache.derby.jdbc.EmbeddedDriver
和org.apache.derby.jdbc.ClientDriver
,而 MySQL Connector/J 的驱动程序是com.mysql.cj.jdbc.Driver
。查看你的 DBMS 驱动程序的文档以获取实现接口java.sql.Driver
的类名。任何在你的类路径中找到的 JDBC 4.0 驱动程序都会自动加载。(但是,在 JDBC 4.0 之前,你必须手动加载任何驱动程序,使用方法
Class.forName
。)
该方法返回一个 Connection
对象,表示与 DBMS 或特定数据库的连接。通过这个对象查询数据库。
指定数据库连接 URL
数据库连接 URL 是你的 DBMS JDBC 驱动程序用来连接数据库的字符串。它可以包含诸如在哪里搜索数据库、要连接的数据库名称和配置属性等信息。数据库连接 URL 的确切语法由你的 DBMS 指定。
Java DB 数据库连接 URL
以下是 Java DB 的数据库连接 URL 语法:
jdbc:derby:[*subsubprotocol*:][*databaseName*][;*attribute*=*value*]*
-
*subsubprotocol*
指定 Java DB 应在何处搜索数据库,可以是目录、内存、类路径或 JAR 文件。通常会省略。 -
*databaseName*
是要连接的数据库的名称。 -
*attribute*=*value*
表示一个可选的、以分号分隔的属性列表。这些属性使你能够指示 Java DB 执行各种任务,包括以下内容:-
创建连接 URL 中指定的数据库。
-
加密连接 URL 中指定的数据库。
-
指定存储日志和跟踪信息的目录。
-
指定用户名和密码以连接到数据库。
-
查看Java DB 技术文档中的Java DB 开发人员指南和Java DB 参考手册以获取更多信息。
MySQL Connector/J 数据库 URL
以下是 MySQL Connector/J 的数据库连接 URL 语法:
jdbc:mysql://[*host*][,*failoverhost*...]
[:*port*]/[*database*]
[?*propertyName1*][=*propertyValue1*]
[&*propertyName2*][=*propertyValue2*]...
-
*host*:*port*
是托管数据库的计算机的主机名和端口号。如果未指定,默认值为*host*
和*port*
分别为 127.0.0.1 和 3306。 -
*database*
是要连接的数据库的名称。如果未指定,将连接到没有默认数据库的连接。 -
*failover*
是一个备用数据库的名称(MySQL Connector/J 支持故障转移)。 -
*propertyName*=*propertyValue*
表示一个可选的、以&符号分隔的属性列表。这些属性使您能够指示 MySQL Connector/J 执行各种任务。
查看MySQL 参考手册 获取更多信息。
使用 DataSource 对象进行连接
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/sqldatasources.html
本节涵盖了DataSource
对象,这是获取与数据源连接的首选方法。除了其他优点,稍后将解释的优点之外,DataSource
对象还可以提供连接池和分布式事务。这种功能对企业数据库计算至关重要。特别是,它对企业 JavaBeans(EJB)技术至关重要。
本节向您展示如何使用DataSource
接口获取连接以及如何使用分布式事务和连接池。这两者在您的 JDBC 应用程序中都涉及非常少的代码更改。
部署执行这些操作的类所需的工作,通常由系统管理员使用工具(如 Apache Tomcat 或 Oracle WebLogic Server)完成,取决于部署的DataSource
对象的类型。因此,本节大部分内容都致力于展示系统管理员如何设置环境,以便程序员可以使用DataSource
对象获取连接。
下面的主题包括:
-
使用 DataSource 对象获取连接
-
部署基本的 DataSource 对象
-
部署其他 DataSource 实现
-
获取和使用池化连接
-
部署分布式事务
-
使用连接进行分布式事务
使用 DataSource 对象获取连接
在建立连接中,您学习了如何使用DriverManager
类获取连接。本节将向您展示如何使用DataSource
对象获取与数据源的连接,这是首选方法。
由实现DataSource
类的类实例化的对象代表特定的 DBMS 或其他数据源,比如文件。DataSource
对象代表特定的 DBMS 或其他数据源,比如文件。如果一个公司使用多个数据源,它将为每个数据源部署一个单独的DataSource
对象。DataSource
接口由驱动程序供应商实现。它可以以三种不同的方式实现:
-
基本的
DataSource
实现会生成标准的未池化或未用于分布式事务的Connection
对象。 -
支持连接池的
DataSource
实现会生成参与连接池的Connection
对象,即可以被回收的连接。 -
支持分布式事务的
DataSource
实现会生成可用于分布式事务的Connection
对象,即访问两个或多个 DBMS 服务器的事务。
JDBC 驱动程序应至少包含基本的DataSource
实现。例如,Java DB JDBC 驱动程序包括org.apache.derby.jdbc.ClientDataSource
的实现,而对于 MySQL,则是com.mysql.jdbc.jdbc2.optional.MysqlDataSource
。如果您的客户端在 Java 8 紧凑配置文件 2 上运行,则 Java DB JDBC 驱动程序是org.apache.derby.jdbc.BasicClientDataSource40
。本教程的示例需要紧凑配置文件 3 或更高版本。
支持分布式事务的DataSource
类通常也实现了对连接池的支持。例如,由 EJB 供应商提供的DataSource
类几乎总是支持连接池和分布式事务。
假设前面示例中茁壮成长的 The Coffee Break 连锁店的所有者已决定通过互联网进一步扩展销售咖啡。随着预期的大量在线业务,所有者肯定需要连接池。打开和关闭连接涉及大量开销,所有者预计这个在线订购系统将需要大量的查询和更新。通过连接池,一组连接可以一遍又一遍地使用,避免为每次数据库访问创建新连接的开销。此外,所有者现在有第二个包含最近收购的咖啡烘焙公司数据的 DBMS。这意味着所有者希望能够编写使用旧 DBMS 服务器和新 DBMS 服务器的分布式事务。
链店所有者已重新配置计算机系统以服务新的、更大的客户群。所有者已购买最新的 JDBC 驱动程序和与之配套的 EJB 应用服务器,以便使用分布式事务并获得连接池带来的性能提升。许多与最近购买的 EJB 服务器兼容的 JDBC 驱动程序可用。现在,所有者拥有三层架构,中间层有一个新的 EJB 应用服务器和 JDBC 驱动程序,第三层是两个 DBMS 服务器。发出请求的客户端计算机是第一层。
部署基本的 DataSource 对象
系统管理员需要部署DataSource
对象,以便 The Coffee Break 的编程团队可以开始使用它们。部署DataSource
对象包括三个任务:
-
创建
DataSource
类的实例 -
设置其属性
-
使用使用 Java 命名和目录接口(JNDI)API 的命名服务进行注册
首先,考虑最基本的情况,即使用DataSource
接口的基本实现,即不支持连接池或分布式事务的实现。在这种情况下,只需要部署一个DataSource
对象。DataSource
的基本实现产生与DriverManager
类产生的相同类型的连接。
创建 DataSource 类的实例并设置其属性
假设一家只想要一个基本的DataSource
实现的公司从 JDBC 供应商 DB Access, Inc 购买了一个驱动程序。该驱动程序包括实现DataSource
接口的类com.dbaccess.BasicDataSource
。以下代码摘录创建BasicDataSource
类的实例并设置其属性。部署BasicDataSource
实例后,程序员可以调用DataSource.getConnection
方法获取连接到公司数据库CUSTOMER_ACCOUNTS
。首先,系统管理员使用默认构造函数创建BasicDataSource
对象*ds
。然后,系统管理员设置三个属性。请注意,以下代码通常由部署工具执行:
com.dbaccess.BasicDataSource ds = new com.dbaccess.BasicDataSource();
ds.setServerName("grinder");
ds.setDatabaseName("CUSTOMER_ACCOUNTS");
ds.setDescription("Customer accounts database for billing");
变量*ds*
现在代表安装在服务器上的数据库CUSTOMER_ACCOUNTS
。*ds*
对象生成的任何连接都将是到数据库CUSTOMER_ACCOUNTS
的连接。
使用使用 JNDI API 的命名服务注册 DataSource 对象
设置属性后,系统管理员可以将BasicDataSource
对象注册到 JNDI(Java 命名和目录接口)命名服务中。通常使用的特定命名服务通常由系统属性确定,这里没有显示。以下代码摘录注册BasicDataSource
对象并将其绑定到逻辑名称jdbc/billingDB
:
Context ctx = new InitialContext();
ctx.bind("jdbc/billingDB", ds);
此代码使用 JNDI API。第一行创建一个InitialContext
对象,它类似于文件系统中的根目录的起始点。第二行将BasicDataSource
对象*ds*
与逻辑名称jdbc/billingDB
关联或绑定。在下一个代码摘录中,您将向命名服务提供此逻辑名称,它将返回BasicDataSource
对象。逻辑名称可以是任何字符串。在这种情况下,公司决定使用名称billingDB
作为CUSTOMER_ACCOUNTS
数据库的逻辑名称。
在前面的示例中,jdbc
是初始上下文下的一个子上下文,就像根目录下的目录是子目录一样。名称jdbc/billingDB
类似于路径名,其中路径中的最后一项类似于文件名。在这种情况下,billingDB
是赋予BasicDataSource
对象*ds*
的逻辑名称。子上下文jdbc
保留用于绑定到DataSource
对象的逻辑名称,因此jdbc
将始终是数据源逻辑名称的第一部分。
使用部署的 DataSource 对象
系统管理员部署了基本的DataSource
实现后,程序员就可以开始使用了。这意味着程序员可以提供绑定到DataSource
类实例的逻辑数据源名称,JNDI 命名服务将返回该DataSource
类的实例。然后可以在该DataSource
对象上调用getConnection
方法,以获取连接到其表示的数据源的连接。例如,程序员可能会编写以下两行代码来获取一个产生与数据库CUSTOMER_ACCOUNTS
连接的DataSource
对象。
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/billingDB");
第一行代码获取一个初始上下文作为检索DataSource
对象的起点。当您向lookup
方法提供逻辑名称jdbc/billingDB
时,该方法将返回系统管理员在部署时绑定到jdbc/billingDB
的DataSource
对象。因为lookup
方法的返回值是一个 Java Object
,我们必须将其转换为更具体的DataSource
类型,然后将其赋给变量*ds*
。
变量*ds*
是实现DataSource
接口的com.dbaccess.BasicDataSource
类的实例。调用*ds*.getConnection
方法将产生与CUSTOMER_ACCOUNTS
数据库的连接。
Connection con = ds.getConnection("fernanda","brewed");
getConnection
方法仅需要用户名和密码,因为变量*ds*
在其属性中具有与CUSTOMER_ACCOUNTS
数据库建立连接所需的其余信息,如数据库名称和位置。
DataSource 对象的优势
由于其属性,DataSource
对象是获取连接的比DriverManager
类更好的选择。程序员不再需要在其应用程序中硬编码驱动程序名称或 JDBC URL,这使它们更具可移植性。此外,DataSource
属性使代码维护更加简单。如果有任何更改,系统管理员可以更新数据源属性,而不必担心更改每个连接到数据源的应用程序。例如,如果数据源被移动到不同的服务器,系统管理员只需将serverName
属性设置为新服务器名称。
除了可移植性和易维护性之外,使用DataSource
对象获取连接还可以提供其他优势。当DataSource
接口被实现以与ConnectionPoolDataSource
实现一起工作时,由该DataSource
类的实例产生的所有连接将自动成为池化连接。同样,当DataSource
实现被实现以与XADataSource
类一起工作时,它产生的所有连接将自动成为可用于分布式事务的连接。下一节将展示如何部署这些类型的DataSource
实现。
部署其他 DataSource 实现
系统管理员或其他从事该职能的人可以部署一个DataSource
对象,以便它产生的连接是连接池连接。为此,他或她首先部署一个ConnectionPoolDataSource
对象,然后部署一个实现与其配合工作的DataSource
对象。设置ConnectionPoolDataSource
对象的属性,以便它表示将生成连接的数据源。在ConnectionPoolDataSource
对象已经注册到 JNDI 命名服务后,部署DataSource
对象。通常只需设置DataSource
对象的两个属性:description
和dataSourceName
。给定给dataSourceName
属性的值是先前部署的标识ConnectionPoolDataSource
对象的逻辑名称,该对象包含用于建立连接所需的属性。
使用ConnectionPoolDataSource
和DataSource
对象部署后,您可以在DataSource
对象上调用DataSource.getConnection
方法并获得一个连接池连接。此连接将连接到ConnectionPoolDataSource
对象属性中指定的数据源。
以下示例描述了如何为 The Coffee Break 的系统管理员部署一个实现提供连接池连接的DataSource
对象。系统管理员通常会使用部署工具,因此本节中显示的代码片段是部署工具将执行的代码。
为了获得更好的性能,The Coffee Break 公司从 DB Access, Inc.购买了一个 JDBC 驱动程序,其中包括实现ConnectionPoolDataSource
接口的com.dbaccess.ConnectionPoolDS
类。系统管理员创建此类的实例,设置其属性,并将其注册到 JNDI 命名服务中。The Coffee Break 从其 EJB 服务器供应商 Application Logic, Inc.购买了其DataSource
类com.applogic.PooledDataSource
。com.applogic.PooledDataSource
类通过使用ConnectionPoolDataSource
类com.dbaccess.ConnectionPoolDS
提供的底层支持来实现连接池。
必须首先部署ConnectionPoolDataSource
对象。以下代码创建了com.dbaccess.ConnectionPoolDS
的实例并设置其属性:
com.dbaccess.ConnectionPoolDS cpds = new com.dbaccess.ConnectionPoolDS();
cpds.setServerName("creamer");
cpds.setDatabaseName("COFFEEBREAK");
cpds.setPortNumber(9040);
cpds.setDescription("Connection pooling for " + "COFFEEBREAK DBMS");
在部署ConnectionPoolDataSource
对象后,系统管理员部署DataSource
对象。以下代码将com.dbaccess.ConnectionPoolDS
对象*cpds*
注册到 JNDI 命名服务中。请注意,与*cpds*
变量关联的逻辑名称在jdbc
的子上下文下添加了pool
,类似于在分层文件系统中的另一个子目录下添加子目录。com.dbaccess.ConnectionPoolDS
类的任何实例的逻辑名称始终以jdbc/pool
开头。Oracle 建议将所有ConnectionPoolDataSource
对象放在jdbc/pool
子上下文下:
Context ctx = new InitialContext();
ctx.bind("jdbc/pool/fastCoffeeDB", cpds);
接下来,实现与*cpds*
变量和com.dbaccess.ConnectionPoolDS
类的其他实例交互的DataSource
类被部署。下面的代码创建了这个类的一个实例并设置了其属性。请注意,为com.applogic.PooledDataSource
的这个实例只设置了两个属性。设置description
属性是因为它总是必需的。另一个设置的属性是dataSourceName
,为*cpds*
给出了逻辑 JNDI 名称,它是com.dbaccess.ConnectionPoolDS
类的一个实例。换句话说,*cpds*
代表了将为DataSource
对象实现连接池的ConnectionPoolDataSource
对象。
下面的代码,可能会被部署工具执行,创建了一个PooledDataSource
对象,设置了其属性,并将其绑定到逻辑名称jdbc/fastCoffeeDB
:
com.applogic.PooledDataSource ds = new com.applogic.PooledDataSource();
ds.setDescription("produces pooled connections to COFFEEBREAK");
ds.setDataSourceName("jdbc/pool/fastCoffeeDB");
Context ctx = new InitialContext();
ctx.bind("jdbc/fastCoffeeDB", ds);
此时,部署了一个DataSource
对象,应用程序可以从中获取到数据库COFFEEBREAK
的连接池连接。
获取和使用连接池连接
连接池是数据库连接对象的缓存。这些对象代表了可以被应用程序用来连接数据库的物理数据库连接。在运行时,应用程序从连接池请求连接。如果连接池包含可以满足请求的连接,则将连接返回给应用程序。如果找不到连接,则创建一个新连接并返回给应用程序。应用程序使用连接对数据库执行一些工作,然后将对象返回到连接池。连接随后可用于下一个连接请求。
连接池促进了连接对象的重复使用,减少了连接对象被创建的次数。连接池显著提高了数据库密集型应用的性能,因为创建连接对象在时间和资源方面都是昂贵的。
现在这些DataSource
和ConnectionPoolDataSource
对象已经部署,程序员可以使用DataSource
对象获取连接池连接。获取连接池连接的代码与获取非连接池连接的代码类似,如下两行所示:
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");
变量*ds*
代表一个DataSource
对象,用于向数据库COFFEEBREAK
生成连接池连接。您只需要检索一次这个DataSource
对象,因为您可以使用它生成所需数量的连接池连接。在*ds*
变量上调用getConnection
方法会自动生成一个连接池连接,因为*ds*
变量表示的DataSource
对象被配置为生成连接池连接。
连接池对程序员通常是透明的。在使用连接池连接时,只需要做两件事情:
-
使用
DataSource
对象而不是DriverManager
类来获取连接。在下面的代码行中,*ds*
是一个已实现和部署的DataSource
对象,它将创建池化连接,username
和password
是代表具有访问数据库权限的用户的凭据的变量:Connection con = ds.getConnection(username, password);
-
使用
finally
语句来关闭池化连接。下面的finally
块将出现在应用于使用池化连接的代码的try/catch
块之后:try { Connection con = ds.getConnection(username, password); // ... code to use the pooled // connection con } catch (Exception ex { // ... code to handle exceptions } finally { if (con != null) con.close(); }
否则,使用池化连接的应用程序与使用常规连接的应用程序相同。当进行连接池化时,应用程序员可能注意到的唯一其他事情是性能更好。
以下示例代码获取一个DataSource
对象,该对象生成到数据库COFFEEBREAK
的连接,并使用它来更新表COFFEES
中的价格:
import java.sql.*;
import javax.sql.*;
import javax.ejb.*;
import javax.naming.*;
public class ConnectionPoolingBean implements SessionBean {
// ...
public void ejbCreate() throws CreateException {
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");
}
public void updatePrice(float price, String cofName,
String username, String password)
throws SQLException{
Connection con;
PreparedStatement pstmt;
try {
con = ds.getConnection(username, password);
con.setAutoCommit(false);
pstmt = con.prepareStatement("UPDATE COFFEES " +
"SET PRICE = ? " +
"WHERE COF_NAME = ?");
pstmt.setFloat(1, price);
pstmt.setString(2, cofName);
pstmt.executeUpdate();
con.commit();
pstmt.close();
} finally {
if (con != null) con.close();
}
}
private DataSource ds = null;
private Context ctx = null;
}
此代码示例中的连接参与连接池化,因为以下条件为真:
-
已部署了一个实现
ConnectionPoolDataSource
的类的实例。 -
已部署了一个实现
DataSource
的类的实例,并为其dataSourceName
属性设置的值是之前部署的ConnectionPoolDataSource
对象绑定的逻辑名称。
请注意,尽管这段代码与您之前看到的代码非常相似,但在以下方面有所不同:
-
它导入了
javax.sql
、javax.ejb
和javax.naming
包,以及java.sql
。DataSource
和ConnectionPoolDataSource
接口位于javax.sql
包中,JNDI 构造函数InitialContext
和方法Context.lookup
属于javax.naming
包。这个特定的示例代码是以一个使用javax.ejb
包中的 API 的 EJB 组件的形式呈现的。这个示例的目的是展示您使用池化连接的方式与使用非池化连接的方式相同,因此您不必担心理解 EJB API。 -
它使用
DataSource
对象来获取连接,而不是使用DriverManager
工具。 -
它使用一个
finally
块来确保连接被关闭。
获取和使用池化连接与获取和使用常规连接类似。当某人作为系统管理员部署了一个ConnectionPoolDataSource
对象和一个正确的DataSource
对象时,应用程序将使用该DataSource
对象来获取池化连接。然而,应用程序应该使用一个finally
块来关闭池化连接。为简单起见,前面的示例使用了一个finally
块但没有catch
块。如果try
块中的方法抛出异常,它将默认抛出,并且finally
子句将在任何情况下执行。
部署分布式事务
可以部署DataSource
对象以获取可用于分布式事务的连接。与连接池一样,必须部署两个不同的类实例:一个XADataSource
对象和一个实现与其一起工作的DataSource
对象。
假设 The Coffee Break 企业家购买的 EJB 服务器包括com.applogic.TransactionalDS
类,该类与com.dbaccess.XATransactionalDS
等XADataSource
类一起工作。它可以与任何XADataSource
类一起工作,使 EJB 服务器在 JDBC 驱动程序之间具有可移植性。当部署DataSource
和XADataSource
对象时,生成的连接将能够参与分布式事务。在这种情况下,com.applogic.TransactionalDS
类被实现为生成的连接也是池化连接,这通常是作为 EJB 服务器实现的一部分提供的DataSource
类的情况。
必须首先部署XADataSource
对象。以下代码创建com.dbaccess.XATransactionalDS
的一个实例并设置其属性:
com.dbaccess.XATransactionalDS xads = new com.dbaccess.XATransactionalDS();
xads.setServerName("creamer");
xads.setDatabaseName("COFFEEBREAK");
xads.setPortNumber(9040);
xads.setDescription("Distributed transactions for COFFEEBREAK DBMS");
以下代码将com.dbaccess.XATransactionalDS
对象*xads*
注册到 JNDI 命名服务。请注意,与*xads*
关联的逻辑名称在jdbc
下添加了子上下文xa
。Oracle 建议com.dbaccess.XATransactionalDS
类的任何实例的逻辑名称始终以jdbc/xa
开头。
Context ctx = new InitialContext();
ctx.bind("jdbc/xa/distCoffeeDB", xads);
部署实现与*xads*
和其他XADataSource
对象交互的DataSource
对象。请注意,DataSource
类com.applogic.TransactionalDS
可以与任何 JDBC 驱动程序供应商的XADataSource
类一起使用。部署DataSource
对象涉及创建com.applogic.TransactionalDS
类的实例并设置其属性。dataSourceName
属性设置为jdbc/xa/distCoffeeDB
,这是与com.dbaccess.XATransactionalDS
关联的逻辑名称。这是实现DataSource
类的分布式事务能力的XADataSource
类。以下代码部署DataSource
类的一个实例:
com.applogic.TransactionalDS ds = new com.applogic.TransactionalDS();
ds.setDescription("Produces distributed transaction " +
"connections to COFFEEBREAK");
ds.setDataSourceName("jdbc/xa/distCoffeeDB");
Context ctx = new InitialContext();
ctx.bind("jdbc/distCoffeeDB", ds);
现在已经部署了com.applogic.TransactionalDS
和com.dbaccess.XATransactionalDS
类的实例,应用程序可以调用TransactionalDS
类的实例上的getConnection
方法,以获取连接到COFFEEBREAK
数据库的连接,该连接可用于分布式事务。
使用连接进行分布式事务
要获取可用于分布式事务的连接,必须使用已经正确实现和部署的DataSource
对象,如部署分布式事务部分所示。使用这样的DataSource
对象,调用其上的getConnection
方法。获得连接后,使用它就像使用任何其他连接一样。因为jdbc/distCoffeesDB
已经与 JNDI 命名服务中的XADataSource
对象关联,因此以下代码生成一个可用于分布式事务的Connection
对象:
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB");
Connection con = ds.getConnection();
在连接作为分布式事务的一部分时,对其使用有一些次要但重要的限制。事务管理器控制分布式事务何时开始以及何时提交或回滚;因此,应用程序代码永远不应调用Connection.commit
或Connection.rollback
方法。应用程序也不应调用Connection.setAutoCommit(true)
,这会启用自动提交模式,因为这也会干扰事务管理器对事务边界的控制。这就解释了为什么在分布式事务范围内创建的新连接默认情况下会禁用其自动提交模式。请注意,这些限制仅适用于连接参与分布式事务时;连接不参与分布式事务时没有限制。
对于以下示例,假设已经发货一份咖啡订单,这将触发对驻留在不同 DBMS 服务器上的两个表进行更新。第一个表是一个新的INVENTORY
表,第二个是COFFEES
表。因为这些表位于不同的 DBMS 服务器上,涉及它们两个的事务将是一个分布式事务。以下示例中的代码获取连接,更新COFFEES
表,并关闭连接,是分布式事务的第二部分。
注意,代码并没有显式提交或回滚更新,因为分布式事务的范围由中间层服务器的基础系统基础设施控制。此外,假设用于分布式事务的连接是一个连接池连接,应用程序使用finally
块来关闭连接。这样可以确保即使抛出异常,也会关闭有效连接,从而确保连接被返回到连接池以进行回收利用。
以下代码示例说明了一个企业 Bean,它是一个实现了可以被客户端计算机调用的方法的类。这个示例的目的是演示分布式事务的应用代码与其他代码没有任何不同,只是它不调用Connection
方法commit
、rollback
或setAutoCommit(true)
。因此,您不需要担心理解所使用的 EJB API。
import java.sql.*;
import javax.sql.*;
import javax.ejb.*;
import javax.naming.*;
public class DistributedTransactionBean implements SessionBean {
// ...
public void ejbCreate() throws CreateException {
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB");
}
public void updateTotal(int incr, String cofName, String username,
String password)
throws SQLException {
Connection con;
PreparedStatement pstmt;
try {
con = ds.getConnection(username, password);
pstmt = con.prepareStatement("UPDATE COFFEES " +
"SET TOTAL = TOTAL + ? " +
"WHERE COF_NAME = ?");
pstmt.setInt(1, incr);
pstmt.setString(2, cofName);
pstmt.executeUpdate();
stmt.close();
} finally {
if (con != null) con.close();
}
}
private DataSource ds = null;
private Context ctx = null;
}
处理 SQLException
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/sqlexception.html
本页涵盖以下主题:
-
SQLException 概述
-
检索异常
-
检索警告
-
分类的 SQLException
-
SQLException 的其他子类
SQLException 概述
当 JDBC 在与数据源交互过程中遇到错误时,会抛出一个SQLException
实例,而不是Exception
。(在这种情况下,数据源代表Connection
对象连接的数据库。)SQLException
实例包含以下信息,可以帮助您确定错误的原因:
-
错误描述。通过调用方法
SQLException.getMessage
检索包含此描述的String
对象。 -
SQLState 代码。这些代码及其相应的含义已经由 ISO/ANSI 和 Open Group(X/Open)标准化,尽管一些代码已保留供数据库供应商自行定义。这个
String
对象由五个字母数字字符组成。通过调用方法SQLException.getSQLState
检索此代码。 -
错误代码。这是一个整数值,标识导致抛出
SQLException
实例的错误。其值和含义是特定于实现的,可能是底层数据源返回的实际错误代码。通过调用方法SQLException.getErrorCode
检索错误。 -
一个原因。
SQLException
实例可能具有因果关系,由导致抛出SQLException
实例的一个或多个Throwable
对象组成。要浏览这些原因链,递归调用方法SQLException.getCause
,直到返回一个null
值。 -
任何链接异常的引用。如果发生多个错误,这些异常通过此链引用。通过在抛出异常上调用方法
SQLException.getNextException
检索这些异常。
检索异常
下面的方法,JDBCTutorialUtilities.printSQLException
,输出了SQLException
中包含的 SQLState、错误代码、错误描述以及原因(如果有的话),以及与之链接的任何其他异常:
public static void printSQLException(SQLException ex) {
for (Throwable e : ex) {
if (e instanceof SQLException) {
if (ignoreSQLException(
((SQLException)e).
getSQLState()) == false) {
e.printStackTrace(System.err);
System.err.println("SQLState: " +
((SQLException)e).getSQLState());
System.err.println("Error Code: " +
((SQLException)e).getErrorCode());
System.err.println("Message: " + e.getMessage());
Throwable t = ex.getCause();
while(t != null) {
System.out.println("Cause: " + t);
t = t.getCause();
}
}
}
}
}
例如,如果您使用 Java DB 调用方法CoffeesTable.dropTable
,表COFFEES
不存在,并且您删除对JDBCTutorialUtilities.ignoreSQLException
的调用,输出将类似于以下内容:
SQLState: 42Y55
Error Code: 30000
Message: 'DROP TABLE' cannot be performed on
'TESTDB.COFFEES' because it does not exist.
您可以先检索 SQLState
,然后相应地处理 SQLException
,而不是打印 SQLException
信息。例如,如果 SQLState
等于代码 42Y55
(并且您正在使用 Java DB 作为您的 DBMS),则方法 JDBCTutorialUtilities.ignoreSQLException
返回 true
,导致 JDBCTutorialUtilities.printSQLException
忽略 SQLException
:
public static boolean ignoreSQLException(String sqlState) {
if (sqlState == null) {
System.out.println("The SQL state is not defined!");
return false;
}
// X0Y32: Jar file already exists in schema
if (sqlState.equalsIgnoreCase("X0Y32"))
return true;
// 42Y55: Table already exists in schema
if (sqlState.equalsIgnoreCase("42Y55"))
return true;
return false;
}
检索警告
SQLWarning
对象是处理数据库访问警告的 SQLException
子类。警告不会像异常那样停止应用程序的执行;它们只是提醒用户某些事情未按计划发生。例如,警告可能会告诉您尝试撤销的权限未被撤销。或者警告可能会告诉您在请求的断开连接期间发生了错误。
可以在 Connection
对象、Statement
对象(包括 PreparedStatement
和 CallableStatement
对象)或 ResultSet
对象上报告警告。这些类中的每一个都有一个 getWarnings
方法,您必须调用它以查看调用对象上报的第一个警告。如果 getWarnings
返回一个警告,您可以调用它的 getNextWarning
方法来获取任何额外的警告。执行语句会自动清除先前语句的警告,因此它们不会累积。然而,这意味着如果您想要检索在语句上报告的警告,您必须在执行另一个语句之前这样做。
JDBCTutorialUtilities.java
中的以下方法演示了如何获取关于 Statement
或 ResultSet
对象上报的任何警告的完整信息:
public static void getWarningsFromResultSet(ResultSet rs)
throws SQLException {
JDBCTutorialUtilities.printWarnings(rs.getWarnings());
}
public static void getWarningsFromStatement(Statement stmt)
throws SQLException {
JDBCTutorialUtilities.printWarnings(stmt.getWarnings());
}
public static void printWarnings(SQLWarning warning)
throws SQLException {
if (warning != null) {
System.out.println("\n---Warning---\n");
while (warning != null) {
System.out.println("Message: " + warning.getMessage());
System.out.println("SQLState: " + warning.getSQLState());
System.out.print("Vendor error code: ");
System.out.println(warning.getErrorCode());
System.out.println("");
warning = warning.getNextWarning();
}
}
最常见的警告是 DataTruncation
警告,是 SQLWarning
的子类。所有 DataTruncation
对象的 SQLState 都是 01004
,表示读取或写入数据时出现问题。DataTruncation
方法让您找出数据在哪一列或参数被截断,截断是在读取还是写入操作中发生的,应该传输多少字节,实际传输了多少字节。
分类的 SQLExceptions
您的 JDBC 驱动程序可能会抛出与常见 SQLState 或与特定 SQLState 类值不相关的常见错误状态对应的 SQLException
子类。这使您能够编写更具可移植性的错误处理代码。这些异常是以下类的子类之一:
-
SQLNonTransientException
-
SQLTransientException
-
SQLRecoverableException
查看 java.sql
包的最新 Javadoc 或您的 JDBC 驱动程序的文档,以获取有关这些子类的更多信息。
其他 SQLException 的子类
以下 SQLException
的子类也可能被抛出:
-
BatchUpdateException
在批量更新操作期间发生错误时抛出。除了SQLException
提供的信息外,BatchUpdateException
还提供了在错误发生之前执行的所有语句的更新计数。 -
SQLClientInfoException
在连接上无法设置一个或多个客户端信息属性时抛出。除了SQLException
提供的信息外,SQLClientInfoException
还提供了未设置的客户端信息属性列表。
设置表
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/tables.html
本页描述了 JDBC 教程中使用的所有表以及如何创建它们:
-
咖啡表
-
供应商表
-
咖啡库存表
-
商品库存表
-
咖啡店表
-
数据存储库表
-
创建表
-
填充表
咖啡表
COFFEES
表存储了在 The Coffee Break 可供销售的咖啡信息:
COF_NAME | SUP_ID | PRICE | SALES | TOTAL |
---|---|---|---|---|
哥伦比亚 | 101 | 7.99 | 0 | 0 |
法式烘焙 | 49 | 8.99 | 0 | 0 |
浓缩咖啡 | 150 | 9.99 | 0 | 0 |
哥伦比亚无咖啡因 | 101 | 8.99 | 0 | 0 |
法式烘焙无咖啡因 | 49 | 9.99 | 0 | 0 |
以下描述了COFFEES
表中的每列:
-
COF_NAME
: 存储咖啡名称。具有 SQL 类型为VARCHAR
,最大长度为 32 个字符。由于每种咖啡的名称都不同,因此名称唯一标识特定的咖啡,并作为主键。 -
SUP_ID
: 存储标识咖啡供应商的数字。具有 SQL 类型为INTEGER
。它被定义为引用SUPPLIERS
表中SUP_ID
列的外键。因此,DBMS 将强制执行此列中的每个值与SUPPLIERS
表中相应列中的值之一匹配。 -
PRICE
: 存储每磅咖啡的成本。具有 SQL 类型为FLOAT
,因为它需要存储带有小数点的值。(请注意,货币值通常存储在 SQL 类型DECIMAL
或NUMERIC
中,但由于不同的 DBMS 之间存在差异,并且为了避免与 JDBC 的早期版本不兼容,本教程使用更标准的类型FLOAT
。) -
SALES
: 存储本周销售的咖啡磅数。具有 SQL 类型为INTEGER
。 -
TOTAL
: 存储迄今为止销售的咖啡磅数。具有 SQL 类型为INTEGER
。
供应商表
SUPPLIERS
表存储了每个供应商的信息:
SUP_ID | SUP_NAME | STREET | CITY | STATE | ZIP |
---|---|---|---|---|---|
101 | Acme, Inc. | 99 Market Street | Groundsville | CA | 95199 |
49 | 优质咖啡 | 1 Party Place | Mendocino | CA | 95460 |
150 | 高地咖啡 | 100 Coffee Lane | Meadows | CA | 93966 |
以下描述了SUPPLIERS
表中的每列:
-
SUP_ID
: 存储标识咖啡供应商的数字。具有 SQL 类型为INTEGER
。这是此表中的主键。 -
SUP_NAME
: 存储咖啡供应商的名称。 -
STREET
、CITY
、STATE
和ZIP
:这些列存储咖啡供应商的地址。
咖啡库存表
COF_INVENTORY
表存储了每个仓库中咖啡的数量信息:
WAREHOUSE_ID | COF_NAME | SUP_ID | QUAN | DATE_VAL |
---|---|---|---|---|
1234 | House_Blend | 49 | 0 | 2006_04_01 |
1234 | House_Blend_Decaf | 49 | 0 | 2006_04_01 |
1234 | 哥伦比亚 | 101 | 0 | 2006_04_01 |
1234 | 法式烘焙 | 49 | 0 | 2006_04_01 |
1234 | 浓缩咖啡 | 150 | 0 | 2006_04_01 |
1234 | Colombian_Decaf | 101 | 0 | 2006_04_01 |
以下描述了COF_INVENTORY
表中每一列的含义:
-
WAREHOUSE_ID
: 存储标识仓库的数字。 -
COF_NAME
: 存储特定类型咖啡的名称。 -
SUP_ID
: 存储标识供应商的数字。 -
QUAN
: 存储表示商品数量的数字。 -
DATE
: 存储时间戳数值,表示行最后更新时间。
MERCH_INVENTORY
表
MERCH_INVENTORY
表存储有关库存中非咖啡商品数量的信息:
ITEM_ID | ITEM_NAME | SUP_ID | QUAN | DATE |
---|---|---|---|---|
00001234 | 大杯 | 00456 | 28 | 2006_04_01 |
00001235 | 小杯 | 00456 | 36 | 2006_04_01 |
00001236 | 碟子 | 00456 | 64 | 2006_04_01 |
00001287 | 咖啡壶 | 00456 | 12 | 2006_04_01 |
00006931 | 咖啡壶 | 00927 | 3 | 2006_04_01 |
00006935 | 隔热垫 | 00927 | 88 | 2006_04_01 |
00006977 | 餐巾纸 | 00927 | 108 | 2006_04_01 |
00006979 | 毛巾 | 00927 | 24 | 2006_04_01 |
00004488 | 咖啡机 | 08732 | 5 | 2006_04_01 |
00004490 | 咖啡研磨机 | 08732 | 9 | 2006_04_01 |
00004495 | 咖啡机 | 08732 | 4 | 2006_04_01 |
00006914 | 食谱书 | 00927 | 12 | 2006_04_01 |
以下描述了MERCH_INVENTORY
表中每一列的含义:
-
ITEM_ID
: 存储标识物品的数字。 -
ITEM_NAME
: 存储物品的名称。 -
SUP_ID
: 存储标识供应商的数字。 -
QUAN
: 存储表示该物品可用数量的数字。 -
DATE
: 存储时间戳数值,表示行最后更新时间。
COFFEE_HOUSES
表
COFFEE_HOUSES
表存储咖啡店的位置信息:
STORE_ID | CITY | COFFEE | MERCH | TOTAL |
---|---|---|---|---|
10023 | 门多西诺 | 3450 | 2005 | 5455 |
33002 | 西雅图 | 4699 | 3109 | 7808 |
10040 | 旧金山 | 5386 | 2841 | 8227 |
32001 | 波特兰 | 3147 | 3579 | 6726 |
10042 | 旧金山 | 2863 | 1874 | 4710 |
10024 | 萨克拉门托 | 1987 | 2341 | 4328 |
10039 | 卡梅尔 | 2691 | 1121 | 3812 |
10041 | 洛杉矶 | 1533 | 1007 | 2540 |
33005 | 奥林匹亚 | 2733 | 1550 | 4283 |
33010 | 西雅图 | 3210 | 2177 | 5387 |
10035 | 旧金山 | 1922 | 1056 | 2978 |
10037 | 洛杉矶 | 2143 | 1876 | 4019 |
10034 | 圣何塞 | 1234 | 1032 | 2266 |
32004 | Eugene | 1356 | 1112 | 2468 |
以下描述了COFFEE_HOUSES
表中每一列的含义:
-
STORE_ID
: 存储标识咖啡店的数字。它表示咖啡店所在州,以 10 开头的值表示加利福尼亚州,以 32 开头的值表示俄勒冈州,以 33 开头的值表示华盛顿州。 -
CITY
: 存储咖啡馆所在城市的名称。 -
COFFEE
: 存储表示售出咖啡量的数字。 -
MERCH
: 存储表示售出商品量的数字。 -
TOTAL
: 存储表示售出咖啡和商品总量的数字。
DATA_REPOSITORY 表
表 DATA_REPOSITORY 存储引用文档和其他与 The Coffee Break 有关的数据的 URL。脚本 populate_tables.sql
不向此表添加任何数据。以下描述了此表中每列的内容:
-
DOCUMENT_NAME
: 存储标识 URL 的字符串。 -
URL
: 存储一个 URL。
创建表
您可以使用 Apache Ant 或 JDBC API 创建表。
使用 Apache Ant 创建表
要创建教程示例代码中使用的表,请在目录 *<JDBC tutorial directory>*
中运行以下命令:
ant setup
此命令运行多个 Ant 目标,包括以下内容,build-tables
(来自 build.xml
文件):
<target name="build-tables"
description="Create database tables">
<sql
driver="${DB.DRIVER}"
url="${DB.URL}"
userid="${DB.USER}"
password="${DB.PASSWORD}"
classpathref="CLASSPATH"
delimiter="${DB.DELIMITER}"
autocommit="false" onerror="abort">
<transaction src=
"./sql/${DB.VENDOR}/create-tables.sql"/>
</sql>
</target>
示例为以下 sql
Ant 任务参数指定值:
Parameter | 描述 |
---|---|
driver | 您的 JDBC 驱动程序的完全限定类名。此示例中,Java DB 使用 org.apache.derby.jdbc.EmbeddedDriver ,MySQL Connector/J 使用 com.mysql.cj.jdbc.Driver 。 |
url | 数据库连接 URL,您的 DBMS JDBC 驱动程序用于连接到数据库的 URL。 |
userid | 您的 DBMS 中有效用户的名称。 |
password | userid 中指定用户的密码 |
classpathref | 包含 driver 中指定的类的 JAR 文件的完整路径名 |
delimiter | 分隔 SQL 语句的字符串或字符。此示例使用分号 (; )。 |
autocommit | 布尔值;如果设置为 false ,则所有 SQL 语句将作为一个事务执行。 |
onerror | 当语句失败时执行的操作;可能的值为 continue 、stop 和 abort 。值 abort 指定如果发生错误,则事务将被中止。 |
示例将这些参数的值存储在单独的文件中。构建文件 build.xml
使用 import
任务检索这些值:
<import file="${ANTPROPERTIES}"/>
transaction
元素指定一个包含要执行的 SQL 语句的文件。文件 create-tables.sql
包含创建本页描述的所有表的 SQL 语句。例如,此文件的以下摘录创建表 SUPPLIERS
和 COFFEES
:
create table SUPPLIERS
(SUP_ID integer NOT NULL,
SUP_NAME varchar(40) NOT NULL,
STREET varchar(40) NOT NULL,
CITY varchar(20) NOT NULL,
STATE char(2) NOT NULL,
ZIP char(5),
PRIMARY KEY (SUP_ID));
create table COFFEES
(COF_NAME varchar(32) NOT NULL,
SUP_ID int NOT NULL,
PRICE numeric(10,2) NOT NULL,
SALES integer NOT NULL,
TOTAL integer NOT NULL,
PRIMARY KEY (COF_NAME),
FOREIGN KEY (SUP_ID)
REFERENCES SUPPLIERS (SUP_ID));
注意:文件 build.xml
包含另一个名为 drop-tables
的目标,用于删除教程中使用的表。setup
目标在运行 build-tables
目标之前运行 drop-tables
。
使用 JDBC API 创建表
以下方法,SuppliersTable.createTable
,创建 SUPPLIERS
表:
public void createTable() throws SQLException {
String createString =
"create table SUPPLIERS " + "(SUP_ID integer NOT NULL, " +
"SUP_NAME varchar(40) NOT NULL, " + "STREET varchar(40) NOT NULL, " +
"CITY varchar(20) NOT NULL, " + "STATE char(2) NOT NULL, " +
"ZIP char(5), " + "PRIMARY KEY (SUP_ID))";
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate(createString);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
以下方法,CoffeesTable.createTable
,创建 COFFEES
表:
public void createTable() throws SQLException {
String createString =
"create table COFFEES " + "(COF_NAME varchar(32) NOT NULL, " +
"SUP_ID int NOT NULL, " + "PRICE numeric(10,2) NOT NULL, " +
"SALES integer NOT NULL, " + "TOTAL integer NOT NULL, " +
"PRIMARY KEY (COF_NAME), " +
"FOREIGN KEY (SUP_ID) REFERENCES SUPPLIERS (SUP_ID))";
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate(createString);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
在这两种方法中,con
是一个Connection
对象,dbName
是你正在创建表的数据库的名称。
要执行 SQL 查询,比如由String
createString
指定的查询,使用一个Statement
对象。要创建一个Statement
对象,从现有的Connection
对象调用方法Connection.createStatement
。要执行 SQL 查询,调用方法Statement.executeUpdate
。
所有的Statement
对象在创建它们的连接关闭时都会被关闭。然而,明确关闭Statement
对象是良好的编程实践,一旦你完成了它们的使用就立即关闭。这样可以立即释放语句正在使用的任何外部资源。通过调用方法Statement.close
来关闭一个语句。将这个语句放在finally
中以确保即使正常程序流被中断,比如抛出异常(比如SQLException
),它也会关闭。
注意:在COFFEES
之前必须先创建SUPPLIERS
表,因为COFFEES
包含一个外键SUP_ID
,它引用SUPPLIERS
。
填充表
同样,你可以使用 Apache Ant 或 JDBC API 向表中插入数据。
使用 Apache Ant 填充表
除了创建本教程使用的表之外,命令ant setup
还会填充这些表。这个命令运行 Ant 目标populate-tables
,该目标运行 SQL 脚本populate-tables.sql
。
下面是从populate-tables.sql
中填充SUPPLIERS
和COFFEES
表的摘录:
insert into SUPPLIERS values(
49, 'Superior Coffee', '1 Party Place',
'Mendocino', 'CA', '95460');
insert into SUPPLIERS values(
101, 'Acme, Inc.', '99 Market Street',
'Groundsville', 'CA', '95199');
insert into SUPPLIERS values(
150, 'The High Ground',
'100 Coffee Lane', 'Meadows', 'CA', '93966');
insert into COFFEES values(
'Colombian', 00101, 7.99, 0, 0);
insert into COFFEES values(
'French_Roast', 00049, 8.99, 0, 0);
insert into COFFEES values(
'Espresso', 00150, 9.99, 0, 0);
insert into COFFEES values(
'Colombian_Decaf', 00101, 8.99, 0, 0);
insert into COFFEES values(
'French_Roast_Decaf', 00049, 9.99, 0, 0);
使用 JDBC API 填充表
下面的方法,SuppliersTable.populateTable
,将数据插入表中:
public void populateTable() throws SQLException {
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate("insert into SUPPLIERS " +
"values(49, 'Superior Coffee', '1 Party Place', " +
"'Mendocino', 'CA', '95460')");
stmt.executeUpdate("insert into SUPPLIERS " +
"values(101, 'Acme, Inc.', '99 Market Street', " +
"'Groundsville', 'CA', '95199')");
stmt.executeUpdate("insert into SUPPLIERS " +
"values(150, 'The High Ground', '100 Coffee Lane', " +
"'Meadows', 'CA', '93966')");
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
下面的方法,CoffeesTable.populateTable
,将数据插入表中:
public void populateTable() throws SQLException {
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate("insert into COFFEES " +
"values('Colombian', 00101, 7.99, 0, 0)");
stmt.executeUpdate("insert into COFFEES " +
"values('French_Roast', 00049, 8.99, 0, 0)");
stmt.executeUpdate("insert into COFFEES " +
"values('Espresso', 00150, 9.99, 0, 0)");
stmt.executeUpdate("insert into COFFEES " +
"values('Colombian_Decaf', 00101, 8.99, 0, 0)");
stmt.executeUpdate("insert into COFFEES " +
"values('French_Roast_Decaf', 00049, 9.99, 0, 0)");
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}