Confused about a many to one relationship

39 views
Skip to first unread message

Dan Vega

unread,
Mar 8, 2010, 2:49:01 PM3/8/10
to cf-orm-dev
Just when i thought I was making some progress I ran over a speed bump
today. This is really the first large application I have written using
hibernate that has a ton of relationships. In this example I have a
broadcast message (Message) entity. We are using this to send
broadcast emails to a particular role or if the role is dealer we can
send it to multiple dealers.

Message.cfc
component persistent="true" {

property name="messageId" ormtype="integer" fieldtype="id"
generator="increment";
property name="name" type="string";
property name="subject" type="string";
property name="body" ormtype="clob";
property name="isSent" type="boolean" ormtype="integer";
property name="isDeleted" type="boolean" ormtype="integer";
property name="dateCreated" ormtype="timestamp";
property name="lastUpdated" ormtype="timestamp";

property name="role" fieldtype="many-to-one" cfc="Role"
fkcolumn="roleid";
property name="dealer" fieldtype="many-to-one" cfc="Dealer"
fkcolumn="dealerid";

}

Dealer.cfc
component persistent="true" {

property name="dealerid" type="numeric" fieldtype="id"
generator="increment";
property name="name" type="string";
property name="account" type="numeric";
property name="email" type="string";
property name="isDeleted" type="boolean";
property name="dateCreated" ormtype="timestamp";
property name="lastUpdated" ormtype="timestamp";

property name="messages" fieldtype="one-to-many" cfc="Message"
fkcolumn="dealerid" singularname="message";

}

So its my understanding that I need to define what side of the
relationship will be in control. Since we will always be adding a
dealer to message the message should be in control of the
relationship.

property name="dealer" fieldtype="many-to-one" cfc="Dealer"
fkcolumn="dealerid" cascade="all" inverse="true";

I am having an issue with the save method. Lets say we chose a dealer
for this message to go to, but before we sent it we needed to change
it to a different dealer. How do you remove the old one, I thought we
could just do this.

var _message = variables.messageService.load(rc.id);
var _dealer = _message.getDealer();
_message.removeDealer(_dealer);

But the remove dealer method was not found? What am I doing wrong
here? I think I misunderstand the use of inverse even after reading a
number of articles.

Dorioo

unread,
Mar 8, 2010, 2:53:41 PM3/8/10
to cf-or...@googlegroups.com
Remove: "This method is generated for one-to-many and many-to-many
relationships."

So, doesn't work with "many-to-one" side like you're doing?

- Gabriel

> --
> You received this message because you are subscribed to the Google Groups "cf-orm-dev" group.
> To post to this group, send email to cf-or...@googlegroups.com.
> To unsubscribe from this group, send email to cf-orm-dev+...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/cf-orm-dev?hl=en.
>
>

Dorioo

