版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Phone_1070333541/article/details/86352422
一、游标
1.简介
oracle 读取数据的工具,类似java集合。
2.示例
DECLART
-- 获取班级集合
CURSOR EMPLIST IS SELECT * FROM EMP ;
-- 定义emp 表的对象 进行解析
V_EMPOBJ EMP%ROWTYPE;
BEGIN
-- 解析游标(集合)
-- 1. 打开游标。
OPEN EMPLIST;
LOOP
EXIT WHEN EMPLIST%NOTFOUND
FETCH EMPLIST INTO V_EMPOBJ;
dbms_output.put_line(v_empobj.empno||v_empobj.ename);
END LOOP;
-- 2. 关闭游标
CLOSE EMPLIST;
END
3.简化版
DECLARE
CURSOR GLIST IS SELECT * FROM GRADE;
BEGIN
FOR g IN GLIST LOOP
dbms_output.put_line(g.gid);
END LOOP;
END
二、事务
1.一个程序是否健壮,主要看异常处理机制
2.异常
- oracle 一共定义有2万个错误。
- 预定义错误 弹窗 终止程序运行。
- 处理异常。
EXCEPTION
WHEN
TOO_MANY_ROWS
THEN
dbms_output.put_line('返回条数过多,不能赋值');
WHEN
NO_DATA_FOUND
THEN
dbms_output.put_line('要查找的数据不存在');
- 自定义异常。
-- 如果有员工工资小于1000 抛出异常提示。
DECLARE
CURSOR emplist IS SELECT * FROM EMP ;
-- 1. 定义异常
SAL_TOO_LOW EXCEPTION;
BEGIN
FOR e IN emplist LOOP
IF e.sal < 1000 THEN
-- 2. 引发异常
RAISE SAL_TOO_LOW;
END IF;
END LOOP;
-- 3. 接收并处理异常
EXCEPTION
WHEN SAL_TOO_LOW THEN
dbms_output.put_line('有员工工资太低');
raise_application_error(-20001,'工资过低,请及时涨工资');
//错误信息标号 必须小于 -20000
END
- 其他异常
所有未拦截的异常都应该被others接收
WHEN OTHERS THEN
dbms_output.put_line('所有未拦截的异常');
空白R拦截顺序
先是预定义 再是自定义 最后是others
三、存储过程
存储过程是一个有名字的 plsql 块
1. 示例
//-->根据员工编号给指定员工涨工资,并打印相关信息。
create or replace procedure updateempbyempno(
eno emp.empno%type //没有;
) as
--声明变量的地方
a_sal emp.sal%type ; --涨工资之前
b_sal emp.sal%type;--涨工资之后
begin
select sal into a_sal from emp where empno = eno;
dbms_output.put_line('涨工资之前为'||a_sal);
--涨工资
update emp set sal=sal+100 where empno = eno;
commit;
--查询涨了之后的工资
select sal into b_sal from emp where empno = eno;
dbms_output.put_line('涨工资之后为' || b_sal);
end;
-- 调用存储过程
begin
updateempbyempno(7521);
end;
四、存储函数
1.示例
根据员工编号查询员工的年薪。
create or replace function FINDSALBYEMPNO(eno EMP.SAL%TYPE)
RETURN NUMBER // eno不能和字段名一致
AS
v_nianxin number(11,2);
BEGIN
-- 查询年薪
select sal*12+nvl(comm,0) into v_nianxin from emp where empno=eno;
return v_nianxin;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('没有找到数据');
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('输出数据过多');
END;
-- 调用存储函数
DECLARE
--定义方法返回值的接受变量
v_sal number(11,2);
BEGIN
v_sal := FINDSALBYEMPNO(7521);
dbms_output.put_line('年薪为'|| v_sal);
END
五、多返回值的存储过程
1.示例1
根据编号返回员工名称和年薪
create or replace procedure findempinfobyempno(
eno in emp.empno%type, [in 输入模式]
empname out emp.ename%type,[out 输出模式]
nianxin out number [不是表中的类型,不需要带长度]
)
as
begin
select ename,sal*12+nvl(comm,0) into empname,nianxin
from emp where empno = eno;
end;
--调用带out的存储过程
DECLARE
ename emp.ename%type;
nianxin number(11,2)
BEGIN
findempinfobyempno(7521,ename,nianxin);
dbms_output.put_line(ename||'的年薪是:'||nianxin);
END;
2.示例2
获取所有人的年薪。//参数为游标类型
create or replace procedure
findALlSal(allsal out sys_refcursor)as
begin
open allsal for select sal*12+nvl(comm.0) from emp;
end;
-- 调用存储过程
DECLARE
--存储游标类型所有的年薪
ALLSAL SYS_REFCURSOR;
SAL EMP.SAL%TYPE;
BEGIN
--调用
FINDALLSAL(ALLSAL);
--循环
LOOP
--出口
EXIT WHEN ALLSAL%NOTFOUND;
--打印
DBMS_OUTPUT.PUT_LINE(SAL);
--注入
FETCH ALLSAL INTO SAL;
END LOOP;
--结束循环
CLOSE ALLSAL;
--关闭游标
END;
3.总结
当存储过程的参数类型是游标类型时,打开游标时设置查询结果进去。
因为在定义存储过程时将游标打开了,所以调用存储过程时不能用for循环
而且记得关闭游标。
六、java调用存储过程
1.驱动包
2.工具类
- 四个属性
driver:oracle.jdbc.OracleDriver
url:jdbc:oracle:thin:@192.168.56.130:1521:orcl
user:test_302
password:ORCL - 调用存储过程
//需求:根据某个人的id 获取他的年薪
@Test
public void testGetSomeOneYearNew() throws Exception{
//获取链接
Connection conn = JdbcUtil.getConn();
//sql语句 调用存储过程 用{call 存储过程名} ?占位符的索引从1开始
String sql = "{call FINDALLINFOBYNO(?,?,?)}";
//获取欲执行语句
CallableStatement statement = conn.prepareCall(sql);
statement.setInt(1, 7521);
//传出模式 参数得注册
statement.registerOutParameter(2,OracleTypes.VARCHAR);
statement.registerOutParameter(3,OracleTypes.NUMBER);
//执行
statement.execute();
//处理结果集
//获取占位符的值
System.out.println(statement.getString(2)+"的年薪为:"+statement.getDouble(3));
//释放链接
JdbcUtil.closeResource(conn, statement, null);
}
七、综合示例
1.分页获取学生数据,包含学生以及班级信息
- 存储过程
CREATE OR REPLACE PROCEDURE FINDSTUDENTSBYPAGE(
PI IN NUMBER,PS IN NUMBER,STUDENTLIST OUT SYS_REFCURSOR)
AS
BEGIN
OPEN STUDENTLIST FOR
SELECT * FROM (
SELECT A.*,ROWNUM RN FROM
(SELECT S.*,G.G_NAME,G.G_DESC FROM STUDENT S INNER JOIN GRADE G ON S.G_ID = G.G_ID ORDER BY S.G_ID ASC,S.S_ID ASC) A) B WHERE B.RN BETWEEN (PI-1)*PS+1 AND PI*PS;
END;
- 客户端来调用存储过程
DECLARE
studentlist SYS_REFCURSOR;
sname student.s_name%TYPE;
gname grade.g_name%TYPE;
BEGIN
FINDSTUDENTSBYPAGE(2,10,studentlist);
-- 解析游标
LOOP
EXIT WHEN studentlist%NOTFOUND;
dbms_output.put_line('学生姓名:'||sname||'所属班级'||gname);
FETCH studentlist INTO sname,gname;
END LOOP;
CLOSE studentlist;
END;
- java代码来调用存储过程
@Test
public void testGetStudentByPage() throws Exception{
//获取链接
Connection conn = JdbcUtil.getConn();
String sql="{call FINDSTUDENTBYPAGE(?,?,?)}";
//获取欲执行语句
CallableStatement st = conn.prepareCall(sql);
//--给参数赋值
st.setInt(1, 2);
st.setInt(2, 10);
st.registerOutParameter(3, OracleTypes.CURSOR);
//执行
st.execute();
//处理结果集
ResultSet rs = ((OracleCallableStatement)st).getCursor(3);
//--遍历取值
while(rs.next()){
System.out.println(
rs.getObject(1)+"\t"+
rs.getObject(2)+"\t"+
rs.getObject(4));
System.out.println();
}
//释放资源
JdbcUtil.closeResource(conn, st, rs);
}
八、触发器
1.触发器的类型
- 语句级触发器。
语句每执行一次就会执行一次。 - 行级的触发器。
2.语句级的触发器。
- 示例1
// 插入班级信息以后,打印添加成功。
create or replace trigger gradetg1
after insert on grade
declare
begin
dbms_output.put_line("添加成功");
end;
// 自己插入一条信息测试。
- 示例2
不能再星期四 添加班级信息。
create or replace tirgger gradetg2
before insert on grade
declare`在这里插入代码片`
v_week varchar(32);
BEGIN
SELECT TO_CHAR(SYSDATE,'DAY') INTO V_WEEK FROM DUAL;
IF V_WEEK = '星期四' THEN
Raise_application_error(-20001,'星期四不能添加班级!');
END IF;
END;
3.行级的触发器
判断员工是否真正涨工资。
怎么获得涨工资之前或者之后的工资 ( for each row )
create or replace trigger emptg1
before update [ of sal ] on emp for each row
declare
begin
if :new.sal = :old.sal < 0 then
raise_application_error(-20002,'不能降工资');
end;
4.实际应用
- 主键自增
--创建序列
create sequence seq_userinfo nocycle nocache;
--行级触发器
create or replace trigger usertg1
before insert on user_info for each row
declare
begin
select seq_userinfo.nextval into :new.uid FROM dual;
end;
- 日志记录
--创建序列
create seqence seq_syslog nocycle nocache;
-- 被班级表进行日志记录
create or replace trigger gradetg3
after update on grade for each row
DECLARE
v_time varchar(128);
v_msg CLOB;
BEGIN
--赋值当前时间
select to_char(SYSDATE,'yyyy-MM-dd hh24:mm:ss') into v_time from dual;
--插入入职表中
v_msg := '更新,发生在'||V_TIME||',操作前['||:old.g_name||']操作后
['||:new.g_name||']';
insert into sys_log values (seq_syslog.Nextval,v_msg);
dbms_output.put_line('日志记录成功!');
END;