处理数据库中的PL/SQL代码库应当遵循一定的规则和组织方法。从Oracle 8i到Oracle 10g的数据库都为组织代码库提供了两种选择,分别是定义者权限模式和调用者权限模式。本章将要探讨的是实现调用者权限模式的方法。
在默认情况下,Oracle一直是使用定义者权限体系结构的,但其实早在Oracle 8i中就已经介绍了调用者权限体系结构。许多的ERP/CRM数据库应用程序都使用了调用者权限这个概念。当处理集中式数据时,使用定义者权限体系结构可以创建健壮的应用程序。
Oracle应用程序套件使用定义者权限体系来共享通用代码组件和管理内部组件接口。因为数据都分布在同一个Oracle应用程序实例的各个子系统中,所以Oracle应用程序使用一个授权和同义词的体系来实现定义者权限。
本章独立于本书的其他章节,因为本章的内容都只是当前Oracle数据库版本才开始支持的,它包含以下几个方面的内容:
● 介绍定义者权限和调用者权限的概念
● 定义者权限的概念
● 调用者权限的概念
● 理解权限体系
● 理解定义者权限体系结构
● 理解调用者权限体系结构
● 比较和对照两种体系结构的实现策略
使用权限体系的原因:
我们使用定义者权限或者调用者权限来执行Oracle数据库中的应用程序。定义者权限模式确保我们能控制对集中式数据的插入、更新和删除操作,而调用者权限模式则确保我们能控制对分布式数据的插入、更新和删除操作。
利用定义者或调用者体系结构创建的应用程序具有更好的可控性和组织结构,并且也更易于使用、支持和维护。如果我们没有理解两种体系结构的特性,只是接受默认的设置,那可能会导致我们犯一些关键性的错误,同时应用程序的复杂度以及支撑运行的成本也会增加。
本章的重点是介绍、比较与对照,以及演示在Oracle中实现定义者权限和调用者权限体系结构的方法。由于本书并不是使用PL/SQL程序体的基础教材,您应该自己研究一下第8章到第10章中的Oracle 10g程序实例,这些示例包含存储程序单元的所有特征—— 存储程序单元是一些独立的过程、函数、包和触发器。
这些概念的演示需要两种用户模式。在运行本章所有的示例之前应该运行create_invoker_user.sql脚本文件,这个文件负责创建、撤销以及重建MYAPP用户/模式和PLSQL用户/模式,但运行这个文件需要在SYSTEM用户/模式下或得到授权DBA的许可。
注意:
用户和模式是同义的,出于使用简单的考虑,我们在本章余下的部分中都使用模式这个词。
4.1 介绍定义者权限和调用者权限的概念
在定义存储PL/SQL程序单元时要选择使用定义者权限或调用者权限。在默认情况下,PL/SQL程序单元都是定义者权限程序。我们先来介绍一下定义者权限和调用者权限的基本概念。
4.1.1 定义者权限的概念
定义者权限PL/SQL程序单元是以这个程序单元拥有者的特权来执行它的,也就是说,任何具有这个PL/SQL程序单元执行权的用户都可以访问程序中的对象。所有具有执行权的用户都有相同的访问权限,当这些用户访问同一张数据表时就出现问题了,这是需要解决的。
使用定义者权限模式的原因:
当遇到下面两种情况之一时,我们使用定义者权限模式。一种情况是,我们对所有的外部模式都授予相同的执行特权,并且所有的数据都在一张单独的表中;另一种情况是,应用程序有一个基于连接和会话信息的安全系统。
Oracle的电子商务应用套件(Applications E-Business Suite)中部署了定义者权限,这是一个强大的体系结构,特别是在部署中包括了能够基于用户权限而改变的上下文视图时。我们推荐在联合式处理模型中使用定义者权限。
小提示:
关于连接和会话信息的安全系统使您只能以受限的或并行的方式,访问同一代码和数据库表的不同信息集。
在存储PL/SQL程序中使用定义者权限可以确保您能控制数据的插入、更新和删除操作。您可以像ERP/CRM应用程序那样通过创建应用程序或授权许可元数据和安全程序来做到这一点。许多ERP/CRM程序通过使用逐行数据库触发器来增强用户权限,但是这样做的代价是巨大的,因为它增加了完成事务的时间。触发器也是函数,它是基于用户级安全对应用层元数据集合的维护。
数据集中处理是一个折中的方法,这样可以避免多数据流之间数据的一致性问题。但同时带来的不利影响是数据量会显著增加,同时数据吞吐量可能会下降。
注意:
以定义者权限模式工作的Oracle应用程序,通过使用一组复杂的存储PL/SQL应用程序元数据集合和共享库,实现了APPS这种单一模式下的并行操作,同时对APPLSYSPUB和APPLSYS模式进行安全管理。
通过在PLSQL模式下使用下面的PL/SQL包,我们可以看到定义者权限开发的效用。执行SQL *Plus命令SHOW USER可以确认我们是在PLSQL模式下,然后我们应该运行create_definer1.sql脚本来创建这个包:
-- Available online as part of create_definer1.sql file.
-- Create a DEFINER_RIGHTS package.
CREATE OR REPLACE PACKAGE definer_rights IS
-- Define a GET_LITERAL function.
FUNCTION get_literal
( literal VARCHAR2 )
RETURN VARCHAR2;
END;
/
-- Create a DEFINER_RIGHTS package body.
CREATE OR REPLACE PACKAGE BODY definer_rights IS
-- Define a GET_LITERAL function.
FUNCTION get_literal
( literal VARCHAR2 )
RETURN VARCHAR2 IS
BEGIN
-- Use an implicit for-loop to avoid declaring variables.
FOR i IN (SELECT literal
FROM dual) LOOP
-- Return the value.
RETURN 'Called by ['||literal||']';
END LOOP;
END get_literal;
END definer_rights;
/
脚本创建的存储PL/SQL包具有下面几个特征:
● 在definer_rights包的包规范中定义get_literal函数;
● 创建包主体并实现get_literal函数,这个函数:
● 有且仅有一个VARCHAR2类型的形参literal;
● 用一个具有隐式游标的for循环从DUAL表中选择实参表示的那一列;
● 返回VARCHAR2类型的实参值。
创建包规范和包主体后,create_definer1.sql脚本给MYAPP模式授予EXECUTE权,如下所示:
-- Available online as part of create_definer1.sql file.
-- Grant EXECUTE privileges to MYAPP schema.
GRANT EXECUTE ON definer_rights TO myapp;
在PLSQL模式下使用下面的语句查询包的定义者:
SELECT definer_rights.get_literal(USER)
FROM dual;
输出如下:
DEFINER_RIGHTS.GET_LITERAL(USER)
--------------------------------------
Called by [PLSQL]
在验证本地的存储PL/SQL包工作在PLSQL模式后,使用下面的命令来连接MYAPP模式:
CONN MYAPP/MYAPP
使用SQL*Plus命令SHOW USER验证您处在正确的模式下,如下所示:
USER is "MYAPP"
我们可以通过运行一个与在所有者模式下运行的查询相类似的查询,来测试具有定义者权限的包。在存储包、函数或过程名之前,我们需要链接到一个用户模式。当我们将一个用户链接到包上的同时,也将用户模式链接到包中定义的函数或过程上了。
注意:
链接是将模式连接到程序体和对象的过程。链接时需要指明模式名和周期。
我们应该在当前点检查用户模式,假如处于MYAPP模式,使用SQL*Plus命令SHOW USER进行检查,然后运行下面的查询:
SELECT plsql.definer_rights.get_literal(USER)
FROM dual;
输出如下所示:
PLSQL.DEFINER_RIGHTS.GET_LITERAL(USER)
--------------------------------------
Called by [MYAPP]
现在我们已经用另一个模式存储的PL/SQL包的执行权限查询了公有DUAL表。create_definer2.sql脚本稍微地改变了这个问题,现在以包定义者的角色创建和使用这个包。您应该以PLSQL用户的身份重新连接,并使用SQL*Plus来验证您就是PLSQL用户。然后,在PLSQL模式下运行create_definer2.sql脚本来定义local_table,如下所示:
-- Available online as part of create_definer2.sql file.
-- Create a local table.
CREATE TABLE local_table
( owner_name VARCHAR2(30 CHAR)
, user_name VARCHAR2(30 CHAR) );
接下来,修改包definer_rights,如下所示:
-- This is found in the create_definer2.sql file.
-- Create a DEFINER_RIGHTS package.
CREATE OR REPLACE PACKAGE definer_rights IS
-- Define a SET_DATA function.
FUNCTION set_data
( user_in VARCHAR2 )
RETURN BOOLEAN;
-- Define a GET_DATA function.
FUNCTION get_data
( user_in VARCHAR2 )
RETURN VARCHAR2;
END;
/
-- Create a DEFINER_RIGHTS package body.
CREATE OR REPLACE PACKAGE BODY definer_rights IS
-- Define a SET_DATA function.
FUNCTION set_data
( user_in VARCHAR2 )
RETURN BOOLEAN IS
-- Define default return value.
retval BOOLEAN := FALSE;
BEGIN
-- Use an implicit for-loop to avoid declaring variables.
INSERT
INTO local_table
VALUES
('PLSQL'
, user_in );
COMMIT;
-- Reset return value.
retval := TRUE;
-- Return value.
RETURN retval;
END set_data;
-- Define a GET_DATA function.
FUNCTION get_data
( user_in VARCHAR2 )
RETURN VARCHAR2 IS
BEGIN
-- Use an implicit for-loop to avoid declaring variables.
FOR i IN (SELECT user_in
, owner_name
, user_name
FROM local_table
WHERE user_name = user_in ) LOOP
-- Return the value.
RETURN 'Called by ['
|| user_in||']['||i.owner_name||']['||i.user_name||']';
END LOOP;
END get_data;
END definer_rights;
/
上面脚本创建的存储PL/SQL包具有如下特征:
● 定义了definer_rights包的包规范,其具有两个函数get_data 和set_data;
● 在包主体中实现了函数get_data 和set_data。这两个函数具有下面的功能:
● 在定义者模式,即PLSQL用户下,set_data函数向local_table表中插入一条记录;
● get_data函数返回实参和local_table表中的两列值。
在重新创建definer_rights包后,我们可以用PLSQL用户的身份运行下面的匿名块PL/SQL程序来测试它。
-- Available online as part of create_definer2.sql file.
-- Test the definer table from the definer schema.
BEGIN
-- Call the function that inserts the user name.
IF definer_rights.set_data(USER) THEN
-- Use an implicit for-loop to avoid declaring variables.
FOR i IN (SELECT definer_rights.get_data(user) data
FROM dual ) LOOP
-- Print the value returned.
DBMS_OUTPUT.PUT_LINE(i.data);
END LOOP;
END IF;
END;
/
上面的脚本执行以下任务:
● 使用if语句检查set_data函数返回的布尔值,若返回值为真,则执行如下操作:
● 用一个具有隐式游标的for循环从DUAL表中选择get_data函数的返回值表示的那一列;
● 使用DBMS_OUTPUT.PUT_LINE打印结果,使用索引处理隐式游标返回的结构,这个结构包含两列值。
以PLSQL用户身份运行匿名块PL/SQL程序将返回下面的结果:
Called by [PLSQL][PLSQL][PLSQL]
create_definer2.sql脚本的执行截断了local_table表并将执行权限赋予MYAPP用户。精确语法请查看本章中前面的示例。将包配置为定义者权限模式后,我们现在可以检查其他用户是否可以更新PLSQL用户的local_table表。
以MYAPP用户身份运行下面的匿名块PL/SQL程序:
-- Available online as part of test_definer1.sql file.
-- Set SQL*Plus environment lost due to schema connection change.
SET SERVEROUTPUT ON SIZE 1000000
-- Anonymous block program to test definer_rights package.
BEGIN
-- Call the function that inserts the user name.
IF plsql.definer_rights.set_data(USER) THEN
-- Use an implicit for-loop to avoid declaring variables.
FOR i IN (SELECT plsql.definer_rights.get_data(user) data
FROM dual ) loop
-- Print the value returned.
DBMS_OUTPUT.PUT_LINE(i.data);
END LOOP;
END IF;
END;
/
使用调用者体系结构的原因:
当处理分布式数据的时候,我们使用调用者权限。调用者权限体系结构可以使得对分布式数据的访问控制变得简单并且代价较低。
如果实际的应用适合创建分布式程序,那么我们建议使用调用者权限。应用调用者权限模式将有机会将数据隔离开,并将数据集中在公司的数据仓库中。我们发现最容易聚集数据源的方法就是在夜间运行批处理程序和计划任务。
这个脚本和前一个在PLSQL模式下运行的测试脚本所做的工作是一样的。惟一的变化是在脚本中在要指出所链接的模式,所以在包名前加上了“plsql”。以PLSQL用户身份运行匿名块PLSQL程序,返回结果如下:
Called by [MYAPP][PLSQL][MYAPP]
注意:
如果您没有看到下面的输出,那可能是您在将会话状态从MYAPP模式变为PLSQL模式时,没有打开SEVEROUTPUT。要设置SQL*Plus的SERVEROUTPUT,可以运行test_definer1.sql脚本。
您现在已经了解了定义者权限模式的基本概念,下面介绍调用者权限模式。
4.1.2 调用者权限的概念
调用者权限是指当前用户而不是程序的创建者执行PL/SQL程序体的权限。这意味着不同用户对于对象所具有的权限可能是不同的。这个思想的提出解决了不同的用户更新不同表的方法。
定义代码时通过使用AUTHID关键字,可以将存储PL/SQL程序体定义为具有调用者权限的对象,但如果不知道使用关键字的正确语法就会有一点棘手了,因为您可能会受一些错误提示信息的误导而更加茫然。使用关键字的一个首要原则是AUTHID只能在模式级的程序中使用,也就是我们可以使用它来定义存储函数、过程和包。
小提示:
包主体是包的实现,包是模式级程序,而包主体则不是。
包中的函数和过程不能使用AUTHID,如果使用了这个关键字,将会碰到下面的错误:
Errors for PACKAGE BODY DEFINER_RIGHTS:
LINE/COL ERROR
-------- --------------------------------------------------------
7/10 PLS-00157: AUTHID only allowed on schema-level programs
下图就是invoker_right_clause的正确语法,这在所有模式级PL/SQL程序体中均适用:
![]()
为了使create_invoker1.sql脚本能在PLSQL模式下创建具有调用者权限包,我们应该以PLSQL用户身份重新连接并使用SQL*Plus验证是否为PLSQL用户。就像前面的示例,它依赖于PLSQL模式下的local_table表,如果local_table表在数据库中被删除了,那么这个脚本会将表放回原处。要检查表的定义,请查看前面的定义者权限一节的相关内容。现在我们可以运行下面给出的create_invoker1.sql脚本了:
-- Available online as part of create_invoker1.sql file.
-- Create an INVOKER_RIGHTS package.
CREATE OR REPLACE PACKAGE invoker_rights AUTHID CURRENT_USER IS
-- Define a SET_DATA function.
FUNCTION set_data
( user_in VARCHAR2 )
RETURN BOOLEAN;
-- Define a GET_DATA function.
FUNCTION get_data
( user_in VARCHAR2 )
RETURN VARCHAR2;
END;
/
-- Create an INVOKER_RIGHTS package body.
CREATE OR REPLACE PACKAGE BODY invoker_rights IS
-- Define a SET_DATA function.
FUNCTION set_data
( user_in VARCHAR2 )
RETURN BOOLEAN AS
-- Define default return value.
retval BOOLEAN := FALSE;
BEGIN
-- Use an implicit for-loop to avoid declaring variables.
INSERT
INTO local_table
VALUES
('PLSQL'
, user_in );
COMMIT;
-- Reset return value.
retval := TRUE;
-- Return value.
RETURN retval;
END set_data;
-- Define a GET_DATA function.
FUNCTION get_data
( user_in VARCHAR2 )
RETURN VARCHAR2 AS
BEGIN
-- Use an implicit for-loop to avoid declaring variables.
FOR i IN (SELECT user_in
, owner_name
, user_name
FROM local_table
WHERE user_name = user_in ) LOOP
-- Return the value.
RETURN 'Called by ['
|| user_in||']['||i.owner_name||']['||i.user_name||']';
END LOOP;
END get_data;
END invoker_rights;
/
上面的脚本创建了存储PL/SQL包,这些包具有下列特性:
● 定义了invoker_rights包的包规范,其具有两个函数get_data和set_data;
● 在包主体中实现了函数get_data和set_data,这两个函数具有以下功能:
● 在定义者模式即PLSQL用户下,set_data函数向local_table表中插入一条记录;
● get_data函数返回实参和local_table表中的两列值。
在PLSQL模式下创建invoker_rights包后,您可以在PLSQL模式下运行下面的匿名块PL/SQL程序来测试它:
-- Available online as part of create_invoker1.sql file.
-- Test the definer table from the definer schema.
BEGIN
-- Call the function that inserts the user name.
IF invoker_rights.set_data(USER) THEN
-- Use an implicit for-loop to avoid declaring variables.
FOR i IN (SELECT invoker_rights.get_data(user) data
FROM dual ) LOOP
-- Print the value returned.
DBMS_OUTPUT.PUT_LINE(i.data);
END LOOP;
END IF;
END;
/
上面的脚本完成如下工作:
● 使用if语句检查set_data函数返回的布尔值,若返回值为真,则执行如下操作:
● 用一个具有隐式游标的for循环从DUAL表中选择get_data函数的返回值表示的那一列;
● 使用DBMS_OUTPUT.PUT_LINE打印结果,使用索引处理隐式游标返回的结构,这个结构包含两列值。
以PLSQL用户身份运行匿名块PL/SQL程序将返回下面的结果:
Called by [PLSQL][PLSQL][PLSQL]
create_definer2.sql脚本的执行截断了local_table表并将执行权限赋予MYAPP用户。精确语法请查看本章前面介绍的示例。
以MYAPP用户连接并运行下面匿名块PL/SQL程序,我们可以知道其他用户是否可以更新PLSQL用户的local_table表:
-- Available online as part of test_invoker1.sql file.
-- Anonymous block program to test invoker_rights package.
BEGIN
-- Call the function that inserts the user name.
IF plsql.invoker_rights.set_data(USER) THEN
-- Use an implicit for-loop to avoid declaring variables.
FOR i IN (SELECT plsql.invoker_rights.get_data(user) data
FROM dual ) LOOP
-- Print the value returned.
DBMS_OUTPUT.PUT_LINE(i.data);
END LOOP;
END IF;
END;
/
这个脚本和前一个在PLSQL模式下运行的测试脚本所做的工作是一样的。惟一的变化是在脚本中在要指出所链接的模式,所以在包的名字前面加上了"plsql"。但当您运行下面的匿名块PL/SQL程序时,它没有返回结果,却引发了下面的异常:
BEGIN
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "PLSQL.INVOKER_RIGHTS", line 15
ORA-06512: at line 4
引发这个异常的原因是MYAPP用户并没有权限来写PLSQL模式的local_table对象。解决的方法是将local_table对象的insert和select权限授予MYAPP模式。
您现在已经了解了调用者权限模式的基本概念。在下一节,我们将比较和对照定义者权限模式和调用者权限模式。





