操作数据无非删除、更新、插入的动作,在Oracle Form依靠下面触发器完成这些操作。
Form Trigger
Key-commit 保存按钮触发/Ctrl+S 键盘触发/代码触发
Block Trigger
Pre-delete Pre-update Pre-insert
On-delete On-update On-insert On-lock
Oracle保存数据的机制:第一步依次从数据库中删除在Block上删除的记录(删除每条记录时先触发Pre-delete,再触发On-delete),第二步依次从数据库中更新在Block上更新的记录(更新每条记录时先触发Pre-update,再触发On-update),第三步依次向数据库中插入在Block上插入的记录(插入每条记录时先触发Pre- insert,再触发On- insert),第四步COMMIT。其中任何一步出错的话都会引发异常,回滚以前的操作。所以我猜想Oracle Form肯定有一套机制能记录Block上每条记录的状态,所以在保存时可触发相应的Trigger。
如果限制Form在一定条件下才允许保存,可以将控制逻辑写在Form级trigger pre-commit或者block级的Pre-delete,Pre-update,Pre-insert代码中(这种情况控制可以针对操作类型进行控制,更加精细),一但条件不满足,立即抛出异常或忽略本次操作。
On-lock触发器在用户在界面上企图修改数据时触发,利用了Oracle悲观封锁的机制,保证数据的完整性和一致性.
在需要设置who字段的block中,Pre-insert和Pre-update都要添加如下代码:
fnd_standard.set_who; oracle会调用API给那几个who字段赋值,不用我们操心了。
另外在pre-insert的触发器中设置某些 Item 的初始值,比如说给org_id字段赋值,比如给一个主键 Item赋一个sequence.nextval,这样与在Item的initial value属性中设定sequence.nextval
相比可以节省很多序列号,也可以更好保证序列号的连续性,这在某些业务中是非常关键的。
Eg:
:BLOCKNAME.ITEM_NAME := FND_PROFILE.VALUE(‘VALUE_NAME’);
SELECT SEQUENCE_A.NEXTVAL INTO :BLOCKNAME.ITEM_NAME ;
操作数据前提是设置Block的Insert_Allowed,Update_Allowed,Delete_Allowed属性为Yes,对于数据源为基表,单表视图或简单多表视图,可以利用Oracle Form自身的机制,不需借助任何代码,可以实现数据的操作。
1.操作基表和单表视图Block
Property Value Note
DML Data Target Type Table
2.操作多表视图
Block
Property Value Note
DML Data Target Type Table
DML Data Target Name Table_Name 操作的基表名
Item(主键字段)
Property Value Note
Primary Key Yes 不指定主键的话编译会出错
以上2种情况,系统会自动构造Insert,Update,Delete,Lock语句。根据我Trace的结果来看,
Update,Delete,Lock都是根据记录的ROWID来唯一确认一条记录的。
3.使用代码操作数据
对于数据的操作,我都建议用代码实现,这样比较直观,出了问题也容易找到原因。Oracle Form会分别调用ON-INSERT触发,ON-UPDATE触发,ON-DELETE触发分别完成插入、更新、删除操作。需要注意的是:在符合上面1,2类型的情况下,创建了ON-XXXXXX触发器,都里面的代码为NULL;这时候系统就不能自动操作数据了,因为Oracle Form检测到了ON-XXXXXX触发器,执行了其中的代码,即执行了 NULL;语句。
PROCEDURE INSERT_ROW IS
CURSOR c is
select return_fix_headers_id
from TVSN_RETURN_FIX_HEADERS_ALL
where return_fix_headers_id = :HEADER.return_fix_headers_id ;
BEGIN
insert into TVSN_RETURN_FIX_HEADERS_ALL
(return_fix_headers_id ,line_type ,order_header_id
,f_to_r_flag ,ORG_ID ,creation_date ,… ,attribute15 )
values
(:HEADER.return_fix_headers_id
,:HEADER.line_type ,:HEADER.order_header_id
,:HEADER.f_to_r_flag ,:PARAMETER.G_ORG_ID
,… ,:HEADER.attribute15 ) ;
open c ;
fetch c into :HEADER.return_fix_headers_id ;
if (c%notfound) then close c ;
raise no_data_found ;
end if ;
close c ;
END INSERT_ROW;
PROCEDURE UPDATE_ROW is
BEGIN
update TVSN_RETURN_FIX_HEADERS_ALL
set
return_fix_headers_id = :HEADER.return_fix_headers_id
,line_type = :HEADER.line_type
,order_header_id = :HEADER.order_header_id
,f_to_r_flag = :HEADER.f_to_r_flag
,org_id = :HEADER.ORG_ID
,creation_date = :HEADER.creation_date
,…
,attribute15 = :HEADER.attribute15
where return_fix_headers_id = :HEADER.return_fix_headers_id ;
if (sql%notfound) then raise no_data_found ;
end if ;
END UPDATE_ROW;
procedure DELETE_ROW is
begin
delete from TVSN_RETURN_FIX_HEADERS_ALL
where return_fix_headers_id = :HEADER.return_fix_headers_id;
if (sql%notfound) then raise no_data_found;
end if;
end DELETE_ROW;
以上3段代码是很经典的插入,修改,删除语句。特点是每次操作完成之后都有一个检验过程,红色粗体语句。可以将这些代码放在数据库,pll文件,form的program unit,甚至是直接写在trigger 中,从性能的角度出发可能放在数据库中更高效,综合维护方便等因素考虑,我建议写在form的program unit中。
4.一个常见的错误图6.1
有时候在输入一条新记录保存后再修改,会发生上图所示错误。错误的原因是ON-LOCK触发器检查到界面数据和后台数据不一致,所以提示用户。看到第3节PROCEDURE INSERT_ROW代码::PARAMETER.G_ORG_ID。系统向数据库中插入了一个参数的值,但是并没有保证界面上的字段:HEADER.ORG_ID有值或者:HEADER.ORG_ID的值与:PARAMETER.G_ORG_ID一致。
一般导致这种错误的原因有:1界面字段未赋值使用了替代值存入数据库;2由于Item字段长度不够导致部分数据截断;3值列表与View字段内容不一致。建议在Lock Row字段中只锁定界面Block上的可见字段,在操作数据前保证Block字段与传入参数保持一致,最后就使用Block字段作为传入参数。
5.Master-Detail Block 的级联删除
创建relation建立主从块关系时,可以设置级联删除属性,如图6.2所示,确定后主块会自动生成Pre-delete tigger代码为删除在数据库中对应的detail记录
图6.2
如果在创建relation为设置级联删除,可以通过修改relation的属性Delete Record Behavior为Cascading(如图6.3),新建主块Pre-delete trigger编写删除detial记录的代码,也能达到同样的效果。
图6.3
通过这种机制,oracle Form实现了前台界面清除数据和后台数据库删除数据的一致。有两点需要注意的是:
1当Detail Block的的数据源是View时,自动生成的Master Block Pre-delete trigger代码的删除对象是View,记得改成基表。
2 当三层Master-Datail关系: C is child of B is child of A,当删除A块记录调用pre-delete代码删除B块记录,但是不会自动调用B块pre-delete代码删除C块记录,所以要将删除C块记录的语句写在A块pre-delete中。