#调用存储过程

JDBC使用CallableStatement调用数据库中的存储过程和函数。应用程序通过Connection.prepareCall()创建CallableStatement,设置输入参数、注册输出参数,执行调用后再读取返回值。

# 调用方法

JDBC定义了统一的存储过程和函数调用格式。YashanDB JDBC支持以下CALL转义语法:

调用对象 JDBC调用格式 说明
存储过程 {call procedure_name(?, ...)} 每个?对应一个过程参数。无参数时使用{call procedure_name()}。
函数 {? = call function_name(?, ...)} 第一个?用于接收函数返回值,后续?对应函数参数。

CallableStatement常用参数方法如下:

参数类型 设置或读取方法 说明
IN setXxx(parameterIndex, value) 执行前设置输入值。
OUT registerOutParameter(parameterIndex, sqlType)、getXxx(parameterIndex) 执行前注册输出类型,执行后读取输出值。
INOUT setXxx()、registerOutParameter()、getXxx() 同一个参数位置既要设置输入值,也要注册并读取输出值。

参数索引从1开始。CallableStatement以及由OUT游标返回的ResultSet使用完毕后应及时关闭。存储过程内执行的DML受当前连接的自动提交和事务设置控制。

# 准备示例对象

以下示例使用jdbc_procedure_employee表和5个存储过程。运行Java示例前,使用具有创建表和存储过程权限的用户执行以下SQL:

DROP PROCEDURE IF EXISTS JDBC_PROC_NO_ARGS;
DROP PROCEDURE IF EXISTS JDBC_PROC_ADD_EMPLOYEE;
DROP PROCEDURE IF EXISTS JDBC_PROC_GET_SALARY;
DROP PROCEDURE IF EXISTS JDBC_PROC_DOUBLE_VALUE;
DROP PROCEDURE IF EXISTS JDBC_PROC_LIST_EMPLOYEE;
DROP TABLE IF EXISTS jdbc_procedure_employee;

CREATE TABLE jdbc_procedure_employee (
    employee_id INTEGER PRIMARY KEY,
    employee_name VARCHAR(50),
    department VARCHAR(30),
    salary NUMBER(10, 2)
);

INSERT INTO jdbc_procedure_employee
VALUES (1001, 'Alice', 'IT', 8000.00);

INSERT INTO jdbc_procedure_employee
VALUES (1002, 'Bob', 'IT', 9000.00);

CREATE OR REPLACE PROCEDURE JDBC_PROC_NO_ARGS AS
BEGIN
    NULL;
END;
/

CREATE OR REPLACE PROCEDURE JDBC_PROC_ADD_EMPLOYEE (
    P_ID IN INTEGER,
    P_NAME IN VARCHAR,
    P_DEPARTMENT IN VARCHAR,
    P_SALARY IN NUMBER
) AS
BEGIN
    INSERT INTO jdbc_procedure_employee
    VALUES (P_ID, P_NAME, P_DEPARTMENT, P_SALARY);
END;
/

CREATE OR REPLACE PROCEDURE JDBC_PROC_GET_SALARY (
    P_ID IN INTEGER,
    P_SALARY OUT NUMBER
) AS
BEGIN
    SELECT salary INTO P_SALARY
    FROM jdbc_procedure_employee
    WHERE employee_id = P_ID;
END;
/

CREATE OR REPLACE PROCEDURE JDBC_PROC_DOUBLE_VALUE (
    P_VALUE IN OUT NUMBER
) AS
BEGIN
    P_VALUE := P_VALUE * 2;
END;
/

CREATE OR REPLACE PROCEDURE JDBC_PROC_LIST_EMPLOYEE (
    P_DEPARTMENT IN VARCHAR,
    P_RESULT OUT SYS_REFCURSOR
) AS
BEGIN
    OPEN P_RESULT FOR
        SELECT employee_id, employee_name
        FROM jdbc_procedure_employee
        WHERE department = P_DEPARTMENT
        ORDER BY employee_id;
END;
/

# 调用示例

# 调用无参数存储过程

无参数存储过程仍使用一对圆括号。以下示例调用JDBC_PROC_NO_ARGS,execute()正常返回表示过程执行成功。

package quickstart;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

public class NoParameterProcedureExample {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:yasdb://192.168.1.2:1688/yasdb";
        Properties properties = new Properties();
        properties.setProperty("user", "username");
        properties.setProperty("password", "passwd");

        Class.forName("com.yashandb.jdbc.Driver");

        try (Connection connection =
                     DriverManager.getConnection(url, properties);
             CallableStatement statement = connection.prepareCall(
                     "{call JDBC_PROC_NO_ARGS()}")) {
            statement.execute();
            System.out.println("Procedure executed");
        }
    }
}

正常情况下,程序输出如下:

Procedure executed

# 调用带IN参数的存储过程

IN参数用于向存储过程传入数据。以下示例使用setInt()、setString()和setBigDecimal()设置4个IN参数,调用过程插入员工,再查询插入结果。

package quickstart;

import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

public class InParameterProcedureExample {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:yasdb://192.168.1.2:1688/yasdb";
        Properties properties = new Properties();
        properties.setProperty("user", "username");
        properties.setProperty("password", "passwd");

        Class.forName("com.yashandb.jdbc.Driver");

