Search code examples
hibernatejpaspring-mvcspring-roo

Spring JPA toggling field over AJAX error: StaleObjectStateException


@RequestMapping(value = "/document/togglevisible/{docId}")
public void toggleImageVisible(@PathVariable Integer docId) {
    Document doc = Document.findDocument(docId);
    if (doc.getVisible() == null) {
        doc.setVisible(1);
    } else {
        doc.setVisible(null);
    }
    doc.merge();
}

I'm hitting this with an ajax call, when a checkbox value changes:

$.get('/document/togglevisible/' + $(this).attr('data'));

When I hit that URL through the browser, it toggles the field in my database just brilliantly, no problems at all.

When I hit it from this ajax call, it's iffy whether it does or not. Digging into Firebug to look at the actual response, I see Spring throwing up this message:

org.hibernate.StaleObjectStateException: Row was updated or deleted by 
another transaction (or unsaved-value mapping was incorrect)

Never heard of any such thing, and google's not much help. Is there something I need to do after my merge() call to complete a transaction or something? I never intentionally turned on transactions (or turned off auto-commit, maybe).


Solution

  • I figured it out. It had nothing to do with Spring JPA.

    In the javascript function where I populate the list of documents (with their checkbox to fire this toggling of visibility behavior) I say:

    if (window.Docs.length > 0) {
        $('.mediaVisibilityToggle').live('change', function () {
            $.get("/document/togglevisible/" + $(this).attr('data-rel'));
        });
    }
    

    See that .live there? That tells jQuery to bind that event and to keep it bound across all DOM changes. So when I DO change the DOM, and list the documents associated with a different parent object, I build my layout of them and call live AGAIN on that selector class, resulting in TWO bindings sitting out there. So each button fires its ajax call as many times as I've redrawn this part of the page. No es bueno.

    I changed it to:

    $('.mediaVisibilityToggle').unbind('change');
    
    window.Docs.each( function(item) {
        // do my thing to clear that element and redraw my list of documents
    });
    if (window.Docs.length > 0) {
        $('.mediaVisibilityToggle').bind('change', function () {
            $.get("/mavenmanagement/admin/document/togglevisible/" + $(this).attr('data-rel'));
        });
    }       
    

    I probably could have just moved my .live call up into a global one-time scope somewhere, but this felt like a better way to control what's what, in terms of my events. So now each .mediaVisibilityToggle checkbox is bound only once on its change event, the toggling seems to work flawlessly, and everybody's happy.