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(a)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(a)redhat.com>
Cc: Guillaume Smet <gsmet(a)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(a)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(a)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(a)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."
>
>
>