这些Java教程是为JDK 8编写的。本页面描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
请参阅Java语言更改,了解Java SE 9及其后续版本中更新的语言功能的概述。
请参阅JDK发行说明,了解所有JDK版本的新功能、增强功能和删除或弃用选项的信息。
存储过程是一组SQL语句,它们形成一个逻辑单元并执行特定任务,用于封装要在数据库服务器上执行的一组操作或查询。例如,对员工数据库的操作(招聘、解雇、晋升、查找)可以编写为由应用程序代码执行的存储过程。存储过程可以编译并以不同的参数和结果执行,并且它们可以具有任何组合的输入、输出和输入/输出参数。
请注意,大多数数据库管理系统都支持存储过程,但在语法和功能上有很多变化。因此,本教程包含两个示例,StoredProcedureJavaDBSample.java
和 StoredProcedureMySQLSample.java
,分别演示了如何在Java DB和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
: 打印咖啡coffeeName
的供应商supplierName
的名称。它需要以下参数:
IN coffeeName varchar(32)
: 咖啡的名称OUT supplierName varchar(40)
: 咖啡供应商的名称当示例以Colombian
作为coffeeName
的值调用此存储过程时,示例会产生类似以下的输出:
咖啡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
的值调用此存储过程时,示例会产生类似以下的输出:
调用 RAISE_PRICE 前 COFFEES 表的内容: 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 调用 RAISE_PRICE 过程 调用 RAISE_PRICE 后 newPrice 的值:8.79 调用 RAISE_PRICE 后 COFFEES 表的内容: 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 |
---|---|---|---|
是否必须在存储过程定义中指定? |
否;如果省略,则形式参数的参数模式为 |
必须指定。 |
必须指定。 |
参数是向存储过程传递值还是返回值? |
向存储过程传递值。 |
向调用者返回值。 |
既传递初始值给存储过程,又将更新后的值返回给调用者。 |
形式参数在存储过程中是作为常量还是变量? |
形式参数作为常量。 |
形式参数作为未初始化的变量。 |
形式参数作为已初始化的变量。 |
形式参数在存储过程中可以被赋值吗? |
形式参数不能被赋值。 |
形式参数不能在表达式中使用;必须被赋值。 |
形式参数必须被赋值。 |
可以传递哪些实际参数(参数)给存储过程? |
实际参数可以是常量、已初始化的变量、文字或表达式。 |
实际参数必须是变量。 |
实际参数必须是变量。 |
注意:有关在Java DB中创建存储过程的更多信息,请参阅《Java DB参考手册》中的"CREATE PROCEDURE语句"部分。
在Java DB中创建和使用存储过程涉及以下步骤:
下面的方法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]
。
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("调用 CREATE PROCEDURE"); stmtCreateShowSuppliers = con.createStatement(); // ... } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { if (stmtCreateShowSuppliers != null) { stmtCreateShowSuppliers.close(); } // ... } }
下面的列表描述了在 CREATE PROCEDURE
语句中可以指定的过程元素:
PARAMETER STYLE
: 标识用于传递参数给存储过程的约定。以下选项是有效的:
JAVA
: 指定存储过程使用符合Java语言和SQL routines规范的参数传递约定。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
和Ant目标javadb-create-procedure
在build.xml
Ant构建脚本中。
以下来自方法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
对象一样,要调用存储过程,可以调用execute
、executeQuery
或executeUpdate
,具体取决于存储过程返回的ResultSet
对象的数量。但是,如果不确定存储过程返回多少个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
,用于调用存储过程。通过调用适当的setter方法,像使用PreparedStatement
对象一样为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
方法进行注册。
Ant构建脚本build.xml
包含了编译和将教程打包成JAR文件的目标。在命令提示符下,将当前目录切换到<JDBC教程目录>
。从该目录下,运行以下命令来编译并将教程打包成JAR文件:
ant jar
JAR文件的名称为<JDBC教程目录>/lib/JDBCTutorial.jar
。
Ant构建脚本将文件JDBCTutorial.jar
添加到了类路径中。你也可以在CLASSPATH
环境变量中指定JAR文件的位置。这样可以让Java DB找到存储过程调用的Java方法。
Java DB首先在类路径中查找所需的类,然后在数据库中查找。本节将向你展示如何直接将JAR文件添加到数据库中。
使用以下系统过程将JDBCTutorial.jar
JAR文件添加到数据库中(为了清晰起见,添加了换行符):
CALL sqlj.install_jar( '<JDBC教程目录>/ lib/JDBCTutorial.jar', 'APP.JDBCTutorial', 0) CALL sqlj.replace_jar( '<JDBC教程目录>/ 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
中查找。
在Java DB中创建和使用存储过程涉及以下步骤:
CALL
SQL语句调用存储过程。参见在MySQL中调用存储过程部分。MySQL使用基于SQL的语法来定义存储过程。以下摘录来自SQL脚本mysql/create-procedures.sql
,创建了一个名为SHOW_SUPPLIERS
的存储过程:
SELECT '删除存储过程SHOW_SUPPLIERS' AS ' '| drop procedure if exists SHOW_SUPPLIERS| # ... SELECT '创建存储过程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("调用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("调用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("调用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
语句为newPrice
参数赋值。为了退出存储过程,首先将语句包含在标记为main
的BEGIN ... END
块中。使用leave main
语句退出过程。
在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
,用于调用存储过程。通过调用相应的setter方法为IN
参数(例如此示例中的coffeeName
)指定值,就像使用PreparedStatement
对象一样。但是,如果存储过程包含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
方法进行注册。