[hibernate-dev] @OneToOne with @PrimaryKeyJoinColumn(s) vs @MapsId without value element
Gail Badner
gbadner at redhat.com
Sat Sep 1 03:33:05 EDT 2018
FWIW, I've already spent a lot of time looking into the possible bugs I've
described. I have a good idea about how to fix each one, so there's no need
to research these. At this point, I'm just trying to get confirmation of
whether they really are bugs.
On Sat, Sep 1, 2018 at 12:21 AM, Gail Badner <gbadner at redhat.com> wrote:
> 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():
>
> 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.
>
> When persisting ChildMapsId:
>
> 1) Hibernate automatically initializes ChildMapsId#id to parent.id [2]
>
> 2) if ChildMapsId#parent is new, parent is automatically
> cascade-persisted (even though CascadeStyle.PERSIST is not mapped), then
> the ChildMapsId entity is persisted.
>
> 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. [3] If ChildPKJC#parent is not optional, a foreign
> key is generated
>
> For ChildMapsId, a foreign key is generated from ChildPKJC referencing
> Parent, even if ChildMapsId#parent is optional.
>
> Is this a bug? My guess is that it is.
>
> 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.
>
> 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.
> 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).
>
> 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