将PL/SQL代码封装在灵巧的包中
将代码封装在灵巧的包中
http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13plsql-1872456.html
绝大多数基于PL/SQL的应用都是由成千上万甚至上百万行代码组成,这里面包含了详细多变的用户需求。
商业逻辑的实现最初是由存储过程和函数完成,但是开发者需要考虑将这些过程和函数放在包中维护。
何为包?
包是一组PL/SQL代码元素(游标、类型、变量、过程、函数)集合的程序单元。
通常由包声明(对象声明)和包体(具体实现)组成。
为什么要使用包?
1)组织和维护一组功能相关的对象;
2)对外隐藏具体实现;
3)提升性能,这一点要说一下:
当你第一次调用包时,整个包被加载入内存。接下来对同一包元素进行调用无需额外的磁盘I/O。
另外,包级别变量可以再会话级别(session-level)缓存起来,从而降低数据读取时间。
4)最小化程序单元重编译
外部程序(没有定义在包中)仅能调用包声明中的子程序。如果你改变并重新编译了包体,那些外部程序
将不会失效。
下面展示一下包的魅力:
1 一个简单的包:
假设我的employees表定义如下:
SQL> desc employees
Name Type
———————————— —————————————
EMPLOYEE_ID NUMBER(38)
FIRST_NAME VARCHAR2(30)
LAST_NAME VARCHAR2(50)
下面我需要定义一个process_employee的过程,返回员工全名(last_name, first_name)以供其他
程序调用。
Code Listing 1: The process_employee procedure
CREATE OR REPLACE PROCEDURE process_employee (
employee_id_in IN employees.employee_id%TYPE)
IS
l_fullname VARCHAR2(100);
BEGIN
SELECT last_name || ',' || first_name
INTO l_fullname
FROM employees
WHERE employee_id = employee_id_in;
...
END;
仔细看,这个过程有几个问题:
1)l_fullname 长度固定为100?
2)l_fullname的表达式固定为 last_name || ‘,’ || first_name?万一哪天客户改变主意:
我们想在所有报告和信息中显示:first_name【空格】last_name咋办?如果你在N个过程中都已经
使用了这种结构,那你是不是去一一找出来修改掉?
3)最后一点,我们很有可能在不同的过程中编写一些重复SQL,这样会大大降低效率和性能
这个时间,我们需要将这种通用逻辑藏在包中,保证一处维护处处受益:
CREATE OR REPLACE PACKAGE employee_pkg
2 AS
3 SUBTYPE fullname_t IS VARCHAR2 (100);
4
5 FUNCTION fullname (
6 last_in employees.last_name%TYPE,
7 first_in employees.first_name%TYPE)
8 RETURN fullname_t;
9
10 FUNCTION fullname (
11 employee_id_in IN employees.employee_id%TYPE)
12 RETURN fullname_t;
13 END employee_pkg;
回头再改写过程,可以这样:
CREATE OR REPLACE PROCEDURE process_employee (
employee_id_in IN employees.employee_id%TYPE)
IS
l_name employee_pkg.fullname_t;
employee_id_in employees.employee_id%TYPE := 1;
BEGIN
l_name := employee_pkg.fullname (employee_id_in);
...
END;
代码变整洁了,还有你压根不需要关心employee_pkg.fullname 如何实现!多省心!
来看下包体是如何实现的:
CREATE OR REPLACE PACKAGE BODY employee_pkg
2 AS
3 FUNCTION fullname (
4 last_in employees.last_name%TYPE,
5 first_in employees.first_name%TYPE
6 )
7 RETURN fullname_t
8 IS
9 BEGIN
10 RETURN last_in || ', ' || first_in;
11 END;
12
13 FUNCTION fullname (employee_id_in IN employee.employee_id%TYPE)
14 RETURN fullname_t
15 IS
16 l_fullname fullname_t;
17 BEGIN
18 SELECT fullname (last_name, first_name) INTO l_fullname
19 FROM employees
20 WHERE employee_id = employee_id_in;
21
22 RETURN l_fullname;
23 END;
24 END employee_pkg;
这里用到了函数重载,使得外部过程只需要传入不同参数即可调用不同版本的函数。
最终都会返回fullname!
2 包级别数据
此类数据由包声明和包体中全局的variables 和 constants组成。
例如:
CREATE OR REPLACE PACKAGE plsql_limits
IS
c_varchar2_length CONSTANT
PLS_INTEGER := 32767;
g_start_time PLS_INTEGER;
END;
当你在一个子程序或匿名块中声明一个变量,称为本地变量,其声明周期限制在一次子程序调用或匿名块执行。
而包级别数据是在整个会话期间都会存活。
如果你在包体中定义包数据(变量和常量),该数据同样在会话期间存活,但是这类数据只能被包中程序使用,即为私有数据。
另一方面,如果是在包声明中定义包数据则对所有具有执行包权限的程序都可使用。
来看一个例子:
DBMS_UTILITY包中GET_CPU_TIME函数可用来计算你的程序耗时
Code Listing 5: DBMS_UTILITY.GET_CPU_TIME measures
DECLARE
l_start PLS_INTEGER;
BEGIN
/* Get and save the starting time. */
l_start := DBMS_UTILITY.get_cpu_time;
/* Run your code. */
FOR indx IN 1 .. 10000
LOOP
NULL;
END LOOP;
/* Subtract starting time from current time. */
DBMS_OUTPUT.put_line (
DBMS_UTILITY.get_cpu_time - l_start);
END;
/
看着足够简单了吧,但是你还是需要声明一个本地变量来存放耗时!
so,我们有更快捷的方式,使用自定义包timer_pkg!!!
Code Listing 6: The timer_pkg package
CREATE OR REPLACE PACKAGE timer_pkg
IS
PROCEDURE start_timer;
PROCEDURE show_elapsed (message_in IN VARCHAR2 := NULL);
END timer_pkg;
/
CREATE OR REPLACE PACKAGE BODY timer_pkg
IS
g_start_time NUMBER := NULL;
PROCEDURE start_timer
IS
BEGIN
g_start_time := DBMS_UTILITY.get_cpu_time;
END;
PROCEDURE show_elapsed (message_in IN VARCHAR2 := NULL)
IS
BEGIN
DBMS_OUTPUT.put_line (
message_in
|| ': '
|| TO_CHAR (DBMS_UTILITY.get_cpu_time - g_start_time));
start_timer;
END;
END timer_pkg;
/
改写之前的匿名块,如下:
BEGIN
timer_pkg.start_timer;
FOR indx IN 1 .. 10000
LOOP
NULL;
END LOOP;
timer_pkg.show_elapsed ('10000 Nothings');
END;
/
哇哦!good job!
不再需要声明本地变量,不再需要理解GET_CPU_TIME function 如何工作!
3 子程序重载
我们都知道DBMS_OUTPUT.PUT_LINE用于往控制台打印字符数据,
BEGIN
DBMS_OUTPUT.PUT_LINE (100);
END;
其有一个弊端,只能输出字符类型!
SQL> BEGIN
2 DBMS_OUTPUT.PUT_LINE (TRUE);
3 END;
4 /
DBMS_OUTPUT.PUT_LINE (TRUE);
*
ERROR at line 2:
ORA-06550: line 2, column 4:
PLS-00306: wrong number or types of
arguments in call to ‘PUT_LINE’
多尴尬! 比较BOOLEAN类型无法转成字符类型!
很多开发者不得不这么搞:
IF l_student_is_registered
THEN
DBMS_OUTPUT.PUT_LINE ('TRUE');
ELSE
DBMS_OUTPUT.PUT_LINE ('FALSE');
END IF;
不得不说精神可嘉!
但是,我们有更好的方式:
Code Listing 7: The my_output package without overloading
CREATE OR REPLACE PACKAGE my_output
IS
PROCEDURE put_line (value_in IN VARCHAR2);
PROCEDURE put_line (value_in IN BOOLEAN);
PROCEDURE put_line (
value_in IN DATE,
mask_in IN VARCHAR2 DEFAULT 'YYYY-MM-DD HH24:MI:SS');
END my_output;
/
这就充分发挥了重载的价值!
4 包状态及ORA-04068错误
这个问题是任何开发包的人都无法回避的。
包有状态?
当一个包有至少一个常量或变量声明在包级别,该包就有了状态!
当一个会话调用有状态包,PGA将包所有包级别数据存储起来!
如果一个状态包重新编译,所有使用该包的会话在下次调用时都会抛出:ORA-04068错误。
因为存储在PGA中包级别数据已经过期了(out of date)!所以包必须再次初始化!
此外,一旦ORA-04068抛出,会话中所有状态包,例如,DBMS_OUTPUT都将标识为未初始化。这通常意味着用户
必须断开会话重新连接。
这个潜在的错误意味着当IT部门需要升级应用,他们需要确保所有用户已注销。 但是在7*24的互联网世界这是
不能容忍的。
所以在Oracle 11g r2中,oracle提供了基于版本的重定义功能(Edition-Based Redefinition feature)。
详细请参考:oracle.com/technetwork/database/features/availability/edition-based-redefinition-1-133045.pdf and docs.oracle.com/cd/E11882_01/appdev.112/e10471/adfns_editions.htm
将PL/SQL代码封装在灵巧的包中的更多相关文章
- 将PL/SQL代码封装在机灵的包中
将代码封装在机灵的包中 http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13plsql-1872456.html 绝大多数基于 ...
- 同样的一句SQL语句在pl/sql 代码块中count 没有数据,但是直接用SQl 执行却可以count 得到结果
pl/sql 代码块: SELECT count(distinct t2.so_nbr) INTO v_count2 FROM KFGL_YW_STEP_qd t2 WHERE t2.partitio ...
- 使用PL/Scope分析PL/SQL代码
使用PL/Scope分析你的PL/SQL代码 从11g開始Oracle引入了PL/Scope 用于编译器收集PL/SQL程序单元的全部标识符(变量名.常量名.程序名等). 收集到的信息可通过一系列静态 ...
- plsql programming 20 管理PL/SQL代码(个人感觉用不到)
这一章的内容, 只完成了一部分, 剩下的用到再补充吧 由于依赖关系, 而编译失败, 需要重新编译. ( 所谓依赖, 是指存储过程, 函数等在运行中调用的对象, 比如table 等, 比如你删除了过程中 ...
- oracle 中使用 pl/sql代码块
1.写匿名块,输入三角形三个表的长度.在控制台打印三角形的面积. declare -- (p=(a+b+c)/2) --声明三角形的面积 三条边 的 v_a number (10,2):=&n ...
- PL/SQL:使用pragma restrict_references限制包权限
在看别人的代码的时候.发现了例如以下的编译指令. pragma restrict_references(get_attribute_name, wnds); get_attribute_name是一个 ...
- PL/SQL 编程(三 )程序包和包体,触发器,视图,索引
一.程序包和包体 程序包(package):存储在数据库中的一组子程序.变量定义.在包中的子程序可以被其它程序包或子程序调用.但如果声明的是局部子程序,则只能在定义该局部子程序的块中调用该局部子程序. ...
- 【Oracle】PL/SQL Developer使用技巧(持续更新中)
1.关键字自动大写 在sql命令窗口中输入SQL语句时,想要关键字自动大写,引人注目该怎么办呢? 一步设置就可以达成了.点击Tools->Preference->Editor,看到截图中这 ...
- 解决PL/SQL Developer 连接oracle 11g 64位中的问题
1.错误1:Initialization error could not initialize 电脑上原本就装有oracle 11g 64位,但是PL/SQL却怎么也连接不上,报出" Ini ...
- pl/sql学习(6): 引号/程序调试/列中的字符串合并/正则表达式
有关自治事务的问题: https://www.cnblogs.com/princessd8251/p/4132649.html 我在plsql development学习中遇到的常见问题: (一) 引 ...
随机推荐
- [转帖]小米Redis的K8s容器化部署实践
https://juejin.cn/post/6844904196924276743 背景 Why K8S How K8s Why Proxy Proxy带来的问题 K8s带来的好处 遇到的问 ...
- [转帖]s3fs
https://github.com/s3fs-fuse/s3fs-fuse s3fs allows Linux, macOS, and FreeBSD to mount an S3 bucket v ...
- [转帖]Prometheus监控系统存储容量优化攻略,让你的数据安心保存!
云原生监控领域不可撼动,Prometheus 是不是就没缺点?显然不是. 一个软件如果什么问题都想解决,就会导致什么问题都解决不好.所以Prometheus 也存在不足,广受诟病的问题就是 单机存储不 ...
- [转帖]【测试】 FIO:ceph/磁盘IO测试工具 fio(iodepth深度)
目录 随看随用 NAS文件系统测试 块系统测试 FIO用法 FIO介绍 FIO 工具常用参数: FIO结果说明 I/O 的重放('录'下实际工况的IO,用fio'重放') fio工作参数可以写入配置文 ...
- 【转帖】【奇技淫巧】Linux | 统计网络-netstat
theme: condensed-night-purple 小知识,大挑战!本文正在参与"程序员必备小知识"创作活动. 在构建生产服务器时,我们有的时候需要统计网络接口状况,比如T ...
- [转帖]开源软件项目中BSD、MIT许可证合规问题探析
https://www.allbrightlaw.com/CN/10475/3be2369275d19e9e.aspx [摘要]本文将探析BSD开源许可证(Berkeley Software Di ...
- Java火焰图简单学习
前言 立的flags倒了太多次 自己还是太菜了.. 课题太大, 自己简单总结一下. 要是自己总结错了. 就不收费, 错就错了 !-_-! 第一步准备环境 一定要设置对java的HOME以及PATH路径 ...
- 我们开源了一个轻量的 Web IDE UI 框架
我们开源了一个轻量的 Web IDE UI 框架 Molecule 一个轻量的 Web IDE UI 框架 简介 Molecule 是一个受 VS Code 启发,使用 React.js 构建的 We ...
- 【解决了一个小问题】vm-agent中,如何对envoy这样的特殊expoter路径做处理?
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 envoy这个组件的expoter路径为 /stats/p ...
- Go-操作redis/redigo
目录 Go-操作redis 安装 连接 使用 设置key过期时间 批量获取mget.批量设置mset 列表操作 hash操作 Pipelining(管道) redis发布会订阅模式 事务操作 万能操作 ...