ORM One-to-One Lazy Loading

Long story short: one-to-one lazy loading does not appear to be working as expected.

Preface:
My understanding of how lazy loading / proxy loading should work for one-to-one relationships is based on the adobe documention. I’ve also looked through this Lucee documentation, but it is pretty sparse.

Example
I have entity A and entity B. These two entities have a one-to-one relationship. I define A’s relation to B like this:

property name="B" fieldType="one-to-one" cfc="B" fkColumn="foriegnKeyColumn" lazy="true";

The expected behavior is that I can now query data for entity A without pulling data for entity B. The observed behavior is that the corresponding data for entity B is getting queried every single time even if I don’t call the getter for B (this is true for fetch="join" and fetch="select"). I’ve tried querying the data using the built-in orm functions (i.e. EntityLoad) and by using the hibernate criteria builder (via the ColdBox cborm module’s interface).

This is resulting in a whole lot of extra data being queried in scenarios where we don’t want the join data (and a TOOOON of queries where the relationship is defined with fetch="select").

Questions
Has anyone else experienced this? Am I doing something wrong? Is my understanding wrong? Are there any work arounds?

Lazy loading is the default setting for relationships. I’ve just set up a basic 1-1 relationship with sql logging and when simply loading entity A without calling its property B, only the A table is queried as expected.

Can you provide more detail? Which versions of Lucee and the Hibernate extension? What are your ORM settings? What’s the exact code being used to call the entity?

Can you produce a really simple test case?

I also has seen this behaviour. Hibernate always do the join of the tables.

I have setup a simple cfml one-to-one app in: GitHub - davidsf/cfml-one2one-lazy: Demo cfml app with a one-to-one relation with lazy option

It’s just a two cfc with one-to-one relation with lazy=true and in the log Hibernate always do the join of the two tables.

Using Lucee 4.5.5.006

Hi David

Actually the SQL join only seems to happen with the entity using mappedBy to define the relationship (as opposed to the other side which has the fkcolumn).

If you load your book_detail entity instead of book, you should see there is no join (that’s what I’m seeing anyway from your test case on Lucee 5.2.5).

Similarly if I load the other entity (with the mappedBy property) in my previously mentioned test case I am seeing the join as well.

So, it sounds a bug to me: the lazy option do nothing in the “parent” model (the model that doesn’t have the id of the “child” model). Always do the join (or two selects in case of fetch=“select” option).

Thanks @David_Sedeno for putting up that example. I got pretty swamped and wasn’t able to get around to it.
I agree that this sounds like a bug, but I will play around with the difference between mapped_by and fkcolumn to see if I can get some performances increases.

As a workaround you might consider changing the “parent” relationship (the one using mappedBy) to one-to-many just to avoid the join.

Yes, that works, but be aware that instead of an object you will have an array of one object.

Yes, that’s right. If you didn’t want to change the code that calls the entity, you could set up a custom getter which returns the object as if it were a one-to-one. In David’s test case book example this might look like the following:

function getbook_detail(){
  return this.hasbook_detail()? variables.book_detail[ 1 ]: NullValue();
}

The setter, which will expect an array, could be overridden with something like:

function setbook_detail( required object ){
  variables.book_detail = [ object ];
}

Customising (ok, hacking!) your Entity rather than changing your calling code will make it easier to switch back to the proper one-to-one relationship should there be a fix (although I wouldn’t hold your breath as I think this may be an issue with Hibernate rather than Lucee’s implementation of it).

FYI, I have opened a ticket:

https://luceeserver.atlassian.net/browse/LDEV-1591

As a workaround you might consider changing the “parent” relationship (the one using mappedBy ) to one-to-many just to avoid the join.

Just came across this again. I think a better workaround is to set independent one-to-one relations on each side. Downside is you need to be sure to call the setRelation() on each side when creating the entities (but I would be doing that anyway).