[hibernate-dev] Fwd: Fwd: @OneToOne with @PrimaryKeyJoinColumn(s) vs @MapsId without value element

Gail Badner gbadner at redhat.com
Mon Sep 17 14:56:16 EDT 2018


Forwarding Emmanuel's responses, which reduces the scope of what I thought
was buggy behavior.

There are still some weird cases though. I'll hone in on those in a
separate thread.

---------- Forwarded message ----------
From: Emmanuel Bernard <ebernard at redhat.com>
Date: Wed, Sep 12, 2018 at 5:54 AM
Subject: Re: Fwd: @OneToOne with @PrimaryKeyJoinColumn(s) vs @MapsId
without value element
To: Gail Badner <gbadner at redhat.com>
Cc: Guillaume Smet <gsmet at redhat.com>


On Wed 18-09-12  0:29, Gail Badner wrote:

> Hibernate treats @OneToOne @PKJC differently from @OneToOne MapsId (without
> a value element). I believe some of the differences are expected, but I
> suspect some of the differences are bugs. In some cases, I'm not sure which
> is treated correctly.
>

All my comments are caveat by me not having looked at this for years.

Note that these are different features.
@PKJC says that we don't create a new FK column, we reuse the id one.
@MapsId says, copy the value of the id from that property and pretend
it's an id generator.



> I've also found that the following mapping has some problems:
>
> @OneToOne
> @MapsId
> @JoinColumn(name = "FK")
> private Employee employee;
>
> Sometimes Hibernate treats that mapping like @OneToOne @PKJC; other cases
> it treats that mapping like @OneToOne @MapsId.
>
> I'm in the process of documenting the differences in a Google document so
> it can all be sorted out.
>
> As a start, it would help a lot if you could address the questions in this
> email.
>
> I've gotten very familiar with the related code, so once I have the
> answers, I'll know how to fix them.
>
> Thanks,
> Gail
>
> ---------- Forwarded message ----------
> From: Gail Badner <gbadner at redhat.com>
> Date: Sat, Sep 1, 2018 at 12:21 AM
> Subject: Re: @OneToOne with @PrimaryKeyJoinColumn(s) vs @MapsId without
> value element
> To: hibernate-dev <hibernate-dev at lists.jboss.org>
>
>
> FYI, I am taking PTO Tuesday, 9/4. I hope to be able to move forward on
> this when I return on 9/5.
>
> I see some differences. Some may be expected, but I think there are some
> bugs.
>
> For example, suppose we have the following entities:
>
> @Entity
> public class Parent {
> @Id
> private Long id;
> }
>
> @Entity
> public class ChildPKJC {
> @Id
> private Long id;
>
> @OneToOne  // note that cascade-persist is not enabled
> @PrimaryKeyJoinColumn
> private Parent parent;
> }
>
> public class ChildMapsId {
> @Id
> private Long id;
>
> @OneToOne  // note that cascade-persist is not enabled
> @MapsId
> private Parent parent;
> }
>
> ------------------------------------------------------------
> ------------------------------------------------------------
> -------------------
>
> When persisting ChildPKJC:
>
> 1) the application must initialize ChildPKJC#id before persisting the
> entity [1]; otherwise,  the following exception is thrown:
> javax.persistence.PersistenceException:
> org.hibernate.id.IdentifierGenerationException:
> ids for this class must be manually assigned before calling save():
>

Sounds fine.


> 2) if ChildPKJC#parent is new with an assigned ID, and ChildPKJC#id is
> assigned parent's ID, the ChildPKJC Entity is persisted with the parent's
> ID, but parent is not persisted.
>

Sounds fine.


> When persisting ChildMapsId:
>
> 1) Hibernate automatically initializes ChildMapsId#id to parent.id [2]
>

Yes that's the expected behavior.


> 2) if ChildMapsId#parent is new, parent is automatically cascade-persisted
> (even though CascadeStyle.PERSIST is not mapped), then the ChildMapsId
> entity
> is persisted.
>

So that is not expressed in the spec but it might be that disabling that
is making things too complex for the Hibernate ORM engine. I would not
sweat too much on it.


> Are these expected difference? (My guess is yes)
>
> ------------------------------------------------------------
> ------------------------------------------------------------
> -------------------
>
> Foreign key generation:
>
> If ChildPKJC#parent is optional there is no foreign key generated from
> ChildPKJC
> referencing Parent.
>

Sounds fine.

[3] If ChildPKJC#parent is not optional, a foreign key
> is generated
>

Sounds fine.


> For ChildMapsId, a foreign key is generated from ChildPKJC referencing
> Parent, even if ChildMapsId#parent is optional.
>

ChildMapsId cannot be optional as it is the generator of the id. So we
ignore that the user has marked it optional.


> Is this a bug? My guess is that it is.
>

Not to me, see above.


