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

Gail Badner gbadner at redhat.com
Wed Sep 5 20:05:41 EDT 2018


Comments below...

On Sun, Sep 2, 2018 at 11:17 PM, Vlad Mihalcea <mihalcea.vlad at gmail.com>
wrote:

> Here are my comments:
>
> 1. I think the @MapsId behavior is the right one even if we don't enable
> cascading on the child-side.
> Although theoretically, we should not do that, in reality, if we disable
> this behavior, some apps will break.
>

I don't know the history of why cascade-persist is forced. I see 2 jiras
related to this (HHH-4858, HHH-4887).

Emmanuel, can you provide some information about why this was added for
@MapsId, but not @PrimaryKeyJoinColumn? I don't see anything in the spec
about this. Am I missing something?

I agree that removing cascade-persist from @MapsId now would cause
applications to break.

If this is Hibernate-specific behavior then shouldn't this functionality be
enabled/disabled via a property ( e.g.,
hibernate.jpa.compliance.cascade_persist_id_associations)
and JpaCompliance? The default would be "enabled".


>
> We could document the behavior so that, when used @MapdId or PKJC,
> Hibernate is allowed to cascade the parent if it's transient.
>
>
Do we really want to add this functionality with @PKJC at this point, since
@MapsId is preferred, and cascade=CascadeType.PERSIST can easily be added
to @OneToOne.


> 2. According to the JPA spec, the @OneToOne optional attribute is defined
> like this:
>
> (Optional) Whether the association is optional.
>> If set to false then a non-null relationship must
>> always exist.
>
>
> I don't think that `optional` should be related to the FK generation. For
> me, `optional` is more like adding NOT NULL to the FK column.
>

I think in this particular case, it is important.

With the following mapping:

@Entity
public class ChildPKJC {
@Id
private Long id;

@OneToOne  // note that cascade-persist is not enabled
@PrimaryKeyJoinColumn
private Parent parent;
}

If ChildPKJC#parent is null, that means that  ChildPKJC#id has a non-null
value that does not correspond to a Parent ID. If this happens and there is
a foreign key constraint, a constraint violation would occur.

It seems to me that the spec supports this use case with @PKJC, and
annotating with @NotFound(IGNORE) should not be necessary.

Emmanuel, do you agree?

Should using @MapsId instead of @PKJC work the same way?


> 3. This reminds me of this Jira issue:
>
> https://hibernate.atlassian.net/browse/HHH-12770
>
> I guess it makes sense to have what you proposed:
>
> Is this expected? If so, then ChildMapsId#parent cannot be optional by
>> default (without @NotFound(IGNORE).
>
>
> As for these questions:
>
> 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():
>
>
> This is a consequence of the current behavior. I've added this phrase to
> explain what happens.
> If we change the behavior, we should update the doc too.
>
> Related to:
>
> [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.
>
>
>  They say "may not", meaning that setting the ID prior to flushing is not
> going to break the spec.
>

For @MapsId, the spec seems to say that, if the application doesn't assign
the ID attributes corresponding to a relationship, the persistence provider
will assign ID attributes during flush (at the latest). I find the
documentation on this topic to be confusing, so maybe I'm missing something.

I don't see anything in the spec that this same behavior is expected for
@PKJC.

Emmanuel, please confirm.


>
> For sequence identifiers, we always get the ID generated prior to the
> flush anyway.
>
> And for:
>
> [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”.
>
>
> I don't think that makes any sense at all. The real one-to-one database
> table relationship is supposed to use a FK.
> Now, without MapsId or PKJC, we actually have a "one-to-many" table
> relationship with a UNIQUE constraint.
> Even so, the FK is always mandatory. I have no idea why the JPA spec says
> that.
>

Please see what I wrote above about this.

Thanks,
Gail


>
> Vlad
>
>
> On Sat, Sep 1, 2018 at 10:33 AM, Gail Badner <gbadner at redhat.com> wrote:
>
>> 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.IdentifierGen
>> erationException:
>> > 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."
>> >>
>> >>
>> _______________________________________________
>> hibernate-dev mailing list
>> hibernate-dev at lists.jboss.org
>> https://lists.jboss.org/mailman/listinfo/hibernate-dev
>>
>
>


More information about the hibernate-dev mailing list