Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Claudio Andreas Rüger
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Claudio Andreas Rüger

Learning ColdFusion 9: Understand ORM Events (Thanks John Whish!)

By on
Tags:

Yesterday, I started to explore the ColdFusion 9 ORM event model. ORM is still something that I'm wrapping my head around and I'll admit that I was a bit confused as to how things were being wired up. In the comments to yesterday's post, John Whish came through with a piece of brilliant insight, guessing that certain ORM events (such as Update and Delete) were not being fired until the ORM session was flushed. With some quick testing, shown below, I confirmed that he was spot-on.

In an effort to gain efficiency, ColdFusion 9's ORM system delays as much persistence-layer interaction as it can until the end of the current Hibernate session (usually at the end of the page request or CFTransaction). As such, ORM events like Update and Delete are not executed right when you call EntitySave() and EntityDelete(), but are rather batch processed later on in the page request. That is why the Update and Delete events were not showing up in my EventLog property yesterday - while I had called EntitySave() and EntityDelete(), the actual events themselves had not yet fired.

To demonstrate that these events do fire eventually, I have updated yesterday's demo with ORMFlush() statements to make sure that all persistence-layer interaction is concluded before the end of the known page request:

<!--- Create a new thought. --->
<cfset thought = entityNew( "Thought" ) />

<!--- Define the thought content. --->
<cfset thought.setContent( "I wonder what kind of undies Tricia is wearing today?" ) />

<!--- Persist the thought - this will be an INSERT. --->
<cfset entitySave( thought ) />


<!--- ----------------------------------------------------- --->
<cfset ormFlush() />
<!--- ----------------------------------------------------- --->
<cfthread action="sleep" duration="#(1 * 1000)#" />
<!--- ----------------------------------------------------- --->


<!---
	Load the thought based on the primary key (which we know
	will be "1" since we just rebuild the database).
--->
<cfset thought = entityLoadByPK( "Thought", 1 ) />

<!---
	I am reloading the object here because otherwise, it will
	just grab it out of the current Hibernate session without
	actually loading it.
--->
<cfset entityReload( thought ) />

<!--- Reset the thought content. --->
<cfset thought.setContent( "That Joanna really looks good with her hair up. Why don't more women wear there hair up?" ) />

<!--- Persist the thought - this *should be* an UPDATE. --->
<cfset entitySave( thought ) />


<!--- ----------------------------------------------------- --->
<cfset ormFlush() />
<!--- ----------------------------------------------------- --->
<cfthread action="sleep" duration="#(1 * 1000)#" />
<!--- ----------------------------------------------------- --->


<!--- Delete the thought. --->
<cfset entityDelete( thought ) />


<!--- ----------------------------------------------------- --->
<cfset ormFlush() />
<!--- ----------------------------------------------------- --->
<cfthread action="sleep" duration="#(1 * 1000)#" />
<!--- ----------------------------------------------------- --->


<!--- Output the object (this will include the event log). --->
<cfdump
	var="#thought#"
	label="Thought"
	/>

In this demo, ORMFlush() is being executed between each cohesive action. This will force our updated domain model to be persisted by the ORM, which in turn, forces our events to fire. And indeed, when we run the above code, we get the following CFDump output:

ColdFusion 9 ORM Event Handling With ORMFlush().

With the ORMFlush() statements now in place, we can clearly see that all ORM events have fired and were successfully logged to the internal EventLog property. Thanks a lot John, you rock!

