[hibernate-dev] Metamodel - Entity primary key mappings

Steve Ebersole steve at hibernate.org
Wed Apr 9 12:23:51 EDT 2014


So we need to decide how to best represent the identifier for the entity.
 That's ultimately the disconnect here.  Hibernate historically had 3 ways
to represent ids:

* simple - these were explicitly basic type ids: longs, ints, strings, etc.
 == <hbm:id/>
* aggregated composite - essentially @EmbeddedId
* non-aggregated composite - like @IdClass cases, but without the @IdClass;
the entity itself was the identifier value.


Notice a few things:
1) A simple identifier could never be an association in legacy Hibernate.
 A "key-many-to-one" was ALWAYS wrapped in a composite, even if it was the
attribute on the composite.  Now Hibernate also has the concept of a 1-1
with a "foreign generator" which is essentially the same as the JPA feature
of `@Id @ManyToOne` or `@Id @OneToOne`.  Recently I made some significant
changes to "simple derived ids" such that they no longer get wrapped in a
virtual embedded composite.
2) For composite ids, JPA requires either a EmbeddedId or a IdClass.
 Legacy Hibernate does not, via its "embedded (virtual) identifier".  While
I think we should continue to support non-aggregated composite without an
IdClass, I do think we should warn users when they do this.  So at the
moment we really have the following ways to represent an id:

* simple basic id
* simple derived id ( @Id + @OneToOne or @ManyToOne )
* aggregated composite
* non-aggregated composite
* non-aggregated composite + IdClass

3) JPA allows EmbeddedId to have (singular) associations. IdClass cannot;
in cases where IdClass maps one or more associations.. well the convoluted
rules in "derived identities" section kick in.  But the important take away
is that the structure of the 2 id representations is very different.  For
example:


@Entity
class Customer {
  @Id Integer id;
  ...
}

class OrderIdClass implements Serializable {
    Integer customer;
    int orderNumber;
}

@Entity
@IdClass(OrderIdClass.class)
class Order implements Serializable {
  @Id @ManyToOne ...
  Customer customer;
  @Id
  int orderNumber;
  ...
}

Looking at Order, the internal representation of its identity (used to
cache it within the Session ,etc) is the Order itself - Hibernate's legacy
"embedded identifier" which includes the many-to-one.  The representation
used in lookups is OrderIdClass which does not contain the many-to-one.

Note that EmbeddedId can follow the same paradigm according to JPA, which
is another use-case of MapsId:


@Embeddable
class OrderId implements Serializable {
    Integer customer;
    int orderNumber;
}

@Entity
class Order implements Serializable {
  @EmbeddedId
  OrderId id;

  @MapsId
  @ManyToOne ...
  Customer customer;
  ...
}

Ugh.



On Tue, Apr 8, 2014 at 6:32 PM, Gail Badner <gbadner at redhat.com> wrote:

