让我们从 Table API 开始。这是通过 PL/SQL API 调解对表的访问的做法。所以,我们每个表都有一个包,它应该从数据字典中生成。该包提供了一组标准程序,用于针对表发布 DML 和一些用于检索数据的函数。
相比之下,事务 API 代表一个工作单元。它根本不公开有关底层数据库对象的任何信息。事务性 API 提供了更好的封装和更简洁的界面。
对比是这样的。考虑以下创建新部门的业务规则:
- 新部门必须有名称和位置
- 新部门必须有经理,经理必须是现有员工
- 其他现有员工可能会调到新部门
- 新员工可能被分配到新部门
- 新部门必须至少分配两名员工(包括经理)
使用 Table API,事务可能看起来像这样:
DECLARE
dno pls_integer;
emp_count pls_integer;
BEGIN
dept_utils.insert_one_rec(:new_name, :new_loc, dno);
emp_utils.update_one_rec(:new_mgr_no ,p_job=>'MGR’ ,p_deptno=>dno);
emp_utils.update_multi_recs(:transfer_emp_array, p_deptno=>dno);
FOR idx IN :new_hires_array.FIRST..:new_hires_array.LAST LOOP
:new_hires_array(idx).deptno := dno;
END LOOP;
emp_utils.insert_multi_recs(:new_hires_array);
emp_count := emp_utils.get_count(p_deptno=>dno);
IF emp_count < 2 THEN
raise_application_error(-20000, ‘Not enough employees’);
END IF;
END;
/
而使用事务 API 则要简单得多:
DECLARE
dno subtype_pkg.deptno;
BEGIN
dept_txns.create_new_dept(:new_name
, :new_loc
, :new_mgr_no
, :transfer_emps_array
, :new_hires_array
, dno);
END;
/
那么为什么在检索数据方面存在差异?因为事务 API 方法不鼓励泛型 get() 函数,以避免盲目使用低效的 SELECT 语句。
例如,如果你只想要一个员工的工资和佣金,查询这个...
select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;
...比执行这个更好...
l_emprec := emp_utils.get_whole_row(p_eno);
...特别是如果 Employee 记录有 LOB 列。
也比:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);
... 如果这些 getter 中的每一个都执行单独的 SELECT 语句。这不是未知的:这是一种糟糕的 OO 实践,会导致糟糕的数据库性能。
Table API 的支持者支持它们,因为它们使开发人员无需考虑 SQL。不赞成使用 Table API 的人出于同样的原因。即使是最好的 Table API 也倾向于鼓励 RBAR 处理。如果我们每次都编写自己的 SQL,我们更有可能选择基于集合的方法。
使用事务性 API 并不一定排除使用 get_resultset() 函数。查询 API 仍有很多价值。但它更有可能是由实现连接的视图和函数构建的,而不是单个表上的 SELECT。
顺便说一句,我认为在 Table API 之上构建事务 API 并不是一个好主意:我们仍然有孤立的 SQL 语句,而不是精心编写的连接。
例如,这里有两个不同的事务 API 实现,用于更新区域中每个员工的薪水(区域是组织的一个大型部分;部门被分配到区域)。
第一个版本没有纯 SQL,只有 Table API 调用,我不认为这是一个稻草人:它使用了我在 Table API 包中看到的那种功能(尽管有些使用动态 SQL 而不是命名为 SET_XXX( ) 程序)。
create or replace procedure adjust_sal_by_region
(p_region in dept.region%type
, p_sal_adjustment in number )
as
emps_rc sys_refcursor;
emp_rec emp%rowtype;
depts_rc sys_refcursor;
dept_rec dept%rowtype;
begin
depts_rc := dept_utils.get_depts_by_region(p_region);
<< depts >>
loop
fetch depts_rc into dept_rec;
exit when depts_rc%notfound;
emps_rc := emp_utils.get_emps_by_dept(dept_rec.deptno);
<< emps >>
loop
fetch emps_rc into emp_rec;
exit when emps_rc%notfound;
emp_rec.sal := emp_rec.sal * p_sal_adjustment;
emp_utils.set_sal(emp_rec.empno, emp_rec.sal);
end loop emps;
end loop depts;
end adjust_sal_by_region;
/
SQL 中的等效实现:
create or replace procedure adjust_sal_by_region
(p_region in dept.region%type
, p_sal_adjustment in number )
as
begin
update emp e
set e.sal = e.sal * p_sal_adjustment
where e.deptno in ( select d.deptno
from dept d
where d.region = p_region );
end adjust_sal_by_region;
/
这比以前版本的嵌套游标循环和单行更新要好得多。这是因为在 SQL 中,编写我们需要按区域选择员工的联接很容易。使用 Table API 要困难得多,因为 Region 不是Employees 的键。
公平地说,如果我们有一个支持动态 SQL 的 Table API,情况会更好,但仍然不理想:
create or replace procedure adjust_sal_by_region
(p_region in dept.region%type
, p_sal_adjustment in number )
as
emps_rc sys_refcursor;
emp_rec emp%rowtype;
begin
emps_rc := emp_utils.get_all_emps(
p_where_clause=>'deptno in ( select d.deptno
from dept d where d.region = '||p_region||' )' );
<< emps >>
loop
fetch emps_rc into emp_rec;
exit when emps_rc%notfound;
emp_rec.sal := emp_rec.sal * p_sal_adjustment;
emp_utils.set_sal(emp_rec.empno, emp_rec.sal);
end loop emps;
end adjust_sal_by_region;
/
最后一句话
话虽如此,在某些情况下,表 API 会很有用,在某些情况下,我们只想以相当标准的方式与单个表进行交互。一个明显的案例可能是生产或使用来自其他系统的数据馈送,例如ETL。
如果您想研究 Table API 的使用,最好从 Steven Feuerstein 的Quest CodeGen Utility(以前的 QNXO)入手。这与 TAPI 生成器一样好,而且是免费的。