了解表 API 和事务 API 之间的差异

发布于 2024-09-06 10:05:42 字数 993 浏览 4 评论 0原文

朋友们,

请询问 Tom 线程 我通过另一个SO问题发现的,提到了表和事务API,我试图理解它们之间的区别。

表 API (TAPI) 是无法访问底层表并且存在“getter”和“getter”的地方。 “设置者”获取信息。

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

   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 调用?

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

Friends,

This Ask Tom thread which I found via another SO question, mentions Table and Transactional API's and I'm trying to understand the difference between them.

A Table API (TAPI) is where there is no access to the underlying tables and there are "getters" & "setters" to obtain information.

For example to select an address I would:

   the_address := get_address(address_id);

Instead of:

   select the_address
   from some_table
   where identifier = address_id

And then to change the address I would invoke another TAPI which takes care of the change:

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

A Transactional API (XAPI) is again where there is no direct access to modify the information in the table but I can select from it? (this is where my understanding is kind of hazy)

To select an address I would:

   select the_address
   from some_table
   where identifier = address_id

and then to change it I would call

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

So the only difference I can see between a TAPI and a XAPI is the method in which a record is retrieved from the database, i.e. a Select Versus a PL/SQL call?

Is that it? or have I missed the point entirely?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

§对你不离不弃 2024-09-13 10:05:45

表 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。插入、更新和删除都通过 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 调用串在一起以构成事务。请参阅我的博客了解该方法的含义。

A table API (TAPI) is a simple API that provides the basic CRUD operations for a table. For example, if we have a tableR MYTABLE (id INTEGER PRIMARY KEY, text VACHAR2(30)), then the TAPI would be something like:

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;

When you use TAPIs, every table has a TAPI, and every insert, update and delete goes through the TAPI.

A transaction API (XAPI) is an API that works at the transaction level rather than at the individual CRUD level (though in some cases this will be the same thing). For example, a XAPI to handle a banking transactions might look something like this:

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

The make_transfer procedure may not perform a single insert, update or delete. It may do something like this:

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;

i.e. it performs an entire transaction, which may consist of 1 or several DML statements.

A TAPI proponent would say that this is coded wrong and should contain no DML, but instead call TAPI code like:

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;

Others (like Tom Kyte and myself) would see this as overkill, adding no real value.

So you can have XAPIs alone (Tom Kyte's way), or XAPIs that call TAPIs (Steve Feuerstein's way). But some systems have TAPIs alone, which is really poor - i.e. they leave it to writers of the user interface to string together the necessary TAPI calls to make up a transaction. See my blog for the implications of that approach.

迎风吟唱 2024-09-13 10:05:44

让我们从表 API 开始。这是通过 PL/SQL API 协调对表的访问的做法。因此,我们每个表都有一个包,它应该从数据字典生成。该包提供了一组用于针对表发出 DML 的标准过程以及一些用于检索数据的函数。

相比之下,事务 API 代表一个工作单元。它根本不公开有关底层数据库对象的任何信息。事务 API 提供更好的封装和更清晰的界面。

对比是这样的。创建新部门时请考虑以下业务规则:

  1. 新部门必须有名称和地点
  2. 新部门必须有一名经理,该经理必须是现有员工
  3. 其他现有员工可能会调到新部门
  4. 新员工可能会被分配到新部门新部门
  5. 新部门必须至少分配两名员工(包括经理)

使用表 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 语句。

例如,如果您只想要某个 Employee 的工资和佣金,则查询此 ...

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 语句更有效。这是众所周知的:这是一种糟糕的面向对象实践,会导致可怕的数据库性能。

Table API 的支持者之所以支持它们,是因为它们使开发人员无需考虑 SQL。出于同样的原因,反对它们的人不喜欢 Table API。即使是最好的 Table API 也倾向于鼓励 RBAR 处理。如果我们每次都编写自己的 SQL,我们更有可能选择基于集合的方法。

使用事务 API 并不一定排除使用 get_resultset() 函数。查询 API 仍然有很多价值。但它更有可能是由实现联接的视图和函数构建的,而不是单个表上的 SELECT。

顺便说一句,我认为在表 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;
/

最后一句话

虽如此,在某些情况下 Table API 会很有用,即当我们只想以相当标准的方式与单个表交互。一个明显的例子可能是生成或使用来自其他系统(例如 ETL)的数据源。

如果您想研究 Table API 的使用,最好的起点是 Steven Feuerstein 的 Quest CodeGen Utility (以前称为 QNXO)。这与 TAPI 生成器所能得到的一样好,而且是免费的。

Let's start with the Table API. This is the practice of mediating access to tables through a PL/SQL API. So, we have a package per table, which should be generated from the data dictionary. The package presents a standard set of procedures for issuing DML against the table and some functions for retrieving data.

By comparison a Transactional API represents a Unit Of Work. It doesn't expose any information about the underlying database objects at all. Transactional APIs offer better encapsulation, and a cleaner interface.

The contrast is like this. Consider these business rules for creating a new Department:

  1. The new Department must have a Name and Location
  2. The new Department must have a manager, who must be an existing Employee
  3. Other existing Employees may be transferred to the new Department
  4. New employees may be assigned to the new Department
  5. The new Department must have at least two Employees assigned (including the manager)

Using Table APIs the transaction might look something like this:

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;
/

Whereas with a Transactional API it is much simpler:

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;
/

So why the difference in retrieving data? Because the Transactional API approach discourages generic get() functions in order to avoid the mindless use of inefficient SELECT statements.

For example, if you just want the salary and commission for an Employee, querying this ...

select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;

... is better than executing this ...

l_emprec := emp_utils.get_whole_row(p_eno);

...especially if the Employee record has LOB columns.

It is also more efficient than:

l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);

... if each of those getters executes a separate SELECT statement. Which is not unknown: it's a bad OO practice that leads to horrible database performance.

The proponents of Table APIs argue for them on the basis that they shield the developer from needing to think about SQL. The people who deprecate them dislike Table APIs for the very same reason. Even the best Table APIs tend to encourage RBAR processing. If we write our own SQL each time we're more likely to choose a set-based approach.

Using Transactional APis doesn't necessarily rule out the use of get_resultset() functions. There is still a lot of value in a querying API. But it's more likely to be built out of views and functions implementing joins than SELECTs on individual tables.

Incidentally, I think building Transactional APIs on top of Table APIs is not a good idea: we still have siloed SQL statements instead of carefully written joins.

As an illustration, here are two different implementations of a transactional API to update the salary of every Employee in a Region (Region being a large scale section of the organisation; Departments are assigned to Regions).

The first version has no pure SQL just Table API calls, I don't think this is a straw man: it uses the sort of functionality I have seen in Table API packages (although some use dynamic SQL rather than named SET_XXX() procedures).

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;
/

The equivalent implementation in 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;
/

This is much nicer than the nested cursor loops and single row update of the previous version. This is because in SQL it is a cinch to write the join we need to select Employees by Region. It is a lot harder using a Table API, because Region is not a key of Employees.

To be fair, if we have a Table API which supports dynamic SQL, things are better but still not ideal:

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;
/

last word

Having said all that, there are scenarios where Table APIs can be useful, situations when we only want to interact with single tables in fairly standard ways. An obvious case might be producing or consuming data feeds from other systems e.g. ETL.

If you want to investigate the use of Table APIs, the best place to start is Steven Feuerstein's Quest CodeGen Utility (formerly QNXO). This is about as good as TAPI generators get, and it's free.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文