Bring Back the Hobgoblin: Dealing with RowInconsistentException

If you scan through the posts at OTN, one exception that comes up again and again as confusing is oracle.jbo.RowInconsistentException, aka JBO-25014. What causes this exception? Well, as we’ll see below, the answer isn’t so simple. But the proximal cause is that all of the following have occurred:

  • The application uses optimistic locking
  • The application has invoked a commit operation
  • A particular entity object instance has been marked as changed (requiring update)
  • The current values of the corresponding row in the database do not match the original values queried for the row (or, if you’re using change indicators, the current values of the change indicator fields in the database do not match the values of the corresponding attributes)

Now sometimes, you get a RowInconsistentException because another user has actually changed and committed the row in the database since it was queried. That’s, in fact, the case that RowInconsistentException was intended to flag–it’s to keep data integrity, so you don’t get people writing over each other willy-nilly. If you’re getting a RowInconsistentException for this reason, then there’s no bug to fix or work around. You’ve simply run afoul of a (hopefully rare) coincidence.

You can handle this coincidence in any number of ways, from refreshing the inconsistent row from the database and making the user repeat all their work (unpleasant, but easy and this situation shouldn’t happen too often) all the way up to implementing your own conflict resolution functionality (hard, but possibly worth it if users really are likely to be working on the same data at the same time). Or you could just switch over to pessimistic locking and avoid the whole issue, but that has it’s own problems, which would be a post in themselves.

Sometimes, though, you’ll get a RowInconsistentException when you’re really quite sure that nobody has changed the row between the time you queried and the time you tried to save your changes. In that case, something a bit sneakier is going on. For some reason or other, the ADF BC framework is seeing a difference between a value in the database and its initial query value for a row.

In this case, the first thing you should do is read Steve Muench’s “Why Do I Get oracle.jbo.RowInconsistentException?” It lists a number of likely culprits for this problem, with their solutions. In all likelihood, you’ll find your answer there.

But what if you’ve checked all of Steve’s possibilities, and you’ve got your Refresh On… settings and mappings set just fine, you’re not using any custom domains (or you’re quite sure they all implement equals() properly) and you’re still getting this error?

Well, the first step is to do some more refined diagnosis. You need to find out what attribute(s) are inconsistent with DB values. The easiest way to find this out is to override the method oracle.jbo.server.EntityImpl.lock() in your entity object class (this requires generating an entity object class, if you don’t already have one) to add some diagnostic output. The following code shows a sample overridden lock() method. It uses a static variable, “sLog”, which is a Logger for the class.

public void lock() {
    try {
        super.lock()
    } catch RowInconsistentException(e) {
        checkInconsistentAttrs();
        throw(e);
    }
}

private void checkInconsistentAttrs() {
    int count = getAttributeCount();
    // Store the current values of the attributes; we're going to munge them during the check
    Object[] currValues = new Object[count];
    boolean[] attributeChanges[] = new boolean[count];
    for (int i=0; i<count; i++) {
        currValues[i] = getAttribute(i);
        attributeChanges[i] = isAttributeChanged(i);
    }
    // Change all attribute values to match current DB values (good thing we saved them first!)
    refresh(REFRESH_WITH_DB_FORGET_CHANGES);
    // For each attribute...
    for (int i=0; i<count; i++) {
        // compare the current value (from the DB) with the original value queried from DB.
        Object origValue = getPostedAttribute(i);
        Object currDbValue = getAttribute(i);
        // Watch your nulls! Nobody likes NPEs.
        if ((origValue == null && currDbValue != null) ||
            (origValue != null &&
             (currDbValue == null ||
              ! origValue.equals(currDbValue))))
         ) {
             // If they don't match, tell us about it.
             sLog.error("Inconsistent attribute " + getAttributeNames()[i]);
             sLog.error("  Queried from DB = " + origValue);
             sLog.error("  Now in DB = " + currDbValue);
         }
         // Set this EO instance back to the way it was
         if (attributeChanges[i]) {
             populateAttributeAsChanged(i, currValues[i]);
         } else {
             populateAttribute(i, currValues[i]);
         }
    }
}

Now, when you run your app, whenever you get a RowInconsistentException, you should see something like this in your log:

Inconsistent attribute SomeAttribute:
    Queried from DB = <value>
    Now in DB = <some other value>

The hope here is that this will help you further identify the attribute problem. If more than one attribute is the issue, you might see several entries like this. Sometimes, once you get the particular attributes, the reason for the problem will be pretty obvious: In all likelihood, you will just have missed one of the cases Steve mentioned. But there are a few more possibilities, a couple of which I’ll mention and solve here:

1. But I did set Refresh After… on that Attribute! I did!

