将代码封装在灵巧的包中
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代码封装在灵巧的包中的更多相关文章

  1. 将PL/SQL代码封装在机灵的包中

    将代码封装在机灵的包中 http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13plsql-1872456.html 绝大多数基于 ...

  2. 同样的一句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 ...

  3. 使用PL/Scope分析PL/SQL代码

    使用PL/Scope分析你的PL/SQL代码 从11g開始Oracle引入了PL/Scope 用于编译器收集PL/SQL程序单元的全部标识符(变量名.常量名.程序名等). 收集到的信息可通过一系列静态 ...

  4. plsql programming 20 管理PL/SQL代码(个人感觉用不到)

    这一章的内容, 只完成了一部分, 剩下的用到再补充吧 由于依赖关系, 而编译失败, 需要重新编译. ( 所谓依赖, 是指存储过程, 函数等在运行中调用的对象, 比如table 等, 比如你删除了过程中 ...

  5. oracle 中使用 pl/sql代码块

    1.写匿名块,输入三角形三个表的长度.在控制台打印三角形的面积. declare -- (p=(a+b+c)/2) --声明三角形的面积 三条边 的 v_a number (10,2):=&n ...

  6. PL/SQL:使用pragma restrict_references限制包权限

    在看别人的代码的时候.发现了例如以下的编译指令. pragma restrict_references(get_attribute_name, wnds); get_attribute_name是一个 ...

  7. PL/SQL 编程(三 )程序包和包体,触发器,视图,索引

    一.程序包和包体 程序包(package):存储在数据库中的一组子程序.变量定义.在包中的子程序可以被其它程序包或子程序调用.但如果声明的是局部子程序,则只能在定义该局部子程序的块中调用该局部子程序. ...

  8. 【Oracle】PL/SQL Developer使用技巧(持续更新中)

    1.关键字自动大写 在sql命令窗口中输入SQL语句时,想要关键字自动大写,引人注目该怎么办呢? 一步设置就可以达成了.点击Tools->Preference->Editor,看到截图中这 ...

  9. 解决PL/SQL Developer 连接oracle 11g 64位中的问题

    1.错误1:Initialization error could not initialize 电脑上原本就装有oracle 11g 64位,但是PL/SQL却怎么也连接不上,报出" Ini ...

  10. pl/sql学习(6): 引号/程序调试/列中的字符串合并/正则表达式

    有关自治事务的问题: https://www.cnblogs.com/princessd8251/p/4132649.html 我在plsql development学习中遇到的常见问题: (一) 引 ...

随机推荐

  1. Django应用中的静态文件处理

    在日常开发中,我们都是把Django的Debug模式打开,方便调试,在这个模式下,由Django内置的Web服务器提供静态文件服务,不过需要进行一些配置,才能正确访问. 配置settings # St ...

  2. [转帖]AES算法(五)GCM工作模式

    https://zhuanlan.zhihu.com/p/376692295 在以前介绍的基本工作模式中,ECB.CFB.OFB 三种模式可以解决 ECB 模式中相同明文生成相同密文的缺陷,CTR 又 ...

  3. [转帖]Oracle JDBC中的语句缓存

    老熊 Oracle性能优化 2013-09-13 在Oracle数据库中,SQL解析有几种: 硬解析,过多的硬解析在系统中产生shared pool latch和library cache liatc ...

  4. Jmeter学习之三_知识梳理

    Jmeter学习之三_知识梳理 背景 简单学习了Jmeter的两个用例 感觉可以继续深入学习一下Jmeter了. 所以想着趁体检入职之前继续学习完善一下. 希望能够继续提高 Jmeter的相关知识 1 ...

  5. 【转帖】千亿参数大模型首次被撬开!Meta复刻GPT-3“背刺”OpenAI,完整模型权重及训练代码全公布

    https://cloud.tencent.com/developer/article/1991011 千亿级参数AI大模型,竟然真的能获取代码了?! 一觉醒来,AI圈发生了一件轰动的事情-- Met ...

  6. CentOS7最小安装注意事项

    CentOS7最小安装注意事项 背景与说明 公司很多项目现在都是最小化安装的了. 自己之前为了方便都是使用GUI安装,很多地方可能都有遗漏 这里进行一下简单的安装总结, 避免以后新安装有缺失的项目 安 ...

  7. 手把手带你开发starter,点对点带你讲解原理

    京东物流 孔祥东 _____ _ ____ _ / ____| (_) | _ \ | | | (___ _ __ _ __ _ _ __ __ _| |_) | ___ ___ | |_ \___ ...

  8. 2024年最新的Python操控微信教程

    自从微信禁止网页版登陆之后,itchat 库实现的功能也就都不能用了,那现在 Python 还能操作微信吗?答案是:可以! 在Github上有一个项目叫<WeChatPYAPI>可以使用 ...

  9. 【DS】P9062 [Ynoi2002] Adaptive Hsearch&Lsearch(区间最近点对)

    Problem Link 给定平面上 \(n\) 个点,\(Q\) 次询问编号在 \([l,r]\) 内的点的最近点对.\(n,Q\le 2.5\times 10^5\). 技巧:平面网格化 乱搞都是 ...

  10. echarts饼图的配置 封装组件的注意点

    1==>tooltip 类似饰hover效果提示框组件.光标放上去会触发 2==>formatter // 饼图 {a}(系列名称),{b}(数据项名称),{c}(数值), {d}(百分比 ...