[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