        try (Connection connection =
                     DriverManager.getConnection(url, properties)) {
            try (Statement statement = connection.createStatement()) {
                statement.executeUpdate(
                        "DELETE FROM jdbc_procedure_employee "
                                + "WHERE employee_id = 1003");
            }

            try (CallableStatement statement = connection.prepareCall(
                    "{call JDBC_PROC_ADD_EMPLOYEE(?, ?, ?, ?)}")) {
                statement.setInt(1, 1003);
                statement.setString(2, "Carol");
                statement.setString(3, "SALES");
                statement.setBigDecimal(4, new BigDecimal("7500.00"));
                statement.execute();
            }

            try (Statement statement = connection.createStatement();
                 ResultSet resultSet = statement.executeQuery(
                         "SELECT employee_name FROM "
                                 + "jdbc_procedure_employee "
                                 + "WHERE employee_id = 1003")) {
                if (resultSet.next()) {
                    System.out.println(
                            "Inserted employee: "
                                    + resultSet.getString(1));
                }
            }
        }
    }
}

正常情况下,程序输出如下:

Inserted employee: Carol

# 调用带OUT参数的存储过程

OUT参数用于从存储过程返回单个值。以下示例在执行前通过registerOutParameter()注册第二个参数为Types.NUMERIC,执行后使用getBigDecimal()读取员工薪资。

package quickstart;

import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
import java.util.Properties;

public class OutParameterProcedureExample {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:yasdb://192.168.1.2:1688/yasdb";
        Properties properties = new Properties();
        properties.setProperty("user", "username");
        properties.setProperty("password", "passwd");

        Class.forName("com.yashandb.jdbc.Driver");

        try (Connection connection =
                     DriverManager.getConnection(url, properties);
             CallableStatement statement = connection.prepareCall(
                     "{call JDBC_PROC_GET_SALARY(?, ?)}")) {
            statement.setInt(1, 1001);
            statement.registerOutParameter(2, Types.NUMERIC);
            statement.execute();

            BigDecimal salary = statement.getBigDecimal(2);
            System.out.println("Employee salary: " + salary);
        }
    }
}

正常情况下,程序输出如下:

Employee salary: 8000

# 调用带INOUT参数的存储过程

INOUT参数在同一位置接收输入值并返回输出值。以下示例先为第一个参数设置4000.00,再将同一参数注册为输出参数;过程执行后返回加倍后的值。

package quickstart;

import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
import java.util.Properties;

public class InOutParameterProcedureExample {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:yasdb://192.168.1.2:1688/yasdb";
        Properties properties = new Properties();
        properties.setProperty("user", "username");
        properties.setProperty("password", "passwd");

        Class.forName("com.yashandb.jdbc.Driver");

        try (Connection connection =
                     DriverManager.getConnection(url, properties);
             CallableStatement statement = connection.prepareCall(
                     "{call JDBC_PROC_DOUBLE_VALUE(?)}")) {
            statement.setBigDecimal(1, new BigDecimal("4000.00"));
            statement.registerOutParameter(1, Types.NUMERIC);
            statement.execute();

            BigDecimal result = statement.getBigDecimal(1);
            System.out.println("Result value: " + result);
        }
    }
}

正常情况下,程序输出如下:

Result value: 8000

# 获取存储过程返回的结果集

YashanDB存储过程可以通过SYS_REFCURSOR类型的OUT参数返回查询结果。JDBC调用时,使用Types.REF_CURSOR注册游标参数,执行后通过该参数的getObject()取得ResultSet。

以下示例将部门名称作为IN参数传入,并读取过程通过第二个OUT参数返回的员工结果集。ResultSet依赖对应的CallableStatement和连接,应在语句及连接关闭前完成读取和关闭。

package quickstart;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Types;
import java.util.Properties;

public class ResultSetProcedureExample {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:yasdb://192.168.1.2:1688/yasdb";
        Properties properties = new Properties();
        properties.setProperty("user", "username");
        properties.setProperty("password", "passwd");

        Class.forName("com.yashandb.jdbc.Driver");

        try (Connection connection =
                     DriverManager.getConnection(url, properties);
             CallableStatement statement = connection.prepareCall(
                     "{call JDBC_PROC_LIST_EMPLOYEE(?, ?)}")) {
            statement.setString(1, "IT");
            statement.registerOutParameter(2, Types.REF_CURSOR);
            statement.execute();

            try (ResultSet resultSet =
                         (ResultSet) statement.getObject(2)) {
                while (resultSet.next()) {
                    System.out.println(
                            resultSet.getInt("employee_id") + ", "
                                    + resultSet.getString(
                                            "employee_name"));
                }
            }
        }
    }
}

正常情况下,程序输出如下:

1001, Alice
1002, Bob

Note:

CallableStatement.getResultSet()用于获取当前执行结果中的直接结果集,不用于读取作为OUT参数返回的SYS_REFCURSOR。OUT游标应从对应参数调用getObject()获取。

# 清理示例对象

示例验证完成后,可以执行以下SQL删除测试对象:

DROP PROCEDURE IF EXISTS JDBC_PROC_NO_ARGS;
DROP PROCEDURE IF EXISTS JDBC_PROC_ADD_EMPLOYEE;
DROP PROCEDURE IF EXISTS JDBC_PROC_GET_SALARY;
DROP PROCEDURE IF EXISTS JDBC_PROC_DOUBLE_VALUE;
DROP PROCEDURE IF EXISTS JDBC_PROC_LIST_EMPLOYEE;
DROP TABLE IF EXISTS jdbc_procedure_employee;
edit-icon
反馈
coperate-icon
coperate
合作
communication-icon
communicate
交流