JDBC之CallableStatement执行存储过程

article/2025/10/16 11:14:03

​ 在前面的一篇文章中,我们学习使用Statement、PreparedStatement来完成对数据表的增删改查。而存储过程作为数据库的重要组成部分(痛点,当时学的时候头发都掉了好几根😭),那JDBC是如何执行存储过程呢?今天我们就来看看如何使用CallableStatement接口来执行存储过程。(本文使用的数据库为MySQL)。

​ 兴奋、紧张,迫不及待,又要学新知识了。

资源分配图

文章目录

    • 1.存储过程简介
    • 2.CallableStatement接口
    • 3.执行无参的存储过程
    • 4.执行有入参的存储过程
    • 5.执行返回多个结果集的存储过程
    • 6.总结

1.存储过程简介

​ 在开始讲如何执行存储过程之前,我们先来简单的看下存储过程的相关概念。

​ 存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,它存储在数据库中,一次编译后永久有效,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象。

​ 上面的释义为百度百科上的词条,讲的也非常清晰了。我们在开发的过程中,经常在一个功能中,会执行多条SQL语句,这时就可以将这一组SQL语句封装在一个存储过程中,在应用端就可以通过调用存储过程来获取对应的结果,而不用执行许多SQL了。

这里说明一件事,关于是否应使用存储过程的争论由来已久,我写这篇文章也没有任何诱导你的想法,是否使用存储过程还需你自行评估。

​ 这里简单的讲下存储过程的优缺点:

​ 优点:1.执行速度快:存储过程预先创建好的,并且预先编译了,省去了每条SQL编译的时间;2.安全性高:可以避免SQL注入问题,避免暴露表结构和字段。

​ 缺点:1.对数据库依赖性比较大,数据库迁移改动会非常大;2.不适用于数据库集群,存储过程依赖于具体的库来实现的,如果数据是分布存储在多个库中,存储过程就很难处理了;3.可维护性差、可读性差:程序员无法直接看到代码,业务发生变更时不利于快速开发。

2.CallableStatement接口

​我们来看一张图:

资源分配图

​ 从上图可以看到,CallableStatement接口继承(extends)了PreparedStatement接口,也就是说,CallableStatement接口不仅可以执行存储过程,还可以使用PreparedStatement接口提供方法的执行DQL、DDL和DML语句。

​ 存储过程可能会包含入参(IN),出参(OUT),出入参(INOUT,既可做入参又可做出参),执行的结果也会返回int或是ResultSet,下面我们一起来看下如何使用CallableStatement来执行存储过程。

3.执行无参的存储过程

​ 环境搭建请参考前面的文章,这里数据库中有一个users表,我们就简单的操作这张表来演示存储过程的使用。

​ 首先我们创建一个无参的存储过程,SQL如下,逻辑很简单,就是查询users表中的所有用户信息。

CREATE PROCEDURE SelectAllUsers()
BEGINSELECT * FROM users;
END;

​ 下面我们来看下JDBC是怎么执行无参的存储过程的,代码如下,其中的JDBCUtil代码参考上文:

