将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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-14 17:47:06

将PL/SQL代码封装在灵巧的包中的相关文章

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

将代码封装在机灵的包中 http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13plsql-1872456.html 绝大多数基于PL/SQL的应用都是由成千上万甚至上百万行代码组成,这里面包括了具体多变的用户需求. 商业逻辑的实现最初是由存储过程和函数完毕,可是开发人员须要考虑将这些过程和函数放在包中维护. 何为包? 包是一组PL/SQL代码元素(游标.类型.变量.过程.函数)集合的程序单元. 通常由包声明(对象声明)和包体

使用PL/SQL developer概览图剖析pl/sql代码

性能优化的关键是找到正确的方向,例如对一段pl/sql 代码,我们觉得它执行起来很慢,但是到底慢在那里,需要一个可测量的工具去分析,我们有时候会喜欢留下调试代码在里面,比如使用dbms_utility.get_time去得到语句执行前后所耗费的时间,如果代码很大的话,这将会很繁琐. PL/SQL developer正好提供了这种功能,这里介绍下 PL/SQL developer概览图可以做到对于每个已运行的代码行,合计时间.最长时间.最短时间.平均时间和运行次数都将被有统计.可以在测试窗口轻松访

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

使用PL/Scope分析你的PL/SQL代码 从11g開始Oracle引入了PL/Scope 用于编译器收集PL/SQL程序单元的全部标识符(变量名.常量名.程序名等). 收集到的信息可通过一系列静态数据字典视图获取. 可帮助我们了解标识符的声明.定义.引用,调用或赋值以及所在源码的位置. 使用PL/Scope, 开发人员能够运行复杂的代码分析. 1.启用 Enabling PL/Scope ALTER SESSION SET plscope_settings='IDENTIFIERS:ALL'

Oracle实践--PL/SQL基础之触发器和程序包

PL/SQL基础之触发器和程序包  程序包 /* 程序包:对相关存储过程,函数,变量,游标和异常等对象的封装 有声明和主体组成 优点:1.模块化:2.更轻松的应用程序设计;3.信息隐藏;4.性能更佳 */ --程序包的声明:关键字:package is end create or replace package my_pack as procedure packageTest(v_num number); end my_pack;--也可以是 end; --程序包的主体 create or re

pl/sql可以封装代码的结构(过程,函数, 包)

1.过程(procedure) what:执行特定的操作 why:封装代码,提高代码重用率,易于维护. style:可以返回多个数据(输入参数IN,输出参数OUT) sample: create procedure name_pro (IN partment1 datatype, IN partment2 datatype, out partment2 datatype) is begin ---sql/plsql执行代码 end; / 2.函数 what:执行数据操作,返回特定的数据 why:

PL/SQL代码编写规则

1.标识符命名规则    当在PL/SQL中使用标识符定义变量.常量时,标识符名称必须以字符开始,并且长度不能超过 30 个字符.另外,为了提高程序的可读性,Oracle 建议用户按照以下规则定义各种标识符:(1)当定义变量时,建议使用 v_ 作为前缀,例如 v_sal, v_job等.(2)当定义常量时,建议使用 c_ 作为前缀,例如 c_rate .(3)当定义游标时,建议使用 _cursor 作为后缀,例如 emp_cursor .(4)当定义例外时,建议使用 e_ 作为前缀,例如 e_i

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

一.程序包和包体 程序包(package):存储在数据库中的一组子程序.变量定义.在包中的子程序可以被其它程序包或子程序调用.但如果声明的是局部子程序,则只能在定义该局部子程序的块中调用该局部子程序. 它具有面向对象程序设计语言的特点,是对这些PL/SQL 程序设计元素的封装.包类似于JAVA语言中的类,其中变量相当于类中的成员变量,过程和函数相当于类方法. create or replace package stuinfo as type stucur is ref cursor; proce

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

1.错误1:Initialization error could not initialize 电脑上原本就装有oracle 11g 64位,可是PL/SQL却怎么也连接不上,报出" Initialization error"的错误,搜集资料找到原因,原来PL/SQL是32位的,而oracle client是64位(当初安装服务器端自带的)的,二者不兼容,当然无法连接. 解决方法: (1)下载32位客户端 下载免安装版的32位oracle客户端,地址:http://www.oracle

PLSQL(PL/SQL)集成Team Foundation Server (TFS),实现数据库代码的版本管理

PL/SQL是面向Oralcle数据库的集成开发环境,是众多Oracle数据库开发人员的主要工具.由于PL/SQL(百度百科)不仅是一种SQL语言,更是一种过程编程语言,在项目实施过程中,会积累大量除存储过程之外的程序代码.既然有代码,就必须对代码进行版本管理,实现代码变更的追溯和备份,这是软件开发过程的基本要求.作为使用Team Foundation Server (TFS)作为团队的应用软件生命周期管理(ALM)平台的企业,自然会要求TFS系统与PLSQL之间实现集成开发.在这个博客中,我主