Améliorer les temps de réponse d'une application Oracle ADF

Les temps de réponse d’une application ADF dépendent essentiellement des accès à la base de données. Entre la demande d’affichage de données (par exemple dans un tableau) et l’affichage proprement dit, il y a un certain nombre d’étapes dans lesquelles peuvent se produire des ralentissements.
 
Le premier qui est souvent mis en avant, est la lenteur de la requête SQL. Les outils tels que SQLDeveloper permettent de contrôler le plan d’exécution et d’apporter des solutions (création d’index, organisation de la requête, mise en place de hints, création de vue matérialisées). La limite de ces outils est bien entendu le modèle conceptuel de la base de données : si le modèle est mal construit, les requêtes ont de grandes chances de ne pas être optimums. A noter qu’une bonne pratique pour les ViewObject readonly est de ne pas stocker l’ordre SQL dans le VO mais de créer une vue Oracle facilitant la maintenance en cas d’ordres SQL complexes.
Reste le framework.
ADF suit l’architecture MVC et se compose de 3 couches :

  • Model
  • Controller
  • View

Ces couches communiquent entre elles (et le model vers la Base de données), par conséquent des problèmes de temps de réponses peuvent être provoqués soit par une mauvaise architecture soit par des paramétrages non adéquats.
L’architecture des pages est fondamentale. ADF permet avec les Task Flows de contruire des applications modulaires. Chaque Task Flow gère ses transactions, son cycle de vie, sa navigation et surtout déclenche lors de son appel les ordres SQL associés aux ViewObjects qui sont utilisés dans le taskFlow.
Prenons le cas d’une page JSPX qui comporte dans sa partie centrale une série d’onglet contenant des tableaux. En l’état, au lancement de l’application, tous les ViewObjects associés à chaque tableau seront peuplés même s’il n’y a que le premier onglet d’affiché. La bonne pratique est de définir autant de Task Flow qu’il y a d’onglet et de créer une région par onglet. Dans ce cas, lors du lancement de l’application, seul le ViewObject du premier onglet sera peuplé.
Ce qu’il faut retenir : pour qu’une application soit rapide à afficher, il faut peupler le moins de ViewObject possible à son lancement.
Mais ce n’est pas suffisant. Il faut aussi paramétrer les échanges entre les 3 couches.
Par défaut un ViewObject est créé avec les paramètres suivants :
Fetch Mode       : FETCH_AS_NEEDED
In Batch of          : 1
Ceci veut dire que le ViewObject lira une ligne dans la base de données puis la passera à l’iterator et ainsi de suite jusqu’à peupler l’iterator (RangeSize). Dans la réalité, ce type de comportement n’est utilisé que pour des pages formulaires où une seule ligne est affichée à la fois. Par contre pour l’affichage sous forme de tableau, l’impact est d’autant plus catastrophique que la table Oracle est volumineuse.
Pour une page devant afficher un tableau comportant 60 lignes, le nombre de ligne de référence sera 180 (3 x 60) afin d’éviter l’affichage du message « Extraction des données »  dès que l’utilisateur scrolle (dans ce cas le message apparaîtra au bout de 180 lignes). Le paramétrage conseillé est le suivant :

  •  Model

ViewObject

Fetch Mode : FETCH_AS_NEEDED

In Batch of : 183

  • Controller

Iterator (Bindings)

RangeSize           : 180

  • View

AF :TABLE

FetchSize            : 180

De plus, par défaut ADF exécute systématiquement un SELECT COUNT(1) avant la requête du contenu. Pour désactiver ce comportement, il faut modifier le paramètre RowCountThreshold de l’iterator (saisir -1) et s’assurer que le paramètre AutoHeightRows du tableau est égal à -1.
Dans certains cas, on peut être amené à contrôler manuellement l’exécution des requêtes des ViewObjects. Reprenons le premier exemple : le cas d’une page JSPX qui comporte dans sa partie centrale une série d’onglet contenant des tableaux.
En l’absence d’utilisation de taskflow, au lancement de l’application, tous les ViewObjects associés à chaque tableau seront peuplés même s’il n’y a que le premier onglet d’affiché. Il est cependant possible peupler à la demande les ViewObjects (lors du clic sur l’onglet) en suivant ce paramétrage :

  • Model

