所谓包, 就是把一组PL/SQL 的代码元素组织在一个命名空间下.
另外, 包的用法就类似java中的类.( 有封装, 有重载, 没有继承和多肽)
1 create or replace procedure process_employee( 2 employee_id_in IN employees.employee_id%type) 3 is 4 l_full_name varchar2(100); 5 begin 6 select last_name || ' , ' || first_name 7 into l_full_name 8 from employees 9 where employee_id = employee_id_in;10 dbms_output.put_line('The full name is : ' || l_full_name);11 end;12 /13 show errors;14 15 -- 运行116 begin17 process_employee(195);18 end;19 /20 -- 运行221 exec process_employee(195);
可以看到, 如上代码还是基本上使用了 cs106中的软件设计方法, 即自顶向下, 分而治之的办法.
现在如果用户又打来电话说"还是用姓-空格-名的格式吧", 我们就不用满腹牢骚, 通宵加班的寻找 ||','|| 了. 相反, 我们只需要花上5秒钟来修改 employee_pke.fullname的实现部分, 然后通知用户一切搞定了.
上例代码实现:
1 package employee_pkg 2 as 3 subtype fullname_t IS varchar2(200); 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;14 /15 show errors;
1 package body employee_pkg 2 as 3 function fullname( 4 last_in employees.last_name%type, 5 first_in employees.first_name%type) 6 return fullname_t 7 is 8 begin 9 return last_in || ' , ' || first_in;10 end;11 12 function fullname( employee_id_in in employees.employee_id%type)13 return fullname_t14 is15 retval fullname_t;16 begin17 select fullname(last_name, first_name) into retval18 from employees19 where employee_id = employee_id_in;20 21 return retval;22 exception23 when no_data_found then 24 dbms_output.put_line('There is no data');25 when too_many_rows then 26 dbms_output.put_line('There are too many rows');27 end;28 end employee_pkg;
上边的内容最好分开编译, 先编译声明部分, 再编译包体部分
初始化:oracle 负责保证每个包在每个会话会被初始化一次.
持久化, 除了数据持久化之外, 还有一个会话持久化, 这意味着如果我们连接到oracle数据库(建立了一个会话) 然后执行一个程序给一个包级别的变量(也就是一个在包的规范部分声明的变量, 或者虽然是在包体中声明, 但又游离于包体中的所有程序之外的变量)赋值, 这个变量设置会一直持续会话生命期, 即便执行赋值操作的程序已经结束, 只要会话还没有结束, 这个变量的值就会一直保持.
缓存静态的会话信息
初始化单元可以缓存静态会话信息, 这些信息可以持续整个会话生命期. 不过这样会占用内存, 比如有1000个会话, 那么就要占用1000份内存, 不过这样可以提高CPU提取数据的速度.
包体中, 把缺省值的赋值操作移动到初始化单元中.
例如:
1 create or replace package valerr2 is3 function get return varchar2;4 end valerr;5 /6 show errors;
1 create or replace package body valerr 2 is 3 -- 一个包级别的, 但是 私有的全局变量 4 v varchar2(1) := 'ABC'; 5 6 function get 7 return varchar2 8 is 9 begin10 return v;11 end;12 13 begin -- 注意这个 begin 没有 end; 与之对应14 dbms_output.put_line('Before i show you v...');15 exception16 when others then17 dbms_output.put_line('Trapped the error!');18 end valerr;
上边的包体中的初始化单元, begin 开始, 但是没有 end; 与之对应, 一定要注意.
为什么要有这个初始化单元的另外一个原因是, 如果你不是在初始化单元定义的变量, 如果发生异常, 是不会被捕获的.我们可以进一步把包设计的标准化, 以保证总有一个初始化过程, 以提示我们的组员避免以上问题, 例如:
也就是把想要初始化的内容放到一个 procedure 中, 也可以再定义一个验证 初始化 procedure 的 verify_initialization, 然后在初始化单元里包含这两个procedure, 这样更标准化.
包数据可以被那些对这个包邮execute权限的程序使用(包括读取和修改这个值) 共有的包数据非常类似于 oracle forms 中的 global 变量, 也和后者同样危险.
如果在一个包过程中打开了一个游标, 这个游标会在整个会话生命期中一直保持着打开和可用状态.
包游标
在包中声明显示游标
如果我们在声明游标时只是声明了游标头, 我们就必须要在游标定义加上 RETURN 子句, 这个子句用来描述从游标中提取的数据元素.
1 create or replace package book_info 2 is 3 cursor byauthor_cur( -- 带sql的游标, 整体 4 author_in in books.author%type) 5 is 6 select * 7 from books 8 where author = author_in; 9 10 cursor bytitle_cur( -- 带 return 的游标头11 title_filter_in in books.title%type)12 return books%rowtype;13 14 type author_summary_rt is record(15 author books.author%type,16 total_page_count pls_integer,17 total_book_count pls_integer);18 19 cursor summary_cur(20 author_id in books.author%type)21 return author_summary_rt;22 end book_info;23 /24 show errors;
1 create or replace package body book_info 2 is 3 cursor bytitle_cur( 4 title_filter_in in books.title%type) 5 return books%rowtype 6 is 7 select * 8 from books 9 where title like upper(title_filter_in);10 11 cursor summary_cur(12 author_id in books.author%type)13 return author_summary_rt14 is15 select author, sum(page_count), count(*)16 from books17 where author = author_id;18 end book_info;19 /20 show errors;21
注意: 上边包体并没有把包声明中的所有内容都实现, 但是带有 return 的游标必须要实现.
直接定义变量, 是包中一个游标的类型, 然后直接打开, 提取, 关闭 游标就可以了.
以上游标要作为一个整体, 打开, 提取, 关闭 一体就没有问题了.
建议, 专门构建一个打开游标, 关闭游标的过程.
这样, 和包游标有关的所有复杂操作都被放在过程中完成.
刚刚说了, 包是在整个会话期间都可以使用, 占用了大量内存, 但是这样也有好处, 同时也有办法不这样. 这就是包的串行化.
包的串行化
在包声明和包体内都使用 SERIALLY_REUSABLE 这个编译指令, 这种包的包状态(变量值, 包游标的打开状态等)的声明周期可以从整个会话减少到对包的一个程序调用.
例如:
1 create or replace package book_info_n 2 is 3 pragma serially_reusable; -- 注意这里 4 5 procedure fill_list; 6 7 procedure show_list; 8 end book_info_n; 9 /10 show errors;
1 create or replace package body book_info_n2 is3 pragma serially_reusable; -- 注意这里4 5 -- ...6 end book_info_n;7 /8 show errors;
关于串行包要注意的内容:
- 串行包使用的是全局内存是从SGA中分配的, 而不是从用户的UGA中分配的, 这样一来包的工作区就可以重用的, 每次包重用时, 包级别变量都会重新初始化成缺省值或者NULL, 包的初始化单元也会重新执行.
- 使用串行包所需要的工作区的最大数量取决于冰法使用这个包的用户的数量, SGA内存使用的增加被UGA或者程序内存的减少所抵消. 最后, 如果数据库由于其他的需求需要回收SGA内存, 数据库就会把不再使用的工作期过期处理.
个人感觉一般还是不要使用 串行包.
何时使用包
封装数据操作.
避免直接硬编码: 比如在包中定义一个常量, 在包外可以直接引用.
把逻辑上相关的功能组织在一起
缓存会话的静态数据从而改善应用程序性能( 在一个session 中会始终保存 )