form端的ON-LOCK触发器用于获取oracle数据表的行级排他锁,当ON-LOCK 触发器fire时,他会试图去获得这个锁,如果这时没有其它session获得这个锁,或者说其它session己经释放这个锁,那么我们的form会很顺利的获得。否则会抛出 app_exceptions.record_lock_exception异常。一般我们会写一个loop ..end loop;块出来,再定义一个计数器counter计录循环次数,把它作为参数传入到app_exception.record_lock_error(counter);处理过程中。这时,当这个counter为2,4,6,8这样的偶数时,在form中会弹出message,用于用户选择是否继续试图获得lock,或中止procedure的执行。因为在app_exception.record_lock_error()过程的最后,会raise一个form_trigger_failure的异常。
哪么on-lock触发器在什么时候触发呢,帮助中说:“Fires whenever Form Builder would normally attempt to lock a row, such as when an operator presses a key to modify data in an item. The trigger fires between the keypress and the display of the modified data. ”
就在当我们进入到form,查询到记录之后,然后,当我们在某一个item中输入内容与这个内容display到显示屏之间那不知多么短的时间内,it fires!
现在我们来看看比较完整的处理代码,为了简便期间,我对代码进行了简化:
procedure lock_row as
counter NUMBER;
cursor C is
select
l_id,
out_date,
back_date,
customer,
from cdm_loan
where rowid = :loan.row_id
for update of l_id nowait;
---for update of <table.columons>, <table.columons> nowait;这里"of"只在多表链接查询时才有用,意为锁定指定 列所对应的表,会根据where条件来获得该表的某一行的锁 ,nowait 表示如果获取不了锁,那么直接返 回错误.
recinfo C%rowtype;
begin
fnd_message.debug('lock_row is begin');
counter := 0; --循环记数器(变量)
loop
begin
counter := counter+1;
open C;
fetch C into recinfo;
if (C%NOTFOUND) then
close C;
fnd_message.set_name('FND','FORM_RECORD_DELETED');
fnd_message.error;
raise form_trigger_failure;
end if;
close C;
if (
-- 检查固定列,数据库中一般为非空字段,form中对应的item的require属性一般为yes,同时,block的query data source columns中相应列的mandatory属性要勾上
(recinfo.l_id = :loan.l_id)
AND (recinfo.status = :loan.s_id)
-- 检查非固定列,数据库中一般指为充许空字段,form中对应的item的require属性一般为no,block的query data source columns中相应列的mandatory属性要去掉勾
AND ((recinfo.out_date = :loan.out_date)
OR ((recinfo.out_date is null)
AND (:loan.out_date is null)))
AND ((recinfo.back_date = :loan.back_date)
OR ((recinfo.back_date is null)
AND (:loan.back_date is null)))
AND ((recinfo.customer = :loan.customer)
OR ((recinfo.customer is null)
AND (:loan.customer is null)))
)
then
return;---满足条件后退出循环
else--当form中的值同数据库中的值不匹配时的处理
fnd_message.set_name('FND', 'FORM_RECORD_CHANGED');
fnd_message.error;
raise form_trigger_failure;
end if;
exception ---当不能获取锁时,的异常代码,该异常由系统自动抛出
when app_exceptions.record_lock_exception then
app_exception.record_lock_error(counter);
end;
end loop;
end lock_row;
这里要明白几点:
1、我们调用此ON-LOCK触发器代码时,相应表中的某行即被锁定,众所周知,oracle数据库执行for update时,即获得锁,如果不commit或rollback或断掉session,这个锁是不会被主动释放的。但是在form中,我们只要保存这条记录时,或关掉form窗口,就会释放锁,不论你是否在ON-UPDATE中真正的执行了update语句,系统都会通过自动给你执行一个commit来实现。
2、loop ..end loop中的begin ..end有什么用? 这个begin..end是无奈之举,因为如果没有begin ..end 的话,我们就不能在procedure中的一个特定的范围内处理异常(exceptions语句),这样的结果会出现一旦loop中某一个记录出了问题,那么整个procedure都会中止,当遇到海量数据的处理时,非常不便。因为没有异常处理代码的异常会被传入上一级。
3,程序要保证在获取锁的时刻form界面上的值要和数据库中值相匹配,才行。如果有不匹配的情况就会强制让你重新查询,再来修改,在修改保存前,你都拥这个锁。
当第一次进入form时,查询后,在某一个item上输入内容,触发on-lock,这时,眼睛看着界面上item的内容明明改变,但是却成功通过了on-lock中数据一致性的验证,这是为什么呢,因为界面上的内容还没有被保存到相应item的实际value中,要通过保存记录,才会更新界面内容。要明白这一点,可以通过不在on-update中写更新代码来搞清楚。
PROCEDURE LOCK_DATA IS
counter NUMBER;
CURSOR c IS
SELECT v.order_type_relationship_id,
v.xnysc_station_id,
v.erp_order_type_id,
v.store_id,
v.xny_station_name,
v.transaction_type_id,
v.erp_order_type_name,
v.last_update_date,
v.last_updated_by,
v.last_update_login,
v.created_by,
v.creation_date
FROM cux_xnysc_order_type_v v
WHERE v.order_type_relationship_id =
:cux_order_type_relationship.order_type_relationship_id
FOR UPDATE OF v.order_type_relationship_id NOWAIT;
---for update of <table.columons>, <table.columons> nowait;这里"of"只在多表链接查询时才有用,意为锁定指定 列所对应的表,会根据where条件来获得该表的某一行的锁 ,nowait 表示如果获取不了锁,那么直接返 回错误.
recinfo c%ROWTYPE;
BEGIN
fnd_message.debug('lock_row is begin');
counter := 0; --循环记数器(变量)
LOOP
BEGIN
counter := counter + 1;
OPEN c;
FETCH c
INTO recinfo;
IF (c%NOTFOUND)
THEN
CLOSE c;
fnd_message.set_name('FND',
'FORM_RECORD_DELETED');
fnd_message.error;
RAISE form_trigger_failure;
END IF;
CLOSE c;
IF (
-- 检查固定列,数据库中一般为非空字段,form中对应的item的require属性一般为yes,同时,block的query data source columns中相应列的mandatory属性要勾上
(recinfo.order_type_relationship_id =
:cux_order_type_relationship.order_type_relationship_id)
-- 检查非固定列,数据库中一般指为充许空字段,form中对应的item的require属性一般为no,block的query data source columns中相应列的mandatory属性要去掉勾
AND ((recinfo.xnysc_station_id =
:cux_order_type_relationship.xnysc_station_id) OR
((recinfo.xnysc_station_id IS NULL) AND
(:cux_order_type_relationship.xnysc_station_id IS NULL))) AND
((recinfo.erp_order_type_id =
:cux_order_type_relationship.erp_order_type_id) OR
((recinfo.v IS NULL) AND
(:cux_order_type_relationship.erp_order_type_id IS NULL))))
THEN
RETURN; ---满足条件后退出循环
ELSE
--当form中的值同数据库中的值不匹配时的处理
fnd_message.set_name('FND',
'FORM_RECORD_CHANGED');
fnd_message.error;
RAISE form_trigger_failure;
END IF;
EXCEPTION
---当不能获取锁时,的异常代码,该异常由系统自动抛出
WHEN app_exceptions.record_lock_exception THEN
app_exception.record_lock_error(counter);
END;
END LOOP;
END LOCK_DATA;