1. Rappel▲
Lorsque l'on souhaite exécuter une procédure externe (procédure stockée en base, par exemple) depuis un écran Forms, l'application reste en attente de la fin d'exécution de la procédure. En d'autres mots, elle est « gelée » et il n'est pas possible de lui faire exécuter d'autres instructions.
Même les timers ne se sont plus actif durant cette phase.
Les règles de base de l'ergonomie voudraient pourtant que l'utilisateur soit informé de l'avancement de toute tâche nécessitant plus de quelques secondes.
La technique décrite dans cet article permet de suivre et d'afficher l'avancement d'une tâche externe.
Elle utilise à la fois la fonction dbms_application_info.set_session_longops() au sein de la procédure stockée pour mettre à jour en temps réel son compteur d'avancement, et également la fonction dbms_scheduler.create_job() pour lancer la procédure en background et de manière asynchrone.
2. La procédure de test▲
Afin de tester la solution, nous allons créer une procédure en base qui ne fait rien d'autre que passer le temps…
create
or
replace
procedure
Progress_Bar
As
rindex pls_integer
:=
-
1
;
slno pls_integer
;
Begin
-----------------------------------------------------
-- procedure that does nothing else than waiting --
-- to illustrate the ProgressBar Forms sample --
-----------------------------------------------------
dbms_application_info.set_session_longops(
RINDEX =>
rindex
,SLNO =>
slno
,OP_NAME =>
'PROGRESS_BAR'
,SOFAR =>
0
,TOTALWORK =>
100
)
;
-- simulating the task progress --
For
i IN
1
..100
loop
DBMS_LOCK.SLEEP
(
.3
)
;
dbms_application_info.set_session_longops(
RINDEX =>
rindex
,SLNO =>
slno
,OP_NAME =>
'PROGRESS_BAR'
,SOFAR =>
i
,TOTALWORK =>
100
)
;
End
loop
;
End
;
La boucle permet de simuler une procédure d' une minute.
L'index (SOFAR) est mis à jour environ chaque seconde.
Remarque :
le droit d'exécution du package dbms_lock est attribué par SYS
sql > grant execute on dbms_lock to nom_user;
3. Le code Forms▲
Declare
v_jobname Varchar2
(
30
)
:=
'PROGRESS_BAR_JOB'
;
v_jobid Number
:=
12345
;
v_percent Number
:=
0
;
v_end Exception
;
v_version Number
:=
9
;
v_nb Pls_integer
;
v_pass Pls_integer
:=
0
;
begin
-- Lancement de la procédure par le package dbms_job pour la version 9i --
If
v_version =
9
Then
dbms_job.isubmit(
v_jobid,'Progress_Bar;'
,sysdate
,null
)
;
forms_ddl(
'commit'
)
;
Else
-- Lancement de la procédure par le package dbms_scheduler pour la version 10g --
dbms_scheduler.create_job(
job_name =>
v_jobname
,job_type =>
'stored_procedure'
,job_action =>
'Progress_Bar'
,start_date =>
SYSDATE
,enabled =>
TRUE
)
;
End
if
;
-- le job tourne ? --
Loop
If
v_version =
9
Then
Select
count
(
job)
Into
v_nb
From
user_jobs
Where
job=
v_jobid
And
total_time!=
0
;
Else
Select
count
(*)
Into
v_nb
From
USER_SCHEDULER_JOBS
Where
JOB_NAME =
v_jobname;
End
if
;
v_pass :=
v_pass +
1
;
If
v_pass >
100
Then
-- le job ne se lance pas --
message(
'Problème de lancement du job'
,acknowledge)
;
Raise
Form_Trigger_Failure ;
End
if
;
exit
when
v_nb >
0
;
dbms_lock.sleep
(
.2
)
;
End
loop
;
set_item_property(
'blo_progress.progressbar'
, width, 0
)
;
v_percent :=
0
;
-- pour suivre la progression de l'exécution de procedure --
Loop
Exit
when
v_percent >=
100
;
Select
(
sofar /
totalwork)
*
100
Into
v_percent
From
v$session_longops
Where
opname =
'PROGRESS_BAR'
and
sofar <
totalwork;
:blo_progress.progressbar:=
v_percent||
'%'
;
set_item_property(
'blo_progress.progressbar'
,width, round
(
v_percent*
2
,2
))
;
synchronize;
End
loop
;
raise
v_end ;
Exception
When
NO_DATA_FOUND then
set_item_property(
'blo_progress.progressbar'
, width, 200
)
;
:blo_progress.progressbar:=
'100%'
;
raise
v_end ;
When
TOO_MANY_ROWS then
raise
v_end ;
When
v_end then
If
v_version =
10
Then
DBMS_SCHEDULER.drop_job (
job_name =>
v_jobname,FORCE
=>
true
)
;
End
if
;
When
Others
then
If
v_version =
10
Then
DBMS_SCHEDULER.drop_job (
job_name =>
v_jobname,FORCE
=>
true
)
;
End
if
;
End
;
Selon la version de la base de données, l'une des deux procédures suivantes est utilisée :
- Oracle 9i : Dbms_Job ;
- Oracle 10g : Dbms_Scheduler.
La barre de progression est simulée avec un simple Text Item dont la longueur de zéro au départ est augmentée pendant l'exécution.
Le pourcentage d'avancement de la procédure est lu depuis la vue v$session_longops, puis la longueur de la barre de progression est augmentée de la valeur * 2.
À la fin de la procédure, qui met à jour 100 fois son état d'avancement, la barre de progression atteint donc une largeur de 200 pixels.
4. La forme de test▲
Un fichier zip contient le code de la procédure stockée ainsi que la forme de test 10gR2 (10.1.2) : progressbar.fmb
progressbar.zip
Idée originale et remerciements▲
L'idée originale de l'utilisation de la fonction dbms_application_info.set_session_longops() pour résoudre ce problème provient de l'article original allemand suivant :
Prozesslogik « im Hintergrund » mit Fortschrittsbalken
Chaleureux remerciements à Mr Sheik Yerbouti, Mr Richer Morin (DBA), Mr plaineR, Developpez.com et l'équipe SGBD.