ADF : oracle.jbo.RowInconsistentException, causes possibles

Il vous est sûrement arrivé de rencontrer cette erreur, bloquante au demeurant, «un autre utilisateur a modifié la ligne dont la clef primaire est oracle.jbo.Key[RowKey]».
Cet article a pour but d’expliquer les origines possibles de son apparition.

Aux origines de l’exception RowInconsistentException

Pourquoi cette exception ?

Elle est là pour prévenir d’un accès concurrent à une même ligne par au moins deux utilisateurs différents afin de garantir la consistance des données persistantes en base.

Comment est-elle détectée ?

Par comparaison des valeurs de la ligne au moment de la sélection de celles-ci (on peut les retrouver en utilisant la méthode getPostedAttribute() de la classe oracle.jbo.server.EntityImpl) et celles au moment de la phase de verrouillage de la ligne.
Si une valeur est différente alors c’est que la ligne a été modifiée entre temps par un autre, l’exception est alors levée.
Ceci se fait au moment du commit (locking mode = optimistic) ou lors de la tentative de modification d’un attribut de la ligne en question (locking mode = pessimistic)

Causes possibles de lancement de l’exception

1 – Accès concurrent d’une même ligne par deux personnes auquel cas l’exception répond parfaitement à son objectif.
2 – Du fait de la méthodologie de détection (comparaison de valeurs entre la base et l’application au niveau de l’entité), elle peut être aussi due à de mauvais paramétrages sur l’ application à savoir :

* La gestion du paramètre Refresh After des attributs des entités n’est pas activée.
Dans le cas où la base modifie les valeurs de certaines colonnes par des triggers, il faut cocher les options «Refresh on Insert» et/ou «Refresh on Update» des attributs correspondants au niveau de l’entité.
Dans le cas où vous utilisez un attribut basé sur le ROWID dans votre entité, et sachant que dans certains cas il peut être modifié si la ligne est mise à jour (partitioning de la base par exemple), il faut cocher les options «Refresh on Insert» et «Refresh on Update» pour celui-ci.
entity_refresh
* La gestion du paramètre «Refresh on Insert» n’est pas activée, cas où une colonne a une valeur par défaut en base.
* En mode expert de création d’un ViewObject, il existe un défaut de synchronisation entre la liste des colonnes sélectionnées dans la requête SQL et les attributs créés dans le ViewObject. Dans ce cas, bien vérifier que le mapping entre les deux est correct.
* Problème d’implémentation de la méthode equals(). La colonne sur laquelle porte l’exception fait partie d’une classe «maison» dont la méthode equals() est mal codée. Bien vérifier et tester celle-ci pour la classe incriminée.
* Confrontation au bug 5115882 si vous utilisez une base 10.1.2.0.2 ou 10.1.2.0.3. Celui-ci a pour effet d’empêcher le fonctionnement normal de l’ordre SQL passé par ADF pour la gestion du paramètre Refresh After d’un attribut si une valeur Null lui est passée. Le mieux est de mettre à jour ou patcher la base !
Une autre solution, si celle-ci ne vous convient pas , est d’ovverider dans une classe customizée héritant de EntityImpl la méthode doDML().

protected void doDML(int operation, TransactionEvent event) {
  int count = getAttributeCount();
    for (int i = 0; i < count; i++) {
      AttributeDefImpl attrI = getDefinitionObject().getAttributeDefImpl(i);
      if ((operation == DML_INSERT &&attrI.isRetrievedOnInsert()) ||
          (operation == DML_UPDATE && attrI.isRetrievedOnUpdate())) {
            if (getAttribute(i) == null) {
                setAttributeChanged(i, true);
            }
       }
    }
    super.doDML(operation, event);
}

* Inconsistance des données due à un attribut qui est en fait un association accessor, lorsque l’association en question est une composition avec le paramètre «Lock Top-Level» activé. Celui-ci a pour but de locker l’entité contenante lorsqu’une entité faisant partie de la composition est modifiée.
Dans certains cas (pas clairement identifiés), des modifications sur l’entité contenante ont entraîné la levée de de la RowInconsistentException sans raison valable.
Une voie de contournement pour faire face à ce comportement anormal est d’overrider dans une classe customizée héritant de EntityImpl la méthode Lock().

public void lock() {
    try {
        super.lock();
    } catch (RowInconsistentException e) {
        refresh(REFRESH_WITH_DB_ONLY_IF_UNCHANGED | REFRESH_CONTAINEES);
        super.lock();
    }
}

Cette liste n’est pas exhaustive et d’autres causes de RowInconsistentException sont possibles.
Cet article est inspiré par les deux articles anglophones suivants :