First of two OneToOne Mappings fails

I’m migrating a project from Hibernate 5.3.13 to 6.1.4. I’ve got an app that loads a set of entities, which I’ll call EA, where there are two OneToOne mappings to entities I’ll call EB1 and EB2:

EA:

    @OneToOne(targetEntity = EB1.class, mappedBy = "ea", cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
    @Cascade(org.hibernate.annotations.CascadeType.ALL)
    private EB1 eb1;

    @OneToOne(targetEntity = EB2.class, mappedBy = "ea", cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
    @Cascade(org.hibernate.annotations.CascadeType.ALL)
    private EB2 eb2;

EB1:

    @OneToOne(targetEntity = EA.class, fetch = FetchType.EAGER)
    @JoinColumn(name = "EA_FK", nullable = false)
    private EA ea;

And EB2 is similarily mapped to EA. All entities have additional mappings not shown here for brevity.

(If it matters, all these extend the same parent class, which I’ll call P, that is annotated with @MappedSuperclass. In fact, there’s a couple levels of inheritance using @MappedSuperclass, which provide standard PK and a name attributes)

When I do an HQL query to load the EAs: select distinct e from EA as e
It correctly queries for all EAs, but also for each EB1 and EB2 for each of the EA. (So lazy isn’t working, but I gather there’s a quirk to that with OnetoOne mapping, and that isn’t the issue I’m concerned about, at the moment.)

The problem I’m having is that while I can see from the logs that it is executing SQL for the data for EB1, and it is extracting that data (so it is getting results), it is setting each EA.eb1 value to null. (I changed the Access to property: @Access(value= AccessType.PROPERTY) so that I could log and debug when they are set.) The OneToOne mapping on eb2 works properly.

Some additional info:

  • If I remove the second mapping (for EB2), the relationship to EB1 works properly.
  • If I instead rename eb1 to zzzEb1 (presumably, it shows up second somewhere along the way), then that works correctly, but eb2 is set to null, instead.

This used to work in 5.3.x. Has something changed in the way this should be mapped?

Thanks

(So lazy isn’t working, but I gather there’s a quirk to that with OnetoOne mapping, and that isn’t the issue I’m concerned about, at the moment.)

That’s how one-to-one mappings that use mappedBy work, they must join/select the other end to know if the field should be initialized with null or something else. If you use byte code enhancement, this can also be made lazy.

This used to work in 5.3.x. Has something changed in the way this should be mapped?

I guess you might simply be running into a bug. Would be great if you could provide us a reproducer for this by using the test case template and then create a bug ticket in the issue tracker(https://hibernate.atlassian.net)

I tried to duplicate the issue pretty much as I described, but not using the template you referenced (in order to quickly fit it into our current project). In the simplest case, using H2 (we actually use oracle 19c), I wasn’t able to duplicate it. I’m going to create a separate project and see if I can duplicate it with more complete implementations, using either H2 and/or oracle.

In the short term, this appears to leave us dead in the water. Short of reworking the db schema (e.g.: using a shared pk), is there some way to work around this? I could eliminate the mapping on the parent (EA) side, but we’ve got a lot of code relying on that mapping. (And, if this is a bug, there’s a good chance we will have a similar issue for other relationship mappings).

Also, not wanting to go off on a tangent (but I will, anyway):
Regarding the LAZY and OneToOne mapping: Yes, we should probably be looking at byte code enhancement, but I’d prefer to address that after we’ve migrated. However, I’d like to be sure I understand the issue, so correct me if I’m wrong: Basically, without bytcode enhancement, LAZY uses proxies only. We can’t use proxies for oneToX mappings because you can’t proxy a null(, whereas you can proxy an empty collection). So, would it be (theoretically) possible to use something like org.apache.commons.lang3.mutable.Mutable? Granted, it would impose changes on the model, but would at least allow for proxies in this case.

I first need to understand the issue by debugging a test case that you need to provide. Only then I can try to help you with a workaround.

So, would it be (theoretically) possible to use something like org.apache.commons.lang3.mutable.Mutable?

Theoretically, yes, but that also requires changes in the Hibernate internals which are non-trivial.

re:

I first need to understand the issue by debugging a test case that you need to provide. Only then I can try to help you with a workaround.

OK, but to be clear: there’s no alternatives I might try that would not require a schema change, correct?

re:

So, would it be (theoretically) possible to use something like org.apache.commons.lang3.mutable.Mutable?

Theoretically, yes, but that also requires changes in the Hibernate internals which are non-trivial.

Understood. I wasn’t expecting that’s you’d consider making that change, just trying to better understand the issue. Bytecode enhancement is probably the better route. Thanks.

OK, but to be clear: there’s no alternatives I might try that would not require a schema change, correct?

I can’t tell you anything unless I see a reproducer, sorry. I don’t understand what goes wrong exactly, so at this moment, you have no alternatives.

FYI, the test template states:

// Entities are auto-discovered, so just add them anywhere on class-path
// Add your tests, using standard JUnit.

However I found that I need to explicitly add them to the persistence.xml:
com.hibertest.ZBook
com.hibertest.ZPublisher

I attempted to create a test case and was at first unable to duplicate the issue using H2. I started creating a persistence.xml that would work with our oracle DB, when I realized that the max_fetch_depth in the test case is 5, whereas our app is set to 1. When I set it to 1 in the test case, I get similar results, in that there are no EB1 on E1 when querying for E1. However, there are also no EB2, which isn’t what I saw originally, but perhaps I originally misinterpreted some logging.

I tried setting the max_fetch_depth in our app, but that broke other queries (which occur before getting to this one) with:

org.hibernate.AssertionFailure: force initializing collection loading

My test case at the moment is basically a copy over of a bunch of classes. I’m going to pare that down to the essentials and rename a few classes, and then I’ll create a bug ticket.
EDIT: [HHH-15628] - Hibernate JIRA