> Hi Steve,
>
> Problem happens when a @ManyToOne is assocated with an entity that has an
> @IdClass.
>
> I looked for a core test that reproduces the problem, but it looks like
> the core tests using @IdClass that fail for a different reason before this
> problem shows up.
>
> The following envers tests reproduce the problem:
>
>
> org.hibernate.envers.test.integration.onetoone.bidirectional.ids.MulIdBidirectional
>
> org.hibernate.envers.test.integration.onetomany.detached.BasicDetachedSetWithMulId
> org.hibernate.envers.test.integration.onetomany.BasicSetWithMulId
> org.hibernate.envers.test.integration.query.ids.MulIdOneToManyQuery
>
> When binding the JdbcDataType for the ID:
>
> EntityType.getIdentifierOrUniqueKeyType( metadataCollector() ) is called,
> which ends up calling:
>
> InFlightMetadataCollectorImpl.getIdentifierType( associatedEntityName )
> which returns
>
>
> entityBinding.getHierarchyDetails().getEntityIdentifier().getAttributeBinding()
>                                         .getHibernateTypeDescriptor()
>                                         .getResolvedTypeMapping();
>
> which returns an EmbeddedComponentType; its PojoComponentTuplizer is
> expecting to get/set values in the entity object.
>
> InFlightMetadataCollectorImpl.getIdentifierType( associatedEntityName )
> should be returning the ComponentType for the @IdClass. Problem is the
> ComponentType for the @IdClass is not built until the persisters are being
> built and PropertyFactory.buildIdentifierProperty() is called. This is
> because I didn't think it was needed until then. Now I see that it is
> needed when binding the JdbcDataType.
>
> The test ultimately fails due to PropertyAccessException when trying to
> insert the many-to-one. This is because the ID value for the associated
> entity is gotten from the EntityEntry and it is an instance of the @IdClass
> class, while the PojoComponentTuplizer for the ID is expecting an entity
> instance.
>
> Gail
>
> ----- Original Message -----
> > From: "Steve Ebersole" <steve at hibernate.org>
> > To: "hibernate-dev" <hibernate-dev at lists.jboss.org>
> > Sent: Monday, April 7, 2014 4:45:18 PM
> > Subject: Re: [hibernate-dev] Metamodel - Entity primary key mappings
> >
> > What I am thinking at the moment is to change up
> > org.hibernate.metamodel.spi.binding.EntityIdentifier to look like:
> >
> > public class EntityIdentifier {
> >   private final EntityBinding entityBinding;
> >
> >   private EntityIdentifierNature nature;
> >   private IdentifierAttributeBindings attributeBindings;
> >   private IdClassBinding idClassBinding;
> >   private Map<String,String> derivedIdMap;
> >   private IdentifierGenerator generator;
> > }
> >
> >
> > 1) IdentifierAttributeBindings would play sort of the same role
> > as
> >
> org.hibernate.metamodel.spi.binding.EntityIdentifier.EntityIdentifierBinding.
> >  Essentially it would hold the binding state for each of the identifier
> > attributes (those marked with @Id or @EmbeddedId).  Still not sure the
> best
> > external representation of this.  Exposing a simple
> > List<SingularAttributeBinding> for all identifier natures versus
> > specialized IdentifierAttributeBindings for each nature are running a
> > neck-and-neck race atm.  Thoughts?  Votes?
> >
> > 2) IdClassBinding would be a
> > specialized
> org.hibernate.metamodel.spi.binding.EmbeddableBindingContributor
> > for describing the @IdClass mapping
> >
> > 3) `derivedIdMap` holds the various @MapsId mappings for the entity.  I
> was
> > tempted to move this off to IdClassBinding except that @MapsId also
> refers
> > to @EmbeddedId attributes.
> >
> > Thoughts?  Worries?  Concerns?
> >
> >
> >
> >
> > On Mon, Apr 7, 2014 at 10:27 AM, Steve Ebersole <steve at hibernate.org>
> wrote:
> >
> > > I've been spending a lot of time the last 2 weeks trying to get a good
> > > "mental model" as to how to best model the information pertaining to an
> > > entity's primary key.  Most of this effort went into trying to
> understand
> > > JPA's "derived identity" support.
> > >
> > > First and foremost I want to get away from modelling this (in the
> > > metamodel) as a singular attribute. This is unnatural in the
> > > "non-aggregated composite id" case forcing us to build a "virtual"
> > > attribute.  I think that this is very doable with the distinction I
> > > recently added to the metamodel between Embedded/Embeddable.
> > >
> > > Next is to finish up support for IdClass, which should be close to
> done.
> > >  Gail, I know you had mentioned a case where that support was lacking.
> > >  Could you send me the specifics so I make sure we get that case
> covered?
> > >
> > > Beyond that is mainly incorporating support for JPA "derived
> identities".
> > >  With that, I want to share some of my understanding and see if I
> missed
> > > anything...
> > >
> > > "Derived identity" support is essentially Hibernate's much older
> "foreign"
> > > identifier generator, namely that the child entity gets (all of or
> part of)
> > > its identifier from a to-one association defined on it, from its
> "foreign
> > > key" value.  But of course the spec verbosely covers all the ways this
> > > might happen.
> > >
> > > The very first thing I noticed is that the examples in the spec come
> in 2
> > > distinct top-level flavors.  Examples 1-3 are cases where the "parent
> id"
> > > is simply part of the "derived (child) id".  Examples 4-6 are cases
> where
> > > the parent id *is* the child id (shared pk).  I am not sure how
> important
> > > this distinction is in practice, but I also noticed that @MapsId is
> only
> > > pertinent wrt the second set of cases where we have the shared pk.
>  This
> > > was the first time I have noticed that distinction.
> > >
> > > The one monkey wrench that JPA throws into the works here is that there
> > > are essentially multiple views of an entity's PK.  As one example,
> take the
> > > spec's "Example 4.a":
> > >
> > > @Entity
> > > public class Person {
> > >   @Id String ssn;
> > >   ...
> > > }
> > >
> > > @Entity
> > > public class MedicalHistory {
> > >   @Id
> > >   @OneToOne
> > >   @JoinColumn(name="FK")
> > >   Person patient;
> > >   ...
> > > }
> > >
> > > Ultimately, the primary key of MedicalHistory is the `ssn` of its
> > > associated Person.  Thus both of these are valid:
> > >
> > > entityManager.find( MedicalHistory.class, somePerson );
> > > entityManager.find( MedicalHistory.class, somePerson.ssn );
> > >
> > > For those who have seen it, this is the reason for the wonkiness inside
> > > Hibernate's runtime engine wrt incoming id type while doing a load.
> > >
> > >
> > > I am still going through all the use cases (ours plus JPA) to make
> sure we
> > > get everything covered.  But I wanted to hopefully get some discussion
> > > started around this and get any thoughts y'all might have.
> > >
> > >
> > >
> > _______________________________________________
> > 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