> Adding the following mapping to ChildMapsId#parent works to disable foreign
> key generation:
> @JoinColumn(foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
> (can be used as a workaround)
>
> ------------------------------------------------------------
> ------------------------------------------------------------
> -------------------
>
> Loading an existing ChildPKJC/ChildMapsId with an optional Parent
> association by ID, when there is no Parent entity with the same ID (IIUC,
> this is the only way that ChildPKJC#parent or ChildMapsId#parent can be
> optional [3]):
>
> For ChildPKJC, the loaded ChildPKJC entity will have a null parent. There
> is no need to add @NotFound(IGNORE) to ChildPKJC#parent.
>

That's a bit weird but if the parent is not optional, why isn't there a
parent in the database with the proper id. Feels like a data incoherence
problem. If people have that,t hey must make it optional.


> If ChildPKJC#parent is optional, it is always eagerly loaded.
>
> This makes sense, since we cannot create a proxy if there is the
> possibility of a null Parent entity.
>
> For ChildMapsId, the loaded value will be null because
> ObjectNotFoundException
> will be thrown when Hibernate tries to load the Parent entity.
>

Right, this is a data incoherence, the parent must not be null.

Adding @NotFound(IGNORE) to ChildMapsId#parent will result in ChildMapsId
> entity being loaded with a null parent association.
>
> Is this expected? If so, then ChildMapsId#parent cannot be optional by
> default (without @NotFound(IGNORE).
>

Again I don't think parent being null is valid for @MapsId



> I think it would make more sense if the ChildMapsId entity is loaded with a
> null parent association, consistent with what happens for ChildPKJC. If we
> go that route, then ChildMapsId#parent will always have to be loaded
> eagerly.
>
> ------------------------------------------------------------
> ------------------------------------------------------------
> -------------------
>
> Please let me know your thoughts on this.
>
> [1] this requirement is documented in Example 178. Derived identifier
> @PrimaryKeyJoinColumn with a note that says: "Unlike @MapsId, the
> application developer is responsible for ensuring that the identifier and
> the many-to-one (or one-to-one) association are in sync as you can see in
> the PersonDetails#setPerson method."
>
> [2] Section 2.4.1 Primary Keys Corresponding to Derived Identities of the
> spec has this footnote:
> [12] If the application does not set the primary key attribute
> corresponding to the relationship, the value of that attribute may not be
> available until after the entity has been flushed to the database.
>
> [3] Section 11.1.44 PrimaryKeyJoinColumn Annotation has a footnote:
> [121]It is not expected that a database foreign key be defined for the
> OneToOne mapping, as the OneToOne relationship may be defined as
> “optional=true”.
>
>
> On Fri, Aug 31, 2018 at 1:29 PM, Gail Badner <gbadner at redhat.com> wrote:
>
> The fix for HHH-12436 involves correcting the foreign key direction for
>> "real" one-to-one associations. I've been looking into the ramifications
>> of
>> this change because I'm concerned that applications can rely on the old
>> (incorrect) foreign key direction.
>>
>> In the process I've found that Hibernate treats:
>>
>> @OneToOne
>> @PrimaryKeyJoinColumn
>> private Employee employee;
>>
>> differently from:
>>
>> @OneToOne
>> @MapsId
>> private Employee employee;
>>
>> I believe they should be treated consistently. You can see my reasoning
>> below. [1]
>>
>> Before going into details about how they are treated differently, I'd like
>> to  get confirmation, in case I am missing some subtlety.
>>
>> Could someone please confirm this?
>>
>> Regards,
>> Gail
>>
>> ------------------------------------------------------------
>> ---------------------------------
>> [1]
>>
>> In 2.4.1.3 Examples of Derived Identities, Example 4(b) uses MapsId
>> without the value element as follows:
>>
>> @MapsId
>> @JoinColumn(name="FK")
>> @OneToOne Person patient;
>>
>> This example has the following footnote:
>> "[15] Note that the use of PrimaryKeyJoinColumn instead of MapsId would
>> result in the same mapping in this example. Use of MapsId
>> is preferred for the mapping of derived identities."
>>
>> The description has a footnote that says that using PrimaryKeyJoinColumn
>> instead of MapsId would result in the same mapping.
>>
>> In 11.1.45 PrimaryKeyJoinColumns Annotation, Example 2 uses
>> @PrimaryKeyJoinColumns as follows:
>>
>> @OneToOne
>> @PrimaryKeyJoinColumns({
>>         @PrimaryKeyJoinColumn(name="ID",
>>                               referencedColumnName="EMP_ID"),
>>         @PrimaryKeyJoinColumn(name="NAME",
>>                               referencedColumnName="EMP_NAME")})
>> EmployeeInfo info;
>>
>> This example has the following footnote:
>> "[123]Note that the derived identity mechanisms decribed in section
>> 2.4.1.1 is now preferred to the use of PrimaryKeyJoinColumn for
>> this case."
>>
>>
>>


More information about the hibernate-dev mailing list