了解 Table 和 Transaction API 之间的区别

Posted

技术标签:

【中文标题】了解 Table 和 Transaction API 之间的区别【英文标题】:Understanding the differences between Table and Transaction API's 【发布时间】:2010-06-22 09:59:34 【问题描述】:

朋友们,

我通过另一个 SO 问题找到的这个 Ask Tom thread 提到了 Table 和 Transactional API,我试图了解它们之间的区别。

表 API (TAPI) 是无法访问基础表的地方,并且有“getter”和“setter”来获取信息。

例如要选择一个地址,我会:

   the_address := get_address(address_id);

代替:

   select the_address
   from some_table
   where identifier = address_id

然后要更改地址,我将调用另一个负责更改的 TAPI:

   ...
   change_address(address_id, new_address);
   ...

事务 API (XAPI) 再次出现在无法直接访问修改表中信息但我可以从中选择的地方? (这就是我的理解有点模糊的地方)

要选择一个地址,我会:

   select the_address
   from some_table
   where identifier = address_id

然后我会调用它来改变它

   ...
   change_address(address_id, new_address);
   ...

所以我可以看到 TAPI 和 XAPI 之间的唯一区别是从数据库中检索记录的方法,即 Select 与 PL/SQL 调用?

是这样吗?还是我完全错过了重点?

【问题讨论】:

【参考方案1】:

让我们从 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 生成器一样好,而且是免费的。

【讨论】:

@APC 感谢您全面而清晰的回复。我可以请您扩展您的最后一段吗? “顺便说一句,我认为在表 API 之上构建事务 API 并不是一个好主意:我们仍然有孤立的 SQL 语句,而不是精心编写的连接。”我已经读了两遍,得出了两个不同的结论(这是我的错,不是你的错)我只是想清楚这一点! 我完全不确定像 SF 这样的 TAPI 人员是否会同意允许您按照上一个示例传递动态 WHERE 子句,因为这意味着编写 SQL 的(片段)。跨度> 如果我可以投票两次,我会 - 很好的例子,很好的答案。 @TonyAndrews - 相反,Steven 的 QCGU 包正是生成了这种程序。 真的吗?这听起来很疯狂……?【参考方案2】:

表 API (TAPI) 是一个简单的 API,它为表提供基本的 CRUD 操作。例如,如果我们有一个 tableR MYTABLE (id INTEGER PRIMARY KEY, text VACHAR2(30)),那么 TAPI 将类似于:

package mytable_tapi is
    procedure create_rec (p_id integer, p_text varchar2);
    procedure update_rec (p_id integer, p_text varchar2);
    procedure delete_rec (p_id integer);
    function get_rec (p_id integer) returns mytable%rowtype;
end;

当您使用 TAPI 时,每个表都有一个 TAPI,并且每次插入、更新和删除都通过 TAPI。

事务 API (XAPI) 是一种在事务级别而不是在单个 CRUD 级别工作的 API(尽管在某些情况下这将是同一件事)。例如,处理银行交易的 XAPI 可能如下所示:

package banking_xapi is
    procedure make_transfer (p_from_account integer, p_to_account integer,
                             p_amount number);
    ... -- other XAPI procs
end;

make_transfer 过程可能不会执行单个插入、更新或删除。它可能会做这样的事情:

procedure make_transfer (p_from_account integer, p_to_account integer,
                         p_amount number)
is
begin
    insert into transfer_details (from_account, to_account, amount)
       values (p_from_account, p_to_account, p_amount);

    update accounts set balance = balance-p_amount
    where account_no = p_from_account;

    update accounts set balance = balance+p_amount
    where account_no = p_to_account;
end;

即它执行整个事务,可能由 1 个或多个 DML 语句组成。

TAPI 支持者会说这是错误的编码,不应包含 DML,而是调用 TAPI 代码,如下所示:

procedure make_transfer (p_from_account integer, p_to_account integer,
                         p_amount number)
is
begin
    transactions_tapi.insert_rec (p_from_account, p_to_account, p_amount);

    accounts_tapi.update_rec (p_from_account, -p_amount);

    accounts_tapi.update_rec (p_to_account, p_amount);
end;

其他人(比如 Tom Kyte 和我自己)会认为这是矫枉过正,不会增加任何实际价值。

因此,您可以单独使用 XAPI(Tom Kyte 的方式),或调用 TAPI 的 XAPI(Steve Feuerstein 的方式)。但是有些系统只有 TAPI,这真的很差 - 即它们留给用户界面的编写者将必要的 TAPI 调用串在一起以构成事务。有关该方法的含义,请参阅 my blog。

【讨论】:

感谢您的澄清

以上是关于了解 Table 和 Transaction API 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

解决错误APB AP transaction error, DAP status f0000021

MySQL--当mysqldump --single-transaction遇到alter table

步执行BDC[CALL_TRANSACTION_FROM_TABLE_CO]

MySQL--当mysqldump --single-transaction遇到alter table

SQLException Table definition has changed, please retry transaction

SQL:LEFT JOIN 基于另一个表的生效日期