使用高级数据类型
原文:
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 创建存储过程。
-
使用
CALL
SQL 语句调用存储过程。参见 在 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"
one rror="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
方法注册它。