[hibernate-dev] Question..

Gail Badner gbadner at redhat.com
Tue Oct 17 01:55:47 EDT 2017


On Sun, Oct 15, 2017 at 10:46 AM, Steve Ebersole <steve at hibernate.org> wrote:
> On Fri, Oct 13, 2017, 3:35 PM Gail Badner <gbadner at redhat.com> wrote:
>>
>> Hi Steve,
>>
>> I'm circling back to this. Please see below...
>>
>> On Sat, Sep 2, 2017 at 8:47 AM, Steve Ebersole <steve at hibernate.org>
>> wrote:
>> > I don't have the original email to reply to.  So I'll reply here.
>> >
>> > Overall, I had not really considered the primitive attribute case, but
>> > yeah
>> > that's clearly an issue.  My mistake.
>> >
>> > A) I agree that the con here is huge.  Not the best option
>> >
>> > B) Is close to better.  We could certainly check this and throw an
>> > error.
>>
>> Throwing an exception is option A.
>
>
> Your (A) and (B) are the exact same thing (same underlying condition check)
> except in (A) you throw an exception and in (B) you log a warning and
> disregard an explicit user setting.

Correct.

>
> WRT to the condition check, you suggest to "compare a primitive value in a
> composite with the default for the primitive type  (e.g, comparing an int
> value with 0 instead of null)".  That's not the best condition check.  I was
> pointing out that we already have better handling for this for ids and
> versions and that we ought to follow that paradigm, mainly on startup we
> would instantiate an instance of said component and check the initial values
> for its attributes.  E.g., consider:
>
> @Embeddable
> public class MyEmbeddable {
>     private int someInt;
>     private int anotherInt = -1;
> }
>
> Here, comparison of `#anotherInt` to 0 is not really correct.  The proper
> comparison is to -1.  Again, this is exactly what we do for ids and
> versions.
>
> So because your (A) and (B) check the same condition I was pointing out that
> they actually are based on the incorrect condition check (0 versus -1), so
> we should correct that assumption.  But even then we have  and further that
> we should update the check and that we still have the different outcome
> between (A) and (B) to decide between.

I have already implemented something that does the check that you
suggested, and I decided against it for a couple of reasons:
1) Any initialized values can be "real" values.
2) Suppose an instantiated embedded value includes something like:
    Date created = new Date();
    double random = Math.random();
For 2), an embedded value would never be considered empty, and its
values would always be persisted instead of null.

I think that it should be up to the `EmptyCompositeStrategy` to decide
if a primitive that has the default value, or something like `created`
or `random` should be ignored when determining if an embedded value is
empty.

>
> The downside I was trying to point out (and it is the same for issue for
> some of the other parts of the discussion) is that we currently do not have
> a singular place where we keep "embeddable" metadata - again, CompositeType
> describes the embedded, not the embeddable.  But maybe it is ok to duplicate
> this "instantiate and check" for each CompositeType.  A alternative is to
> keep a mapping of this information as part of the bootstrap data structures
> to help do that just once for every embedded of that embeddable.
>
> Further, in discussing these various options it is important to make the
> distinction between the best solution in terms of
>
> short-term - how do we handle this for 5.x
> longer-term - how do we address this in 6.0+ because here we have some more
> flexibility.
>
> Longer term, I ultimately think the following is the best solution:
>
> correct the condition as discussed above

As I mention, I don't think correcting the condition is the right thing to do.

> slightly modified version of (C) - see below
> slightly modified version of (D) - see below
>
> The change I'd make to `EmptyCompositeStrategy` is specifically the
> signatures and (slightly) the intent to work with (D).  Best case scenario,
> again for 6.0+) I'd pass in
> `org.hibernate.metamodel.model.domain.spi.EmbeddedTypeDescriptor` whose
> intent is kind of, sort of the same as `ComponentMetamodel`[1].  Worst case
> we'd pass in this `org.hibernate.metamodel.model.domain.NavigableRole` you
> can see exposed here.  NavigableRoles can be resolved to their Navigable via
> the TypeConfiguration (the Navigable here would be the
> EmbeddedTypeDescriptor).  Additionally, these roles are nice in terms of
> configuration as we will see below.
>
> In terms of (D), I think configuration here is a good idea especially in
> combination with (C).  In fact I can see the following settings:
>
> hibernate.empty_composite_creation
>
> enable - enable creation & throw exceptions when we deem we cannot create
> them (condition check discussion).  This is basically (A)
> warn - same as "enable", but warn when we cannot create them - basically (B)
> disable (default) - "opt out"
> strategy - consult the configured EmptyCompositeStrategy
>
> hibernate.empty_composite_strategy - names a `EmptyCompositeStrategy` to use
>

I like what you describe above.

Currently the property is named `hibernate.create_empty_composites.enabled`.

Are you suggesting we rename the property in 6.0+?

For 5.x, these would be options for hibernate.create_empty_composites.enabled:

true - enable creation & throw exceptions when we deem we cannot
create them (condition check discussion).  This is basically (A)
warn - same as "enable", but warn when we cannot create them - basically (B)
false - disable (default) - "opt out"
strategy - consult the configured EmptyCompositeStrategy

