That is (somewhat) known behavior that just rarely comes up.

The saveOrUpdate calls just add to the objects currently in the session.

Closing a session forces a flush to the db.
Doing a read forces a flush to the db. (to prevent stale data)
Closing a session/transaction forces a flush.

Doing a clearSession before any of these flushes will cause all previously
attached objects to not be persisted.

Perhaps a big change to the API we could make is to put a flushSession call
after each save method?  That makes more sense in my mind.

In fact, we might want to put a clearSession right before it:

clearSession();
sessionFactory.saveOrUpdate(object);
flushSession();

This would prevent other object changes from flushing when we didn't want
them to...a long-standing "bug" that we've just lived with.

Ben

On Mon, Nov 28, 2011 at 7:38 PM, Mark Goodrich <[email protected]> wrote:

> Burke—****
>
> ** **
>
> Good point… yes, as it turns out it looks like although the reference is
> being changed, the new timestamp is a clone of the old timestamp.****
>
> ** **
>
> I dug in a little deeper and found something else… reading up on
> hibernate, it seems that the saveOrUpdate method should do **nothing** to
> objects that are currently persisted within the current session.  It only
> persists new objects (save) or reattachs objects that have been deattached
> (update).****
>
> ** **
>
> This would mean in the second test script (I’ll copy it below for
> convenience), the saveEncounter—which calls the underlying saveOrUpdate via
> the dao--really shouldn’t do anything.****
>
> ** **
>
> public void editingExistingEncounter_shouldUpdateDateCreated() throws
> Exception {
>               EncounterService es = Context.getEncounterService();
>
>               // First, load an existing Encounter
>               Encounter enc =
> Context.getEncounterService().getEncounter(3);
>
>               // confirm that the date changed is null
>               assertNull(enc.getDateChanged());
>
>               // Now edit the obs and resave the encounter
>               for (Obs o : enc.getAllObs()) {
>                       o.setValueNumeric(75d);
>               }
>
>               // resave the encounter
>               es.saveEncounter(enc);
>
>               // PASSES: confirm that the date changed has been set
>               assertNotNull(enc.getDateChanged());
>       }
>
> ****
>
> ** **
>
> So I did a little more tracing and it turns out that the two flushes that
> occur when you call saveEncounter **are not** caused by
> sessionFactory.getCurrentSession().saveOrUpdate(encounter); in the dao.
>  The flushes occur only occur when DAO queries are made during the
> execution of the saveEncounter method (they happen during the dao calls
> made at EncounterServiceImpl:100 and EncounterServiceImpl:102).  So it
> seems that the flush that is caused by calling saveEncounter is
> coincidental and not intentional.****
>
> ** **
>
> To test this out further I set out to test a save method  that that was
> simpler (and therefore might not coincidentially cause a flush) and see if
> I could get it to fail.****
>
> ** **
>
> I took the following unit test:****
>
> ** **
>
> *public* *void*saveEncounterType_shouldUpdateAnExistingEncounterTypeName()
> *throws* Exception {****
>
>               EncounterService encounterService = Context.*
> getEncounterService*();****
>
>               ****
>
>               EncounterType encounterTypeToChange =
> encounterService.getEncounterType(1);****
>
>               ****
>
>               // change the name of the type****
>
>               encounterTypeToChange.setName("another test");****
>
>               ****
>
>               // save the type to the database****
>
>               encounterService.saveEncounterType(encounterTypeToChange);**
> **
>
>               ****
>
>               // make sure the encounter type id didn't change****
>
>               *assertEquals*(1,
> encounterTypeToChange.getEncounterTypeId().intValue());****
>
>               ****
>
>               // *refetch* the encounter type from the database****
>
>               EncounterType fetchedEncounterType =
> encounterService.getEncounterType(1);****
>
>               *assertTrue*(fetchedEncounterType.getName().equals("another
> test"));****
>
>        }****
>
> ** **
>
> I traced this test and discovered that an onDirtyFlush is never called
> during its execution.****
>
> ** **
>
> So  I tested commenting out the “encounterService.saveEncounterType” line
> in the above test, and, as I suspected, the test still passed.****
>
> ** **
>
> But, if I keep the saveEncounterType method but add a
> Context.clearSession() after it **the test fails**.****
>
> ** **
>
> My conclusion here is that in this test the change is never committed to
> the database (of course we are dealing with a mock db, which may affect
> things), and the only reason the test passes is because hibernate checks
> its internal session cache when refetching the encounter.  Is this known
> behavior that I didn’t completely understand, or have we all been missing
> this?****
>
> ** **
>
> Mark****
>
> ** **
>
> ** **
>
> ** **
>
> ** **
>
> ** **
>
> *From:* [email protected] [mailto:[email protected]] *On Behalf Of *Burke
> Mamlin
> *Sent:* Monday, November 28, 2011 9:34 AM
>
> *To:* [email protected]
> *Subject:* Re: [OPENMRS-DEV] Auditable Interceptor functionality****
>
> ** **
>
> Mark,****
>
> ** **
>
> Thanks so much for this investigating.  It was a perfect addition to a
> plate of turkey and a glass of Australian shiraz. :-)****
>
> ** **
>
> At the end, when you say "it is odd that dateCreated is being changed",
> you mean that the dateCreated *reference* has changed, not the value,
> right?  Presumably because hibernate is doing some cloning or similar
> trickery under the hood?****
>
> ** **
>
> -Burke****
>
> On Thu, Nov 24, 2011 at 9:39 AM, Mark Goodrich <[email protected]> wrote:*
> ***
>
> No, even after flushing the session, the assert still fails.
>
> There's only one intercepted method in the AuditableInterceptor... the
> onDirtyFlush.  The interceptor methods are called on an object-by-object
> basis.  I added a dummy onSave method to the interceptor and debugged and
> found the following:
>
> 1 )After the first saveEncounter in the editNewEncounter method:
> The onSave method is called three times, once for the Encounter, and once
> for each of the two Obs added
> The onDirtyFlush method is not called
>
> 2) After the second saveEncounter in the editNewEncounter method:
> The onDirtyFlush method is called four times, twice for each of the two
> edited Obs
> The onSave method is not callled
>
> 3) After the only saveEncounter in the editExistingEncounter method:
> The onDirtyFlush method is called six times, twice for the Encounter, and
> twice for the two edited Obs
> The onSave method is not called
>
> I looked a little deeper into #3 to see exactly why the onDirtyFlush
> method was being called to hopefully determine why it wasn't being called
> in  case #2.
>
> First, during the saveEncounter in the editExisingEncounter method,
> onDirtyFlush is called with the Encounter, because three encounter fields
> have changing:
> encounterDate and dateCreated are both changing from an existing timestamp
> object to a new timestamp object
> voidReason is changing from and empty string to null
>
> Next, onDirtyFlush is called for each of the two Obs, with the following
> fields changing:
> obsDatetime and dateCreated are both changing from an existing timestamp
> object to a new timestamp object
> the valueNumeric is updated, as expected
> the personId is updated from one Integer object to another Integer object
> (though with the same integer value, thankfully!)
>
> Next, onDirtyFlush is called again with the Encounter.  Now, six fields
> have changed... the three fields mentioned previous (encounterDate,
> dateCreated, and voidReason) all have the same previous and current values
> as when the last dirty flush was called, and three new fields have changed:
> voided from null to false
> dateChanged from null to a timestamp
> changedBy from null to the current user
> (So it looks like this caused by the voided value change, or it is a
> recursive call caused by the changes made in the AuditableInterceptor
> itself.)
>
> Then, finally, onDirtyFlush is called again with the two obs, and checking
> the current previous states show the exact same changes as the first call.
>
> I don't know if I can come to any conclusions here except that it is odd
> that dateCreated is being changed.  One tangential point is that when obs
> are updated this way (saved via a cascade from an encounter) they are not
> voided and recreated, but I believe this is a known issue I entered a
> ticket about at some point?
>
> I hope I've provided you all with some fun Thanksgiving reading.
>
> Mark
>
>
>
> ________________________________________
> From: [email protected] [[email protected]] On Behalf Of Ben Wolfe [
> [email protected]]
> Sent: Thursday, November 24, 2011 7:40 AM
> To: [email protected]
> Subject: Re: [OPENMRS-DEV] Auditable Interceptor functionality****
>
>
> Do you know which method in the interceptor is called on that first save?
>
> Is the dateChanged set at the end of the transaction?  If you flush the
> session right before your failing assert, does the assert then pass?
>
> Ben****
>
> On Thu, Nov 24, 2011 at 12:22 AM, Mark Goodrich <[email protected]<mailto:
> [email protected]>> wrote:
> Sorry I missed the design call on hibernate interceptors today, but,
> coincidentally, I was investigating an issue with the Auditable interceptor
> that I uncovered when running the HFE unit tests against 1.8.  Or, rather,
> something that may be an issue.
>
> Take the following unit test, which creates a new encounter, saves it,
> updates the obs associated with it, and saves it again.  Using the old save
> handlers, the dateChanged of the encounter would be set upon the second
> save.  But, with the hibernate interceptor, this is not the case and so
> this test will fail:
>
> public void editingNewEncounter_shouldUpdateDateCreated() throws Exception
> {
>               EncounterService es = Context.getEncounterService();
>
>               // First, create a new Encounter
>               Encounter enc = new Encounter();
>               enc.setLocation(new Location(1));
>               enc.setEncounterType(new EncounterType(1));
>               enc.setEncounterDatetime(new Date());
>               enc.setPatient(new Patient(3));
>               enc.setProvider(new Person(1));
>
>               // Now add a couple obs to it
>               Obs newObs = new Obs();
>               newObs.setConcept(new Concept(1));
>               newObs.setValueNumeric(50d);
>               enc.addObs(newObs);
>
>               Obs anotherNewObs = new Obs();
>               anotherNewObs.setConcept(new Concept(1));
>               anotherNewObs.setValueNumeric(100d);
>               enc.addObs(anotherNewObs);
>
>               // now save the encounter
>               es.saveEncounter(enc);
>
>               // flush the session, just to be safe
>               Context.flushSession();
>
>               // confirm that the encounter has been assigned an id, but
> the date changed is still null
>               assertNotNull(enc.getId());
>               assertNull(enc.getDateChanged());
>
>               // confirm tha the obs have been added to the encounter
>               assertEquals(2, enc.getAllObs().size());
>
>               // Now edit the obs and resave the encounter
>               for (Obs o : enc.getAllObs()) {
>                       o.setValueNumeric(75d);
>               }
>
>               // resave the encounter
>               es.saveEncounter(enc);
>
>               // FAILS: confirm that the date changed has been set
>               assertNotNull(enc.getDateChanged());
>       }
>
> Note, however, that if I load an existing encounter and update the obs,
> the date changed will be set:
>
> public void editingExistingEncounter_shouldUpdateDateCreated() throws
> Exception {
>               EncounterService es = Context.getEncounterService();
>
>               // First, load an existing Encounter
>               Encounter enc =
> Context.getEncounterService().getEncounter(3);
>
>               // confirm that the date changed is null
>               assertNull(enc.getDateChanged());
>
>               // Now edit the obs and resave the encounter
>               for (Obs o : enc.getAllObs()) {
>                       o.setValueNumeric(75d);
>               }
>
>               // resave the encounter
>               es.saveEncounter(enc);
>
>               // PASSES: confirm that the date changed has been set
>               assertNotNull(enc.getDateChanged());
>       }
>
> In the first case the encounter is never passed to the interceptor
> onDirtyFlush method...  I would assume because the Encounter is still in
> the new/save state?  Is this a problem?  This is a contrived case, and it
> seems to make sense that if an encounter is saved twice in rapid succession
> it's date changed isn't set--but this isn't the behavior I would have
> expected, and it is different from how the old save handlers worked.
>
> Thoughts?
> Mark
>
>
>
> _________________________________________****
>
> To unsubscribe from OpenMRS Developers' mailing list, send an e-mail to
> [email protected]<mailto:[email protected]> with
> "SIGNOFF openmrs-devel-l" in the  body (not the subject) of your e-mail.
>
> [mailto:[email protected]<mailto:[email protected]
> >?body=SIGNOFF%20openmrs-devel-l]
>
> ________________________________
> Click here to 
> unsubscribe<mailto:[email protected]?body=SIGNOFF%20openmrs-devel-l>
> from OpenMRS Developers' mailing list****
>
>
> _________________________________________
>
> To unsubscribe from OpenMRS Developers' mailing list, send an e-mail to
> [email protected] with "SIGNOFF openmrs-devel-l" in the  body
> (not the subject) of your e-mail.
>
> [mailto:[email protected]?body=SIGNOFF%20openmrs-devel-l]****
>
> ** **
> ------------------------------
>
> Click here to 
> unsubscribe<[email protected]?body=SIGNOFF%20openmrs-devel-l>from 
> OpenMRS Developers' mailing list
> ****
>

_________________________________________

To unsubscribe from OpenMRS Developers' mailing list, send an e-mail to 
[email protected] with "SIGNOFF openmrs-devel-l" in the  body (not 
the subject) of your e-mail.

[mailto:[email protected]?body=SIGNOFF%20openmrs-devel-l]

Reply via email to