Java 中文官方教程 2022 版(三十六)
使用高级数据类型
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/sqltypes.html
本节介绍的高级数据类型使关系数据库在表列值方面更加灵活。例如,列可以用于存储 BLOB(二进制大对象)值,可以以原始字节形式存储非常大量的数据。列也可以是 CLOB(字符大对象)类型,能够以字符格式存储非常大量的数据。
ANSI/ISO SQL 标准的最新版本通常被称为 SQL:2003。该标准指定了以下数据类型:
-
SQL92 内置类型,包括熟悉的 SQL 列类型,如
CHAR、FLOAT和DATE -
SQL99 内置类型,包括 SQL99 添加的类型:
-
BOOLEAN: 布尔(真或假)值 -
BLOB: 二进制大对象 -
CLOB: 字符大对象
-
-
SQL:2003 添加的新内置类型:
XML: XML 对象
-
用户定义类型:
-
结构化类型: 用户定义类型; 例如:
CREATE TYPE PLANE_POINT AS (X FLOAT, Y FLOAT) NOT FINAL -
DISTINCT类型: 基于内置类型的用户定义类型; 例如:CREATE TYPE MONEY AS NUMERIC(10,2) FINAL
-
-
构造类型: 基于给定基本类型的新类型:
-
REF(*structured-type*): 持久地指示驻留在数据库中的结构化类型实例的指针 -
*base-type* ARRAY[*n*]: n 个基本类型元素的数组
-
-
定位器: 逻辑指针,指向驻留在数据库服务器上的数据。定位器 存在于客户端计算机上,是对服务器上数据的瞬时、逻辑指针。定位器通常指向无法在客户端上具体化的数据,如图像或音频。(具体化视图 是事先存储或“具体化”为模式对象的查询结果。) 在 SQL 级别定义了操作符,用于检索由定位器指示的数据的随机访问部分:
-
LOCATOR(*structured-type*): 服务器中结构化实例的定位器 -
LOCATOR(*array*): 服务器中数组的定位器 -
LOCATOR(*blob*): 服务器中二进制大对象的定位器 -
LOCATOR(*clob*): 服务器中字符大对象的定位器
-
-
Datalink: 用于管理数据源外部数据的类型。Datalink值是 SQL MED(管理外部数据)的一部分,是 SQL ANSI/ISO 标准规范的一部分。
映射高级数据类型
JDBC API 为 SQL:2003 标准指定的高级数据类型提供了默认映射。以下列表列出了数据类型及其映射到的接口或类:
-
BLOB:Blob接口 -
CLOB:Clob接口 -
NCLOB:NClob接口 -
ARRAY:Array接口 -
XML:SQLXML接口 -
结构化类型:
Struct接口 -
REF(structured type):Ref接口 -
ROWID:RowId接口 -
DISTINCT: 基础类型映射的类型。例如,基于 SQLNUMERIC类型的DISTINCT值映射到java.math.BigDecimal类型,因为在 Java 编程语言中,NUMERIC映射到BigDecimal。 -
DATALINK:java.net.URL对象
使用高级数据类型
检索、存储和更新高级数据类型的方式与处理其他数据类型的方式相同。您可以使用 ResultSet.get*DataType* 或 CallableStatement.get*DataType* 方法来检索它们,PreparedStatement.set*DataType* 方法来存储它们,以及 ResultSet.update*DataType* 方法来更新它们。(变量 *DataType* 是映射到高级数据类型的 Java 接口或类的名称。)大概有 90% 的高级数据类型操作涉及使用 get*DataType*、set*DataType* 和 update*DataType* 方法。以下表格显示了要使用哪些方法:
| 高级数据类型 | get*DataType* 方法 |
set*DataType* 方法 |
update*DataType* 方法 |
|---|---|---|---|
BLOB |
getBlob |
setBlob |
updateBlob |
CLOB |
getClob |
setClob |
updateClob |
NCLOB |
getNClob |
setNClob |
updateNClob |
ARRAY |
getArray |
setArray |
updateArray |
XML |
getSQLXML |
setSQLXML |
updateSQLXML |
Structured type |
getObject |
setObject |
updateObject |
REF(structured type) |
getRef |
setRef |
updateRef |
ROWID |
getRowId |
setRowId |
updateRowId |
DISTINCT |
getBigDecimal |
setBigDecimal |
updateBigDecimal |
DATALINK |
getURL |
setURL |
updateURL |
注意:DISTINCT 数据类型与其他高级 SQL 数据类型的行为不同。作为一个基于已存在内置类型的用户定义类型,它在 Java 编程语言中没有接口。因此,您需要使用与 DISTINCT 数据类型基础的 Java 类型对应的方法。请参阅使用 DISTINCT 数据类型获取更多信息。
例如,以下代码片段检索了一个 SQL ARRAY 值。在此示例中,假设表 STUDENTS 中的列 SCORES 包含 ARRAY 类型的值。变量 *stmt* 是一个 Statement 对象。
ResultSet rs = stmt.executeQuery(
"SELECT SCORES FROM STUDENTS " +
"WHERE ID = 002238");
rs.next();
Array scores = rs.getArray("SCORES");
变量 *scores* 是指向存储在表 STUDENTS 中学生 002238 行中的 SQL ARRAY 对象的逻辑指针。
如果要将值存储在数据库中,可以使用相应的 set 方法。例如,以下代码片段中,*rs* 是一个 ResultSet 对象,存储了一个 Clob 对象:
Clob notes = rs.getClob("NOTES");
PreparedStatement pstmt =
con.prepareStatement(
"UPDATE MARKETS SET COMMENTS = ? " +
"WHERE SALES < 1000000");
pstmt.setClob(1, notes);
pstmt.executeUpdate();
此代码将 *notes* 设置为发送到数据库的更新语句的第一个参数。由 *notes* 指定的 Clob 值将存储在表 MARKETS 的 COMMENTS 列中,在该列中的值小于一百万的每一行中。
使用大对象
Blob、Clob和NClob Java 对象的一个重要特性是,您可以在不将所有数据从数据库服务器传输到客户端计算机的情况下对它们进行操作。一些实现使用定位器(逻辑指针)来表示这些类型的实例,指向实例所代表的数据库中的对象。由于BLOB、CLOB或NCLOB SQL 对象可能非常大,使用定位器可以显著提高性能。但是,其他实现会在客户端计算机上完全实现大对象。
如果要将BLOB、CLOB或NCLOB SQL 值的数据传输到客户端计算机,请使用为此目的提供的Blob、Clob和NClob Java 接口中的方法。这些大对象类型对象将它们所代表的对象的数据实现为流。
以下主题涵盖:
-
向数据库添加大对象类型对象
-
检索 CLOB 值
-
添加和检索 BLOB 对象
-
释放大对象占用的资源
向数据库添加大对象类型对象
以下摘录自ClobSample.addRowToCoffeeDescriptions向COFFEE_DESCRIPTIONS表中添加CLOB SQL 值。Clob Java 对象myClob包含由fileName指定的文件的内容。
public void addRowToCoffeeDescriptions(String coffeeName,
String fileName) throws SQLException {
String sql = "INSERT INTO COFFEE_DESCRIPTIONS VALUES(?,?)";
Clob myClob = this.con.createClob();
try (PreparedStatement pstmt = this.con.prepareStatement(sql);
Writer clobWriter = myClob.setCharacterStream(1);){
String str = this.readFile(fileName, clobWriter);
System.out.println("Wrote the following: " + clobWriter.toString());
if (this.settings.dbms.equals("mysql")) {
System.out.println("MySQL, setting String in Clob object with setString method");
myClob.setString(1, str);
}
System.out.println("Length of Clob: " + myClob.length());
pstmt.setString(1, coffeeName);
pstmt.setClob(2, myClob);
pstmt.executeUpdate();
} catch (SQLException sqlex) {
JDBCTutorialUtilities.printSQLException(sqlex);
} catch (Exception ex) {
System.out.println("Unexpected exception: " + ex.toString());
}
}
下一行创建一个Clob Java 对象:
Clob myClob = this.con.createClob();
下一行检索一个流(在本例中为名为clobWriter的Writer对象),用于将一系列字符写入Clob Java 对象myClob。ClobSample.readFile方法写入这些字符流;流来自由String fileName指定的文件。方法参数1表示Writer对象将从Clob值的开头开始写入字符流:
Writer clobWriter = myClob.setCharacterStream(1);
ClobSample.readFile方法逐行读取由文件fileName指定的文件,并将其写入由writerArg指定的Writer对象:
private String readFile(String fileName, Writer writerArg) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String nextLine = "";
StringBuffer sb = new StringBuffer();
while ((nextLine = br.readLine()) != null) {
System.out.println("Writing: " + nextLine);
writerArg.write(nextLine);
sb.append(nextLine);
}
// Convert the content into to a string
String clobData = sb.toString();
// Return the data.
return clobData;
}
}
以下摘录创建一个PreparedStatement对象pstmt,将Clob Java 对象myClob插入COFFEE_DESCRIPTIONS中:
String sql = "INSERT INTO COFFEE_DESCRIPTIONS VALUES(?,?)";
Clob myClob = this.con.createClob();
try (PreparedStatement pstmt = this.con.prepareStatement(sql);
// ...
) {
// ...
pstmt.setString(1, coffeeName);
pstmt.setClob(2, myClob);
pstmt.executeUpdate();
// ...
检索 CLOB 值
方法ClobSample.retrieveExcerpt从COFFEE_DESCRIPTIONS表中COF_NAME列的值等于coffeeName参数指定的String值的行中检索存储在COF_DESC列中的CLOB SQL 值:
public String retrieveExcerpt(String coffeeName,
int numChar) throws SQLException {
String description = null;
Clob myClob = null;
String sql = "select COF_DESC from COFFEE_DESCRIPTIONS where COF_NAME = ?";
try (PreparedStatement pstmt = this.con.prepareStatement(sql)) {
pstmt.setString(1, coffeeName);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
myClob = rs.getClob(1);
System.out.println("Length of retrieved Clob: " + myClob.length());
}
description = myClob.getSubString(1, numChar);
} catch (SQLException sqlex) {
JDBCTutorialUtilities.printSQLException(sqlex);
} catch (Exception ex) {
System.out.println("Unexpected exception: " + ex.toString());
}
return description;
}
下一行从ResultSet对象rs中检索Clob Java 值:
myClob = rs.getClob(1);
以下行从myClob对象中检索子字符串。子字符串从myClob值的第一个字符开始,最多有numChar指定的连续字符数,其中numChar是一个整数。
description = myClob.getSubString(1, numChar);
添加和检索 BLOB 对象
添加和检索BLOB SQL 对象类似于添加和检索CLOB SQL 对象。使用Blob.setBinaryStream方法检索一个OutputStream对象,以写入调用该方法的Blob Java 对象(表示BLOB SQL 值)的BLOB SQL 值。
释放大对象所持有的资源
Blob、Clob和NClob Java 对象在它们被创建的事务持续时间内至少保持有效。这可能导致应用程序在长时间运行的事务中耗尽资源。应用程序可以通过调用它们的free方法来释放Blob、Clob和NClob资源。
在以下摘录中,调用方法Clob.free来释放先前创建的Clob对象所持有的资源:
Clob aClob = con.createClob();
int numWritten = aClob.setString(1, val);
aClob.free();
使用 SQLXML 对象
Connection 接口支持使用 createSQLXML 方法创建 SQLXML 对象。创建的对象不包含任何数据。可以通过在 SQLXML 接口上调用 setString、setBinaryStream、setCharacterStream 或 setResult 方法向对象添加数据。
下面涵盖了以下主题:
-
创建 SQLXML 对象
-
在 ResultSet 中检索 SQLXML 值
-
访问 SQLXML 对象数据
-
存储 SQLXML 对象
-
初始化 SQLXML 对象
-
释放 SQLXML 资源
-
示例代码
创建 SQLXML 对象
在以下摘录中,使用 Connection.createSQLXML 方法创建一个空的 SQLXML 对象。使用 SQLXML.setString 方法将数据写入创建的 SQLXML 对象。
Connection con = DriverManager.getConnection(url, props);
SQLXML xmlVal = con.createSQLXML();
xmlVal.setString(val);
在 ResultSet 中检索 SQLXML 值
SQLXML 数据类型类似于更基本的内置类型。可以通过在 ResultSet 或 CallableStatement 接口中调用 getSQLXML 方法来检索 SQLXML 值。
例如,以下摘录从 ResultSet rs 的第一列检索一个 SQLXML 值:
SQLXML xmlVar = rs.getSQLXML(1);
SQLXML 对象在创建它们的事务持续时间内至少保持有效,除非调用它们的 free 方法。
访问 SQLXML 对象数据
SQLXML 接口提供了 getString、getBinaryStream、getCharacterStream 和 getSource 方法来访问其内部内容。以下摘录使用 getString 方法检索 SQLXML 对象的内容:
SQLXML xmlVal= rs.getSQLXML(1);
String val = xmlVal.getString();
可以使用 getBinaryStream 或 getCharacterStream 方法获取可直接传递给 XML 解析器的 InputStream 或 Reader 对象。以下摘录从 SQLXML 对象获取一个 InputStream 对象,然后使用 DOM(文档对象模型)解析器处理流:
SQLXML sqlxml = rs.getSQLXML(column);
InputStream binaryStream = sqlxml.getBinaryStream();
DocumentBuilder parser =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document result = parser.parse(binaryStream);
getSource 方法返回一个 javax.xml.transform.Source 对象。源用作 XML 解析器和 XSLT 转换器的输入。
以下摘录使用通过调用 getSource 方法返回的 SAXSource 对象从 SQLXML 对象中检索和解析数据:
SQLXML xmlVal= rs.getSQLXML(1);
SAXSource saxSource = sqlxml.getSource(SAXSource.class);
XMLReader xmlReader = saxSource.getXMLReader();
xmlReader.setContentHandler(myHandler);
xmlReader.parse(saxSource.getInputSource());
存储 SQLXML 对象
SQLXML 对象可以像其他数据类型一样作为输入参数传递给 PreparedStatement 对象。setSQLXML 方法使用 SQLXML 对象设置指定的 PreparedStatement 参数。
在以下摘录中,authorData 是一个 java.sql.SQLXML 接口的实例,其数据先前已初始化。
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO bio " +
"(xmlData, authId) VALUES (?, ?)");
pstmt.setSQLXML(1, authorData);
pstmt.setInt(2, authorId);
updateSQLXML 方法可用于更新可更新结果集中的列值。
如果在调用setSQLXML或updateSQLXML之前未关闭SQLXML对象的java.xml.transform.Result、Writer或OutputStream对象,将抛出SQLException。
初始化 SQLXML 对象
SQLXML接口提供了setString、setBinaryStream、setCharacterStream或setResult方法来初始化通过调用Connection.createSQLXML方法创建的SQLXML对象的内容。
以下摘录使用setResult方法返回一个SAXResult对象以填充一个新创建的SQLXML对象:
SQLXML sqlxml = con.createSQLXML();
SAXResult saxResult = sqlxml.setResult(SAXResult.class);
ContentHandler contentHandler = saxResult.getXMLReader().getContentHandler();
contentHandler.startDocument();
// set the XML elements and
// attributes into the result
contentHandler.endDocument();
以下摘录使用setCharacterStream方法获取一个java.io.Writer对象以初始化一个SQLXML对象:
SQLXML sqlxml = con.createSQLXML();
Writer out= sqlxml.setCharacterStream();
BufferedReader in = new BufferedReader(new FileReader("xml/foo.xml"));
String line = null;
while((line = in.readLine() != null) {
out.write(line);
}
同样地,SQLXML的setString方法可用于初始化一个SQLXML对象。
如果尝试在先前初始化过的SQLXML对象上调用setString、setBinaryStream、setCharacterStream和setResult方法,将抛出SQLException。如果对同一SQLXML对象多次调用setBinaryStream、setCharacterStream和setResult方法,则会抛出SQLException,并且先前返回的javax.xml.transform.Result、Writer或OutputStream对象不受影响。
释放 SQLXML 资源
SQLXML对象在创建它们的事务持续时间内至少保持有效。这可能导致应用程序在长时间运行的事务中耗尽资源。应用程序可以通过调用它们的free方法释放SQLXML资源。
在以下摘录中,调用method SQLXML.free来释放先前创建的SQLXML对象所持有的资源。
SQLXML xmlVar = con.createSQLXML();
xmlVar.setString(val);
xmlVar.free();
示例代码
MySQL 和 Java DB 及其各自的 JDBC 驱动程序并不完全支持本节中描述的SQLXML JDBC 数据类型。然而,示例RSSFeedsTable.java演示了如何处理 MySQL 和 Java DB 中的 XML 数据。
The Coffee Break 的所有者关注来自各种网站的几个 RSS 订阅源,涵盖餐厅和饮料行业新闻。RSS(真正简单的聚合或富站点摘要)订阅源是一个包含一系列文章和相关元数据的 XML 文档,如每篇文章的发布日期和作者。所有者希望将这些 RSS 订阅源存储到数据库表中,包括 The Coffee Break 博客的 RSS 订阅源。
文件rss-the-coffee-break-blog.xml是 The Coffee Break 博客的一个示例 RSS 订阅源。文件rss-coffee-industry-news.xml是(虚构的)Coffee Industry News 的一个示例 RSS 订阅源。
在 MySQL 中处理 XML 数据
示例RSSFeedsTable将 RSS 订阅源存储在名为RSS_FEEDS的表中,该表是使用以下命令创建的:
create table RSS_FEEDS
(RSS_NAME varchar(32) NOT NULL,
RSS_FEED_XML longtext NOT NULL,
PRIMARY KEY (RSS_NAME));
MySQL 不支持 XML 数据类型。相反,此示例将 XML 数据存储在类型为 LONGTEXT 的列中,这是一种 CLOB SQL 数据类型。MySQL 有四种 CLOB 数据类型;LONGTEXT 数据类型在这四种类型中包含的字符数量最多。
方法 RSSFeedsTable.addRSSFeed 将一个 RSS 订阅添加到 RSS_FEEDS 表中。此方法的第一条语句将 RSS 订阅(在此示例中表示为 XML 文件)转换为类型为 org.w3c.dom.Document 的对象,该对象表示 DOM(文档对象模型)文档。这个类以及包含在 javax.xml 包中的类和接口包含了使您能够操作 XML 数据内容的方法。例如,以下语句使用 XPath 表达式从 Document 对象中检索 RSS 订阅的标题:
Node titleElement =
(Node)xPath.evaluate("/rss/channel/title[1]",
doc, XPathConstants.NODE);
XPath 表达式 /rss/channel/title[1] 检索第一个 <title> 元素的内容。对于文件 rss-the-coffee-break-blog.xml,这是字符串 The Coffee Break Blog。
以下语句将 RSS 订阅添加到表 RSS_FEEDS 中:
// For databases that support the SQLXML
// data type, this creates a
// SQLXML object from
// org.w3c.dom.Document.
System.out.println("Adding XML file " + fileName);
String insertRowQuery =
"insert into RSS_FEEDS " +
"(RSS_NAME, RSS_FEED_XML) values " +
"(?, ?)";
insertRow = con.prepareStatement(insertRowQuery);
insertRow.setString(1, titleString);
System.out.println("Creating SQLXML object with MySQL");
rssData = con.createSQLXML();
System.out.println("Creating DOMResult object");
DOMResult dom = (DOMResult)rssData.setResult(DOMResult.class);
dom.setNode(doc);
insertRow.setSQLXML(2, rssData);
System.out.println("Running executeUpdate()");
insertRow.executeUpdate();
方法 RSSFeedsTable.viewTable 检索 RSS_FEEDS 的内容。对于每一行,该方法创建一个名为 doc 的类型为 org.w3c.dom.Document 的对象,用于存储列 RSS_FEED_XML 中的 XML 内容。该方法检索 XML 内容并将其存储在名为 rssFeedXML 的类型为 SQLXML 的对象中。rssFeedXML 的内容被解析并存储在 doc 对象中。
在 Java DB 中处理 XML 数据
注意:有关在 Java DB 中处理 XML 数据的更多信息,请参阅 Java DB 开发人员指南 中的 "XML 数据类型和运算符" 部分。
示例 RSSFeedsTable 将 RSS 订阅存储在表 RSS_FEEDS 中,该表是使用以下命令创建的:
create table RSS_FEEDS
(RSS_NAME varchar(32) NOT NULL,
RSS_FEED_XML xml NOT NULL,
PRIMARY KEY (RSS_NAME));
Java DB 支持 XML 数据类型,但不支持 SQLXML JDBC 数据类型。因此,您必须将任何 XML 数据转换为字符格式,然后使用 Java DB 运算符 XMLPARSE 将其转换为 XML 数据类型。
方法 RSSFeedsTable.addRSSFeed 将一个 RSS 订阅添加到 RSS_FEEDS 表中。此方法的第一条语句将 RSS 订阅(在此示例中表示为 XML 文件)转换为类型为 org.w3c.dom.Document 的对象。这在 在 MySQL 中处理 XML 数据 部分中有描述。
RSSFeedsTable.addRSSFeed 方法使用方法 JDBCTutorialUtilities.convertDocumentToString 将 RSS 订阅转换为 String 对象。
Java DB 有一个名为XMLPARSE的操作符,将字符字符串表示解析为 Java DB XML 值,以下摘录演示了这一点:
String insertRowQuery =
"insert into RSS_FEEDS " +
"(RSS_NAME, RSS_FEED_XML) values " +
"(?, xmlparse(document cast " +
"(? as clob) preserve whitespace))";
XMLPARSE操作符要求您将 XML 文档的字符表示转换为 Java DB 识别的字符串数据类型。在本例中,它将其转换为CLOB数据类型。有关 Apache Xalan 和 Java DB 要求的更多信息,请参阅入门指南和 Java DB 文档。
方法RSSFeedsTable.viewTable检索RSS_FEEDS的内容。因为 Java DB 不支持 JDBC 数据类型SQLXML,您必须将 XML 内容检索为字符串。Java DB 有一个名为XMLSERIALIZE的操作符,将 XML 类型转换为字符类型:
String query =
"select RSS_NAME, " +
"xmlserialize " +
"(RSS_FEED_XML as clob) " +
"from RSS_FEEDS";
与XMLPARSE操作符一样,XMLSERIALIZE操作符要求在您的 Java 类路径中列出 Apache Xalan。
使用数组对象
注意:MySQL 和 Java DB 目前不支持ARRAY SQL 数据类型。因此,没有可用的 JDBC 教程示例来演示Array JDBC 数据类型。
下面的主题包括:
-
创建数组对象
-
检索和访问 ResultSet 中的数组值
-
存储和更新数组对象
-
释放数组资源
创建数组对象
使用方法Connection.createArrayOf创建Array对象。
例如,假设您的数据库包含一个名为REGIONS的表,该表已经通过以下 SQL 语句创建并填充;请注意,这些语句的语法将根据您的数据库而变化:
create table REGIONS
(REGION_NAME varchar(32) NOT NULL,
ZIPS varchar32 ARRAY[10] NOT NULL,
PRIMARY KEY (REGION_NAME));
insert into REGIONS values(
'Northwest',
'{"93101", "97201", "99210"}');
insert into REGIONS values(
'Southwest',
'{"94105", "90049", "92027"}');
Connection con = DriverManager.getConnection(url, props);
String [] northEastRegion = { "10022", "02110", "07399" };
Array anArray = con.createArrayOf("VARCHAR", northEastRegion);
Oracle 数据库 JDBC 驱动程序使用oracle.sql.ARRAY类实现java.sql.Array接口。
在 ResultSet 中检索和访问数组值
与 JDBC 4.0 大对象接口(Blob,Clob,NClob)一样,您可以操作Array对象,而无需将所有数据从数据库服务器传输到客户端计算机。Array对象将其表示的 SQL ARRAY作为结果集或 Java 数组实现。
以下摘录检索列ZIPS中的 SQL ARRAY值,并将其赋给java.sql.Array对象z对象。摘录检索z的内容并将其存储在zips中,zips是一个包含String类型对象的 Java 数组。摘录遍历zips数组并检查每个邮政(邮编)代码是否有效。此代码假定类ZipCode已经在先前定义,并且具有方法isValid,如果给定的邮政编码与有效邮政编码主列表中的一个匹配,则返回true:
ResultSet rs = stmt.executeQuery(
"SELECT region_name, zips FROM REGIONS");
while (rs.next()) {
Array z = rs.getArray("ZIPS");
String[] zips = (String[])z.getArray();
for (int i = 0; i < zips.length; i++) {
if (!ZipCode.isValid(zips[i])) {
// ...
// Code to display warning
}
}
}
在以下语句中,ResultSet方法getArray将当前行的列ZIPS中存储的值作为java.sql.Array对象z返回:
Array z = rs.getArray("ZIPS");
变量*z*包含一个定位器,这是指向服务器上 SQL ARRAY的逻辑指针;它不包含ARRAY本身的元素。作为逻辑指针,*z*可用于在服务器上操作数组。
在以下行中,getArray是Array.getArray方法,而不是前一行中使用的ResultSet.getArray方法。因为Array.getArray方法在 Java 编程语言中返回一个Object,并且每个邮政编码都是一个String对象,所以在分配给变量zips之前,结果被转换为String对象的数组。
String[] zips = (String[])z.getArray();
Array.getArray方法将 SQL ARRAY元素在客户端作为String对象数组实现。因为实际上变量*zips*包含数组的元素,所以可以在for循环中遍历zips,查找无效的邮政编码。
存储和更新数组对象
使用方法PreparedStatement.setArray和PreparedStatement.setObject将Array值作为输入参数传递给PreparedStatement对象。
以下示例将在先前创建的Array对象anArray设置为pstmt的第二个参数:
PreparedStatement pstmt = con.prepareStatement(
"insert into REGIONS (region_name, zips) " + "VALUES (?, ?)");
pstmt.setString(1, "NorthEast");
pstmt.setArray(2, anArray);
pstmt.executeUpdate();
同样,使用方法PreparedStatement.updateArray和PreparedStatement.updateObject来使用Array值更新表中的列。
释放数组资源
Array对象在创建它们的事务持续时间内保持有效。这可能导致应用程序在长时间运行的事务中耗尽资源。应用程序可以通过调用它们的free方法来释放Array资源。
在以下摘录中,调用方法Array.free来释放先前创建的Array对象所持有的资源。
Array aArray = con.createArrayOf("VARCHAR", northEastRegionnewYork);
// ...
aArray.free();
使用 DISTINCT 数据类型
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/distinct.html
注意:MySQL 和 Java DB 目前不支持DISTINCT SQL 数据类型。因此,在本节描述的功能没有 JDBC 教程示例可用来演示。
DISTINCT 数据类型与其他高级 SQL 数据类型的行为不同。作为一个基于已有内置类型之一的用户定义类型,它没有接口作为其在 Java 编程语言中的映射。相反,DISTINCT 数据类型的标准映射是其底层 SQL 数据类型映射到的 Java 类型。
为了说明,创建一个DISTINCT数据类型,然后看看如何检索、设置或更新它。假设你总是使用两个字母的缩写表示一个州,并且想要创建一个用于这些缩写的DISTINCT数据类型。你可以使用以下 SQL 语句定义你的新DISTINCT数据类型:
CREATE TYPE STATE AS CHAR(2);
一些数据库使用另一种语法来创建DISTINCT数据类型,如下面的代码行所示:
CREATE DISTINCT TYPE STATE AS CHAR(2);
如果一个语法不起作用,你可以尝试另一个。或者,你可以查看你的驱动程序文档,以查看它期望的确切语法。
这些语句创建了一个新的数据类型STATE,它可以作为列值或作为 SQL 结构化类型的属性值使用。因为STATE类型的值实际上是两个CHAR类型的值,所以你使用与检索CHAR值相同的方法来检索它,即getString。例如,假设ResultSet *rs*的第四列存储了STATE类型的值,下面的代码行检索了它的值:
String state = rs.getString(4);
同样,你会使用setString方法将STATE值存储在数据库中,使用updateString方法修改其值。
使用结构化对象
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/sqlstructured.html
注意:MySQL 和 Java DB 目前不支持用户定义类型。因此,没有 JDBC 教程示例可用来演示本节描述的功能。
下列主题包括:
-
结构化类型概述
-
在结构化类型中使用 DISTINCT 类型
-
使用结构化类型的引用
-
创建 SQL REF 对象的示例代码
-
将用户定义类型用作列值
-
将用户定义类型插入表中
结构化类型概述
SQL 结构化类型和DISTINCT类型是用户可以在 SQL 中定义的两种数据类型。它们通常被称为 UDT(用户定义类型),您可以使用 SQL 的CREATE TYPE语句来创建它们。
回到 The Coffee Break 的例子,假设所有者的成功超出了所有预期,并且一直在扩张新分店。所有者决定向数据库添加一个STORES表,其中包含有关每个机构的信息。STORES将有四列:
-
STORE_NO代表每个店铺的识别号 -
LOCATION代表其地址 -
COF_TYPES代表其销售的咖啡 -
MGR代表店长的姓名
所有者将LOCATION列设为 SQL 结构化类型,COF_TYPES列设为 SQL ARRAY,MGR列设为REF(MANAGER),其中MANAGER是 SQL 结构化类型。
所有者首先必须为地址和经理定义新的结构化类型。SQL 结构化类型类似于 Java 编程语言中的结构化类型,它具有称为属性的成员,可以是任何数据类型。所有者编写以下 SQL 语句来创建新数据类型ADDRESS:
CREATE TYPE ADDRESS
(
NUM INTEGER,
STREET VARCHAR(40),
CITY VARCHAR(40),
STATE CHAR(2),
ZIP CHAR(5)
);
在此语句中,新类型ADDRESS有五个属性,类似于 Java 类中的字段。属性NUM是一个INTEGER,属性STREET是一个VARCHAR(40),属性CITY是一个VARCHAR(40),属性STATE是一个CHAR(2),属性ZIP是一个CHAR(5)。
下面的摘录中,con是一个有效的Connection对象,将ADDRESS的定义发送到数据库:
String createAddress =
"CREATE TYPE ADDRESS " +
"(NUM INTEGER, STREET VARCHAR(40), " +
"CITY VARCHAR(40), STATE CHAR(2), ZIP CHAR(5))";
Statement stmt = con.createStatement();
stmt.executeUpdate(createAddress);
现在ADDRESS结构化类型已在数据库中注册为数据类型,所有者可以将其用作表列或结构化类型属性的数据类型。
在结构化类型中使用 DISTINCT 类型
The Coffee Break 的所有者计划在新的结构化类型MANAGER中包含的属性之一是经理的电话号码。因为所有者总是将电话号码列为一个 10 位数(以确保包括区号),并且永远不会将其作为数字进行操作,所以所有者决定定义一个名为PHONE_NO的新类型,该类型由 10 个字符组成。这种数据类型的 SQL 定义,可以被视为只有一个属性的结构化类型,如下所示:
CREATE TYPE PHONE_NO AS CHAR(10);
或者,如前面提到的,对于某些驱动程序,定义可能如下所示:
CREATE DISTINCT TYPE PHONE_NO AS CHAR(10);
DISTINCT类型始终基于另一个数据类型,该数据类型必须是预定义类型。换句话说,DISTINCT类型不能基于用户定义类型(UDT)。要检索或设置DISTINCT类型的值,请使用基础类型(其基础类型)的适当方法。例如,要检索基于CHAR类型的PHONE_NO实例,您将使用getString方法,因为这是检索CHAR的方法。
假设ResultSet对象*rs*的当前行的第四列中有一个类型为PHONE_NO的值,以下代码行检索它:
String phoneNumber = rs.getString(4);
类似地,以下代码行设置了一个输入参数,该参数具有类型PHONE_NO,用于发送到数据库的预备语句:
pstmt.setString(1, phoneNumber);
在前面的代码片段中添加,PHONE_NO的定义将通过以下代码行发送到数据库:
stmt.executeUpdate(
"CREATE TYPE PHONE_NO AS CHAR(10)");
在向数据库注册类型PHONE_NO之后,所有者可以将其用作表中的列类型或作为结构化类型中属性的数据类型。以下 SQL 语句中MANAGER的定义使用PHONE_NO作为属性PHONE的数据类型:
CREATE TYPE MANAGER
(
MGR_ID INTEGER,
LAST_NAME VARCHAR(40),
FIRST_NAME VARCHAR(40),
PHONE PHONE_NO
);
重用之前定义的*stmt*,以下代码片段将结构化类型MANAGER的定义发送到数据库:
String createManager =
"CREATE TYPE MANAGER " +
"(MGR_ID INTEGER, LAST_NAME " +
"VARCHAR(40), " +
"FIRST_NAME VARCHAR(40), " +
"PHONE PHONE_NO)";
stmt.executeUpdate(createManager);
使用结构化类型的引用
The Coffee Break 的所有者创建了三种新的数据类型,用作数据库中的列类型或属性类型:结构化类型LOCATION和MANAGER,以及DISTINCT类型PHONE_NO。企业家将PHONE_NO用作新类型MANAGER中属性PHONE的类型,并将ADDRESS用作表STORES中列LOCATION的数据类型。MANAGER类型可以用作列MGR的类型,但企业家更喜欢使用类型REF(MANAGER),因为企业家经常让一个人管理两到三家店铺。将REF(MANAGER)用作列类型可以避免在一个人管理多家店铺时重复所有MANAGER的数据。
已经创建了结构化类型 MANAGER,所有者现在可以创建一个包含可以被引用的 MANAGER 实例的表。对 MANAGER 实例的引用将具有类型 REF(MANAGER)。SQL REF 只不过是指向结构化类型的逻辑指针,因此 REF(MANAGER) 实例充当对 MANAGER 实例的逻辑指针。
因为 SQL REF 值需要永久与其引用的结构化类型实例关联在一起,所以它存储在一个特殊的表中,与其关联的实例一起。程序员不直接创建 REF 类型,而是创建将存储特定结构化类型实例的表,这些实例可以被引用。每个要被引用的结构化类型都将有自己的表。当你将结构化类型的实例插入表中时,数据库会自动创建一个 REF 实例。例如,为了包含可以被引用的 MANAGER 实例,所有者使用 SQL 创建了以下特殊表:
CREATE TABLE MANAGERS OF MANAGER
(OID REF(MANAGER)
VALUES ARE SYSTEM GENERATED);
这个语句创建了一个带有特殊列 OID 的表,该列存储 REF(MANAGER) 类型的值。每次将 MANAGER 实例插入表中时,数据库都会生成一个 REF(MANAGER) 实例并将其存储在列 OID 中。隐式地,另外一列存储已插入表中的 MANAGER 的每个属性。例如,以下代码片段展示了企业家如何创建了三个 MANAGER 结构化类型的实例来代表三个经理:
INSERT INTO MANAGERS (
MGR_ID, LAST_NAME,
FIRST_NAME, PHONE) VALUES
(
000001,
'MONTOYA',
'ALFREDO',
'8317225600'
);
INSERT INTO MANAGERS (
MGR_ID, LAST_NAME,
FIRST_NAME, PHONE) VALUES
(
000002,
'HASKINS',
'MARGARET',
'4084355600'
);
INSERT INTO MANAGERS (
MGR_ID, LAST_NAME,
FIRST_NAME, PHONE) VALUES
(
000003,
'CHEN',
'HELEN',
'4153785600'
);
表 MANAGERS 现在将有三行,每个已插入的经理一行。列 OID 将包含三个 REF(MANAGER) 类型的唯一对象标识符,每个 MANAGER 实例一个。这些对象标识符是由数据库自动生成的,并将永久存储在表 MANAGERS 中。隐式地,另外一列存储 MANAGER 的每个属性。例如,在表 MANAGERS 中,一行包含一个引用 Alfredo Montoya 的 REF(MANAGER),另一行包含一个引用 Margaret Haskins 的 REF(MANAGER),第三行包含一个引用 Helen Chen 的 REF(MANAGER)。
要访问 REF(MANAGER) 实例,您可以从其表中选择。例如,所有者使用以下代码片段检索了对 ID 号为 000001 的 Alfredo Montoya 的引用:
String selectMgr =
"SELECT OID FROM MANAGERS " +
"WHERE MGR_ID = 000001";
ResultSet rs = stmt.executeQuery(selectMgr);
rs.next();
Ref manager = rs.getRef("OID");
现在变量 *manager* 可以被用作引用 Alfredo Montoya 的列值。
创建 SQL REF 对象的示例代码
以下代码示例创建了表MANAGERS,这是结构化类型MANAGER的实例表,可以引用,并将三个MANAGER实例插入表中。此表中的列OID将存储REF(MANAGER)的实例。执行此代码后,MANAGERS表将为插入的三个MANAGER对象的每个对象插入一行,并且OID列中的值将是标识存储在该行中的MANAGER实例的REF(MANAGER)类型。
package com.oracle.tutorial.jdbc;
import java.sql.*;
public class CreateRef {
public static void main(String args[]) {
JDBCTutorialUtilities myJDBCTutorialUtilities;
Connection myConnection = null;
if (args[0] == null) {
System.err.println("Properties file not specified " +
"at command line");
return;
} else {
try {
myJDBCTutorialUtilities = new JDBCTutorialUtilities(args[0]);
} catch (Exception e) {
System.err.println("Problem reading properties " +
"file " + args[0]);
e.printStackTrace();
return;
}
}
Connection con = null;
Statement stmt = null;
try {
String createManagers =
"CREATE TABLE " +
"MANAGERS OF MANAGER " +
"(OID REF(MANAGER) " +
"VALUES ARE SYSTEM " +
"GENERATED)";
String insertManager1 =
"INSERT INTO MANAGERS " +
"(MGR_ID, LAST_NAME, " +
"FIRST_NAME, PHONE) " +
"VALUES " +
"(000001, 'MONTOYA', " +
"'ALFREDO', " +
"'8317225600')";
String insertManager2 =
"INSERT INTO MANAGERS " +
"(MGR_ID, LAST_NAME, " +
"FIRST_NAME, PHONE) " +
"VALUES " +
"(000002, 'HASKINS', " +
"'MARGARET', " +
"'4084355600')";
String insertManager3 =
"INSERT INTO MANAGERS " +
"(MGR_ID, LAST_NAME, " +
"FIRST_NAME, PHONE) " +
"VALUES " +
"(000003, 'CHEN', 'HELEN', " +
"'4153785600')";
con = myJDBCTutorialUtilities.getConnection();
con.setAutoCommit(false);
stmt = con.createStatement();
stmt.executeUpdate(createManagers);
stmt.addBatch(insertManager1);
stmt.addBatch(insertManager2);
stmt.addBatch(insertManager3);
int [] updateCounts = stmt.executeBatch();
con.commit();
System.out.println("Update count for: ");
for (int i = 0; i < updateCounts.length; i++) {
System.out.print(" command " + (i + 1) + " = ");
System.out.println(updateCounts[i]);
}
} catch(BatchUpdateException b) {
System.err.println("-----BatchUpdateException-----");
System.err.println("Message: " + b.getMessage());
System.err.println("SQLState: " + b.getSQLState());
System.err.println("Vendor: " + b.getErrorCode());
System.err.print("Update counts for " + "successful commands: ");
int [] rowsUpdated = b.getUpdateCounts();
for (int i = 0; i < rowsUpdated.length; i++) {
System.err.print(rowsUpdated[i] + " ");
}
System.err.println("");
} catch(SQLException ex) {
System.err.println("------SQLException------");
System.err.println("Error message: " + ex.getMessage());
System.err.println("SQLState: " + ex.getSQLState());
System.err.println("Vendor: " + ex.getErrorCode());
} finally {
if (stmt != null) { stmt.close(); }
JDBCTutorialUtilities.closeConnection(con);
}
}
}
使用用户定义类型作为列值
我们的企业家现在拥有创建表STORES所需的 UDT。结构化类型ADDRESS是列LOCATION的类型,类型REF(MANAGER)是列MGR的类型。
UDT COF_TYPES基于 SQL 数据类型ARRAY,是列COF_TYPES的类型。以下代码行创建了类型COF_ARRAY作为具有 10 个元素的ARRAY值。COF_ARRAY的基本类型是VARCHAR(40)。
CREATE TYPE COF_ARRAY AS ARRAY(10) OF VARCHAR(40);
定义了新数据类型后,以下 SQL 语句创建了表STORES:
CREATE TABLE STORES
(
STORE_NO INTEGER,
LOCATION ADDRESS,
COF_TYPES COF_ARRAY,
MGR REF(MANAGER)
);
将用户定义类型插入表中
以下代码片段向STORES表中插入一行,按顺序提供了列STORE_NO,LOCATION,COF_TYPES和MGR的值:
INSERT INTO STORES VALUES
(
100001,
ADDRESS(888, 'Main_Street',
'Rancho_Alegre',
'CA', '94049'),
COF_ARRAY('Colombian', 'French_Roast',
'Espresso', 'Colombian_Decaf',
'French_Roast_Decaf'),
SELECT OID FROM MANAGERS
WHERE MGR_ID = 000001
);
以下逐个列出每列及其插入的值。
STORE_NO: 100001
此列类型为INTEGER,数字100001是INTEGER类型,类似于之前在COFFEES和SUPPLIERS表中插入的条目。
LOCATION: ADDRESS(888, 'Main_Street',
'Rancho_Alegre', 'CA', '94049')
此列的类型为结构化类型ADDRESS,此值是ADDRESS实例的构造函数。当我们将ADDRESS的定义发送到数据库时,其中一件事是为新类型创建构造函数。括号中的逗号分隔值是ADDRESS类型属性的初始化值,它们必须按照ADDRESS类型定义中属性列出的顺序出现。888是属性NUM的值,是INTEGER值。"Main_Street"是STREET的值,"Rancho_Alegre"是CITY的值,这两个属性都是VARCHAR(40)类型。属性STATE的值为"CA",是CHAR(2)类型,属性ZIP的值为"94049",是CHAR(5)类型。
COF_TYPES: COF_ARRAY(
'Colombian',
'French_Roast',
'Espresso',
'Colombian_Decaf',
'French_Roast_Decaf'),
列COF_TYPES的类型为COF_ARRAY,基本类型为VARCHAR(40),括号中的逗号分隔值是数组元素为String对象。所有者定义了类型COF_ARRAY最多有 10 个元素。此数组有 5 个元素,因为企业家仅提供了 5 个String对象。
MGR: SELECT OID FROM MANAGERS
WHERE MGR_ID = 000001
列MGR的类型是REF(MANAGER),这意味着该列中的值必须是指向结构化类型MANAGER的引用。所有MANAGER的实例都存储在表MANAGERS中。所有REF(MANAGER)的实例也存储在该表中,存储在列OID中。此表行中描述的商店的经理是 Alfredo Montoya,他的信息存储在具有属性MGR_ID为100001的MANAGER实例中。要获取与 Alfredo Montoya 的MANAGER对象关联的REF(MANAGER)实例,请选择表MANAGERS中MGR_ID为100001的行中的列OID。将存储在STORES表的MGR列中的值(REF(MANAGER)值)是 DBMS 生成的用于唯一标识此MANAGER结构化类型实例的值。
将上述 SQL 语句发送到数据库,使用以下代码片段:
String insertMgr =
"INSERT INTO STORES VALUES " +
"(100001, " +
"ADDRESS(888, 'Main_Street', " +
"'Rancho_Alegre', 'CA', " +
"'94049'), " +
"COF_ARRAY('Colombian', " +
"'French_Roast', 'Espresso', " +
"'Colombian_Decaf', " +
"'French_Roast_Decaf'}, " +
"SELECT OID FROM MANAGERS " +
"WHERE MGR_ID = 000001)";
stmt.executeUpdate(insertMgr);
然而,因为你将发送多个INSERT INTO语句,将它们一起作为批量更新发送会更有效,就像以下代码示例中所示:
package com.oracle.tutorial.jdbc;
import java.sql.*;
public class InsertStores {
public static void main(String args[]) {
JDBCTutorialUtilities myJDBCTutorialUtilities;
Connection myConnection = null;
if (args[0] == null) {
System.err.println(
"Properties file " +
"not specified " +
"at command line");
return;
} else {
try {
myJDBCTutorialUtilities = new
JDBCTutorialUtilities(args[0]);
} catch (Exception e) {
System.err.println(
"Problem reading " +
"properties file " +
args[0]);
e.printStackTrace();
return;
}
}
Connection con = null;
Statement stmt = null;
try {
con = myJDBCTutorialUtilities.getConnection();
con.setAutoCommit(false);
stmt = con.createStatement();
String insertStore1 =
"INSERT INTO STORES VALUES (" +
"100001, " +
"ADDRESS(888, 'Main_Street', " +
"'Rancho_Alegre', 'CA', " +
"'94049'), " +
"COF_ARRAY('Colombian', " +
"'French_Roast', " +
"'Espresso', " +
"'Colombian_Decaf', " +
"'French_Roast_Decaf'), " +
"(SELECT OID FROM MANAGERS " +
"WHERE MGR_ID = 000001))";
stmt.addBatch(insertStore1);
String insertStore2 =
"INSERT INTO STORES VALUES (" +
"100002, " +
"ADDRESS(1560, 'Alder', " +
"'Ochos_Pinos', " +
"'CA', '94049'), " +
"COF_ARRAY('Colombian', " +
"'French_Roast', " +
"'Espresso', " +
"'Colombian_Decaf', " +
"'French_Roast_Decaf', " +
"'Kona', 'Kona_Decaf'), " +
"(SELECT OID FROM MANAGERS " +
"WHERE MGR_ID = 000001))";
stmt.addBatch(insertStore2);
String insertStore3 =
"INSERT INTO STORES VALUES (" +
"100003, " +
"ADDRESS(4344, " +
"'First_Street', " +
"'Verona', " +
"'CA', '94545'), " +
"COF_ARRAY('Colombian', " +
"'French_Roast', " +
"'Espresso', " +
"'Colombian_Decaf', " +
"'French_Roast_Decaf', " +
"'Kona', 'Kona_Decaf'), " +
"(SELECT OID FROM MANAGERS " +
"WHERE MGR_ID = 000002))";
stmt.addBatch(insertStore3);
String insertStore4 =
"INSERT INTO STORES VALUES (" +
"100004, " +
"ADDRESS(321, 'Sandy_Way', " +
"'La_Playa', " +
"'CA', '94544'), " +
"COF_ARRAY('Colombian', " +
"'French_Roast', " +
"'Espresso', " +
"'Colombian_Decaf', " +
"'French_Roast_Decaf', " +
"'Kona', 'Kona_Decaf'), " +
"(SELECT OID FROM MANAGERS " +
"WHERE MGR_ID = 000002))";
stmt.addBatch(insertStore4);
String insertStore5 =
"INSERT INTO STORES VALUES (" +
"100005, " +
"ADDRESS(1000, 'Clover_Road', " +
"'Happyville', " +
"'CA', '90566'), " +
"COF_ARRAY('Colombian', " +
"'French_Roast', " +
"'Espresso', " +
"'Colombian_Decaf', " +
"'French_Roast_Decaf'), " +
"(SELECT OID FROM MANAGERS " +
"WHERE MGR_ID = 000003))";
stmt.addBatch(insertStore5);
int [] updateCounts = stmt.executeBatch();
ResultSet rs = stmt.executeQuery(
"SELECT * FROM STORES");
System.out.println("Table STORES after insertion:");
System.out.println("STORE_NO " + "LOCATION " +
"COF_TYPE " + "MGR");
while (rs.next()) {
int storeNo = rs.getInt("STORE_NO");
Struct location = (Struct)rs.getObject("LOCATION");
Object[] locAttrs = location.getAttributes();
Array coffeeTypes = rs.getArray("COF_TYPE");
String[] cofTypes = (String[])coffeeTypes.getArray();
Ref managerRef = rs.getRef("MGR");
PreparedStatement pstmt = con.prepareStatement(
"SELECT MANAGER " +
"FROM MANAGERS " +
"WHERE OID = ?");
pstmt.setRef(1, managerRef);
ResultSet rs2 = pstmt.executeQuery();
rs2.next();
Struct manager = (Struct)rs2.getObject("MANAGER");
Object[] manAttrs = manager.getAttributes();
System.out.print(storeNo + " ");
System.out.print(
locAttrs[0] + " " +
locAttrs[1] + " " +
locAttrs[2] + ", " +
locAttrs[3] + " " +
locAttrs[4] + " ");
for (int i = 0; i < cofTypes.length; i++)
System.out.print( cofTypes[i] + " ");
System.out.println(
manAttrs[1] + ", " +
manAttrs[2]);
rs2.close();
pstmt.close();
}
rs.close();
} catch(BatchUpdateException b) {
System.err.println("-----BatchUpdateException-----");
System.err.println("SQLState: " + b.getSQLState());
System.err.println("Message: " + b.getMessage());
System.err.println("Vendor: " + b.getErrorCode());
System.err.print("Update counts: ");
int [] updateCounts = b.getUpdateCounts();
for (int i = 0; i < updateCounts.length; i++) {
System.err.print(updateCounts[i] + " ");
}
System.err.println("");
} catch(SQLException ex) {
System.err.println("SQLException: " + ex.getMessage());
System.err.println("SQLState: " + ex.getSQLState());
System.err.println("Message: " + ex.getMessage());
System.err.println("Vendor: " + ex.getErrorCode());
} finally {
if (stmt != null) { stmt.close(); }
JDBCTutorialUtilities.closeConnection(con);
}
}
}
}
使用自定义类型映射
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/sqlcustommapping.html
注意:MySQL 目前不支持用户定义类型。MySQL 和 Java DB 目前不支持结构化类型或DISTINCT SQL 数据类型。没有可用的 JDBC 教程示例来演示本节中描述的功能。
随着生意蒸蒸日上,The Coffee Break 的老板经常添加新店铺并对数据库进行更改。老板决定为结构化类型ADDRESS使用自定义映射。这使老板可以对映射ADDRESS类型的 Java 类进行更改。Java 类将为ADDRESS的每个属性都有一个字段。类的名称和字段的名称可以是任何有效的 Java 标识符。
下面涵盖了以下主题:
-
实现 SQLData
-
使用连接的类型映射
-
使用自定义类型映射
实现 SQLData
实现自定义映射所需的第一步是创建一个实现接口SQLData的类。
结构化类型ADDRESS的 SQL 定义如下:
CREATE TYPE ADDRESS
(
NUM INTEGER,
STREET VARCHAR(40),
CITY VARCHAR(40),
STATE CHAR(2),
ZIP CHAR(5)
);
为了自定义映射ADDRESS类型而实现SQLData接口的类可能如下所示:
public class Address implements SQLData {
public int num;
public String street;
public String city;
public String state;
public String zip;
private String sql_type;
public String getSQLTypeName() {
return sql_type;
}
public void readSQL(SQLInput stream, String type)
throws SQLException {
sql_type = type;
num = stream.readInt();
street = stream.readString();
city = stream.readString();
state = stream.readString();
zip = stream.readString();
}
public void writeSQL(SQLOutput stream)
throws SQLException {
stream.writeInt(num);
stream.writeString(street);
stream.writeString(city);
stream.writeString(state);
stream.writeString(zip);
}
}
使用连接的类型映射
编写一个实现SQLData接口的类之后,设置自定义映射的唯一其他事项就是在类型映射中进行条目设置。例如,这意味着输入ADDRESS类型的完全限定 SQL 名称和Address类的Class对象。类型映射是java.util.Map接口的一个实例,与每个新创建的连接相关联,因此您可以使用它。假设con是活动连接,以下代码片段向与con关联的类型映射添加了一个 UDT ADDRESS的条目。
java.util.Map map = con.getTypeMap();
map.put("SchemaName.ADDRESS", Class.forName("Address"));
con.setTypeMap(map);
每当调用getObject方法检索ADDRESS类型的实例时,驱动程序将检查与连接关联的类型映射,并看到它有一个ADDRESS的条目。驱动程序将注意到Address类的Class对象,创建其实例,并在后台执行许多其他操作以将ADDRESS映射到Address。您只需生成映射的类,然后在类型映射中进行条目设置,让驱动程序知道有一个自定义映射。驱动程序将完成其余所有工作。
存储具有自定义映射的结构化类型时情况类似。当调用setObject方法时,驱动程序将检查要设置的值是否是实现了SQLData接口的类的实例。如果是(表示存在自定义映射),驱动程序将使用自定义映射将值转换为其 SQL 对应项,然后返回给数据库。再次强调,驱动程序在后台执行自定义映射;你只需向setObject方法提供具有自定义映射的参数即可。稍后在本节中将看到一个示例。
查看使用标准映射(Struct对象)和使用 Java 编程语言中的自定义映射(类)之间的区别。下面的代码片段展示了标准映射到Struct对象的映射,这是在连接的类型映射中没有条目时驱动程序使用的映射。
ResultSet rs = stmt.executeQuery(
"SELECT LOCATION " +
"WHERE STORE_NO = 100003");
rs.next();
Struct address = (Struct)rs.getObject("LOCATION");
变量address包含以下属性值:4344、"First_Street"、"Verona"、"CA"、"94545"。
下面的代码片段展示了当连接的类型映射中存在结构化类型ADDRESS的条目时会发生什么。请记住,列LOCATION存储类型为ADDRESS的值。
ResultSet rs = stmt.executeQuery(
"SELECT LOCATION " +
"WHERE STORE_NO = 100003");
rs.next();
Address store_3 = (Address)rs.getObject("LOCATION");
变量store_3现在是Address类的实例,每个属性值都是Address的一个字段的当前值。请注意,在将getObject方法检索的对象转换为Address对象并将其分配给store_3之前,必须记得进行转换。还要注意,store_3必须是一个Address对象。
将使用Struct对象与使用Address类的实例进行比较。假设商店搬到了邻近城镇的更好位置,因此您必须更新数据库。使用自定义映射,重置store_3的字段,如下面的代码片段所示:
ResultSet rs = stmt.executeQuery(
"SELECT LOCATION " +
"WHERE STORE_NO = 100003");
rs.next();
Address store_3 = (Address)rs.getObject("LOCATION");
store_3.num = 1800;
store_3.street = "Artsy_Alley";
store_3.city = "Arden";
store_3.state = "CA";
store_3.zip = "94546";
PreparedStatement pstmt = con.prepareStatement(
"UPDATE STORES " +
"SET LOCATION = ? " +
"WHERE STORE_NO = 100003");
pstmt.setObject(1, store_3);
pstmt.executeUpdate();
列LOCATION中的值是ADDRESS类型的实例。驱动程序检查连接的类型映射,看到将ADDRESS与类Address关联的条目,因此使用Address中指示的自定义映射。当代码使用变量*store_3*作为第二个参数调用setObject方法时,驱动程序检查并看到*store_3*表示Address类的实例,该类实现了结构化类型ADDRESS的SQLData接口,并再次自动使用自定义映射。
没有ADDRESS的自定义映射,更新将更像这样:
PreparedStatement pstmt = con.prepareStatement(
"UPDATE STORES " +
"SET LOCATION.NUM = 1800, " +
"LOCATION.STREET = 'Artsy_Alley', " +
"LOCATION.CITY = 'Arden', " +
"LOCATION.STATE = 'CA', " +
"LOCATION.ZIP = '94546' " +
"WHERE STORE_NO = 100003");
pstmt.executeUpdate;
使用您自己的类型映射
到目前为止,您仅使用了与连接关联的类型映射进行自定义映射。通常情况下,大多数程序员只会使用这种类型映射。但是,也可以创建一个类型映射并将其传递给某些方法,以便驱动程序将使用该类型映射而不是与连接关联的类型映射。这允许为同一用户定义类型(UDT)使用两种不同的映射。实际上,可以为相同的 UDT 拥有多个自定义映射,只要每个映射都设置为实现SQLData接口的类和类型映射中的条目。如果您没有向可以接受类型映射的方法传递类型映射,则驱动程序将默认使用与连接关联的类型映射。
在使用与连接关联的类型映射之外的类型映射的情况非常少见。例如,如果几个程序员共同开发 JDBC 应用程序并且在使用相同连接,则可能需要为方法提供一个类型映射。如果两个或更多程序员为相同的 SQL UDT 创建了自定义映射,则每个人都需要提供自己的类型映射,从而覆盖连接的类型映射。
使用 Datalink 对象
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/sqldatalink.html
DATALINK值通过 URL 引用底层数据源之外的资源。URL,统一资源定位符,是指向万维网上资源的指针。资源可以是简单的文件或目录,也可以是对更复杂对象的引用,比如对数据库查询或搜索引擎的查询。
下列主题包括:
-
存储外部数据引用
-
检索外部数据引用
存储外部数据引用
使用方法PreparedStatement.setURL来为预编译语句指定一个java.net.URL对象。在 URL 类型不被 Java 平台支持的情况下,可以使用setString方法存储 URL。
例如,假设 The Coffee Break 的所有者想要在数据库表中存储一组重要的 URL。以下方法DatalinkSample.addURLRow向表DATA_REPOSITORY添加一行数据。该行包括标识 URL 的字符串,DOCUMENT_NAME和 URL 本身,URL:
public void addURLRow(String description, String url) throws SQLException {
String query = "INSERT INTO data_repository(document_name,url) VALUES (?,?)";
try (PreparedStatement pstmt = this.con.prepareStatement(query)) {
pstmt.setString(1, description);
pstmt.setURL(2,new URL(url));
pstmt.execute();
} catch (SQLException sqlex) {
JDBCTutorialUtilities.printSQLException(sqlex);
} catch (Exception ex) {
System.out.println("Unexpected exception");
ex.printStackTrace();
}
}
检索外部数据引用
使用方法ResultSet.getURL检索外部数据引用作为java.net.URL对象。在getObject或getURL方法返回的 URL 类型不被 Java 平台支持的情况下,通过调用getString方法将 URL 作为String对象检索。
下列方法DatalinkSample.viewTable显示了表DATA_REPOSITORY中所有 URL 的内容:
public static void viewTable(Connection con, Proxy proxy)
throws SQLException, IOException {
String query = "SELECT document_name, url FROM data_repository";
try (Statement stmt = con.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
if ( rs.next() ) {
String documentName = null;
java.net.URL url = null;
documentName = rs.getString(1);
// Retrieve the value as a URL object.
url = rs.getURL(2);
if (url != null) {
// Retrieve the contents from the URL.
URLConnection myURLConnection = url.openConnection(proxy);
BufferedReader bReader =
new BufferedReader(new InputStreamReader(myURLConnection.getInputStream()));
System.out.println("Document name: " + documentName);
String pageContent = null;
while ((pageContent = bReader.readLine()) != null ) {
// Print the URL contents
System.out.println(pageContent);
}
} else {
System.out.println("URL is null");
}
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} catch(IOException ioEx) {
System.out.println("IOException caught: " + ioEx.toString());
} catch (Exception ex) {
System.out.println("Unexpected exception");
ex.printStackTrace();
}
}
示例DatalinkSample.java将 Oracle URL www.oracle.com 存储在表DATA_REPOSITORY中。然后,它显示了所有在DATA_REPOSITORY中存储的 URL 引用的文档内容,其中包括 Oracle 主页,www.oracle.com。
以下语句从结果集中检索 URL 作为java.net.URL对象:
url = rs.getURL(2);
示例使用以下语句访问URL对象引用的数据:
// Retrieve the contents from the URL.
URLConnection myURLConnection = url.openConnection(proxy);
BufferedReader bReader =
new BufferedReader(new InputStreamReader(myURLConnection.getInputStream()));
System.out.println("Document name: " + documentName);
String pageContent = null;
while ((pageContent = bReader.readLine()) != null ) {
// Print the URL contents
System.out.println(pageContent);
}
方法URLConnection.openConnection可以不带参数,这意味着URLConnection表示直接连接到互联网。如果需要代理服务器连接到互联网,openConnection方法接受一个java.net.Proxy对象作为参数。以下语句演示如何创建一个 HTTP 代理,服务器名称为www-proxy.example.com,端口号为80:
Proxy myProxy;
InetSocketAddress myProxyServer;
myProxyServer = new InetSocketAddress("www-proxy.example.com", 80);
myProxy = new Proxy(Proxy.Type.HTTP, myProxyServer);
使用 RowId 对象
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/sqlrowid.html
注意:MySQL 和 Java DB 目前不支持RowId JDBC 接口。因此,没有可用的 JDBC 教程示例来演示本节中描述的功能。
RowId对象表示数据库表中一行的地址。但请注意,ROWID类型不是标准 SQL 类型。ROWID值可能很有用,因为它们通常是访问单个行的最快方式,并且是表中行的唯一标识符。但是,您不应将ROWID值用作表的主键。例如,如果从表中删除特定行,则数据库可能会将其ROWID值重新分配给稍后插入的行。
下面涵盖了以下主题:
-
检索 RowId 对象
-
使用 RowId 对象
-
RowId 有效期
检索 RowId 对象
通过调用接口ResultSet和CallableStatement中定义的 getter 方法检索java.sql.RowId对象。返回的RowId对象是一个不可变对象,您可以将其用作后续引用的唯一标识符。以下是调用ResultSet.getRowId方法的示例:
java.sql.RowId rowId_1 = rs.getRowId(1);
使用 RowId 对象
您可以将RowId对象设置为参数传递给参数化的PreparedStatement对象:
Connection conn = ds.getConnection(username, password);
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO BOOKLIST" +
"(ID, AUTHOR, TITLE, ISBN) " +
"VALUES (?, ?, ?, ?)");
ps.setRowId(1, rowId_1);
您还可以在可更新的ResultSet对象中使用特定的RowId对象更新列:
ResultSet rs = ...
rs.next();
rs.updateRowId(1, rowId_1);
RowId对象的值通常在数据源之间不可移植,并且在分别使用PreparedStatement和ResultSet对象的设置或更新方法时应被视为特定于数据源。因此,不建议从连接到一个数据源的ResultSet对象获取RowId对象,然后尝试在连接到不同数据源的不相关ResultSet对象中使用相同的RowId对象。
RowId 有效期
只要识别的行未被删除,RowId对象就有效,并且RowId对象的生命周期在数据源为RowId指定的生命周期范围内。
要确定数据库或数据源中RowId对象的生命周期,请调用方法DatabaseMetaData.getRowIdLifetime。它返回一个RowIdLifetime枚举数据类型的值。以下方法,JDBCTutorialUtilities.rowIdLifeTime,返回RowId对象的生命周期:
public static void rowIdLifetime(Connection conn)
throws SQLException {
DatabaseMetaData dbMetaData = conn.getMetaData();
RowIdLifetime lifetime = dbMetaData.getRowIdLifetime();
switch (lifetime) {
case ROWID_UNSUPPORTED:
System.out.println("ROWID type not supported");
break;
case ROWID_VALID_FOREVER:
System.out.println("ROWID has unlimited lifetime");
break;
case ROWID_VALID_OTHER:
System.out.println("ROWID has indeterminate lifetime");
break;
case ROWID_VALID_SESSION:
System.out.println(
"ROWID type has lifetime that " +
"is valid for at least the " +
"containing session");
break;
case ROWID_VALID_TRANSACTION:
System.out.println(
"ROWID type has lifetime that " +
"is valid for at least the " +
"containing transaction");
break;
}
}
使用存储过程
原文:
docs.oracle.com/javase/tutorial/jdbc/basics/storedprocedures.html
存储过程是一组 SQL 语句,形成一个逻辑单元并执行特定任务,它们用于封装一组操作或查询以在数据库服务器上执行。例如,对员工数据库的操作(雇佣、解雇、晋升、查找)可以编码为应用程序代码执行的存储过程。存储过程可以编译并使用不同的参数和结果执行,并且可以具有任何组合的输入、输出和输入/输出参数。
请注意,大多数 DBMS 都支持存储过程,但它们的语法和功能有相当多的变化。因此,本教程包含两个示例,StoredProcedureJavaDBSample.java 和 StoredProcedureMySQLSample.java,分别演示如何在 Java DB 和 MySQL 中创建存储过程。
本页涵盖以下主题:
-
存储过程示例概述
-
参数模式
-
在 Java DB 中创建存储过程
-
使用 SQL 脚本或 JDBC API 在 Java DB 中创建存储过程
-
使用 SQL 脚本或 JDBC API 在 Java DB 中创建存储过程
-
在 Java DB 中调用存储过程
-
将 Java 类打包到 JAR 文件中
-
-
在 MySQL 中创建存储过程
-
使用 SQL 脚本或 JDBC API 在 MySQL 中创建存储过程
-
在 MySQL 中调用存储过程
-
存储过程示例概述
示例StoredProcedureJavaDBSample.java 和 StoredProcedureMySQLSample.java 创建并调用以下存储过程:
-
SHOW_SUPPLIERS: 打印一个包含咖啡供应商名称和他们向 The Coffee Break 供应的咖啡的结果集。此存储过程不需要任何参数。当示例调用此存储过程时,示例产生类似以下内容的输出:Acme, Inc.: Colombian_Decaf Acme, Inc.: Colombian Superior Coffee: French_Roast_Decaf Superior Coffee: French_Roast The High Ground: Espresso -
GET_SUPPLIER_OF_COFFEE: 打印供应商supplierName为咖啡coffeeName的名称。它需要以下参数:-
IN coffeeName varchar(32): 咖啡的名称 -
OUT supplierName varchar(40): 咖啡供应商的名称
当示例以
Colombian作为coffeeName的值调用此存储过程时,示例产生类似以下内容的输出:Supplier of the coffee Colombian: Acme, Inc. -
-
RAISE_PRICE:将咖啡coffeeName的价格提高到价格newPrice。如果价格增加大于百分比maximumPercentage,则价格将按该百分比提高。如果价格newPrice低于咖啡的原始价格,则此过程不会更改价格。它需要以下参数:-
IN coffeeName varchar(32):咖啡的名称 -
IN maximumPercentage float:提高咖啡价格的最大百分比 -
INOUT newPrice numeric(10,2):咖啡的新价格。调用RAISE_PRICE存储过程后,此参数将包含咖啡coffeeName的当前价格。
当示例以
Colombian作为coffeeName的值,0.10作为maximumPercentage的值,19.99作为newPrice的值调用此存储过程时,示例会产生类似以下输出:Contents of COFFEES table before calling RAISE_PRICE: Colombian, 101, 7.99, 0, 0 Colombian_Decaf, 101, 8.99, 0, 0 Espresso, 150, 9.99, 0, 0 French_Roast, 49, 8.99, 0, 0 French_Roast_Decaf, 49, 9.99, 0, 0 Calling the procedure RAISE_PRICE Value of newPrice after calling RAISE_PRICE: 8.79 Contents of COFFEES table after calling RAISE_PRICE: Colombian, 101, 8.79, 0, 0 Colombian_Decaf, 101, 8.99, 0, 0 Espresso, 150, 9.99, 0, 0 French_Roast, 49, 8.99, 0, 0 French_Roast_Decaf, 49, 9.99, 0, 0 -
参数模式
参数属性IN(默认值)、OUT和INOUT是参数模式。它们定义形式参数的操作。以下表总结了有关参数模式的信息。
| 参数模式的特征 | IN | OUT | INOUT |
|---|---|---|---|
| 必须在存储过程定义中指定吗? | 不需要;如果省略,则形式参数的参数模式为IN。 |
必须指定。 | 必须指定。 |
| 参数是否向存储过程传递值或返回值? | 向存储过程传递值。 | 返回值给调用者。 | 两者都是;向存储过程传递初始值;返回更新后的值给调用者。 |
| 形式参数在存储过程中是作为常量还是变量? | 形式参数像常量一样起作用。 | 形式参数像未初始化的变量一样起作用。 | 形式参数像初始化的变量一样起作用。 |
| 形式参数是否可以在存储过程中分配值? | 形式参数不能分配值。 | 形式参数不能在表达式中使用;必须分配值。 | 形式参数必须分配值。 |
| 可以传递给存储过程的实际参数(参数)有哪些? | 实际参数可以是常量、初始化变量、文字常量或表达式。 | 实际参数必须是一个变量。 | 实际参数必须是一个变量。 |
在 Java DB 中创建存储过程
注意:有关在 Java DB 中创建存储过程的更多信息,请参阅Java DB 参考手册中的“CREATE PROCEDURE 语句”部分。
在 Java DB 中创建和使用存储过程涉及以下步骤:
-
在 Java 类中创建一个公共静态 Java 方法:此方法执行存储过程所需的任务。
-
创建存储过程:此存储过程调用您创建的 Java 方法。
-
调用存储过程
-
将包含您之前创建的公共静态 Java 方法的 Java 类打包到一个 JAR 文件中。
创建公共静态 Java 方法
下面的方法,StoredProcedureJavaDBSample.showSuppliers,包含存储过程SHOW_SUPPLIERS调用的 SQL 语句:
public static void showSuppliers(ResultSet[] rs)
throws SQLException {
Connection con = DriverManager.getConnection("jdbc:default:connection");
Statement stmt = null;
String query =
"select SUPPLIERS.SUP_NAME, " +
"COFFEES.COF_NAME " +
"from SUPPLIERS, COFFEES " +
"where SUPPLIERS.SUP_ID = " +
"COFFEES.SUP_ID " +
"order by SUP_NAME";
stmt = con.createStatement();
rs[0] = stmt.executeQuery(query);
}
SHOW_SUPPLIERS存储过程不接受任何参数。您可以通过在公共静态 Java 方法的方法签名中定义参数来指定存储过程中的参数。请注意,方法showSuppliers包含一个类型为ResultSet[]的参数。如果您的存储过程返回任意数量的ResultSet对象,请在您的 Java 方法中指定一个类型为ResultSet[]的参数。此外,请确保此 Java 方法是公共的和静态的。
从 URL jdbc:default:connection 中检索Connection对象。这是 Java DB 中的一种约定,表示存储过程将使用当前存在的Connection对象。
请注意,在此方法中未关闭Statement对象。不要在存储过程的 Java 方法中关闭任何Statement对象;如果这样做,当您调用存储过程时,ResultSet对象将不存在。
为了使存储过程返回一个生成的结果集,您必须将结果集分配给ResultSet[]参数的一个数组组件。在本例中,生成的结果集分配给了数组组件rs[0]。
使用 SQL 脚本或 JDBC API 在 Java DB 中创建存储过程
Java DB 使用 Java 编程语言进行存储过程。因此,当您定义存储过程时,您需要指定要调用的 Java 类以及 Java DB 可以找到它的位置。
以下摘录自StoredProcedureJavaDBSample.createProcedures创建了一个名为SHOW_SUPPLIERS的存储过程:
public void createProcedures(Connection con)
throws SQLException {
Statement stmtCreateShowSuppliers = null;
// ...
String queryShowSuppliers =
"CREATE PROCEDURE SHOW_SUPPLIERS() " +
"PARAMETER STYLE JAVA " +
"LANGUAGE JAVA " +
"DYNAMIC RESULT SETS 1 " +
"EXTERNAL NAME " +
"'com.oracle.tutorial.jdbc." +
"StoredProcedureJavaDBSample." +
"showSuppliers'";
// ...
try {
System.out.println("Calling CREATE PROCEDURE");
stmtCreateShowSuppliers = con.createStatement();
// ...
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
if (stmtCreateShowSuppliers != null) {
stmtCreateShowSuppliers.close();
}
// ...
}
}
以下列表描述了您可以在CREATE PROCEDURE语句中指定的过程元素:
-
PARAMETER STYLE:标识用于将参数传递给存储过程的约定。以下选项有效:-
JAVA:指定存储过程使用符合 Java 语言和 SQL 例程规范的参数传递约定。 -
DERBY:指定存储过程支持参数列表中的最后一个参数作为可变参数。
-
-
LANGUAGE JAVA:指定存储过程的编程语言(目前,JAVA是唯一的选项)。 -
DYNAMIC RESULT SETS 1:指定检索的最大结果集数量;在本例中为1。 -
EXTERNAL NAME 'com.oracle.tutorial.jdbc.StoredProcedureJavaDBSample.showSuppliers'指定了此存储过程调用的完全限定的 Java 方法。注意:Java DB 必须能够在类路径或直接添加到数据库的 JAR 文件中找到此处指定的方法。请参阅以下步骤,将 Java 类打包到 JAR 文件中。
以下语句(位于 StoredProcedureJavaDBSample.createProcedures 中)创建了一个名为 GET_SUPPLIERS_OF_COFFEE 的存储过程(为了清晰起见添加了换行符):
CREATE PROCEDURE GET_SUPPLIER_OF_COFFEE(
IN coffeeName varchar(32),
OUT supplierName
varchar(40))
PARAMETER STYLE JAVA
LANGUAGE JAVA
DYNAMIC RESULT SETS 0
EXTERNAL NAME 'com.oracle.tutorial.jdbc.
StoredProcedureJavaDBSample.
getSupplierOfCoffee'
此存储过程有两个形式参数,coffeeName 和 supplierName。参数说明符 IN 和 OUT 被称为参数模式。它们定义了形式参数的操作。有关更多信息,请参阅参数模式。此存储过程不检索结果集,因此过程元素 DYNAMIC RESULT SETS 为 0。
以下语句创建了一个名为 RAISE_PRICE 的存储过程(为了清晰起见添加了换行符):
CREATE PROCEDURE RAISE_PRICE(
IN coffeeName varchar(32),
IN maximumPercentage float,
INOUT newPrice float)
PARAMETER STYLE JAVA
LANGUAGE JAVA
DYNAMIC RESULT SETS 0
EXTERNAL NAME 'com.oracle.tutorial.jdbc.
StoredProcedureJavaDBSample.raisePrice'
您可以使用 SQL 脚本在 Java DB 中创建存储过程。查看脚本 javadb/create-procedures.sql 和 build.xml Ant 构建脚本中的 Ant 目标 javadb-create-procedure。
在 Java DB 中调用存储过程
以下摘录自方法StoredProcedureJavaDBSample.runStoredProcedures 调用存储过程 SHOW_SUPPLIERS 并打印生成的结果集:
cs = this.con.prepareCall("{call SHOW_SUPPLIERS()}");
ResultSet rs = cs.executeQuery();
while (rs.next()) {
String supplier = rs.getString("SUP_NAME");
String coffee = rs.getString("COF_NAME");
System.out.println(supplier + ": " + coffee);
}
注意:与 Statement 对象一样,要调用存储过程,可以根据过程返回多少个 ResultSet 对象来调用 execute、executeQuery 或 executeUpdate。但是,如果不确定过程返回多少个 ResultSet 对象,请调用 execute。
以下摘录自方法 StoredProcedureJavaDBSample.runStoredProcedures 调用存储过程 GET_SUPPLIER_OF_COFFEE:
cs = this.con.prepareCall("{call GET_SUPPLIER_OF_COFFEE(?, ?)}");
cs.setString(1, coffeeNameArg);
cs.registerOutParameter(2, Types.VARCHAR);
cs.executeQuery();
String supplierName = cs.getString(2);
接口 CallableStatement 扩展了 PreparedStatement。它用于调用存储过程。像使用 PreparedStatement 对象一样,通过调用适当的 setter 方法为 IN 参数(例如本例中的 coffeeName)指定值。但是,如果存储过程包含 OUT 参数,则必须使用 registerOutParameter 方法进行注册。
以下摘录自方法 StoredProcedureJavaDBSample.runStoredProcedures 调用存储过程 RAISE_PRICE:
cs = this.con.prepareCall("{call RAISE_PRICE(?,?,?)}");
cs.setString(1, coffeeNameArg);
cs.setFloat(2, maximumPercentageArg);
cs.registerOutParameter(3, Types.NUMERIC);
cs.setFloat(3, newPriceArg);
cs.execute();
因为参数 newPrice(过程 RAISE_PRICE 中的第三个参数)具有参数模式 INOUT,您必须通过调用适当的 setter 方法指定其值,并使用 registerOutParameter 方法进行注册。
将 Java 类打包到 JAR 文件中
Ant 构建脚本 build.xml 包含编译和打包教程为 JAR 文件的目标。在命令提示符下,将当前目录更改为 *<JDBC tutorial directory>*。从该目录运行以下命令编译并打包教程为 JAR 文件:
ant jar
JAR 文件的名称是 *<JDBC tutorial directory>*/lib/JDBCTutorial.jar。
Ant 构建脚本将文件 JDBCTutorial.jar 添加到类路径中。您还可以在 CLASSPATH 环境变量中指定 JAR 文件的位置。这样可以使 Java DB 找到存储过程调用的 Java 方法。
直接向数据库添加 JAR 文件
Java DB 首先在类路径中查找所需的类,然后在数据库中查找。本节展示了如何直接向数据库添加 JAR 文件。
使用以下系统存储过程将 JDBCTutorial.jar JAR 文件添加到数据库中(为了清晰起见已添加换行符):
CALL sqlj.install_jar(
'*<JDBC tutorial directory>*/
lib/JDBCTutorial.jar',
'APP.JDBCTutorial', 0)
CALL sqlj.replace_jar(
'*<JDBC tutorial directory>*/
lib/JDBCTutorial.jar',
'APP.JDBCTutorial')";
CALL syscs_util.syscs_set_database_property(
'derby.database.classpath',
'APP.JDBCTutorial')";
注意:方法 StoredProcedureJavaDBSample.registerJarFile 演示了如何调用这些系统存储过程。如果调用此方法,请确保已修改 javadb-sample-properties.xml,使属性 jar_file 的值设置为 JDBCTutorial.jar 的完整路径名。
SQL 模式中的 install_jar 过程向数据库添加 JAR 文件。此过程的第一个参数是在运行此过程的计算机上 JAR 文件的完整路径名。第二个参数是 Java DB 用于引用 JAR 文件的标识符。(标识符 APP 是 Java DB 默认模式。)replace_jar 过程替换数据库中已有的 JAR 文件。
系统存储过程 SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY 在当前连接上设置或删除数据库属性的值。此方法将属性 derby.database.classpath 设置为 install_jar 文件中指定的标识符。Java DB 首先在 Java 类路径中查找类,然后查找 derby.database.classpath。
在 MySQL 中创建存储过程
在 Java DB 中创建和使用存储过程涉及以下步骤:
-
使用 SQL 脚本或 JDBC API 创建存储过程。
-
使用
CALLSQL 语句调用存储过程。参见 在 MySQL 中调用存储过程 部分。
使用 SQL 脚本或 JDBC API 在 MySQL 中创建存储过程
MySQL 使用基于 SQL 的语法来编写存储过程。以下摘录来自 SQL 脚本 mysql/create-procedures.sql 创建了名为 SHOW_SUPPLIERS 的存储过程:
SELECT 'Dropping procedure SHOW_SUPPLIERS' AS ' '|
drop procedure if exists SHOW_SUPPLIERS|
# ...
SELECT 'Creating procedure SHOW_SUPPLIERS' AS ' '|
create procedure SHOW_SUPPLIERS()
begin
select SUPPLIERS.SUP_NAME,
COFFEES.COF_NAME
from SUPPLIERS, COFFEES
where SUPPLIERS.SUP_ID = COFFEES.SUP_ID
order by SUP_NAME;
end|
DROP PROCEDURE 语句会删除存储过程 SHOW_SUPPLIERS(如果存在的话)。在 MySQL 中,存储过程中的语句用分号分隔。然而,结束 create procedure 语句需要一个不同的分隔符。这个示例使用了竖线(|)字符;你可以使用其他字符(或多个字符)。分隔语句的字符在调用这个脚本的 Ant 目标中的 delimiter 属性中定义。这段摘录来自 Ant 构建文件 build.xml(为了清晰起见插入了换行符):
<target name="mysql-create-procedure">
<sql driver="${DB.DRIVER}"
url="${DB.URL}" userid="${DB.USER}"
password="${DB.PASSWORD}"
classpathref="CLASSPATH"
print="true"
delimiter="|"
autocommit="false"
onerror="abort">
<transaction
src="./sql/${DB.VENDOR}/
create-procedures.sql">
</transaction>
</sql>
</target>
或者,你可以使用 DELIMITER SQL 语句来指定一个不同的分隔符字符。
CREATE PROCEDURE 语句由过程的名称、括号中以逗号分隔的参数列表以及 BEGIN 和 END 关键字内的 SQL 语句组成。
你可以使用 JDBC API 来创建存储过程。下面的方法 StoredProcedureMySQLSample.createProcedureShowSuppliers 执行了与前面脚本相同的任务:
public void createProcedureShowSuppliers() throws SQLException {
String queryDrop = "DROP PROCEDURE IF EXISTS SHOW_SUPPLIERS";
String createProcedure =
"create procedure SHOW_SUPPLIERS() " +
"begin " +
"select SUPPLIERS.SUP_NAME, COFFEES.COF_NAME " +
"from SUPPLIERS, COFFEES " +
"where SUPPLIERS.SUP_ID = COFFEES.SUP_ID " +
"order by SUP_NAME; " +
"end";
try (Statement stmtDrop = con.createStatement()) {
System.out.println("Calling DROP PROCEDURE");
stmtDrop.execute(queryDrop);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate(createProcedure);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
请注意,在这个方法中分隔符没有被改变。
存储过程 SHOW_SUPPLIERS 生成一个结果集,尽管方法 createProcedureShowSuppliers 的返回类型是 void,并且该方法不包含任何参数。当使用方法 CallableStatement.executeQuery 调用存储过程 SHOW_SUPPLIERS 时,会返回一个结果集:
CallableStatement cs = null;
cs = this.con.prepareCall("{call SHOW_SUPPLIERS}");
ResultSet rs = cs.executeQuery();
下面从方法 StoredProcedureMySQLSample.createProcedureGetSupplierOfCoffee 中提取的内容包含了创建名为 GET_SUPPLIER_OF_COFFEE 的存储过程的 SQL 查询:
public void createProcedureGetSupplierOfCoffee() throws SQLException {
String queryDrop = "DROP PROCEDURE IF EXISTS GET_SUPPLIER_OF_COFFEE";
String createProcedure =
"create procedure GET_SUPPLIER_OF_COFFEE(IN coffeeName varchar(32), OUT supplierName varchar(40)) " +
"begin " +
"select SUPPLIERS.SUP_NAME into supplierName " +
"from SUPPLIERS, COFFEES " +
"where SUPPLIERS.SUP_ID = COFFEES.SUP_ID " +
"and coffeeName = COFFEES.COF_NAME; " +
"select supplierName; " +
"end";
try (Statement stmtDrop = con.createStatement()) {
System.out.println("Calling DROP PROCEDURE");
stmtDrop.execute(queryDrop);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate(createProcedure);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
这个存储过程有两个形式参数,coffeeName 和 supplierName。参数说明符 IN 和 OUT 被称为参数模式。它们定义了形式参数的作用。更多信息请参见 参数模式。形式参数在 SQL 查询中定义,而不是在方法 createProcedureGetSupplierOfCoffee 中。为了给 OUT 参数 supplierName 赋值,这个存储过程使用了一个 SELECT 语句。
下面从方法 StoredProcedureMySQLSample.createProcedureRaisePrice 中提取的内容包含了创建名为 RAISE_PRICE 的存储过程的 SQL 查询:
public void createProcedureRaisePrice() throws SQLException {
String queryDrop = "DROP PROCEDURE IF EXISTS RAISE_PRICE";
String createProcedure =
"create procedure RAISE_PRICE(IN coffeeName varchar(32), IN maximumPercentage float, INOUT newPrice numeric(10,2)) " +
"begin " +
"main: BEGIN " +
"declare maximumNewPrice numeric(10,2); " +
"declare oldPrice numeric(10,2); " +
"select COFFEES.PRICE into oldPrice " +
"from COFFEES " +
"where COFFEES.COF_NAME = coffeeName; " +
"set maximumNewPrice = oldPrice * (1 + maximumPercentage); " +
"if (newPrice > maximumNewPrice) " +
"then set newPrice = maximumNewPrice; " +
"end if; " +
"if (newPrice <= oldPrice) " +
"then set newPrice = oldPrice;" +
"leave main; " +
"end if; " +
"update COFFEES " +
"set COFFEES.PRICE = newPrice " +
"where COFFEES.COF_NAME = coffeeName; " +
"select newPrice; " +
"END main; " +
"end";
try (Statement stmtDrop = con.createStatement()) {
System.out.println("Calling DROP PROCEDURE");
stmtDrop.execute(queryDrop);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
try (Statement stmt = con.createStatement()) {
stmt.executeUpdate(createProcedure);
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
存储过程使用 SET 和 SELECT 语句给 INOUT 参数 newPrice 赋值。为了退出存储过程,存储过程首先将语句封装在一个标记为 main 的 BEGIN ... END 块中。为了退出过程,方法使用语句 leave main。
在 MySQL 中调用存储过程
在 MySQL 中调用存储过程与在 Java DB 中调用它们相同。
下面是从方法StoredProcedureMySQLSample.runStoredProcedures中调用存储过程SHOW_SUPPLIERS并打印生成的结果集:
cs = this.con.prepareCall("{call SHOW_SUPPLIERS}");
ResultSet rs = cs.executeQuery();
while (rs.next()) {
String supplier = rs.getString("SUP_NAME");
String coffee = rs.getString("COF_NAME");
System.out.println(supplier + ": " + coffee);
}
注意:与Statement对象一样,要调用存储过程,可以根据过程返回的ResultSet对象数量调用execute、executeQuery或executeUpdate。但是,如果不确定过程返回多少个ResultSet对象,请调用execute。
下面是从方法StoredProcedureMySQLSample.runStoredProcedures中调用存储过程GET_SUPPLIER_OF_COFFEE的摘录:
cs = this.con.prepareCall("{call GET_SUPPLIER_OF_COFFEE(?, ?)}");
cs.setString(1, coffeeNameArg);
cs.registerOutParameter(2, Types.VARCHAR);
cs.executeQuery();
String supplierName = cs.getString(2);
接口CallableStatement扩展了PreparedStatement。它用于调用存储过程。像使用PreparedStatement对象一样,通过调用适当的 setter 方法为IN参数(例如本例中的coffeeName)指定值。但是,如果存储过程包含OUT参数,必须使用registerOutParameter方法注册它。
下面是从方法StoredProcedureMySQLSample.runStoredProcedures中调用存储过程RAISE_PRICE的摘录:
cs = this.con.prepareCall("{call RAISE_PRICE(?,?,?)}");
cs.setString(1, coffeeNameArg);
cs.setFloat(2, maximumPercentageArg);
cs.registerOutParameter(3, Types.NUMERIC);
cs.setFloat(3, newPriceArg);
cs.execute();
因为参数newPrice(过程RAISE_PRICE中的第三个参数)具有参数模式INOUT,您必须通过调用适当的 setter 方法指定其值,并使用registerOutParameter方法注册它。


浙公网安备 33010602011771号