Java 中文官方教程 2022 版(三十六)(3)https://developer.aliyun.com/article/1488074
使用 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 文件的目标。在命令提示符下,将当前目录更改为 **
。从该目录运行以下命令编译并打包教程为 JAR 文件:
ant jar
JAR 文件的名称是 **/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" 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
方法注册它。