unread,
Mar 8, 2010, 2:59:41 PM3/8/10
to cf-or...@googlegroups.com
So maybe your relationship is many-to-many? Where a message can have
many dealers (since you're trying to remove one and place another)
but, in practice, only one dealer would be set on the message.

Just brainstorming so could be off base.

- Gabriel

Dan Vega

unread,
Mar 8, 2010, 3:09:11 PM3/8/10
to cf-orm-dev
Ooops, sorry for the misprint. The message can only have 1 dealer
assigned to it but multiple messages can be assigned to a dealer.

On Mar 8, 2:59 pm, Dorioo <dor...@gmail.com> wrote:
> So maybe your relationship is many-to-many? Where a message can have
> many dealers (since you're trying to remove one and place another)
> but, in practice, only one dealer would be set on the message.
>
> Just brainstorming so could be off base.
>
> - Gabriel
>
>
>
> On Mon, Mar 8, 2010 at 2:53 PM, Dorioo <dor...@gmail.com> wrote:
> > Remove: "This method is generated for one-to-many and many-to-many
> > relationships."
>
> > So, doesn't work with "many-to-one" side like you're doing?
>
> > - Gabriel
>

Bob Silverberg

unread,
Mar 8, 2010, 3:09:51 PM3/8/10
to cf-or...@googlegroups.com
Gabriel makes a couple of good points. First off, you cannot use remove with a many-to-one.  I _think_ you can set the dealer to NULL (using javaCast()) but I cannot remember if I've tested that.  What might make more sense would be to work from the dealer side and do dealer.removeMessage(message) as that method _will_ be there.

The second point is to make sure you've modelled this correctly. Can each message go to one and only one dealer?  If so then yes, it's a many-to-one, but if a message can go to more than one dealer then you do have a many-to-many on your hands.

Bob
--
Bob Silverberg
www.silverwareconsulting.com

Hands-on ColdFusion ORM Training @ cf.Objective() 2010
www.ColdFusionOrmTraining.com


Dorioo

unread,
Mar 8, 2010, 3:13:28 PM3/8/10
to cf-or...@googlegroups.com
Then you may have to take it from one dealer and give it to another.
Don't think the message can kick its dealer out.

- Gabriel

Bob Silverberg

unread,
Mar 8, 2010, 3:19:00 PM3/8/10
to cf-or...@googlegroups.com
Actually, I just thought of something far simpler.  If you are assigning a message to a different dealer, just use setDealer(newDealer). No need to remove it from the existing dealer, not from the perspective of the message anyway.  It's good practice to remove it from the message though.

Dorioo

unread,
Mar 8, 2010, 3:22:33 PM3/8/10
to cf-or...@googlegroups.com
That is better. Do you know offhand if hibernate updates the L2 cache
when you do it that way? It's really smart about updating the L2 cache
so I'd assume yes.

- Gabriel

Barney Boisvert

unread,
Mar 8, 2010, 3:23:03 PM3/8/10
to cf-or...@googlegroups.com
msg.setDealer(javaCast('null', 0)) is the proper way to disassociate a
Message from it's Dealer.

The removeDealer-style methods are only available on collections, and
Message doesn't have a collection of Dealers, it has zero or one. So
you WOULD have Dealer.removeMessage available to you, since Dealer has
a collection of Messages.

It's also important to remember that you must deal with both ends of
the relationship. With a database, the foreign key is the single
bridge between the entities, but in the JVM there are a pair of
references (Message.dealer, and an item in Dealer.messages). BOTH
need to be severed if you truly want it to be severed. That has
nothing to do with Hibernate (or even Java), just standard reference
semantics. The gotcha is that depending on how you've mapped your
relationships to the DB, Hibernate may end up doing the right thing
even if you only sever one of the references when you next load the
entities. But for the remainder of the current session, your entities
are going to be inconsistent if you don't take care to sever both
ends.

Typically the way you manage this is via wrapper methods, so you'd
never manipulate the properties directly, but instead have a wrapper
that you use which ensures that both sides are severed:

function clearDealer() {
getDealer().removeMessage(this);
setDealer(javaCast('null', 0));
}

This is REALLY important, and typically forgotten, because most of the
time it happens to work correctly by coincidence. Not keeping it in
mind, however, can create some horribly subtle bugs.

cheers,
barneyb

--
Barney Boisvert
bboi...@gmail.com
http://www.barneyb.com/

Dan Vega

unread,
Mar 8, 2010, 3:26:31 PM3/8/10
to cf-orm-dev
@bob

You are correct, you can send a message to only one dealer at a time.
So if I am working on a message I should take this approach?

var _message = variables.messageService.load(rc.id);
var _dealer = _message.getDealer();

_dealer.removeMessage( _message );

I am not even sure that will work I just threw it together but is that
the idea? Seems kind of backwards to me. I like the idea of setting
the dealer to null, i might try that route.

Barney Boisvert

unread,
Mar 8, 2010, 3:28:18 PM3/8/10
to cf-or...@googlegroups.com
This is exactly what I was talking about. If you do this, your
in-memory model is broken, because the message thinks it is owned by
Dealer X, but the Dealer X doesn't think it owns the message, and
Dealer Y _does_ think it owns the message. This broken state will
persist until the entities are garbage collected and reloaded or
explicitly resynced with the database.

cheers,
barneyb

--

Dan Vega

unread,
Mar 8, 2010, 3:34:43 PM3/8/10
to cf-orm-dev
Awesome explanation Barney! I can see how this can confuse people
because as you stated you may not even realize its broken. Thanks
everyone, makes a lot more sense to me now!

Dennis Clark

unread,
Mar 10, 2010, 2:10:33 PM3/10/10
to cf-or...@googlegroups.com
I also want to thank Barney for his explanation.

I'm still using Transfer for ORM stuff, and Transfer has no support at all for bidirectional relationships. All the examples I've seen so far on managing bidirectional relationships in CF ORM show manipulating relationships from one end only, implying that the inverse relationship will be fixed automatically. I've been suspicious of this idea as manging cyclic relationships in any setting tends to be quite tricky.

From what I've seen about Hibernate sessions in CF, I'm not really surprised that people don't notice that the technique of manipulating bidirectional relationships from only one end breaks the in-memory model. The issue would only be exposed if your code tries to follow the inverse relationship between the time you change the relationship in an ORM session and the end of the ORM session. Loading the same entity in a new ORM session should have the relationship correct in both directions.

I'm not a Hibernate expert so if I've made any invalid assumptions here please let me know.

Cheers,

-- Dennis



--

Bob Silverberg

unread,
Mar 10, 2010, 2:49:03 PM3/10/10
to cf-or...@googlegroups.com
I agree, Barney's explanation was excellent. And Dennis, you're right that in most situations developers won't encounter the problem as it "fixes itself" once the data is persisted to the database.

The one thing I'm still uncertain about, mainly because I haven't taken the time to experiment with it yet, is how this affects the L2 cache.  I would think that it's possible that if you have the second level cache turned on and are caching an object that is in a bi-directional relationship, and only one side of the relationship is set, then the cache could end up out of synch with the database.  In that case asking Hibernate for an object, which it finds in the cache, might result in inaccurate information, if both sides of the relationship haven't been set properly.

Does anyone know off the top of their head whether this is in fact a risk of setting just one side of the relationship?

Cheers,
Bob
Reply all
Reply to author
Forward
0 new messages