/*** 调用无参的存储过程,返回users表中的所有数据* * @return* @throws ClassNotFoundException* @throws SQLException*/
public List<User> callNoParamProcedure() throws ClassNotFoundException, SQLException {Connection connection = null;CallableStatement statement = null;ResultSet resultSet = null;try {// 获取数据库连接connection = JDBCUtil.getConnection();// 构建SqlString sql = "{ call SelectAllUsers() }";// 创建CallableStatement对象statement = connection.prepareCall(sql);// 执行存储过程resultSet = statement.executeQuery();// 迭代获取所有的用户while (resultSet.next()) {// new一个User实例User user = new User();user.setId(resultSet.getInt("id"));user.setUserName(resultSet.getString("name"));user.setPassword(resultSet.getString("password"));user.setEmail(resultSet.getString("email"));user.setBirthday(resultSet.getDate("birthday"));// 输出用户信息System.out.println("获取的用户信息为:" + user);}return userList;} catch (ClassNotFoundException e) {e.printStackTrace();throw e;} catch (SQLException e) {e.printStackTrace();throw e;} finally {JDBCUtil.release(resultSet, statement, connection);}
}

​ 上述代码执行后的运行结果如下:

资源分配图

4.执行有入参的存储过程

​ 上面我们讲了,存储过程可能会包含IN、OUT、INOUT参数,这里我们需要注意下,当存储过程中有OUT参数事,在执行存储过程前需要通过CallableStatement中的registerOutParameter方法先注册。

​ 这里创建如下存储过程:

CREATE PROCEDURE SelectNameByIdWithOut(IN in_id int, OUT out_name CHAR(50))
BEGINSELECT `name` into out_name from users where id = in_id;SELECT * FROM users WHERE id = in_id;
END;

​ 代码如下,:

*** 调用含参的存储过程, 返回users表中id对应的数据* * @return* @throws ClassNotFoundException* @throws SQLException*/
public void callInOutParamProcedure(int id) throws ClassNotFoundException, SQLException {Connection connection = null;CallableStatement statement = null;ResultSet resultSet = null;try {// 获取数据库连接connection = JDBCUtil.getConnection();// 构建SqlString sql = "{call SelectNameByIdWithParam(?,?)}";// 创建CallableStatement对象statement = connection.prepareCall(sql);// 设置IN参数的值statement.setInt(1, id);// 注册OUT参数statement.registerOutParameter(2, Types.VARCHAR);// 执行存储过程resultSet = statement.executeQuery();// 获取上面注册的OUT参数,这里的index和入参顺序一致System.out.println("Out参数返回的结果为:" + statement.getString(2));// 迭代获取用户while (resultSet.next()) {User user = new User();user.setId(resultSet.getInt("id"));user.setUserName(resultSet.getString("name"));user.setPassword(resultSet.getString("password"));user.setEmail(resultSet.getString("email"));user.setBirthday(resultSet.getDate("birthday"));// 输出用户信息System.out.println("获取的用户信息为:" + user);}} catch (ClassNotFoundException e) {e.printStackTrace();throw e;} catch (SQLException e) {e.printStackTrace();throw e;} finally {JDBCUtil.release(resultSet, statement, connection);}
}

​ 执行测试代码callInOutParamProcedure(2)的运行结果如下图所示:

资源分配图

5.执行返回多个结果集的存储过程

​ 在上面两个例子中,执行查询语句,查询的结果就是一个结果集,但是如果一个存储过程中有多个查询语句呢?我们在程序中要如何将所有的结果集都获取到呢?

​ 这里我们创建如下存储过程:

CREATE PROCEDURE SelectUserWithMlutiRs(IN in_id int, OUT out_name CHAR(50))
BEGINSELECT `name` into out_name from users where id = in_id;SELECT * FROM users WHERE id = in_id;SELECT * FROM users;
END;

​ 我们现在mysql中执行此存储过程(可以在WorkBench或者Navicat中,执行SQL即可,或者在MySQL客户端中执行),代码如下:

set @in_out='test-2';
CALL SelectUserWithMlutiRs(2, @in_out);
SELECT @in_out

​ 上述SQL的执行结果如下,其中结果1、结果1(2)为执行存储过程返回的结果集,可以看到,SelectUserWithMlutiRs返回了两个结果集。

资源分配图

​ 那我们在应用程序中又要怎么获取呢?

​ 这里我们需要通过调Statement对象中的getMoreResults方法切换到下一个结果集,并通过getResult方法获取。我们来看下代码:

/*** 调用含参的存储过程, 返回users表中id对应的数据* * @param id* @throws ClassNotFoundException* @throws SQLException*/
public void callMultiRsProcedure(int id) throws ClassNotFoundException, SQLException {Connection connection = null;CallableStatement statement = null;ResultSet resultSet = null;try {// 获取数据库连接connection = JDBCUtil.getConnection();// 构建SqlString sql = "{ call SelectUserWithMlutiRs(?,?) }";// 创建CallableStatement对象statement = connection.prepareCall(sql);// 设置IN参数的值statement.setInt(1, id);// 注册OUT参数statement.registerOutParameter(2, Types.VARCHAR);// 执行存储过程resultSet = statement.executeQuery();// 获取上面注册的OUT参数,这里的index和入参顺序一致System.out.println("Out参数返回的结果为:" + statement.getString(2));int i = 1;System.out.println("第" + i + "个结果集中的数据为:");while (resultSet.next()) {User user = new User();user.setId(resultSet.getInt("id"));user.setUserName(resultSet.getString("name"));user.setPassword(resultSet.getString("password"));user.setEmail(resultSet.getString("email"));user.setBirthday(resultSet.getDate("birthday"));System.out.println("获取的用户信息为:" + user);}i++;// 判断是否还有下一个结果集// 注意,此处不能直接调用,需先获取当前的ResultSet才可调用,否则上个结果集会丢失while (statement.getMoreResults()) {resultSet = statement.getResultSet();// 迭代获取用户// 迭代获取用户System.out.println("第" + i + "个结果集中的数据为:");// 判断是否还有下一个结果集// 注意,此处不能直接调用,需先获取当前的ResultSet才可调用,否则上个结果集会丢失while (resultSet.next()) {User user = new User();user.setId(resultSet.getInt("id"));user.setUserName(resultSet.getString("name"));user.setPassword(resultSet.getString("password"));user.setEmail(resultSet.getString("email"));user.setBirthday(resultSet.getDate("birthday"));System.out.println("获取的用户信息为:" + user);}i++;}} catch (ClassNotFoundException e) {e.printStackTrace();throw e;} catch (SQLException e) {e.printStackTrace();throw e;} finally {JDBCUtil.release(resultSet, statement, connection);}
}

​ 执行测试代码callMultiRsProcedure(3)的运行结果如下如所示:

资源分配图

6.总结

​ 本文是对如何使用CallableStatement接口调用存储过程的简单示例。存储过程因为其自身优缺点非常明显,具体是否需要使用还需自己进行评估。

参考阅读:

  1. 减少存储过程封装业务逻辑-web开发与传统软件开发的思维模式不同
  2. 支付宝数据库架构师冯大辉:谈数据库架构

​ 又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

​ Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。

​ 有任何疑问,可以评论区留言。


http://chatgpt.dhexx.cn/article/PkyMqjfd.shtml

相关文章

mysql创建定时任务执行存储过程

存储过程已添加好&#xff1a;https://blog.csdn.net/YXWik/article/details/127283316 1.创建定时器用来执行存储过程函数 create event delete_data on schedule every 10 second do call delete_data();这里的第一行代表的创建名称为delete_data的事件 第二行是执行周期为…

Oracle 定时任务执行存储过程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、创建存储过程一、创建定时任务 一、创建存储过程 CREATE OR REPLACE PROCEDURE TESTCREATE AS --存储过程名称I INT : 1; --变量 BEGINWHILE I < 10 LOOP -…

oracle执行存储过程参数,Oracle 执行存储过程四种方法(带参数 不带参数)

1.如果是命令窗口就用exec 存储过程名&#xff1a; 1 EXEC procedure;--procedure是存储过程名 2.如果是 SQL窗口就用 begin 存储过程名 end; 1 2 3 begin procedure;--procedure是存储过程名 end; 3.如果是程序中调用就用 call 存储过程名 &#xff0c;举个栗子&#xff1…

plsql定时执行存储过程

1、创建定时向表中插入数据的存储过程&#xff0c;名为testJob。 CREATE OR REPLACE PROCEDURE "testJob" AS BEGIN EXECUTE IMMEDIATE INSERT INTO TABLE_HIS SELECT * FROM TABLE_AI; COMMIT; END; 2、使用plsql找到定时器对应的DBMS_Jobs文件夹&#xff0c;…

SQLserver存储过程简单写法与设置定时执行存储过程方法

最近工作中需要写SQLserver的存储过程&#xff0c;第一次使用&#xff0c;简单记录下&#xff0c;以防遗忘。 在SQLserver可视化工具中编写&#xff0c;我的工具如下图&#xff1a; 首先点击你的数据库&#xff0c;找到可编程性&#xff0c;在可编程性里面右击存储过程-->点…

symlink() 函数

查看更多 https://www.yuque.com/docs/share/10f959a4-bd7e-47a9-ad78-11a1310613f3

Install fail! Error: EPERM: operation not permitted, symlink

这个是在安装lodash的时候报错的情况 尝试了下方命令&#xff0c;但是还是有误 在这里可能是因为npm缓存的问题&#xff0c;我先删除了 C:\Users\abc(自己电脑的用户名) 文件夹下的 .npmrc 文件&#xff0c;但是没反应。然后运行了下方的命令&#xff1a; 运行&#xff1a;…

error: eperm: operation not permitted, symlink

查询当前配置的镜像 npm get registry > https://registry.npmjs.org/ 设置成淘宝镜像 npm config set registry http://registry.npm.taobao.org/

Linux Symbolic Links(软链接)

linux 中的链接有两种&#xff0c;分别是 Hard Links&#xff08;硬链接&#xff09;和Symbolic Links(软链接)。 1. 什么是硬链接、软链接&#xff1f; 硬链接&#xff1a;在另外一个位置创建源文件的链接文件&#xff0c;相当于复制了一份&#xff0c;占用资源会倍增。硬链…

linux link/symlink/unlink 硬连接和软连接介绍

文章目录 硬连接和软连接的区别硬连接软连接 链接命令lnln指令参数含义例子 硬链接函数link()符号链接函数symlink()解除链接函数unlink() link/symlink/unlink函数头文件为#include <unistd.h> 硬连接和软连接的区别 硬连接 硬连接指通过索引节点来进行连接。 在Lin…

​2021-12-13 Android 的 init.rc 文件​里面的symlink,把<target>链接到目录<path>下。

一、symlink <target> <path>&#xff0c;把<target>链接到目录<path>下。 二、来看一下实际的例子 三、参考文章&#xff1a; Android 的 init.rc 文件简介 &#xff08;转&#xff09; - 陈wei的个人空间 - OSCHINA - 中文开源技…

pnpm安装使用教程以及pnpm node版本管理以及EPERM operation not permitted symlink问题解决

pnpm安装使用教程以及pnpm node版本管理以及EPERM operation not permitted symlink问题解决 pnpm&#xff1a;目前来看是一个大趋势&#xff0c;如&#xff1a;nest-cli目前除了npm、yarn增加了pnpm 优势&#xff1a;节约磁盘空间并提升安装速度&#xff08;改善node_modules…

Cannot create symlink/symbolic to `xxx': Operation not supported

1、Cannot create symlink to xxx: Operation not supported 在虚拟机的共享目录中&#xff0c;解压内核源码&#xff0c;试过了几种解压方法都报出如下错误&#xff1a; tar: linux-4.15/tools/testing/selftests/powerpc/vphn/vphn.h: Cannot create symlink to ../../../.…

Flutter报错Building with plugins requires symlink support的解决方法

错误 Building with plugins requires symlink support. Please enable Developer Mode in your system settings. Runstart ms-settings:developers to open settings. 原因 这个错误表示你的系统尚未启用开发者模式,所以无法使用Flutter的插件功能。 Flutter插件会通过符号…

硬链接,软链接,link,rename,symlink,opendir和readdir

什么是硬链接 struct stat {nlink_t st_nlink; /* Number of hard links};stat结构体就有一个成员变量----硬链接数 使用ln命令就可以创建硬链接 创建硬链接&#xff0c;就是再为文件创建一个名字 每创建一个硬链接&#xff0c;文件就多一个文件名&#xff0c;硬件链…

符号链接symlink_什么是符号链接或符号链接? 如何为Windows和Linux创建Symlink?

符号链接symlink Symbolic Links are also known as Symlinks. Symlinks are used to create a shortcut for a given file or folder. Symlinks are very useful for different purposes which also prevents copy the same data over and over again. In this tutorial, we w…

Visual Studio2019使用nmake编译调用libcurl库

编译 1.下载地址&#xff1a; curl downloads 我下载的是7.61.0 2.编译&#xff1a; 使用的是&#xff1a;VS2019 x64 本机工具命令提示&#xff0c;当然如果想编译成X86的库&#xff0c;可以选择VS2013 x86 本机工具命令提示或者VS2019 x64 兼容工具命令提示。 打开VS201…

cl.exe nmake.exe

1. 如果已经有vc6的dsp工程&#xff0c;可直接导出nmake脚本文件(.mak) “Project - Export Makefile...” nmake -f nMakeTest.mak CFG"nMakeTest - Win32 Debug" nmake -f nMakeTest.mak CFG"nMakeTest - Win32 Debug" all nmake -f nMakeTest.mak CFG&q…

Windows环境下用nmake编译libevent

Windows环境下用nmake编译libevent 一、方法总结1) nmake 命令找不到2) 头文件找不到3) lib库找不到4) 不知道如何生成“print-winsock-errors.obj” 二、详细说明1、nmake 和 cl 命令2、各种头文件缺失3、lib 库无法打开4、“print-winsock-errors.obj” 三、结语 最近在将一个…

nmake、makefile、cmake学习笔记

1.nmake 1.1 nmake reference NMAKE.EXE是Visual Studio附带的一个命令行工具&#xff0c;它基于描述文件中包含的命令生成项目。 要使用NMAKE&#xff0c;必须在开发人员命令提示符窗口中运行它。开发人员命令提示符窗口为工具、库设置了环境变量&#xff0c;并包含在命令行…