ViewObject

Fetch Mode       : FETCH_AS_NEEDED

In Batch of          : 183

MaxfetchSize    : 0

  • Controller

Iterator (Bindings)

RangeSize           : 180

  • View

AF :TABLE

FetchSize            : 180

Le paramètre MaxfetchSize (0) permet de ne pas peupler le ViewObject à l’arrivée sur la page. Pour pouvoir déclencher le peuplement, je propose la méthode suivante :

  • Créer un binding pour le tableau
<af:table
…
binding="#{pageFlowScope.backing_home.tableToProcess}">
  • Créer une méthode de gestion de l’exécution des requêtes SQL dans le Managed Bean
private void executeQuery(String iteratorProcess, RichTable richTable) {
DCIteratorBinding iterator = ADFUtils.findIterator(iteratorProcess);
int rowCount = iterator.getRowSetIterator().getFetchedRowCount();
if (rowCount == 0) {
ViewObject vo = iterator.getViewObject();
vo.setMaxFetchSize(-1);
vo.executeQuery();
AdfFacesContext fc = AdfFacesContext.getCurrentInstance();
fc.addPartialTarget(tableToProcess);
}
}
  • Créer une méthode associée à l’événement déclenchant la lecture (clic sur l’onglet)
public void workSheetListener(DisclosureEvent disclosureEvent) {
if (disclosureEvent.isExpanded()) {
executeQuery("Iterator", tableToProcess);
}
}
  •  Ajouter un listener sur le composant ShowDetailItem
<af:showDetailItem
…
disclosureListener="#{pageFlowScope.backing_home.workSheetListener}">
  • Déclaration
private RichTable tableToProcess;
  • Getter et Setter
public void setTableToProcess (RichTable tableToProcess) {
this.tableToProcess = tableToProcess;
}
public RichTable getTableToProcess () {
return tableToProcess;
}

Restent les cas particuliers tels que les graphiques et les traitements Batch.
Pour les graphiques, il n’est pas nécessaire d’effectuer des aller-retours entre la couche View, la couche Model et la base de données car l’utilisateur ne scrolle pas les données comme dans les tableaux. Le paramétrage suivant est adapté aux graphiques :

  • Model

ViewObject

Fetch Mode       : FETCH_ALL

MaxfetchSize    : -1

  • Controller

Iterator (Bindings)

RangeSize           : -1

Pour les traitements Batch tels que la création de documents Excel avec POI (Java API for Microsoft Documents) effectuant une lecture séquentielle d’un ViewObject, le paramétrage suivant est conseillé :

  • Model

ViewObject

Fetch Mode       : FETCH_ALL

MaxfetchSize    : -1

mais ce n’est pas suffisant. Le code Java de traitement doit se situer dans la couche Model (application Module Class) et non pas dans la couche Controller (Managed Bean) afin d’éviter la création d’un iterator et les aller-retours induits.
Exemple :

  • Model (Application Module Class)
public XSSFWorkbook formatToExcelGatheredData(String viewObjectName, String sheetName) {
XSSFWorkbook workBookExportGatheredData = new XSSFWorkbook();
ViewObject voToProcess = this.findViewObject(viewObjectName);
voToProcess.setMaxFetchSize(-1);
voToProcess.executeQuery();
RowSetIterator rsi = voToProcess.createRowSetIterator(null);
rsi.reset();
if (null == rsi.getRowAtRangeIndex(0)) {
//
// Document vide
//
...
} else {
//
// Création document
//
...
}
return workBookExportGatheredData;
}