I'm tempted to say "See Title" and end the blog entry here, but that probably wouldn't be helpful. ;) So I ran into something unexpected today. After talking it over with Brian Kotek it makes more sense to me, but what I'm about to explain certainly was behavior I did not expect. I was doing some testing with ORM entities today, specifically one with a One-To-Many relationship. During my testing I ran a few set operations but I never persisted my changes. In my mind I was simply testing methods and didn't really care to persist the change.
However - I noticed that after I reloaded the page, the changes I made were being persisted. I had thought that to persist changes you always needed to use entitySave. However, that appears to not be the case. If you run setX on an entity, Hibernate assumes you mean to persist that change.
It just feels wrong to me that this happens automatically. In my mental model, it's like opening a Word document. I may modify the title and print it, but I don't expect my changes to persist unless I explicitly click the Save button.
Brian shared with this URL over at the help documentation. It talks a bit about ORM session management. Now I'm already aware of how Hibernate handles sessions. What I didn't expect was that an update to a persistent component would be automatically persisted. However, from the doc page I think it is quite clear from the second item (2. all entity updates) that this would indeed be the case.
In my example I was just writing a test script, but Brian pointed out that one place this tends to trip people up quite a bit is in edit handlers. You take in some form input, run your SETs, and then you may conditionally entitySave based on error checking. However, even without the entitySave there is a chance your changes will be persisted anyway.
So... maybe I'm alone in this. Ben Nadel mentioned to me that this behavior (save existing entities automatically) was what he expected. Did other people know this?
Archived Comments
Although I was aware of this, it's still good to be reminded. I'm assuming that if you need to control whether data gets persisted, you could can set flushAtRequestEnd to false.
Yep, Brian mentioned that specifically. He said he uses that mod. (I'm bringing him into the thread to correct me if wrong.)
Didn't know it so thank you. My mental model and transfer experience would have had me assume what you did.
I know that even if I'm not been coding for several years,
I'm just a simple manager nowdays :p
Some comments on Bens blog are the best explonation I've seen about this. Read all comments from Justice in this blogpost http://www.bennadel.com/blo...
ps. I regularly follow your blog and many others as well, and I think your blog is great!
Not that that entry isn't good - it is good - but to be clear - I do "get" how the session works in terms of timing. This to me isn't a session issue/question but more a "Under what circumstances do you consider data changed?" I guess that Hibernate thinks that as soon as you change something, it should be saved, but to me, it shouldn't.
On the other hand - I may be nitpicking. ;)
Although - I think this raises the point - ORM is super easy, super sexy, etc, but sometimes the little details can really bite you in the ass.
Hate to let you know this Ray, but I mentioned exactly this in my original DevNet ORM article:
http://www.adobe.com/devnet... ;o(
My articles on my blog on Hibernate Sessions also explain how and also why this happens (its mainly a performance thing).
http://www.compoundtheory.c...
Hope they explain things a little better, in case you (or anyone else is wondering what is going on)
Just a further note on this - if you are looking to turn this off (and do more manual control of when Sessions flush, you can set the the ormsettings 'flushatrequestend'.
http://help.adobe.com/en_US...
Who reads your stuff though? ;) Seriously - yeah - it all makes sense now - it just wasn't what I expected. I'm glad to see I wasn't alone. I'll be sure to mention this in the WACK too. Thanks all.
Yeah, I would have totally figured that you'd have to run: set, set, set, save.
Ray, I am going to agree with you. There is nothing, either in the Adobe documentation nor Mark Mandel's blogs that specifically say that if you ommit the EntitySave/EntityDelete that it will be persisted.
All I am reading and interpreting is that the save/delete will only occur at the end (provided that use use entitySave/EntityDelete) of the request unless you flush it manually.
Or am I not reading this correctly!!
@Andrew: You didn't see the call out in the URL Mark listed? Technically that isn't his blog - but I just want to make sure you see it. He does call it out there. As for the Adobe docs, I definitely can't say I've read them all, but I know I haven't seen this.
And if I read your second para right - then yes - it is my understanding that you are right. The saves/deletes happen at the end of the Hibernate session (which you can , normally, mentally map to ColdFusion Request).
Again though - that wasn't a surprise for me. I expected that. I just didn't except that if I _changed_ a persistent CFC it would auto save the change.
Ray, yes I read both links Mark posted. And still do not see where it says, that if you load/create an entity without an EntitySave the object will be persisted to the database.
Also I fail to see how this a performance issue as Mark has suggested, in my eyes if one hasn't used the save then it wont create a sql script. Which means that by the end of the request, there is no SQL to run. How that is a performance issue is making me really scratch my head.
The performance issue that everyone seems to mention is the fact that the flush is done at the end of the request, that is a performance issue. Whether you have modified an entity or not, it should not save the entity back to the database. Although I have not seen this behaviour, I would be more inclined to call this a bug. If one was to write a multi-step process, one could find that they would not be able to roll back the data. This is just one of many examples I can come up with off the top of my head for this being an major bug.
@AS: To your first comment - he didn't say you didn't need entitySave for NEW entities, he said it for existing entities. Here is the quote from his first URL:
"Note that the code did not explicitly tell Hibernate to update larry. Hibernate keeps track of what CFCs have been loaded and if they have changed from their initial state. In this case, it can see that the age property of Larry has changed, and at the end of the request it will update the database accordingly. You could call EntitySave() on Larry when you are updating him, but the call simply gets ignored by Hibernate when the object is already persisted in the database."
To me that is pretty cut and dry. To your second para - my understanding is that Hibernate is storing up changes and running only what it needs to. So if I take a user ob, set its name to Bob, entitySave it, then set it to Joe, and save it, Hibernate will be smart enough to say, "I only really need to save it to Joe." Again, that's my take.
As for it being a bug... well, to me, it was counterintuitive. I don't expect that I think the same as the Hibernate devs out there. In my personal opinion, this isn't a bug. Just... how it works. But I can see your point.
Ok, I did miss that statement.
However I will stand by the fact that it is an unwanted feature then, as I explained this means one can not write a multistep application and if something goes wrong one can't roll it back to its existing data.
That is not intuitive to me, and will becausing people to look around for solutions to that.
I guess I am different here, I see this as a major unwanted feature. That is going to be raising so many blogs and how do I get around this, but to have a switch to turn it off is just plain wrong.
The docs do mention it (for the most part)... When the session is flushed the following happens:
1.all entity insertions, in the same order the corresponding objects were saved using EntitySave()
2.all entity updates
3.all collection deletions
4.all collection element deletions, updates, and insertions
5.all collection insertions
6.all entity deletions, in the same order the corresponding objects were deleted using EntityDelete()
Steps 1 & 6 mention the use of EntitySave/Delete for inserts and deletions. Steps 2 through 5 have no mention of EntitySave, inferring that it's not needed for those types of changes. Yes, I know... it's not in-your-face obvious from that bit of documenation and should probably be a bit more clear.
As for whether its an unwanted feature, it doesn't really bother me. Once an entity is made persistent, I think of it as having a direct connection to the persistence layer... making a change to an entity is really just making a change to the persistent data through an interface, and that interface happens to be an entity.
Of course, once I run into a use-case in which I don't want changes automatically persisted (which I just haven't yet) I may be annoyed at having to disconnect that object from the session. But if that's how hibernate works internally, then maybe disconnecting from the session (or something like that) is a generally acceptable practice amongst the Hibernate community and we're all just complaining because it's a change from the norm for us?
Devin - duh - thank you. I linked to the doc up top. Brain fart there. So Andrew, I'd say it _is_ documented... although maybe not 100% clear/blunt.
Devin - also - one thing Brian Kotek suggested was that you could simply use a new entity (var foo = new User()) during an edit. When you are sure the user is edited correctly, you can then merge it with the persistent copy. Please take what I say with a grain of salt - I don't want to put the wrong words in Brian's mouth.
That's correct, Ray. If you don't want this behavior, your options are basically: to turn it off (I do); to update the object, validate it, and remove it from the persistence context using ORMEvictEntity(); or create a new, non-persistent object, populate it, validate it, and then merge it with the existing entity using EntityMerge().
@Ray,
Just for the record, when I responded on Twitter, it wasn't what I "expected". It was what I learned after wondering why something was happening that I didn't expect ;) I'm still wrapping my head around all of this ORM stuff :)
Oops, sorry man!
No worries - I just didn't want you thinking I know what the heck I'm talking about :)
This is definitely not the behavior one would expect. I wish there was a setting (similar to flushAtRequestEnd) to just tell CF to persist the changes only if entitySave() or entityDelete() is called on the object.
PS: Correction to Brian's comment (this will trip people as well). ORMEvictEntity() does _not_ remove the object from the hibernate session. It only removes it from the secondary cache. To evict from the hibernate session (and to prevent persistence) use: ormGetSession().evict(entity).
How about using cftransactions? Adobe seems to recommend using transactions with ORM: http://help.adobe.com/en_US...
So my code would be:
<cftransaction action="begin">
<cfset game = entityLoadByPK("Game", 1) />
<cfset game.setName("Mass Effect 2") />
<cfset valid = game.validate() />
<cfif valid>
<cftransaction action="commit" />
<cfelse>
<cftransaction action="rollback" />
</cfif>
</cftransaction>
So if there's some kind of validation error with editing this record, it will get rolled back and not persisted to the database.