目录
-
核心概念
(图片来源网络,侵删)- JDBC 是什么?
- Oracle 存储过程的几种类型
- Java 调用存储过程的核心步骤
-
环境准备
- Java 开发环境
- Oracle JDBC 驱动 (ojdbc.jar)
- Oracle 数据库和存储过程
-
详细代码示例
- 示例 1:无参数的存储过程
- 示例 2:带 IN 参数的存储过程
- 示例 3:带 OUT 参数的存储过程
- 示例 4:带 IN OUT 参数的存储过程
- 示例 5:返回结果集的存储过程 (最复杂也最常用)
-
最佳实践与注意事项
- 使用
try-with-resources管理资源 - 使用
PreparedStatement防止 SQL 注入 - 处理大数据类型 (CLOB, BLOB)
- 事务管理
- 使用
核心概念
JDBC (Java Database Connectivity)
JDBC 是 Java 语言用来规范客户端程序如何访问数据库的应用程序接口,它提供了查询和更新数据库中数据的方法,简单说,Java 和数据库之间的“翻译官”。

Oracle 存储过程的几种类型
在 Java 中调用时,我们需要区分参数的类型:
- IN 参数:默认类型,数据从 Java 应用程序传递到存储过程,存储过程可以读取但不能修改这个值。
- OUT 参数:数据从存储过程传递回 Java 应用程序,调用前需要先注册,存储过程会为这个参数赋值。
- IN OUT 参数:兼具 IN 和 OUT 的功能,数据可以从 Java 传入,存储过程可以修改,然后将修改后的值传回 Java。
Java 调用存储过程的核心步骤
无论调用哪种类型的存储过程,基本流程都相似:
- 加载驱动:加载 Oracle JDBC 驱动类。
- 获取连接:使用
DriverManager获取到数据库的Connection对象。 - 创建 CallableStatement:这是关键一步,使用
Connection对象的prepareCall()方法,并传入一个调用存储过程的 SQL 字符串,这个字符串的格式是{call procedure_name(?, ?, ...)}。 - 设置参数:
- 对于
IN参数,使用setXxx()方法(如setString(),setInt())设置值。 - 对于
OUT参数,必须使用registerOutParameter()方法注册其数据类型。 - 对于
IN OUT参数,需要先registerOutParameter,再setXxx。
- 对于
- 执行:调用
CallableStatement的execute()或executeUpdate()方法。 - 获取结果:
- 对于
OUT参数,使用getXxx()方法(如getString(),getInt())获取返回值。 - 对于返回结果集的存储过程,需要通过
CallableStatement.getObject()获取REF CURSOR,然后像处理普通ResultSet一样处理它。
- 对于
- 关闭资源:关闭
CallableStatement,Connection等对象,释放数据库连接。
环境准备
Oracle JDBC 驱动
你需要下载 Oracle JDBC 驱动,通常是一个名为 ojdbcX.jar 的文件,你可以从 Oracle 官网或 Maven 仓库获取。
使用 Maven (推荐):
在你的 pom.xml 中添加依赖:

