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]<mailto:[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]<mailto:[email protected]>
[[email protected]<mailto:[email protected]>] On Behalf Of Ben Wolfe
[[email protected]<mailto:[email protected]>]
Sent: Thursday, November 24, 2011 7:40 AM
To:
[email protected]<mailto:[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]><mailto:[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]><mailto:[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]><mailto:[email protected]<mailto:[email protected]>>?body=SIGNOFF%20openmrs-devel-l]
________________________________
Click here to
unsubscribe<mailto:[email protected]<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]<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