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(a)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(a)hibernate.org>
> To: "hibernate-dev" <hibernate-dev(a)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(a)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(a)lists.jboss.org
>
https://lists.jboss.org/mailman/listinfo/hibernate-dev
>