>
> Additionally, I think it would be awesome to allow configuring (1) per role,
> e.g. (assuming Person#name as a composite):
>
> hibernate.empty_composite_creation = strategy
>  hibernate.empty_composite_creation.com.acme.Person.name = disable
>
> So for all composites we will use the configured strategy, except for
> Person#name, which we have explicitly disabled / opted-out-of.
>

I like this as well.

For 5.x, I guess we'd have something like this to opt out:

hibernate.create_empty_composites.enabled=strategy
hibernate.create_empty_composites.enabled.com.acme.Person.name=false

Should we also allow to disable in general, then opt-in for Person.name, as in:

hibernate.create_empty_composites.enabled=false
hibernate.create_empty_composites.enabled.com.acme.Person.name=true
(this would fail like case (A) if the newly instantiated value has
initialized values)

Also, allow a strategy to be used for the opted-in value:

hibernate.create_empty_composites.enabled=false
hibernate.create_empty_composites.enabled.com.acme.Person.name=strategy

>
>
>
>>
>> >  A better logical option is to do similar to what we do for ids and
>> > versions...
>> > on startup instantiate one of these and grab/store its initial state
>> > when
>> > freshly instantiated.  We can later use those values to perform the
>> > empty
>> > check.  This is more logical, but not sure how "practical" it is given
>> > that
>> > we do not really have a good place to keep this relative to an
>> > embeddable,
>> > nor relative to an embedded, aside from CompositeType, but that does not
>> > feel right.
>>
>> Is this what you refer to as (B) (expanded)?
>>
>>
>> My first email in the thread (which I told you to disregard) basically
>> suggests doing this. I told you to disregard it because I realized
>> that it would have serious issues.
>
>
> I'm not seeing where that first email suggests checking based on the actual
> initialized value rather than the "primitive type default".  But if it did,
> then yes that is a better check as discussed above.
>
>
>
>> Please see an example of the consequences when the embedded represents
>> an ID:
>> https://github.com/hibernate/hibernate-orm/pull/1993/commits/329354bfa4bd86190252f1d5a7019f8b06c21b17
>
>
> Ids should be excluded from this completely.  Hibernate does not support any
> part of a PK to be nullable - ergo this entire discussion is completely
> irrelevant for composite PKs.
>

My example involves a nullable property that happens to be a composite ID.

Please take a look at the mapping in my example:
https://github.com/hibernate/hibernate-orm/pull/1993/commits/329354bfa4bd86190252f1d5a7019f8b06c21b17#diff-a16e0cd57dc44a1cabeed67869babacfR162

IMO, it's a really bad idea for Hibernate to assume, by default, that
initialized values in a newly instantiated embedded value would be
considered empty.

>
>
>>
>> If I'm understanding what you mean by (B) (expanded), then I don't
>> think that's is a good idea.
>
>
> As I said in my initial reply and above, I am not a fan of (B) - I think it
> is, generally speaking, a bad option to ignore settings that the user has
> explicitly set.  But again, we need to keep short/long term in mind because
> short-term I think every other option has some significant drawbacks.
>
> And long-term it comes down to what we allow for configuration.  E.g. if we
> do not allow the full breath of config options I mentioned, then that
> changes things.
>
>
>
>> I think C is a good starting point in H5, preferably using role (if
>> that can be done).
>
>
> For 5.x, (C) may be the best option aside from the argument discussion.
> Like I said, in terms of 6 the best argument to pass would be
> EmbeddedTypeDescriptor.  Or, depending on the timing (boot model versus
> runtime model) `org.hibernate.boot.model.domain.EmbeddedMapping` /
> `org.hibernate.boot.model.domain.EmbeddedValueMapping`.
>
> We *could* pass in this role as a String, but I really hate passing Strings.
> Another option would be to back-port NavigableRole and expose these from
> EntityPersister/EntityType, CollectionPersister/CollectionType and
> ComponentType.

I'll have to look at NavigableRole to see what you mean. We are hoping
to backport to 5.1. I don't think I can make changes to
EntityPersister/CollectionPersister in 5.1, since we can't use default
methods.

>
> I'm trying to get something that will work in both 5 and 6 to minimize
> changes for users as they migrate.  Assuming we back-port NavigableRole,
> something like:
>
>
> public interface EmptyCompositeStrategy {
>     /**
>      * Should a composite/embeddable be instantiated all of
>      * its composed attribute values are null?
>      */
>     boolean supportsEmptyComposite(NavigableRole compositeRole);
>
>     /**
>      * Is the given composite value considered "empty"?
>      */
>     boolean isEmptyComposite(
>             NavigableRole compositeRole,
>             Object value,
>             Object component,
>             SessionFactoryImplementor factory);
> }

I mentioned in my previous email that there was an extra argument and
corrected it there. With your change it should be:

>     boolean isEmptyComposite(
>             NavigableRole compositeRole,
>             Object value,
>             SessionFactoryImplementor factory);

>
> Although, tbh I am not really understanding the intent of your second
> method.  Are you asking the strategy to check each individual sub-attribute
> for emptiness?  Also, are you thinking this gets called when reading the
> values?  Or when writing the values?
>

It is intended to determine if a particular composite value should be
considered empty. It will be needed by ComponentType#isEqual,
#compare, #isDirty, #isModified to determine if 2 values are both
considered empty.

In the case that an embeddable has an indeterminate sub-attribute, the
2 values could have a different sub-attribute value, but
EmptyCompositeStrategy#isEmptyComposite could ignore that
sub-attribute in its determination.

>
> [1]
> https://github.com/sebersole/hibernate-core/blob/wip/6.0/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/spi/EmbeddedTypeDescriptor.java
>
>


More information about the hibernate-dev mailing list