<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.3.0.0</version> <!-- 请根据你的Oracle版本选择合适的版本 -->
</dependency>
数据库和存储过程示例
假设我们有一个 HR schema,下面是我们将要调用的几个存储过程。
-- 1. 无参数的存储过程:打印一条信息
CREATE OR REPLACE PROCEDURE say_hello AS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello from Oracle Stored Procedure!');
END say_hello;
/
-- 2. 带IN参数的存储过程:根据ID查询员工姓名
CREATE OR REPLACE PROCEDURE get_employee_name (
p_emp_id IN NUMBER,
p_emp_name OUT VARCHAR2
)
AS
BEGIN
SELECT first_name INTO p_emp_name FROM employees WHERE employee_id = p_emp_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_emp_name := 'Employee not found';
END get_employee_name;
/
-- 3. 带IN OUT参数的存储过程:计算两个数的和
CREATE OR REPLACE PROCEDURE add_numbers (
p_num1 IN NUMBER,
p_num2 IN NUMBER,
p_sum OUT NUMBER
)
AS
BEGIN
p_sum := p_num1 + p_num2;
END add_numbers;
/
-- 4. 返回结果集的存储过程:查询某个部门的员工
-- 注意:需要先创建一个包来定义REF CURSOR的类型
CREATE OR REPLACE PACKAGE emp_data_pkg AS
TYPE emp_cursor_type IS REF CURSOR;
END emp_data_pkg;
/
CREATE OR REPLACE PROCEDURE get_employees_by_dept (
p_dept_id IN NUMBER,
p_emp_cursor OUT emp_data_pkg.emp_cursor_type
)
AS
BEGIN
OPEN p_emp_cursor FOR
SELECT employee_id, first_name, last_name
FROM employees
WHERE department_id = p_dept_id;
END get_employees_by_dept;
/
详细代码示例
我们将使用一个通用的数据库工具类来管理连接。
DatabaseUtil.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseUtil {
private static final String URL = "jdbc:oracle:thin:@//localhost:1521/ORCLPDB1"; // 请替换为你的数据库URL
private static final String USER = "hr"; // 请替换为你的用户名
private static final String PASSWORD = "your_password"; // 请替换为你的密码
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
}
示例 1:无参数的存储过程
调用 say_hello。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
public class CallSimpleProcedure {
public static void main(String[] args) {
// 使用 try-with-resources 自动关闭资源
try (Connection conn = DatabaseUtil.getConnection();
CallableStatement cstmt = conn.prepareCall("{call say_hello}")) {
// 执行存储过程
cstmt.execute();
System.out.println("Procedure 'say_hello' called successfully.");
// 注意:如果存储过程使用DBMS_OUTPUT,需要在Java中启用并捕获输出
// 这通常需要额外的配置,这里省略。
} catch (SQLException e) {
e.printStackTrace();
}
}
}
示例 2:带 IN 参数的存储过程
调用 get_employee_name,传入员工ID 101,获取姓名。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;
public class CallProcedureWithInParam {
public static void main(String[] args) {
String sql = "{call get_employee_name(?, ?)}";
try (Connection conn = DatabaseUtil.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
// 设置IN参数
cstmt.setInt(1, 101); // 第一个参数是IN类型,设置ID为101
// 注册OUT参数
// 第二个参数是OUT类型,类型为VARCHAR,对应Types.VARCHAR
cstmt.registerOutParameter(2, Types.VARCHAR);
// 执行
cstmt.execute();
// 获取OUT参数的值
String empName = cstmt.getString(2);
System.out.println("Employee Name: " + empName);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
示例 3:带 IN OUT 参数的存储过程
调用 add_numbers。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;
public class CallProcedureWithInOutParam {
public static void main(String[] args) {
String sql = "{call add_numbers(?, ?, ?)}";
try (Connection conn = DatabaseUtil.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
int num1 = 50;
int num2 = 25;
// 设置IN参数
cstmt.setInt(1, num1);
cstmt.setInt(2, num2);
// 注册OUT参数
cstmt.registerOutParameter(3, Types.INTEGER);
// 执行
cstmt.execute();
// 获取OUT参数的值
int sum = cstmt.getInt(3);
System.out.println(num1 + " + " + num2 + " = " + sum);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
示例 4:返回结果集的存储过程 (最常用)
这是最复杂也最实用的场景,Oracle 使用 REF CURSOR 来返回结果集。
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
public class CallProcedureWithResultSet {
public static void main(String[] args) {
// 注意:这里需要指定参数的类型,Oracle中REF CURSOR的类型是OracleCursor
// 或者使用标准的Types.REF_CURSOR (JDBC 3.0+)
String sql = "{call get_employees_by_dept(?, ?)}";
try (Connection conn = DatabaseUtil.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
int deptId = 50; // 查询部门ID为50的员工
// 设置IN参数
cstmt.setInt(1, deptId);
// 注册OUT参数
// 对于REF CURSOR,使用 Types.REF_CURSOR
cstmt.registerOutParameter(2, Types.REF_CURSOR);
// 执行
// 对于返回结果集的存储过程,execute() 返回 false
// 如果是 update/insert/delete,executeUpdate() 返回影响的行数
boolean hasResults = cstmt.execute();
// 获取结果集
// 使用 cstmt.getObject() 来获取 REF CURSOR 对象,并将其转换为 ResultSet
if (hasResults) {
// 注意:在Oracle驱动中,通常可以直接通过getResultSet获取
ResultSet rs = (ResultSet) cstmt.getObject(2);
System.out.println("Employees in Department " + deptId + ":");
System.out.println("---------------------------------");
while (rs.next()) {
int empId = rs.getInt("employee_id");
String firstName = rs.getString("first_name");
String lastName = rs.getString("last_name");
System.out.println("ID: " + empId + ", Name: " + firstName + " " + lastName);
}
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
最佳实践与注意事项
使用 try-with-resources
从 Java 7 开始,推荐使用 try-with-resources 语句来自动管理 Connection, Statement, ResultSet 等资源,它能确保这些资源在代码块执行完毕后自动关闭,即使发生异常也是如此。
// 推荐
try (Connection conn = DatabaseUtil.getConnection();
CallableStatement cstmt = conn.prepareCall(sql)) {
// ... your code
} catch (SQLException e) {
// ...
}
使用 PreparedStatement
对于带参数的存储过程,CallableStatement 本身就是 PreparedStatement 的子类,它已经内置了防止 SQL 注入的机制。永远不要使用字符串拼接的方式来构建调用语句。
// 错误示例!有SQL注入风险
String empId = "101";
String sql = "{call get_employee_name(" + empId + ", ?)}";
// 正确示例
String sql = "{call get_employee_name(?, ?)}";
CallableStatement cstmt = conn.prepareCall(sql);
cstmt.setInt(1, 101); // 使用set方法设置参数
处理大数据类型 (CLOB, BLOB)
处理 CLOB (字符大对象) 和 BLOB (二进制大对象) 时,需要使用专门的 java.sql.Clob 和 java.sql.Blob 接口。
写入 CLOB 示例:
// 假设存储过程 p_update_clob(p_id IN NUMBER, p_clob IN CLOB) cstmt.setInt(1, 123); String largeText = "This is a very large text content..."; // 将字符串转换为Clob java.sql.Clob clob = conn.createClob(); clob.setString(1, largeText); cstmt.setClob(2, clob);
读取 CLOB 示例:
// 假设存储过程 p_get_clob(p_id IN NUMBER, p_clob OUT CLOB) cstmt.registerOutParameter(2, Types.CLOB); cstmt.execute(); java.sql.Clob clob = cstmt.getClob(2); String content = clob.getSubString(1, (int) clob.length());
事务管理
默认情况下,JDBC 的 Connection 是在自动提交模式下的,在调用多个存储过程或执行多个SQL语句时,如果它们属于同一个业务逻辑单元,应该手动管理事务。
Connection conn = null;
try {
conn = DatabaseUtil.getConnection();
conn.setAutoCommit(false); // 关闭自动提交
// 调用存储过程1
CallableStatement cstmt1 = conn.prepareCall("{call procedure1(?)}");
// ... 设置参数并执行
cstmt1.execute();
// 调用存储过程2
CallableStatement cstmt2 = conn.prepareCall("{call procedure2(?)}");
// ... 设置参数并执行
cstmt2.execute();
conn.commit(); // 所有操作成功,提交事务
System.out.println("Transaction committed successfully.");
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // 发生异常,回滚事务
System.out.println("Transaction rolled back.");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true); // 恢复自动提交模式
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
希望这份详细的指南能帮助你掌握在 Java 中调用 Oracle 存储过程的全部要点!
