PL/SQL编程java
pl/sql(procedural language/sql)是Oracle在标准的sql语言上的扩展。pl/sql不只容许嵌入式sql语言,还能够定义变量和常量,容许使用条件语句和循环语句,容许使用例外处理各类错误。这样使得他的功能变的更强大。缺点是移植性很差。sql
编写一个存储过程,向表中添加数据。数据库
begin编程
insert into mytest values ('小红','m123');c#
end;数组
① exec xxc_pro1; 或者是安全
② call xxc_pro1;oracle
pl/sql能够作什么?ide
块:包括过程、函数、触发器、包。函数
编写规范:
eg:select * from emp where empno=7788;--取得员工信息
/*……*/多行注释
① 当定义变量时,建议用v_做为前缀:v_ename
② 当定义常量时,建议用c_做为前缀:c_rate
③ 当定义游标时,建议用_cursor做为后缀:emp_cursor
④ 当定义例外时,建议用e_做为前缀:e_error
块(block)是pl/sql的今本程序单元,编写pl/sql程序实际上就是在编写pl/sql块;pl/sql块由三部分组成:定义部分,执行部分,例外处理部分。
declare --可选部分
/*定义部分:定义常量,变量,游标,例外,复杂数据类型*/
begin --必选部分
/*执行部分:要执行的pl/sql语句和sql语句*/
exception --可选部分
/*例外处理部分:处理运行的各类错误*/
实例1:只包含执行部分的pl/sql块
SQL> set serveroutput on --打开输出
SQL> begin
2 dbms_output.put_line('hello');
3 end;
4 /
说明:dbms_output是oracle提供的包,该包包含一些过程,put_line就是其中之一。
实例2:包含定义部分和执行部分
SQL> declare
2 v_ename varchar2(5);
3 begin
4 select ename into v_ename from emp where empno = &no;
5 dbms_output.put_line('雇员名'||v_ename);
6 end;
7 /
说明:&:从控制台输入变量,会弹出一个对话框。
实例3.同时输出雇员名和工资
SQL> declare
2 v_ename varchar2(20);
3 v_sal number(10,2);
4 begin
5 select ename,sal into v_ename,v_sal from emp where empno=&no;
6 dbms_output.put_line('雇员名:'||v_ename||' 工资:'||v_sal);
7 end;
8 /
包含定义,执行,和例外处理的pl/sql块。
实例4.当输入的员工号不存在时
SQL> declare
2 v_ename varchar2(20);
3 v_sal number(10,2);
4 begin
5 select ename,sal into v_ename,v_sal from emp where empno =&no;
6 dbms_output.put_line('雇员名:'||v_ename||' 工资:'||v_sal);
7 exception --异常处理部分
8 when no_data_found then
9 dbms_output.put_line('请输入正确的员工号!');
10 end;
11 /
以上为块的基础,下面来介绍块的各个组成:过程,函数,触发器,包。
过程
过程用于执行特定的操做,当执行过程的时候,能够指定输入参数(in),也能够指定输出参数(out)。经过在过程当中使用输入参数,能够讲数据输入到执行部分,经过使用输出参数,能够将执行部分的数据输出到应用环境,在pl/sql中可使用create procedure命令来建立过程。
编写一个存储过程,能够输入雇员名和新工资来改变员工工资。
--案例
create or replace procedure xxc_pro3(newname in varchar2,newsal in number) is
begin
update emp set sal=newsal where ename=newname;
end;
调用 exec xxc_pro3(‘SCOTT’,2900);[A1]
--in表示是输入参数,能够不写,默认是in,但out必须写。
在java程序中调用存储过程来修改工资;
//演示java程序调用oracle中的存储过程
package TestOraPro;//根据不一样的包,包名不一样
import java.sql.*;
public class TestOraPro {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
//加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//获得链接 1521为端口号
Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:CUIXIAO2","scott","xxc");
//建立callablestatement
CallableStatement cs=ct.prepareCall("{call xxc_pro3(?,?)}");
//给?赋值
cs.setString(1, "SMITH");
cs.setInt(2,2600);
//执行
cs.execute();
}catch(Exception e){
e.printStackTrace();
} finally{
//关闭资源
cs.close();
ct.close();
}
}
}
以上为过程基础,后会详细讲。
函数
函数用于返回特定的数据,当创建函数时,在函数头部必须包含return子句,而在函数体内必须包含return语句来返回数据,可使用create function来创建函数。
案例1.返回工人年工资。
--函数案例
create or replace function xxc_fun1(newname varchar2)
return number is yearSal number(10,2);
begin
select sal*12+nvl(comm,0)*12 into yearSal from emp where ename=newname;
return yearSal;
end;
在sqlplus中调用函数
sql>var income number;
sql>call xxc_fun1(‘SCOTT’) into: income;
sql>print income;
在java中调用函数
select xxc_fun1(‘SCOTT’) from dual;
这样能够经过rs.setInt(1)获得返回结果。
包
包用于在逻辑上组合过程和函数,它由包规范和包体两部分组成。
1.咱们能够用create(or replace) package命令来建包;
实例:create package xxc_package is
Procedure update_sal (name varchar2,newsal number);
Function annual_sal(name varchar2) return number;
End;
包规只包含过程和函数的说明,可是没有函数和过程的实现代码。[A2]
包体用于实现包规范中的过程和函数。
2.创建包体能够用create package body命令。
create package body xxc_package is
Procedure update_sal(name varchar2,newsal number) is
begin
update emp set sal=newsal where ename=name;
end;
function annual_sal(name varchar2) return number is
annual_salary number;
begin
select sal*12+nvl(comm,0)*12 into annual_salary from emp where ename=name;
return;
end;
end;
[A3] 3.如何调用包中的过程和函数。
在过程和函数前加上包名。
exec xxc_package.update_sal(‘SCOTT’,120);
触发器
触发器是指隐含的存储过程,当定义触发器是,必须指定触发事件和触发的操做,经常使用的触发操做有insert、update和delete。触发器操做其实是一个pl/sql块,可使用create trigger命令来建立触发器。由于触发器内容不少,会在后面详细介绍,触发器是很是有用的,能够用来维护数据库的安全和一致性。
在编写pl/sql块时能够定义的变量和常量;
标量类型(scalar)—经常使用类型
语法格式(即前边最多见的格式)
变量名 [constant] datatype [not null] [:=/default expr]
expr:指定初始值的pl/sql表达式,能够是文本,其余变量,函数等。
定义标量案例
① 定义一个变长字符串
V_ename varchar2(10);
② 定义一个小数,范围在-9999.99~9999.99
v_sal number(6,2);
③ 定义一个小数并赋初值 :=是pl/sql的赋值符号;
v_sal number(6,2):=12.3;
④ 定义一个日期类型数
v_birthday date;
⑤ 定义一个布尔类型变量,不能为空,默认是false;
v_valid boolen not null default false;
[A4] 下面以输入员工号,显示员工姓名,工资,我的所得税(税率为0.03)为例,说明变量的使用。
declare
c_tax_rate number(3,2):=0.03;
v_ename varchar2(5);
v_sal number(10,2);
v_tax_sal number(10,2);
begin
select ename,sal into v_ename,v_sal from emp where empno=&no;
v_tax_sal:=v_sal*c_tax_rate;
dbms_output.put_line(‘员工名:’||v_ename||’工资:’||v_sal||’缴税:’||v_tax_sal);
end;
对于上边的pl/sql块,有一个问题。
若是员工的名字长于5个字符,就会出现错误,为了下降pl/sql的维护工做量,可使用%type属性来定义变量,这样它会按照数据库列来肯定你定义的变量的类型和长度。
变量名 表名.列名%type
对于上例的改动
v_ename emp.ename%type;
v_sal emp.sal%type;
复合类型(composite)-介绍
用于存放多个值的变量,主要包括这几种:
① pl/sql记录;
② pl/sql表;
③ 嵌套表;
④ varray;
其中① ② 用的多,③ ④ 用的少。
1.pl/sql的记录。
相似于高级语言的结构体,当引用pl/sql记录成员的时候,必需要加记录变量做为前缀(记录变量.记录成员)。
如:declare
type emp_record_type is record(name emp.ename%type, salary emp.sal%type,title emp.job%type);[A5]
xxc_record emp_record_type;
begin
select ename,sal,job into xxc_record from emp where empno=&no;
dbms_output.put_line(‘员工名:’||xxc_record.name||’工资:’||xxc_record.salary);
end;
2.pl/sql表
至关于高级语言中的数组,须要注意的是高级语言中的数组的下标不能为负,但表的下标没有限制,能够为负。[A6]
例如:declare -- binary_integer表示下标是整数
type xxc_table_type is table of emp.ename%type index by binary_integer;
xxc_table xxc_table_type;
begin --下标0能够是任何整数,-1,-2,23均可以,但输出的时候也要是同一个数,否则会出错。
select ename into xxc_table(0) from emp where empno=7788;
dbms_output.put_line(‘员工名:’||xxc_table(0));
end;
在这个实例中,若是把where子句去掉,就会出错,由于你只请求了一个数据,而返回了多行数据。解决办法是使用参照变量。
参照变量
参照变量是指用于存放数据指针的变量,经过使用参照变量可使得应用程序共享相同的对象,从而下降占用的空间。在编写pl/sql程序时,可使用游标变量(ref cursor)和对象类型变量(ref obj_type)这两种参照变量,用得多的是游标变量。
游标变量
定义游标是,不须要指定select语句,但当使用游标(open)时,须要指定select语句,这样一个游标就与一个游标就与一个select语句结合了。
实例:① 编写一个pl/sql块,输入部门号,显示全部员工名和工资。
declare
type xxc_emp_cursor is ref cursor;
test_cursor xxc_emp_cursor;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open test_cursor for select ename,sal from emp where deptno=&no;
loop
fetch test_cursor into v_ename,v_sal;
exit when test_cursor%notfound;
dbms_output.put_line(‘名字:’||v_ename||’工资:’||v_sal);
end loop;
close test_cursor;
[A7] end;
② 在① 的基础上,若是员工的工资低于2000,加100
declare
type xxc_emp_cursor is ref cursor;
test_cursor xxc_emp_cursor;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open test_cursor for select ename,sal from emp where deptno=&no;
loop
fetch test_cursor into v_ename,v_sal;
exit when test_cursor%notfound;
if v_sal<2000 then
update emp set sal=sal+100 where ename=v_ename;
v_sal=v_sal+100;
end if;
dbms_output.put_line(‘名字:’||v_ename||’工资:’||v_sal);
end loop;
close test_cursor;
end;
[A8] Pl/sql分支控制语句
1.条件分支语句
if…then if…then…else if…then…eslif…then…else…
当if语句结束时要有end if。
不等于号是<>,不是!=,
if job=’MANAGER’,是单引号,不是双引号。
2.循环语句
--loop是pl/sql中最简单的循环语句,这种循环以loop开始,以end loop结束,此循环至少会被执行一次。
例:编写一个循环过程,可输入用户名,并循环添加10个用户到user表中,用户编号从1开始。
Sql>create table user(userid number(10),username varchar2(20));
Sql>create procedure xxc_insert_user1(name varchar2) is
declare
v_Num number:=1; --不用指定大小吗?
begin
loop
insert into user values(v_num,name);
exit when v_num=10;[A9]
v_num:=v_num+1; --Oracle中没有++符号
end loop;
end;
--while循环
当while条件为真时,执行循环体。
以while…loop开头,以end loop结尾。
例:上题,用户号从11开始,添加10名用户。
create procedure xxc_insert_user2(name varchar2) is
declare
v_Num number:=11; --不用指定大小吗?
begin
while v_num<=20 loop
insert into user values(v_num,name);
v_num:=v_num+1;
end loop;
end;
--for循环
循环基本结构以下:
begin
for i in reverse 1..10 loop
insert into users(I,’小红’);
end loop;
end;
顺序控制语句 --goto,null
Goto语句会增长程序的复杂性,下降可读性,通常不使用,但有时会很方便。
基本语法 goto lable ,lable是定义好了的标签。
例:declare
i int:=1;
begin
loop
dbms_output.put_line(‘i=’||i);
if i=10 then
goto end_loop;
end if;
end loop;
<<end_loop>> --此为标签,注意格式
dbms_output.put_line(‘循环结束’);
end;
null语句不执行任何操做,好处是能够提升程序的可读性。
例:if sal<2000 then
update emp set sal=sal+100 where ename=name;
else
null;
编写分页过程
从易到难,先写简单的过程。
写无返回值的过程,并被Java程序调用,前边已写过,再也不重写。如今写一个有返回值的过程,并用Java程序调用。
例:输入员工号,输出员工姓名。
Create procedure xxc_pro4 (empid in number,empname out varchar2) is
Begin
Select ename into empname from emp where empno = empid;
End;
Java程序调用。
import java.sql.*;
public class TestOraPro1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
//加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//获得链接 1521为端口号
Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:CUIXIAO2","scott","xxc");
//建立callablestatement
CallableStatement cs=ct.prepareCall("{call xxc_pro4(?,?)}");
//给第一个?赋值
cs.setInt(1,7788);
//给第二个?赋值
cs.registerOutParameter(2,oracle.jdbc.OracleTypes.VARCHAR);
//执行
cs.execute();
//取出返回值,要注意?的顺序,第几个?是返回值就写几。
String name = cs.getString(2);
System.out.println(“7788的名字是”+name);
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭资源
cs.close();
ct.close();
}
}
}
案例扩展:输入一个员工号,输出该员工的名字,工资和岗位。
create procedure xxc_pro5(empid in number,empname out varchar2,empsal out number,empjob out varchar2) is
begin
select ename,sal,job into empname,empsal,empjob from emp where empno=empid;
end;
Java调用
import java.sql.*;
public class TestOraPro1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
//加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//获得链接 1521为端口号
Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:CUIXIAO2","scott","xxc");
//建立callablestatement
CallableStatement cs=ct.prepareCall("{call xxc_pro4(?,?,?,?)}");
//给第一个?赋值
cs.setInt(1,7788);
//给后3个?赋值
cs.registerOutParameter(2,oracle.jdbc.OracleTypes.VARCHAR);
cs.registerOutParameter(3,oracle.jdbc.OracleTypes.DOUBLE);
cs.registerOutParameter(4,oracle.jdbc.OracleTypes.VARCHAR);
//执行
cs.execute();
//取出返回值,要注意?的顺序,第几个?是返回值就写几。
String name = cs.getString(2);
String job = cs.getString(4);
System.out.println(“7788的名字是”+name+” 工做是”+job);
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭资源
cs.close();
ct.close();
}
}
}
带有返回值的存储过程(结果集是个列表)。
案例:编写个存储过程,输入部门号,输出该部门的全部员工的信息。
分析以下:
因为Oracle的存储过程没有返回值,它的全部返回值都是经过out参数来替代的,列表一样也不例外,但因为是个集合,因此不能用通常参数,必需要用package。因此分为两部分。
① 建立一个包,在包中定义一个test_package,是个游标;
create package test_package as type test_cursor is ref cursor;
End test_package;
② 建立存储过程
create procedure xxc_pro6(xxcno in number,xxc_cursor out test_package.test_cursor) is
begin
open xxc_cursor for select * from emp where deptno=xxcno;
end;
在Java中调用此存储过程
import java.sql.*;
public class TestOraPro1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
//加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//获得链接 1521为oracle端口号
Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:CUIXIAO2","scott","xxc");
//建立callablestatement
CallableStatement cs=ct.prepareCall("{call xxc_pro6(?,?)}");
//给第一个?赋值
cs.setInt(1,10);
//给第二个?赋值
cs.registerOutParameter(2,oracle.jdbc.OracleTypes.CURSOR);
//执行
cs.execute();
//获得结果集
ResultSet rs = (ResustSet)cs.getObject(2);
While(rs.next()){
System.out.println(rs.getInt(1)+” ”+rs.getString(2)+” ”+rs.getString(3));
}
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭资源
cs.close();
ct.close();
}
}
}
开始编写分页过程。
例:请编写一个存储过程,要求能够输入表名、每页显示记录数、当前页,返回总记录数、总页数和返回的结果集。
如今先回顾一下分页的语句,这是个模板。
select * from (select t1.*,rownum rn from (select * from emp) t1 where rownum<=10) where rn>=6;
① 建立一个包,在包中定义一个test_package2,是个游标;
create package test_package2 as type test_cursor2 is ref cursor;
End test_package2;
② 建立存储过程;
create procedure fenye is
(tablename in varchar2, --表名
rowsize in number, --每页记录数
pagenow in number, --当前页
recordnum out number, --总记录数
pagenum out number, --总页数
set_cursor out test_package2.test_cursor2 --结果集的游标
) is
v_sql varchar2(1000);
v_begin nuber:=(pagenow-1)*rowsize+1;
v_end nuber:=pagenow*rowsize;
begin
--v_sql是链接字符串,有点绕,看仔细
v_sql := 'SELECT * FROM (SELECT T.*,ROWNUM rn FROM (SELECT * FROM ' ||
TABLENAME || ' ) T WHERE ROWNUM <= ' || V_END ||
' ) WHERE rn >= ' || V_BEGIN;
--打开游标,把游标和sql语句结合起来
open set_cursor for v_sql;
--计算recordnum和pagenum
v_sql:= 'SELECT COUNT(*) FROM ' || TABLENAME;
execute immediate v_sql into recordnum;
if mod(recordnum,rowsize)=0 then
pagenum:=recordnum/rowsize;
else
pagenum:=recordnum/rowsize+1;
end if;
close set_cursor;
end;
用Java程序调用:
import java.sql.*;
public class TestOraPro1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
//加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//获得链接 1521为端口号
Connection ct = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:CUIXIAO2","scott","xxc");
//建立callablestatement
CallableStatement cs=ct.prepareCall("{call fenye(?,?,?,?,?,?)}");
//给?赋值
cs.setString(1,”emp”);
cs.setInt(2,5);
cs.setInt(3,1);
//注册总记录数
cs.registerOutParameter(4,oracle.jdbc.OracleTypes.INTEGER);
//注册总页数
cs.registerOutParameter(5,oracle.jdbc.OracleTypes.INTEGER);
//注册结果集
cs.registerOutParameter(6,oracle.jdbc.OracleTypes.CURSOR);
//执行
cs.execute();
//取出返回值,要注意?的顺序,第几个?是返回值就写几。
int rnum=cs.getInt(4);
int pnum=cs.getInt(5);
ResultSet rs=(ResultSet)cs.getObject(6);
System.out.println(”记录数是:”+rnum+” 总页数是:”+pnum);
while(rs.next()){
System.out.println(”编号:”+rs.getInt(1)+”姓名:”+rs.getString(2));
}
}catch(Exception e){
e.printStackTrace();
}finally{
//关闭资源
cs.close();
ct.close();
}
}
}
例外处理
例外处理分为预约义例外,其余预约义例外和自定义例外。
案例:当输入的雇员编号不存在时
declare
v_ename emp.ename%type;
begin
select ename into v_name from emp where empno=&no;
dbms_output.putline(’名字是:’||v_name);
exception
when no_data_found then
dbms_output.putline(’编号不存在!’);
end;
no_data_found就是一个预约义例外,在执行select语句时会出异常,但在执行update等语句时不会触发该例外。
预约义例外是由pl/sql所提供的系统例外。当pl/sql违反Oracle的规定时就会隐含触发一个内部例外。
经常使用的例外:
例:create or replace procedure xxc_pro7(no number) is
v_sal emp.sal%type;
begin
select sal into v_sal from emp where empno=no;
case
when v_sal<1000 then
update emp set sal=sal+200 where empno=no;
when 1000<=v_sal<2000 then
update emp set sal=sal+300 where empno=no;
end case;
exception
when case_no_found then
dbms_output.putline(’case中没有和’||v_sal||’相匹配的条件’);
end;
declare
cursor emp_cursor is select ename,sal from emp;
begin
open emp_cursor;
for emp_record1 in emp_cursor loop
dbms_output.putline(emp_cursor.ename);
end loop;
exception
when cursor_already_open then
dbms_output.putline(’游标已打开!’);
end;
3.dup_val_on_index例外
在惟一索引所对应的列上插入重复的值时,会隐含的触发此例外。
begin
insert into dept values(10,’公关部’,’北京’);
exception
when dup_val_on_index then
dbms_output.putline(’在deptno列上不能有重复值!’);
end;
4.invaild_cursor
当试图在不合法的游标上执行操做时,就会触发该例外。例如当试图从没有打开的游标提取数据或是关闭没有打开的游标时,就会触发。
declare
cursor emp_cursor is select ename,sal from emp;
emp_record emp_cursor%rowtype;
begin
--open emp_cursor; --打开游标(但被注释掉了)
fetch emp_cursor into emp_record;
dbms_output.putline(emp_cursor.ename);
close emp_cursor;
exception
when invalid_cursor then
dbms_output.putline(’请检查游标是否打开’);
end;
5.invalid_number例外
当输入的数据有误时,会触发该例外。好比把100写成1oo就会触发。
begin
update emp set sal=sal+’1oo’;
exception
when invalid_number then
dbms_output.putline(’输入的数字不正确!’);
end;
6.too_many_rows例外
当执行select into语句时,若是返回超过了一行,则会触发该例外。
declare
v_ename emp.ename%type;
begin
select ename into v_ename from emp;
exception
when too_many_rows then
dbms_output.putline(’返回了多行!’);
end;
7.zero_divide例外 当2/0,即0作分母的时候触发。
8.value_error例外
当在执行赋值操做时,若是变量的长度不足以容纳实际数据,就会触发该例外。
declare
v_ename varchar2(5);
begin
select ename into v_ename from emp where empno=&no;
dbms_output.putline(’名字是:’||v_ename);
exception
when value_error then
dbms_output.putline(’数据超出变量长度!’);
end;
其余预约义例外。
自定义例外
预约义例外是和oracle的错误相关的,而自定义例外于oracle错误没有任何关系,它是由开发人员为特定的状况所定义的例外。
例:编写一个pl/sql块,接收一个雇员号,并给该雇员的工资增长1000元,若是该雇员不存在,请提示。
create or replace procedure test_ex(no number) is
declare
myex exception; --定义一个例外
begin
update emp set sal=sal+1000 where empno=no;
if sql%notfound then -- sql%notfound表示没有更新数据
raise myex; --触发myex
end if;
exception
when myex then
dbms_output.putline(’未更新成功!’);
end;
视图
视图与表的区别
建立视图
create view 视图名 as select语句 [with read only];
例:建立视图,把emp表的sal<1000的雇员映射到该视图;
create view myview as select * from emp where sal<1000;
为简化操做,用视图显示员工编号,姓名和所在部门
create view myview2 as select emp.empno,emp.ename,dept.dname from emp,dept where emp.deptno=dept.deptno;
建立或修改视图
create or replace view 视图名 as select语句 [with read only];
删除视图
drop view 视图名;