If you’re using Oracle Database 10g releases 10.1.2.0.2 and 10.1.2.0.3, I totally believe you. You’re running up against DB bug 5115882, which prevents the SQL used by ADF for Refresh After… settings from functioning properly if a field value is being set to NULL. The most direct solution to this, of course, is to upgrade or patch your database–this is a bug that may come back to bite you whenever you use DML with a RETURNING clause. “Most direct,” however, does not always mean “most feasible.” While we’d all love to live in a world where the merest word from a developer triggers a DB upgrade (well, all of us except the DBAs would), that’s just not how things work. And you’ve got to admit, the DBAs have a fair point.

Fortunately, you can work around this on your application side by overriding EntityImpl.doDML(). This may come in handy regularly if you’re working against a problematic DB release, so I’d suggest putting it in an custom framework entity object class (the code below is a little more complex than it would have to be for a particular EO, where we know at coding time which attributes have refresh properties set on them, to make it refactorable this way).

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);
}

2. Hey, wait. That’s not an attribute at all! It’s an association accessor!

Sometimes, you’ll run the diagnostic code, and the “attribute” that is inconsistent is actually the accessor for an association, in particular, an association that is a composition with “Lock Top-Level Container” set on it. What this is supposed to do is that, when any composed entity is locked, the containing entity gets locked too. So if you try to acquire a lock on one of your composing entity object instances, and someone else has changed the top-level containing row, you’ll get a RowInconsistentException on the top-level row even if you haven’t changed that row yourself.

That’s all well and good. But the problem is that sometimes this happens when all that’s changed is the top-level containing entity–and when that entity doesn’t have any real consistency issues. Why? Beats me. It seems to be especially common with UIs that involve intersection shuttles.

I don’t know what causes it, but I do know how to fix it. You have to remind the framework that, no no, the entity attribute accessor rowset hasn’t changed. Overriding your lock() method like so will do it:

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

What does that do? Well, it should do nothing. REFRESH_WITH_DB_ONLY_IF_UNCHANGED is a flag telling refresh() to update the row with the current value only if it’s unchanged. And REFRESH_CONTAINEES is a flag saying to refresh all composed entities, with any other flags passed down to them. So, what is it doing? It’s refreshing this entity if it’s unchanged, and then refreshing all composed entities if they’re unchanged. But an unchanged entity shouldn’t be the cause of a consistency problem, so this shouldn’t fix anything.

But it does. I’d go ahead and do something like that in a framework class as well.

Now this stuff isn’t guaranteed to solve all your RowInconsistentException problems. There’s a thread going on on OTN right now, involving the value pulled from the DB for the consistency check getting truncated. But these are, at lease, a couple more pieces of bait to lure back the hobgoblin of little minds.

10 thoughts on “Bring Back the Hobgoblin: Dealing with RowInconsistentException”

  1. I also want to add another case similar to the case no 1 you mentioned. A field that is marked as refresh after appers after update/insert having null value and of course JBO-25014 is here again.
    This is happening in case that the field is being mentioned in the trigger but is value is not set. This is happening due to an update .. returning bug see http://forums.oracle.com/forums/message.jspa?messageID=2740885

    for more info

    By the way, i run your code and i think that the instruction
    refresh(REFRESH_WITH_DB_ONLY_IF_UNCHANGED | REFRESH_CONTAINEES);
    prevents the appearance of JBO-25014 even if really someone change the row in between!

    Tilemahos

  2. Thanks for your additional example.

    I’m a bit curious about your second case. Does it actually prevent JBO-25014 for you if someone has changed *the same row* that has been changed in this transaction? REFRESH_WITH_DB_ONLY_IF_UNCHANGED should have no effect on rows that have been changed within the transaction.

  3. yes i change the same row, if you give it a try yourself please let me know of the result

    thanks
    Tilemahos

  4. Well, accorting to John Stegeman (http://stegemanoracle.blogspot.com/), whom I trust highly, this is simply part of the behavior or lock(). I’m still inclined to stand by my claim that known or not, it’s a bug–data loss protection should not be nearly so easily overcome. The best I can suggest, then, is to combine the diagnostic and locking code I suggested above: In case of a RowInconsistentException, check this row and its composed children for genuine inconsistencies, and either throw the exception or simply re-call lock() depending on whether you find a real inconsistency or not.

  5. Nice post. I just wanted to add another case and also if you set -Djbo.debugoutput=console then JBO outputs the attribute with values that failed the consistency check.

    The case that I ran into had to do with TIMESTAMP column. Our DBA’s set the TIMESTAMP column in the database with precision (0). So whenever we set the value from jbo the row became inconsistent right away because in the database it set milliseconds as 0 because of precision.

    Of course, the refresh flag did not help in this case because this was a history column and history columns cannot have refresh flags set to true.

  6. Thanks Avromo,
    This was the fix I wanted. My case was the association accessor case.
    No idea what caused it though.

Leave a Reply

Your email address will not be published. Required fields are marked *