<div style="display:inline;"> <img height="1" width="1" style="border-style:none;" alt="" src="//googleads.g.doubleclick.net/pagead/viewthroughconversion/1066880148/?value=0&amp;label=4oTQCMyJzwQQlJnd_AM&amp;guid=ON&amp;script=0">

By: Martin Nichol on April 4th, 2018

Print/Save as PDF

Solving “Record has been updated by another user. Refetch and try again.” Problems.

Refetch | Blog

We’ve all seen the dreaded “Record has been updated by another user. Refetch and try again.”  It can happen when the record has been updated by another user, but it can also happen when the record is updated by the same user more than once.

In memory, an MboSet owns zero or more MBOs.  Each of those MBOs can have zero or more MboSets which in turn own zero or more MBOs.  Naturally, if your MboSets contain zero MBOs you won’t have problems with records updated by another user.  Problems will arise if two different MBOs reference the same database record and both MBOs attempt to update data.  In memory, these MBOs will be represented as separate Java objects and will be owned by different MboSets.  If only one MBO is updated, it won’t be a problem.  If both MBOs are updated, the first will update the database record and change the ROWSTAMP value.  The second will attempt to update the record but will fail because the ROWSTAMP doesn’t match.  This will trigger an MXRowUpdateException.

Let’s look at an example.  In this diagram, ellipses represent MBO objects in memory.  Rectangular boxes represent database tables and their records.  Solid lines represent an in-memory association through the named relationship.  Dashed lines point to the database record.

In this example, a WORKORDER MBO loads work order wo1.  Following the woactivity relationship, WOACTIVITY wo1.2 is loaded into an MBO.  From here, the parent relationship is followed to WORKORDER wo1.  The two WORKORDER objects reference the same database record, but they are represented in memory by two separate MBO objects because they were loaded through two different relationships.

If either the first WORKORDER MBO or the second WORKORDER MBO is updated, it will save properly.  If both MBOs are updated, the save will fail with an MXRowUpdateException.  This occurs because the first update is applied to the database and updates the ROWSTAMP column.  The second update is then applied, but the ROWSTAMP column has changed so it fails with an MXRowUpdateException.

To find where a single database record is updated by multiple MBOs, I have developed a MboDumper class.  Starting from a single MBO, it will travel down all instantiated relationships and display the relationship name and all MBOs contained there-in.  If it detects the same MBO in more than one place, it will highlight it.  It’s then a matter of finding where in the code the MBOs are modified.

Code for the MboDumper is available on GitHub at https://github.com/ThatMaximoGuy/MboDumper.

To use the MboDumper, place a try-catch block around the call to save.  Exactly where will depend on whether the save takes place in a DataBean or in a Cron Task or something else.  The code for the above example could look like:

MXServer mxs = MXServer.getMXServer();
UserInfo ui = mxs.getSystemUserInfo();
MboSetRemote workorders = mxs.getMboSet("WORKORDER", ui);
workorders.setWhere("wonum = '10190548'");
MboRemote workorder = workorders.getMbo(0);
workorder.setValue("DESCRIPTION", "Example");

MboSetRemote woactivities = workorder.getMboSet("WOACTIVITY");
MboRemote woactivity = woactivities.getMbo(0);

MboSetRemote parents = woactivity.getMboSet("PARENT");
MboRemote parent = parents.getMbo(0);
parent.setValue("DESCRIPTION", "Example 2");
try {
   workorders.save();
} catch (MXRowUpdateException e) {
   MboDumper.dump(FixedLoggers.MAXIMOLOGGER, mbo);
   throw e;
}

Data is output to the given logger at the INFO level.

Given the example above, MboDumper would generate output like this:

*** MBO Data Dump Start
Mbo: WORKORDER [ToBeSaved ToBeUpdated] <=== DUPLICATE MBO
Key SITEID: EAGLENA
Key WONUM: 10190548
Modified DESCRIPTION: Example
Modified CHANGEBY: MAXADMIN
Modified CHANGEDATE: 2/27/18 3:03 PM
Relationship: WOACTIVITY parent= '10190548'  and siteid= 'AMTRKENG'
   Mbo: WOACTIVITY [ToBeSaved ToBeUpdated]
   Key SITEID: EAGLENA
   Key WONUM: 10190549
   Relationship: $SECGROUPS userid = 'MAXADMIN' and groupname in ('MAXADMIN','SUPERUSER')
      Mbo: GROUPUSER []
      Key USERID: MAXADMIN
      Key GROUPNAME: MAXADMIN
   Relationship: __LONGDESCRIPTIONMLFALSEWORKORDER ldownertable = 'WORKORDER' and ldkey =  196399
   Relationship: PARENT wonum= '10190548'  and siteid= 'AMTRKENG'
      Mbo: WORKORDER [ToBeSaved ToBeUpdated ] <=== DUPLICATE MBO
      Key SITEID: EAGLENA
      Key WONUM: 10190548
      Modified DESCRIPTION: Example 2
      Modified CHANGEBY: MAXADMIN
      Modified CHANGEDATE: 2/27/18 3:03 PM
      Relationship: CLASSSTRUCTURE classstructureid =  ''
      Relationship: __LONGDESCRIPTIONMLFALSEWORKORDER ldownertable = 'WORKORDER' and ldkey =  196398
Relationship: CLASSSTRUCTURE classstructureid =  ''
Relationship: __LONGDESCRIPTIONMLFALSEWORKORDER ldownertable = 'WORKORDER' and ldkey =  196398
*** MBO Data Dump Done

The WORKORDER updated in two places is marked with “<== DUPLICATE MBO.“

Given the MBO and the relationship name, it’s usually enough to find where in the code these multiple updates occur.

About Martin Nichol

Martin Nichol has a computer science degree from Western University (formerly University of Western Ontario). He has been with Interloc for 10 years. He has created a Maximo testing framework based on JUnit that permits running unit tests in a Maximo instance within Eclipse. He also created a Maximo/Twitter integration that can send tweets and create Service Requests from tweets.