基于oracle数据库存储过程的创建及调用
教学大纲:
- PLSQL编程: Hello World、程序结构、变量、流程控制、游标
- 存储过程: 概念、无参存储、有参存储(输入、输出)
- JAVA调用存储过程
1、PLSQL编程
1.1、概念和目的
什么是PL/SQL
- PL/SQL(Procedure Language/SQL)
- PLSQL是Oracle对sql语言的过程化扩展(类似于Basic)
- 指在SQL命令语言中增加了过程处理语句(如分支、循环等),使SQL语言具有过程处理能力
1.2、程序结构
通过Plsql Developer工具的Test Window创建 程序模板或者通过语句在SQL Window编写
提示:PLSQL语言是不区分大小写的
PL/SQL可以分为三个部分: 声明部分、可执行部分、异常处理部分
-- Created on 2020/11/23 by HASSD
declare
-- 声明游标、变量
i integer;
begin
-- 执行语句
-- [异常处理]
end;
其中 declare部分用来声明变量或游标(结果集类型变量),如果程序中无变量声明则可以省略
1.3、Hello World
begin
-- 打印Hello World
dbms_output.put_line('Hello World');
end;
其中 dbms_output为oracle内置程序包,相当于Java中的Syste.out,而put_line()是调用其中的方法,相当于println().
1.4、变量
PLSQL编程中常见的变量分两大类:
- 普通数据类型(char,varchar2,date,number,boolean,long)
- 特殊变量类型(引用型变量,记录型变量)
变量的声明方式为:
-- 变量名 变量类型(变量长度)
v_name varchar2(20);
1.4.1、普通变量
为了验证本节,我们需要提前创建一个表
create table EMP
(
empno NUMBER not null,
ename VARCHAR2(20),
sal NUMBER
)
并在其中至少存入一条数据(我在里面存了一条empno为7839的数据,其它值可以自己编写,自己填入其它数据也可以,只需要在后续验证过程中修改对应数值即可)
变量赋值的方式有两种:
- 直接赋值语句: =
- 语句赋值: 使用select…into…赋值 (select 值 into 变量)
-- 打印人员个人信息,包括姓名,薪水,地址
declare
-- 姓名
v_name varchar2(20) := '小杨'; -- 声明变量时直接赋值
-- 薪水
v_sal number;
--地址
v_addr varchar2(200);
begin
-- 在程序中直接赋值
v_sal :=0;
-- 语句赋值
select '湖南工业大学' into v_addr from dual;
-- 打印输出 拼接(使用"||"拼接,效果与java语句中使用"+"拼接相同)
dbms_output.put_line('姓名:'||v_name||',薪水:' ||v_sal ||',地址:'|| v_addr );
end;
1.4.2、引用型变量
变量的类型和长度取决于表中字段的类型和长度
通过表名.列名%type指定变量的类型和长度,例如:v_name emp.ename%TYPE;
示例:查询emp表中7839号员工的个人信息,打印姓名和薪水
对比:
这是我们使用普通变量:
-- Created on 2020/11/23 by HASSD
-- 查询emp表中7839号员工的个人信息,打印姓名和薪水
declare
--姓名
v_name varchar2(20);
--薪水
v_sal number;
begin
select ename,sal into v_name,v_sal from emp where empno=7839;
--打印输出
dbms_output.put_line('姓名:'||v_name||',薪水:'||v_sal);
end;
使用引用型变量:
-- Created on 2020/11/23 by HASSD
-- 查询emp表中7839号员工的个人信息,打印姓名和薪水
declare
--姓名
v_name emp.ename%type;
--薪水
v_sal emp.sal%type;
begin
select ename,sal into v_name,v_sal from emp where empno=7839;
--打印输出
dbms_output.put_line('姓名:'||v_name||',薪水:'||v_sal);
end;
观察上面两段语句,可以清楚的看到,二者的区别之处就在于v_name varchar2(20); v_sal number;和v_name emp.ename%type; v_sal emp.sal%type;上,在使用普通变量时.我们在声明变量时必须指明变量的数据类型及长度,而在使用引用型变量时则不需要指明,只要在变量名后面写上表名(A).字段名(a)%type,那么此变量的数据类型和长度就会自动与A表中的a字段保持一致
显然,普通变量可以在我们对我们使用的表有足够了解的情况下(了解表中各个字段的数据类型,长度等信息),而在我们对表没有那么了解时,则适合使用引用型变量
1.4.3、记录型变量
接受表中的一整行记录,相当于Java中的一个对象
语法: 变量名称 表名%rowtype 例如:v_emp emp%rowtype
示例:
查询并打印7839号员工的姓名与薪水
-- Created on 2020/11/23 by HASSD
-- 查询emp表中7839号员工的个人信息,打印姓名和薪水
declare
--记录型变量
v_emp emp%rowtype;
begin
select * into v_emp from emp where empno=7839;
--打印输出
dbms_output.put_line('姓名:'||v_emp.ename||',薪水:'||v_emp.sal);
end;
如上面的语句所示,我们申明了一个记录型变量v_emp,也就是表示v_emp记录的是emp表中的某一行数据(包括里面所有字段),而在我们调用里面的字段时,采取适的方法与java类似(把这个v_emp看成一个java对象),我们采取v_emp.ename和v_emp.sal的方式(记录型变量名.表中字段名)来调取里面某一字段的值
记录型变量适合用于我们需要使用的字段较多时,比如某个表存在100个变量,我们都需要使用,这个时候如果采取其它两种变量,那么我们需要对应的创建100个变量,而使用记录型变量则只需要一个
1.5、流程控制
1.5.1、条件分支
语法:
begin
if 条件1 then 执行1
elsif 条件2 then 执行2
else 执行3
end if;
end;
注意关键字:elsif
示例:
判断emp表中记录是否超过20条,10-20之间或10条以下:
-- 判断emp表中记录是否超过20条,10-20之间或者10条以下
declare
-- 声明变量接收emp表中的数量
v_count number;
begin
select count(1) into v_count from emp;
if v_count > 20 then
dbms_output.put_line('emp表中的记录数超过了20条,一共:'||v_count||'条数据');
elsif v_count >=10 then
dbms_output.put_line('emp表中的记录数在10-20之间,一共:'||v_count||'条数据');
else
dbms_output.put_line('emp表中的记录数在10条以下,一共:'||v_count||'条数据');
end if;
end;
1.5.2、循环
在Oracle中有三种循环方式,这里只介绍一种:loop循环
语法:
begin
loop
exit when 退出循环的条件
循环体
end loop;
end;
示例:
打印数字1-10:
declare
--声明循环变量并赋初值
v_num number :=1;
begin
loop
exit when v_num > 10;
dbms_output.put_line(v_num);
--循环变量自增
v_num := v_num+1;
end loop;
end;
2、游标
2.1、什么是游标
用于临时存储一个查询返回的多行数据(结果集,类似于Java的jdbc连接返回的ResultSet集合),通过遍历游标,可以逐行访问该处理结果集的数据
游标的使用方式:声明–>打开–>读取–>关闭
2.2、语法
cursor 游标名[(参数列表)] is 查询语句
--游标的打开:
open 游标名;
--游标的取值:
fetch 游标名 into 变量列表;
--游标的关闭:
close 游标名;
2.3、游标的属性
游标的属性 | 返回值类型 | 说明 |
---|---|---|
%ROWCOUNT | 整型 | 获得FETCH语句返回的数据行数 |
%FOUND | 布尔型 | 最近的FETCH语句返回一行数据则为真,否则为假 |
%NOTFOUND | 布尔型 | 与%FOUND属性返回值相反 |
%ISOPEN | 布尔型 | 游标已经打开时为真,否则为假 |
其中%NOTFOUND是在游标中找不到元素时返回TRUE,通常用来判断退出循环
2.4、创建和使用(不带参数的游标)
示例:
使用游标查询emp表中所以员工的姓名和工资,并将其依次打印出来
--使用游标查询emp表中所有员工的姓名和工资,并将其以此打印出来
declare
--声明游标
cursor c_emp is select ename,sal from emp;
--声明变量接收游标中的数据
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
--游标的打开:
open c_emp;
--遍历游标
loop
--获取游标中的数据
fetch c_emp into v_ename,v_sal;
--退出循环的条件
exit when c_emp%notfound;
dbms_output.put_line('员工姓名为:'||v_ename||',他的薪水是:'||v_sal);
end loop;
--游标的关闭:
close c_emp;
end;
2.5、带参数的游标
示例;
使用游标查询并打印某部门的员工的姓名和薪资,部门编号为运行时手动输入
--使用游标查询emp表中某部门员工的姓名和工资,部门编号为运行时手动输入
declare
--声明游标 同时声明了一个参数,且在后面加了一个判断
cursor c_emp(v_deptno emp.deptno%type) is select ename,sal from emp where deptno = v_deptno;
--声明变量接收游标中的数据
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
--游标的打开,打开游标时传入参数:
open c_emp(1);
--遍历游标
loop
--获取游标中的数据
fetch c_emp into v_ename,v_sal;
--退出循环的条件
exit when c_emp%notfound;
dbms_output.put_line('员工姓名为:'||v_ename||',他的薪水是:'||v_sal);
end loop;
--游标的关闭:
close c_emp;
end;
在以上语句中,我们在声明游标时还添加了一个参数用来表示员工的部门号,并且在后面加了一个条件"where deptno = v_deptno",而后在打开游标时传入了一个参数1(“open c_emp(1)”)那么游标里的内容表示的就是所有部门编号为1的员工的姓名和薪水
要注意判断退出循环语句和获取数据语句的顺序!!!
3、存储过程
3.1、概念作用
之前我们编写的QLSQL程序可以进行表的操作判断、循环逻辑处理的工作,但无法重复调用
可以理解之前的代码全部编写在了main方法中,是匿名程序,Java可以通过调用封装对象和方法来解决复用问题
PLSQL是将一个个PLSQL的业务处理过程存储起来进行复用,这些被存储起来的PLSQL程序被称之为存储过程
存储过程作用:
- 在开发程序中,为了一个特定的业务功能,会向数据库多次连接关闭(连接和关闭是很耗费资源的),需要对数据库进行多次I/O读写,如果把这些业务放到PLSQL中,在应用程序中只需要调用PLSQL就可以做到连接关闭一次,数据库就可以实现我们的业务,可以大大提高效率
- Oracle官方给的建议:能够让数据库操作的不要放在程序中,在数据库中实现基本上不会出现错误,在程序中操作可能会存在错误(如果在数据库中操作,可以有一定的日志恢复功能等).
3.2、语法
create or replace procedure 过程名称[(参数列表)] is
begin
end;
根据参数的类型,我们将其分为三类讲解:
- 不带参数的
- 带输入参数的
- 带输出参数的(带返回值)
3.3、无参存储
3.3.1、创建存储过程
通过PLsql Developer或者语句创建存储过程:
在我们使用PLsql Developer工具时,可以通过 新建–>程序窗口(Program Window)–>Procedure来新建存储过程
示例:
创建一个存储过程用来打印hello world:
create or replace procedure p_hello is --此处的is也可以改成as
begin
dbms_output.put_line('hello world');
end p_hello;
编辑完之后需要点击执行或者按F8来运行,当然,此处运行并不会输出"hello world"只是把我们刚才编写的这个存储过程保存起来了,而后通过调用这个存储过程来进行输出
注意在存储过程中没有declare关键字,declare用在语句块中
3.3.2、调用存储过程
- 通过PLSQL程序调用
begin
p_hello;
end;
点击执行之后,输出结果为"hello world"
- 在SQLPLUS中通过exec命令调用:
exec p_hello;
3.4、 带输入参数的存储过程
示例:
创建一个查询并打印某个员工(如7839号员工)的姓名和薪水的存储过程;要求: 调用的时候传入员工编号,自动控制台打印:
--查询并打印某个员工的姓名和薪水
create or replace procedure p_query(i_empno in emp.empno%type) is
--声明变量
v_name emp.ename%type;
v_sal emp.sal%type;
begin
-- 查询emp表中某个员工的姓名和薪水并赋值给变量
select ename,sal into v_name,v_sal from emp where empno = i_empno;
dbms_output.put_line('第'||i_empno||'号员工的姓名是'||v_name||',薪水是:'||v_sal);
end p_query;
而后在调用时 按照创建时的参数类型传入即可:
begin
p_query(7839);
end;
上列代码块表示的是调用p_query存储过程,并传入一个参数为7839
3.5、带输出参数的存储过程
示例:
输入员工号查询某个员工信息,要求:将薪水作为返回值输出,给调用的程序使用
--查询某个员工的薪水,并将薪水返回
create or replace procedure p_query_out(i_empno in emp.empno%type,o_sal out emp.sal%type) is
begin
-- 查询emp表中某个员工的姓名和薪水并赋值给变量
select sal into o_sal from emp where empno = i_empno;
end p_query_out;
调用过程如下:
declare
--声明一个变量用于接收存储过程中的输出参数
v_sal emp.sal%type;
begin
p_query_out(7839,v_sal);
dbms_output.put_line(v_sal);
end;
由于p_query_out这个存储过程存在输出参数(相当于java中的返回值),那么我们在调用此存储过程的时候就需要声明一个变量用来接收这个输出参数
3.6、Java程序调用存储过程
需求:如果一条语句无法实现结果集,比如需要多表查询,或者需要复杂逻辑查询,我们可以选择调用存储过程查询出你的结果
3.6.1、分析jdk API
通过Connection对象的prepareCall方法可以调用存储过程
通过Connection对象调用prepareCall方法传递一个转义sql语句调用存储过程,输入参数直接调用set方法传递,输出参数需要注册后执行存储过程,通过get方法获取,参数列表的下标是从1开始的
3.6.2、实现代码
准备环境:
-
导入Oracle的jar包(略,这个在前面MySQL里面就有,本次导入ojdbc8.jar即可)
-
实现代码
代码如下:
public class oracleTest { public static void main(String[] args) throws Exception { // 1.加载驱动 Class.forName("oracle.jdbc.OracleDriver"); // 2.获取连接对象 // 这里""内写法:jdbc:oracle:thin:主机:端口:实例名 String url = "jdbc:oracle:thin:@localhost:1521:orcl"; String user = "c##hassder"; String password = "******"; // 手动打码(这里填写你们的用户密码) Connection conn = DriverManager.getConnection(url,user,password); // 3.获得语句对象 // 这里""内写法:{call 存储过程名(?...)}//(这个括号里的?表示占位符,在mysql里有写过的 // 不知道的可以去看我前面关于mysql部分的,总之,你用的这个存储过程里有几个参数就打几个问号就行了,中间用,隔开) String sql = "{call p_query_out(?,?)}"; CallableStatement call = conn.prepareCall(sql); // 4.设置输入参数 // 这里也在mysql里说过 ()里面的1表示对应上面第一个?处位置的参数,参数值为7839 call.setInt(1,7839); // 5.注册输出参数 // 同理 ()表示第二个?处的参数,为OracleTypes.DOUBLE(注册哟) call.registerOutParameter(2, OracleTypes.DOUBLE); // 6.执行存储过程 call.execute(); // 7.获取输出参数 double sal = call.getDouble(2); System.out.println(sal); // 8.释放资源 call.close(); conn.close(); } }
如果路径没有问题的话,就会输出前面几节一直在使用的7839号员工的薪水数
文章最后 附带ojdbc下载途径(途径由作者于别处获取,不保证有效):
链接:https://pan.baidu.com/s/1agHs5vWeXf90r3OEeVGniw
提取码:wsgm