Long title, I know. Short post.
I thought that you needed cascade=”all-delete-orphan” any time you wanted to ensure an element in a one-to-many property was removed from the join table. For example, blog.removeComment( comment ) should remove the row from BlogComments. Turns out, it’s not always required.
I still think you should use it all the time, however.
This is one of those “I learned something new but it doesn’t change anything” type of posts.
all-delete-orphan and LinkTable
When removing elements from a one-to-many collection, you typically specify cascade=”all-delete-orphan” on that property. Then, when you remove the item and re-save the entity, it’ll remove that item from the join table. Here’s an example, using a typical “linktable”, which you use when you have a simple 2-key join table. Given an entity named “Event.cfc”, with a property named “attendees” which maps to an Attendee.cfc:
property name="attendees" fieldtype="one-to-many" cfc="Attendee" linktable="J_Events_Attendees" fkcolumn="EventID" inversejoincolumn="AttendeeID" singularname="attendee" cascade="all-delete-orphan";
With a relationship like this, if you call event.removeAttendee( someAttendee ), then ORM will remove the link from that linktable for you, thanks to the cascade.
Here’s what I learned… cascade=”all-delete-orphan” doesn’t matter in the case of a simple linktable. If you set it to cascade=”all”, it’ll do exactly what you want.
all-delete-orphan and intermediary entity
BUT: it absolutely matters when you have a property that is not a simple link table but is instead an entity in itself, representing a link in addition to other data.
Consider this… an Event entity mapping to an “EventComment” entity. EventComment comprises more than a simple “eventid,commentid” relationship; instead, it has a comment string, and also an Attendee and a date and who knows what else. These “joins with additional data” are more common than simple joins in my experience, if for no other reason than everyone seems to *need* CreatedBy, CreatedOn, ModifiedBy, and ModifiedOn fields even though you never use them.
That mapping looks like:
property name="eventComments" fieldtype="one-to-many" cfc="EventComment" fkcolumn="EventID" singularname="eventComment" inverse="true" cascade="all-delete-orphan";
Now check this out. If you call event.removeEventComment( someComment ), it’ll remove it from EventComment’s table as expected. But unlike the earlier example, if you change it to cascade=”all”, it will not behave the same way. In fact, the behavior I observed, using MySQL, is this:
- The element is removed from Event’s array of EventComments
- refetching the object in the same request (i.e. entityLoadByPK( “Event”, event.getId() ); returns an object without that EventComment… even if you have FlushAtRequestEnd=false, and AutomanageSession=false, and perform the entitySave() in a transaction (suck it, Hibernate Session Object)
- The EventComment is not in fact deleted. If you refetch in another request or check it out in the query browser, it’s still there.
Bottom line: no deletion, though it *looks* like it’s deleted. These ORM headfakes’ll drive a dude to drink (More).
This is perhaps completely obvious that it should behave this way. I so rarely work with 2-column join tables, and while futzing about tonight with some examples I stumbled onto this difference in behavior accidentally.
You don’t need all-delete-orphan when working with simply a linktable. However, I strongly suggest using it, since it doesn’t cause damage and more clearly expresses intent. And in the event that your simple join turns into a multi-column affair requiring an intermediary entity, you save yourself one fewer “now why in the hell isn’t this deleting anymore?” moment.