Précaution Vérifiez la version, les droits, l’environnement de test et la sauvegarde avant intervention.

Comprendre le verrouillage optimiste dans Maximo

Maximo repose sur un mécanisme de verrouillage optimiste (Optimistic Locking) géré via la colonne système ROWSTAMP présente sur chaque table métier. Lorsqu’un utilisateur ou un processus charge un enregistrement, Maximo mémorise son ROWSTAMP. Au moment du SAVE, le framework vérifie que la valeur en base est toujours identique avant d’exécuter l’instruction UPDATE.

Si un autre processus — ou un second pointeur MBO ouvert dans la même transaction — a déjà modifié et validé l’enregistrement entre-temps, Maximo interrompt la sauvegarde pour éviter l’écrasement silencieux des données et affiche :

BMXAA4200E - The record has been modified by another user. Refetch and try again.

L’anti-pattern classique des scripts d’automatisation

Dans 80 % des cas rencontrés en audit technique, l’erreur BMXAA4200E n’est pas due à un second utilisateur humain, mais à l’auto-collision d’un script Jython qui ouvre un MboSet indépendant sur un objet déjà présent dans la transaction courante et appelle explicitement .save().

Schéma 62A — Auto-collision transactionnelle via appel explicite à save()

flowchart TB UI["Session UI : Modification OT (ROWSTAMP = 100)"] --> EVT["Point de lancement : SAVE sur WORKORDER"] EVT --> SCR["Script Jython : MXServer.getMboSet('WORKORDER')"] SCR --> SAV1["Appel explicite woSet.save() -> ROWSTAMP passe à 101"] SAV1 --> FIN["Fin du script : Maximo tente le commit UI (attend ROWSTAMP = 100)"] FIN --> ERR["Conflit détecté : BMXAA4200E"]
Schéma technique

Comparaison des approches de manipulation MBO

Approche Comportement transactionnel Risque de BMXAA4200E
MXServer.getMXServer().getMboSet(...) Ouvre une transaction isolée hors du contexte parent Élevé si l’objet est déjà en cours d’édition
mbo.getMboSet("RELATION") Rattache le MboSet enfant à la transaction parente Nul (si .save() n’est pas appelé manuellement)
Appel de set.save() dans un événement SAVE Force un commit prématuré et incrémente ROWSTAMP Critique (collision garantie)

Exemple de code Jython : correction d’un script défaillant

Voici comment refactoriser un script qui met à jour un équipement depuis un ordre de travail afin de respecter le cycle transactionnel natif :

# ✅ BONNE PRATIQUE : Utiliser la relation MBO et laisser le framework gérer le commit
from psdi.mbo import MboConstants

assetSet = mbo.getMboSet("ASSET")
if not assetSet.isEmpty():
    assetMbo = assetSet.getMbo(0)
    # Utilisation des constantes d'accès appropriées sans appel à assetSet.save()
    assetMbo.setValue("STATUS", "OPERATING", MboConstants.NOACCESSCHECK)

Si vous devez impérativement exécuter un traitement asynchrone ou isolé (par exemple depuis un bouton d’action personnalisé hors sauvegarde), réinitialisez explicitement l’objet courant après la validation distante via mbo.getThisMboSet().reset().

Diagnostic sur un environnement en erreur

  1. Activer la journalisation des scripts : passez le logger maximo.script au niveau DEBUG dans l’application Logging.
  2. Rechercher les appels .save() : interrogez la table AUTOSCRIPT en lecture seule pour lister tous les scripts contenant l’instruction .save(.
-- Détecter les scripts d'automatisation appelant explicitement .save()
SELECT autoscript, description, status, scriptlanguage
FROM autoscript
WHERE UPPER(source) LIKE '%.SAVE(%'
  AND status = 'ACTIVE';
  1. Vérifier les points de lancement : croisez les résultats avec SCRIPTLAUNCHPOINT pour identifier ceux déclenchés sur OBJECT (événements ADD, UPDATE, DELETE) ou ATTRIBUTE (VALIDATE, ACTION).

Schéma 62B — Règle d’or de validation dans le cycle MBO

flowchart LR A["Déclenchement Script"] --> B{"Point de lancement de type SAVE / VALIDATE ?"} B -->|"Oui"| C["Utiliser mbo.getMboSet('RELATION')"] C --> D["Interdire .save() et .close()"] B -->|"Non (Action / Cron)"| E["Transaction autonome autorisée"] E --> F["Appeler .save() puis .close() en bloc finally"]
Schéma technique

Limites et cas particuliers

Dans le cadre d’un Cron Task ou d’une intégration entrante MIF traitant des lots volumineux, le commit transactionnel doit être maîtrisé par paquets (batch). Si un même enregistrement est modifié deux fois au sein d’un même lot XML, activez le paramètre mxe.int.enableoptimisticlock ou triez les messages entrants par clé métier afin d’éviter les accès concurrents entre fils d’exécution (JMS Consumers).

Checklist de revue de code Jython

  • Tous les MboSet liés à l’enregistrement courant sont-ils récupérés via mbo.getMboSet("RELATION") ?
  • Les appels à .save() sont-ils strictement absents des scripts liés aux événements de sauvegarde ?
  • Tout MboSet ouvert manuellement via MXServer dans un script autonome est-il fermé dans un bloc try...finally ?
  • Le flag MboConstants.NOACCESSCHECK est-il utilisé à bon escient sans contourner les règles métier critiques ?

À retenir : dans Maximo, un script rattaché au cycle de vie d’un objet ne doit jamais décider lui-même du moment du commit ; il prépare les données et laisse le framework MBO valider l’ensemble atomiquement.

Références