And, just as a reminder, here is the ColdFusion component that I was testing with (same as in yesterday's demo):

Thought.cfc

<cfcomponent
	output="false"
	hint="I represent a thought."
	persistent="true"
	table="thought">

	<!--- Define the CFC properties. --->

	<cfproperty
		name="id"
		type="numeric"
		setter="false"
		hint="I am the unique ID of the thought at the persistence layer."

		fieldtype="id"
		ormtype="integer"
		length="10"
		generator="identity"
		notnull="true"
		/>

	<cfproperty
		name="content"
		type="string"
		validate="string"
		validateparams="{ minlength=1 }"
		hint="I am the thought."

		fieldtype="column"
		ormtype="text"
		notnull="true"
		/>

	<cfproperty
		name="dateCreated"
		type="date"
		validate="date"
		hint="I am the date the thought was created."

		fieldtype="column"
		ormtype="timestamp"
		notnull="true"
		/>

	<cfproperty
		name="dateUpdated"
		type="date"
		hint="I am the date the thought was updated."

		fieldtype="column"
		ormtype="timestamp"
		notnull="true"
		/>

	<!---
		This property is not a persistent property but is
		one that we are using simply to track the events
		that take place.
	--->
	<cfproperty
		name="eventLog"
		type="array"
		hint="I am an internal log used to track events."

		persistent="false"
		/>


	<!---
		Set default values for complext objects. These cannot
		be set by default using the CFProperty tags.
	--->
	<cfset this.setEventLog( [] ) />


	<cffunction
		name="init"
		access="public"
		returntype="any"
		output="false"
		hint="I initialize this object.">

		<!--- Return this object reference. --->
		<cfreturn this />
	</cffunction>


	<cffunction
		name="preLoad"
		access="public"
		returntype="void"
		output="false"
		hint="I run before this entity is loaded.">

		<!--- Track event. --->
		<cfset this.trackEvent( "preLoad" ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="postLoad"
		access="public"
		returntype="void"
		output="false"
		hint="I run after this entity is loaded.">

		<!--- Track event. --->
		<cfset this.trackEvent( "postLoad" ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="preInsert"
		access="public"
		returntype="void"
		output="false"
		hint="I run before this entity is inserted.">

		<!--- Track event. --->
		<cfset this.trackEvent( "preInsert" ) />

		<!---
			Set the date/time properties. This way, we can
			keep track of the time at which this object was
			created / inserted.
		--->
		<cfset this.setDateCreated( Now() ) />
		<cfset this.setDateUpdated( Now() ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="postInsert"
		access="public"
		returntype="void"
		output="false"
		hint="I run before this entity is inserted.">

		<!--- Track event. --->
		<cfset this.trackEvent( "postInsert" ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="preUpdate"
		access="public"
		returntype="void"
		output="false"
		hint="I run before this entity is updated.">

		<!--- Define the arguments. --->
		<cfargument
			name="oldData"
			type="struct"
			required="true"
			hint="I am the collection of data held over from the load time."
			/>

		<!--- Track event. --->
		<cfset this.trackEvent( "preUpdate", arguments.oldData ) />

		<!---
			Set the date/time properties. This way, we can
			keep track of the most recent time at which this
			object was updated.
		--->
		<cfset this.setDateUpdated( Now() ) />

		<!---
			Return out. NOTE: If this method return TRUE, then
			the update is cancelled.
		--->
		<cfreturn />
	</cffunction>


	<cffunction
		name="postUpdate"
		access="public"
		returntype="void"
		output="false"
		hint="I run after this entity is updated.">

		<!--- Track event. --->
		<cfset this.trackEvent( "postUpdate" ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="preDelete"
		access="public"
		returntype="void"
		output="false"
		hint="I run before this entity is deleted.">

		<!--- Track event. --->
		<cfset this.trackEvent( "preDelete" ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="postDelete"
		access="public"
		returntype="void"
		output="false"
		hint="I run after this entity is deleted.">

		<!--- Track event. --->
		<cfset this.trackEvent( "postDelete" ) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>


	<cffunction
		name="trackEvent"
		access="public"
		returntype="void"
		output="false"
		hint="I log an event to the internal event log.">

		<!--- Define arguments. --->
		<cfargument
			name="eventName"
			type="string"
			required="true"
			hint="I am the name of the event being logged."
			/>

		<cfargument
			name="eventData"
			type="any"
			required="false"
			default=""
			hint="I am the data associated with the event."
			/>

		<!--- Create a log item from this event. --->
		<cfset local.logItem = {
			eventName = arguments.eventName,
			eventData = arguments.eventData,
			dateExecuted = now()
			} />

		<!--- Add this event to the internal log. --->
		<cfset arrayAppend(
			variables.eventLog,
			local.logItem
			) />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>

</cfcomponent>

Want to use code from this post? Check out the license.

Reader Comments

45 Comments

Can preUpdate be called with an abort/return to prevent the ORM from updating? For example, a user is trying to update a record he does not own, so I want to bail out of the update. What's the best way to handle this?

15,640 Comments

@David,

According to the documentation, you can return "True" from a preUpdate() and it will cancel the update. I have not tested this yet, though.

113 Comments

@Ben,

Why do you use EntitySave() on entities which you got from EntityLoad()? In addition, why do you have a comment noting that "this SHOULD be an update"?

You are still thinking about this (maybe less so than in the past) from the standpoint of database interaction.

Your objects, once saved to the Hibernate session, are completely managed for you. Every aspect of their persistence and retrieval is transparent and implicit. The database interaction happens when Hibernate wants it to happen, not when you want it to happen. Hibernate makes guarantees about what happens to your objects and when, but it takes care of figuring out how best to carry out those guarantees by fiddling with records.

Note that you should not be flushing the session just to get the events to fire. If that's what you're doing now, then you should actually be doing something else instead, because that's not the way Hibernate works.

When you work within a Hibernate session, you should always be working within a Hibernate transaction. This is not a database transaction: it is a business objects transaction (of course, it may come down to being a database transaction if that's what Hibernate decides). You should always commit the Hibernate transaction when you are done with the work, and that's the cue to Hibernate to ensure that the database matches the objects in the Hibernate session.

EntitySave will take an object created with new or EntityNew and add that object to the current Hibernate session. Hibernate will then choose what to do with it and when to do it.

EntityUpdate will take an object that the Hibernate session knows about (e.g., there is an associated record in the database so Hibernate can build an object from that record, or you have recently called EntitySave on a new object), and update the properties on that object that it knows about from the properties of the object that you provide to EntityUpdate. Note again that EntityUpdate does not update a record from its associated object. It updates an object that the session knows about from some other object gotten from somewhere else. This may require that Hibernate execute an update statement later on, but not necessarily.

Again, if you want to use Hibernate, then you must first repeat this mantra ten thousand times, or until you believe it: There is no database, but only objects in Hibernate sessions.

With the caveat: there are performance optimisations you can have Hibernate do, and you can certainly judge which optimisations are necessary based on what's happening at the database level. But don't do this when learning Hibernate. For example, Hibernate can batch multiple statements together instead of sending statements one-by-one, Hibernate can fetch associated records through joins or through separate queries, Hibernate can defer the actual creation of objects from the associated records until the point of use or create them immediately, Hibernate can defer the actual SQL statements to create/update/delete until the point of necessity or execute them immediately, etc. But ignore these for now, until you figure out what your actual performance metrics and requirements are.

Justice

113 Comments

@David,

You should do this at the controller level or at the domain model level. But you should not do it at the Hibernate-internals level, because that's not what it's there for and it goes against the whole point of Hibernate.

Hibernate is there to allow you to program a data-driven application purely in terms of objects that know nothing about any database. The SQL events are not there for your use in building applications, but only in case you need to customize the way Hibernate works inside.

Justice

113 Comments

@Ben,

Try this:

transaction {

var s = EntityLoad("Soda", 1);
s.setManufacturer("PepsiCo");

}

See what happens to your database. If ColdFusion ORM is anything like Hibernate, then the database record will magically be updated at the end of the transaction (I am not sure if ColdFusion transactions have anything to do with ColdFusion ORM and Hibernate, but they might). Note that I did not call EntitySave after making changes to the object s. I am assuming that I have previously created a Soda object and saved somewhere else.

Justice

15,640 Comments

@Justice,

Absolutely, I agree with everything you are saying - we should be thinking in terms of objects, not databases, and I am moving in that direction.

However, from what I can see, the whole point of ORM events is that you have hooks into the object-persistence-layer cross-over. I'm simply exploring this aspect of the ORM system - this isn't something that I would do normally; I mean, certainly, I would have no need to manually log all ORM events internal to each object - that'd be bananas :)

I'm just exploring the functionality that ColdFusion is providing. That's the only reason that I am flushing the ORM session all over the place to force the events to fire. This is purposely circumventing all optimizations that ORM brings to the table, but only so I can get a concrete understanding of how it all ties together.

As far as transactions go, I am just starting to look into that. Remember that all of this Hibernate stuff is very new to me. I really appreciate all of your advice as you are clearly much more experienced than I in this area.

15,640 Comments

@Justice,

From what I have read so far, all ORM updates are flushed at the end of the CFTransaction tag (or your script-based equivalent). I just haven't tried this myself yet.

113 Comments

@Ben,

Thanks for the tip. A cursory google search didn't show one way or the other. I've used the .NET port of Hibernate commercially, but I have not yet tried out ColdFusion ORM and do not really know what its capabilities are.

ORM events are certainly there to allow you to hook into the persistence mechanism. However, they are not there to allow you to do business-level logic as David suggested. There will certainly be plenty of business-level logic that you need to do, but don't do it with the events.

Almost all of the time, Hibernate already does what you need it to do, so almost all of the time, you will not actually need to use the events. For example, Hibernate already has excellent logging functionality that you can turn on with some simple configuration switches. I have no idea how to do this with ColdFusion ORM, but once you get that working, Hibernate will give you either summary information or up to hundreds of lines describing every little thing that it's doing. I believe Hibernate uses log4j (NHibernate uses log4net, which obviously is a port of log4j), so I hope that Adobe gets around to integrating log4j with cflog and the CF log files, if it hasn't yet done so. If you configure it right, Hibernate can probably even log the SQL it executes and plenty of other details out to the CF page, at the point of execution.

Justice

45 Comments

For the record:

"ColdFusion will automatically save any changes made to a persisted object at the end of the request regardless of whether you call EntitySave or not.

You can override this behaviour by adding this line to Application.cfc

this.ormsettings.flushatrequestend = false;"

Then, you have to manually Flush.

113 Comments

@David,

That is true. In fact, in the general case, you should always use transactions with your Hibernate sessions, and then Hibernate will auto-flush no later than the end of each transaction. In the general case, it may be a good idea never to permit Hibernate to commit your changes outside of a transaction - just like you should never use a database at all outside of a transaction.

Disclaimer: applications that require massive scalability have their own, separate, requirements, and do not fall under this general case.

@Ben,

People typically do not use Hibernate events, and I would recommend against using them in most cases.

They're there to provide you with the ability to add behavior to Hibernate, to get it to do things that it does not already have built-in support for. But if you're doing what most people are doing, Hibernate already supports it very well.

If your objects have complex behavior, then you should implement that complex behavior in the properties and methods of that object. For example, if calling 'setManufacturer' should do more than simply update a single internal variable, then you need to implement your own 'setManufacturer' function with that complex behavior and not rely on the auto-generated function. This is the most essential aspect of the programming methodology known as Object-Oriented, and Hibernate is built to support this methodology from the ground up.

You should not implement that complex behavior in the hooks into the Hibernate persistence pipeline.

Justice

113 Comments

@Ben,

Think about the session as a self-contained, self-sufficient, isolated, implicitly transactionally persisted window into a universe of objects.

You can put things into the universe behind the window with EntitySave, where they will forever remain.

You can reach into the window and modify things already in the isolated universe - just by updating the properties and calling methods on the objects, but also by calling EntityUpdate in certain corner cases.

And you can remove things from that universe with EntityDelete.

Actually, the window is even more magical than that. Not only is it a window into a complete universe of objects, but when you open the shutters, the window actually creates an alternate dimension with a snapshot-in-time view of the real universe of objects. Everything you do through that window only affects the alternate dimension. But then when you close the window, that alternate dimension is automatically merged back into the real dimension, along with everything in it.

You don't need to know anything about how the window works in order to use it to reach into that isolated universe (except in order to configure the right mappings and except in order to effect the right performance optimisations).

It is unlikely that you will need to hook into how the window works internally, because it is already a very sophisticated window built by the best minds in the field, and they have already taken your case into account.

Justice

113 Comments

@Ben,

You might use the ColdFusion ORM events for such things as: updating an audit table when a record is deleted, posting a message to a message queue when a record is inserted, ensure that a record is replicated properly to a secondary database when the original record is updated, etc.

Justice

3 Comments

@Justice:
Just wanted to say that the discussion happening here is extremely interesting. Thanks for all your insights!

5 Comments

Thanks John Whish and you guys for sharing the blog post on "Learning ColdFusion 9: Understand ORM Events." I can relate…no doubt!
I really thankful to you for doing such a great job

1 Comments

Wouldn't it be easier to use 'old-fashioned' cfquery with some SQL?

nb: i do hope Tricia doesn't wear the same undies as yesterday ;)

45 Comments

@Ben,@ Justice

In Hibernate, if I run a query to retrieve a specific User() object using ORMExecuteQuery(), I successfully get back all of the object properties.

I want to return the entire object to my client (Flex).

Some of those properties, like the "uPassword", I want to eliminate from the returned User() object. Is there an annotation/property in Hibernate that would allow me to do this?

@Martin,
Tricia believes in water-conservation.

113 Comments

@David,

Hibernate is a tool that lets you create objects, work with them, tuck them away, and then retrieve them again. Objects are not rows where you can simply select a few of its properties and not other properties - objects are whole, indivisible things. When you ask Hibernate for an object, it gives you the object.

When you then want to take the object and extract some data out of it and move the data from one place to another, then what you are looking for is some kind of serialization or DTO. That is far beyond the realm of Hibernate, and there are plenty of other tools for the job. For example, you can hook into the serialization process and remove password from the serialized form of User objects.

But remember, the entire point of Hibernate is to give you a magical window into a universe of objects, and it is far and away the best tool for the job. But Hibernate isn't intended to help you with anything else.

Justice

45 Comments

@Justice,

It is magical. I "finished" rebuilding my user login last night using Hibernate, and to make a single Hibernate request to retrieve all of the same information that once took 1000+ lines of functions and SQL absolutely astounded me.

It's a scary time to be a DBA.

113 Comments

@David,

Unless your system is proprietary, would you be interested in sharing some before- and after- snapshots of your user login system? I would be interested in seeing how people are beginning to use ColdFusion ORM on real projects, and the real differences it can make in those projects. I am sure others would be interested in seeing this stuff as well.

Justice

45 Comments

@Justice,

I'll do it.

I need a couple of days to finish cleaning up the login script, and it would eat up most of Ben's database volume if I posted the related methods (and their SQL), but if I post the old Login versus the new Login with the ORM script, the differences should be apparent.

I'd feel comfortable saying that, forgetting about the size of the Persistent Objects, the actual code to perform a login routine is now 1/25th the size of the original codebase to do the same thing.

113 Comments

@David,

Perhaps you could convince Ben to host your before- and after- snapshots as a "guest entry".

While no doubt the differences would be apparent, a before- snapshot with 25x the amount of custom code would be a very good selling point. But you could put all of the code in an attached .zip file, and include only the side-by-side comparisons of the top-level login code in the guest entry.

Justice

47 Comments

@David, would it be safe to say that you're writing better code now than you were when you originally wrote the login script? Maybe had you refactored the code the 1000 lines would be a lot less, i'm speaking from my own experience. I write much less code than i used to as I gain more knowledge. I guess i'm just trying to find out if it was really hibernate or if it was you as a better coder and more experienced.

45 Comments

@Hatem,

Looking at the current project I am rebuilding in CF9, the knowledge portion of the equation, with regard to 'writing better code', is the improvement in the use of abstract classes for security, and the addition of more strongly typed objects.

The use of Hibernate ORM in CF9 has allowed me to use functions, without cfquery. Now only do I get to skip the use of cfquery, but I'm not writing a DAO. I'm just calling the setters for an object, and then EntitySave(item). The setters and getters, along with ColdFusion Builder's object introspection, means I don't have to think about the consistent use of field names, or the order of my insert/update statements. I know someone would like to chime in here, and say, "Just come up with a good field name in your SQL and you're done, Buhler." But in the real-world that serves me up my handy paycheck, I work in teams of people that love to come up with better naming conventions on an almost hourly basis. Hibernate in CF9, along with the ORMSettings.namingStrategy, will ensure I no longer have to figure out why my "user_id" property has nulled out, because someone thinks "UserID" is a better choice.

If I want to return a Structure, instead of a list, I only need to add a single parameter to my ORM method. I no longer need to think about adding a LIMIT 1 in SQL, and this gives me the chance to redefine what I expect my method call to return.

The ORM Relationships mean I no longer have to craft an Object that returns an Object with properties, arrays, etc. I'm free of all the setters and StructNew()s. I no longer have to use cheesy QueryToArrayOfStructures(param) methods.

I no longer have to write SQL to get the last identifier added. Rather, Hibernate takes care of this for me, provided my Object relationships are defined correctly.

The Concurrency control in Hibernate means I write a single version property, and I no longer have to write massive operations to see if a record is open or not. My CFTransaction verbosity is gone (in some cases).

The pagination strategies are much more straightforward, too. I add to the same line of my ORMExecuteQuery the offset and maxresults, and I'm home-free.

Filter criteria is far less code to write in Hibernate than it is with SQL, and it's also much more straight-forward and, as is the case with Hibernate/ORM...it's consistently the same across all databases. I don't have to ask my coworkers how to do something in MSSQL SQL, vs. MySQL SQL. Instead, I just add a couple of parameters inline with naming conventions that make sense.

If my users want to have highly custom searches, it's so amazingly easy to use ORMLoadByExample(item), instead of writing a long SQL query with endless Joins based on the arguments added.

My basic understanding (and stop me if I'm wrong here, people), is that I no longer have to parameterize my queries, because I can write the validation requirements inline as an annotation on my object property.

CF9 also makes the Object Modeling fun, because I can make changes to my schema, populate some fake data, and then do a dump to see the relationships, naming conventions, results, and take into consideration all the fun stuff I forgot to think about. But the comprehension of the data is Purely as Objects, instead of SQL. So I no longer think about how I'm going to write a join, but I'm thinking about how the object defines its relationships (down to the query level).

I also spend far less time in EMS, my MySQL editor, changing the table permissions, since I can annotate the table properties, like read-only, as a single property in my object.

The use of well-respected, enterprise caching strategies also changes how much code I write. I'm no longer having to write method calls, with different CacheTimes as a parameter to store/purge the cache. I no longer have to write these methods that call these methods! Rather, the caching strategy is defined in 3 lines of my Application.cfc, and and then in another couple of lines in my Object. I can clear out my entire cache in one single line of code, or I can clear Object Caches by their name.

I like how Adobe has created a VERY SIMPLE Hibernate API. That is a thankless task, but it's impressive. At the office, we abstract most of the operations, like the Hibernate sessions, and Adobe has done this for you. The same is true with ehCache, that has a simple API.

GRIPES FOLLOW:
I had been writing Java, with Hibernate, running on Tomcat with Eclipse JEE as my editor. ColdFusion Builder still has a long, long ways to go before I feel better about the toolset at hand. Forgetting that the product is in Beta, the refactoring capabilities of CFBuiler (moving, renaming), the lack of a working formatter, the inability to click a classpath that is defined in an abstract class (ARGH!), the inability to click an entity name and open the entity, the inability to identify incorrectly typed objects at author-time, and the inability to identify unused methods and variables slow down my productivity. I'll reserve final judgement for CFBuilder's final release, but the aforementioned drive me nuts.

45 Comments

You can omit all of the setters and just save a strongly typed Entity, too.

So in my case, I'm just updating two properties on the server, and the client-side is updating the other 100 updated properties. I pass in the object already modified from the client, and all I write in CF for a Create or Update is:

private Event function saveEvent(Event event)
{
if (isNull(arguments.event)==false)
{
event = EntityLoad('Event', variables.sessionManagement.getDirectorsWorkingEventID(), true);
} else {
event = EntityNew('Event');
}
event.setEventName( "Some Event Name" );
event.setUpdatedDate(now());
event.setSubmittedByUserID(variables.sessionManagement.getUsersID());
EntitySave( event );
variables.sessionManagement.setDirectorsWorkingEventID(event.getEventID());
ORMFlush();
return event;
}

I am so in love.

113 Comments

@David,

Good post.

Just a few items to tighten down that code:

The argument 'event' is not used.

I would only call EntitySave immediately after calling EntityNew (within the else block). There is no reason to call EntitySave on an object returned from EntityLoad, because it is already saved. EntitySave does not mean 'send the changes to this entity down to the database.' Instead, EntitySave means 'take this object that the session does not know about, and make the session now know about it, possibly resulting at some later point in time in a SQL INSERT statement.' If you EntitySave an already-saved entity, such as an entity acquired from EntityLoad, then Hibernate doesn't do anything.

Most of the time, you should not need to be using the OrmFlush function. Most of the time, you will be letting Hibernate deciding when to flush the session (auto-flush mode) in best way that combines efficiency with transactional integrity given all of the queries and modifications you are doing in that session. I don't see any reason here why you would want to flush the session early. If it's to ensure data integrity before returning a value, then just use a normal transaction. Just wrap all of the code in that function in a transaction { } block.

Because Hibernate is fully object-oriented (not id-oriented), it gives you the ability to do things like:
event.setSubmittedByUser(variables.sessionManagement.getUsers());
or:
event.setSubmittedByUser(EntityLoad('User', variables.sessionManagement.getUsersID(), true));
Typically, you should not be using the IDs of related entities. You should be using IDs for cases such as parameters to EntityLoad or as url or form parameters, where you are getting the IDs from outside the Hibernate session. Plus, there are good reasons for not storing entity objects in the CFSession.

It's a good idea always to persist UTC dates (as returned by GetHttpTimeStrimg()) instead of local-time-zone dates (as returned by Now()). Local-time-zone dates are useful for formatting output, but are not useful for maintaining reliable data.

private Event function saveEvent(boolean createNew)
var event = 0;
transaction {
{
if (createNew)
{
event = EntityNew('Event');
EntitySave(event);
} else {
event = EntityLoad('Event', variables.sessionManagement.getDirectorsWorkingEventID(), true);
}
event.setEventName( "Some Event Name" );
event.setUpdatedDate(getHttpTimeString());
event.setSubmittedByUser(EntityLoad('User', variables.sessionManagement.getUsersID(), true));
}
if(createNew) {
variables.sessionManagement.setDirectorsWorkingEventID(event.getEventID());
}
return event;
}

Justice

45 Comments

@Justice,

I'm looking at the Stub Generator from Adobe, and in their Beta stub generator, the 'event' parameter it generates (specific to my project) is type-casted to an "Event" (schedules, owner, ID, etc).

Then, Adobe's stub generator is calling EntitySave(event) for both Update and Create Operations. I'm wondering if I can further reduce the footprint if, by passing in the event as a Typed Object, Hibernate's going to decide if it wants to load the Entity based on an existing PK, or if she'll create a new Entity altogether (because that's what appears to be happening).

You're right about the ORMFlush(). I was having an issue with the sequencing of the updates (the ID of the Event in the session was not recognized by other requests). Sticking the operations in a transaction() tag fixed the issue.

Also, is there a way to annotate the UpdatedDate() property of an object with GetHttpTimeString()? I was hoping to take advantage of MySQL's generate_timestamp_on_update_function() , but I can't seem to annotate the property to make that happen. I'm wondering if the TimeStamp field, because of the Optimistic Locking, can't be auto-generated at the database-level (or this could be a bug with CF9, or maybe I missed the right way to annotate the property).

Lastly, when you're setting the event to 0, should this be the event's primary key that is set to zero? I thought Hibernate decided what to save vs. update based on PKs being null, but this is a gray area for me.

I'm still blown away by Hiberate's ORM. I was supposed to go out with friends tonight (but I'm just sitting here at the computer, listening to Moby and refactoring :D ).

Thanks,
Buhler

113 Comments

@David,

The argument 'event' is not used anywhere in the current function, regardless of its declared type.

ColdFusion does not do anything with the declared type of an argument name, but only with the actual type of the value passed in. So the declared type of the argument does not matter in this case.

You may want to experiment with EntityLoad, update the resultant entity, and then not use EntitySave.

I'm not sure what you meant in your second paragraph.

I do not know if the auto-update-timestamp is a standard feature. If it isn't, you can certainly implement that feature and plug it into Hibernate. You may need some fluency in Java, or you may need to override the event functions on your domain model persistent components. My main point in the section of my previous post was simply to use GetHttpTimeString instead of Now because the former is universal and not local time.

I set the event variable to 0 initially because I wanted to use the 'var' keyword to introduce the name (previously, it was introduced as an argument). For no good reason, CF8 habits stuck with me, so I moved the 'var' to the top and assigned a default value to it. Obviously you no longer have to do that in CF9.

15,640 Comments

Hey all, sorry for getting out of the conversation - had a HUGE deadline at work which has consumed my last few weeks (and weekends!). Launched successfully Monday night, so now catching up on all the missed comments.

@Justice,

I believe CF9's ORM is already integrated with the LOG4J stuff. As far as the ORM events, what you say makes a ton of sense. I especially like the concept of updating the audit tables for the entity. Very cool.

@David,

Good point re: applications settings. I believe you can also disassociate a given entity from the current session, using evict:

http://www.aliaspooryorik.com/blog/index.cfm/e/posts.details/post/cf9-orm-clearing-the-session-cache-ii-228

@Martin,

Thanks to the persistence layer, they are totally the same as today; but I don't judge - she's still a hottie.

15,640 Comments

@David,

Sounds like you are doing some really cool stuff with the ORM. That is very inspiring. I want to go and create some sort of test app now that is written in full ORM style code.

1 Comments

thanks for this interesting article and the discussion.

i'm in need of a soft-delete functionality, is there a possibility to config hibernate to do so or do i have to write some functions in each model?

thanks, thomas

113 Comments

@wiesion,

Hibernate may support soft-deletes. I don't know how well CF ORM will support them.

In any event, soft-deletes are a bad idea. If they are purely for auditing, you will want a better solution that includes hard-deleting. Or, you will want a better, append-only (no updating or deleting, just creating) object model.

Justice

15,640 Comments

@Justice,

From what I have been told, ColdFusion ORM should support all aspects of standard Hibernate. The only thing that you might have to do is actually write some of the config files yourself (if the CF facade doesn't support it directly).

Soft deletes are an interesting thing. I built a very large app (all procedural - yikes!) that makes huge use of soft deletes. The reason for that is that after things are deleted, they still might be referenced in the system, just no longer linked.

For example, say you have a course that is taught by a teacher. If that teacher is deleted from the system, then the course is still listed as being taught by that teacher; the difference, however is that that teacher is no longer linked to their contact details.

We accomplish this by having lots of "is_deleted" soft delete flags.

There is no doubt that this DOES make are lives more complicated most of the time; but, I am not sure that I know a better way to accomplish such a thing?? If you have any suggestions, I would be very interested. Obviously, this app can't be changed, but going forward, with other apps, I'd love to make things a bit easier.

68 Comments

For my own future reference ...

I was mucking about with this today and was trying to make it so that my createdDate and updatedDate properties were read-only (no setters). I am dumb, so I assumed it would be trickier than the obvious solution. It's not.

public void function preInsert() output="false" {
Variables.createdDate = now();
preUpdate();
} // preInsert

public void function preUpdate(struct oldData) output="false" {
Variables.updatedDate = now();
} // preUpdate

By using the Variables scope you set the properties internally, as you have no public setter. Very much a "duh".

(Though, I must rant -- I'm not keen on having to make those functions public. Having those functions exposed is just inviting people to call them.)

45 Comments

John,

Can you show how your ORM Object properties reference the preInsert() and preUpdate() methods?

This is a problem I was thinking about this morning, without a solution, so your post couldn't have come at a better time.

Best,
David

2 Comments

Can someone explain to me why I have to return false from the preDelete() method? I found that it solves an issue I had:

I wanted to clear certain properties that didn't allow for cascade="delete" and delete them in the process by modifying the setter. But then I looked into the database and they were still there! By adding return false; to the preDelete I managed to get it working.

3 Comments

Anyone know how to apply a filter at preLoad() ?

For example...

remote void function preLoad(e) {
	var HQL = "where accountID = 1";
	this.createFilter(e.HQL).list();
	return e;
}

This would ensure that every time entityLoad() was called, the only row that were returned would be the rows that have accountID of 1.

Does that make sense? What is the best way to accomplish this?

21 Comments

I'm currently using these events to automatically create revision history of several different entity types and was having a lot of trouble with preDelete. It seems as though preDelete won't fire at all unless I use ORMFlush manually, letting it flush at the end of the request isn't enough. Is this the same behavior you were getting? I'm not logging the history in an array like you are, i'm adding it to a database table. If i don't use ORMFlush, it never reaches the table. The entity gets deleted, but the action never gets logged.

I doubt it matters, but i'm using Railo 4.1.2, just wanted to see if you were seeing the same issue in coldfusion, or if this was something specific to Railo.

21 Comments

I didn't, but adding it didn't change anything. (tried true as well.) I still had to use ORMFlush for it to be called.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel