Introduce `VirtualIdEmbeddable` and `IdClassEmbeddable` + instantiators

Prep work for EmbeddableInstantiator - initializer

Still need to
  - integrate EmbeddableInstantiator work
  - integrate embedded forms.  `VirtualIdEmbeddable` does not really need it as it can use the id-mapping itself as the embedded form.  But `IdClassEmbedded` should really be integrated
  - integrate `VirtualKeyEmbeddable` and `VirtualKeyEmbedded` for use as inverse composite fks
  - share `#finishInit` handling for `EmbeddableMappingType`, `VirtualIdEmbeddable` and `IdClassEmbeddable`
  - ability to use the containing composite owner as the parent of a composite (legacy behavior is to always use the "first" entity
This commit is contained in:
Steve Ebersole 2021-11-30 15:26:16 -06:00
parent 29ed0a0566
commit 142299e7a8
2 changed files with 53 additions and 81 deletions

View File

@ -43,7 +43,12 @@ import static org.hibernate.internal.util.collections.CollectionHelper.arrayList
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentAccess implements EmbeddableInitializer { public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentAccess implements EmbeddableInitializer {
public static final Object NULL_MARKER = new Object(); private static final Object NULL_MARKER = new Object() {
@Override
public String toString() {
return "Composite NULL_MARKER";
}
};
private final NavigablePath navigablePath; private final NavigablePath navigablePath;
private final EmbeddableValuedModelPart embedded; private final EmbeddableValuedModelPart embedded;
@ -143,10 +148,55 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
@Override @Override
public void resolveInstance(RowProcessingState processingState) { public void resolveInstance(RowProcessingState processingState) {
reallyResolve( processingState ); // nothing to do
} }
private void reallyResolve(RowProcessingState processingState) { @Override
public void initializeInstance(RowProcessingState processingState) {
EmbeddableLoadingLogger.INSTANCE.debugf(
"Initializing composite instance [%s]",
navigablePath
);
extractRowState( processingState );
prepareCompositeInstance( processingState );
handleParentInjection( processingState );
if ( !createEmptyCompositesEnabled && allValuesNull == TRUE ) {
compositeInstance = NULL_MARKER;
}
else {
notifyResolutionListeners( compositeInstance );
if ( compositeInstance instanceof HibernateProxy ) {
final Initializer parentInitializer = processingState.resolveInitializer( navigablePath.getParent() );
if ( parentInitializer != this ) {
( (FetchParentAccess) parentInitializer ).registerResolutionListener(
entity -> setPropertyValuesOnTarget( entity, processingState.getSession() )
);
}
else {
Object target = representationStrategy
.getInstantiator()
.instantiate( null, sessionFactory );
setPropertyValuesOnTarget( target, processingState.getSession() );
( (HibernateProxy) compositeInstance ).getHibernateLazyInitializer().setImplementation( target );
}
}
// At this point, createEmptyCompositesEnabled is always true.
// We can only set the property values on the compositeInstance though if there is at least one non null value.
// If the values are all null, we would normally not create a composite instance at all because no values exist.
// Setting all properties to null could cause IllegalArgumentExceptions though when the component has primitive properties.
// To avoid this exception and align with what Hibernate 5 did, we skip setting properties if all values are null.
// A possible alternative could be to initialize the resolved values for primitive fields to their default value,
// but that might cause unexpected outcomes for Hibernate 5 users that use createEmptyCompositesEnabled when updating.
// You can see the need for this by running EmptyCompositeEquivalentToNullTest
else if ( allValuesNull == FALSE ) {
setPropertyValuesOnTarget( compositeInstance, processingState.getSession() );
}
}
}
private void prepareCompositeInstance(RowProcessingState processingState) {
if ( compositeInstance != null ) { if ( compositeInstance != null ) {
return; return;
} }
@ -188,51 +238,6 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
); );
} }
@Override
public void initializeInstance(RowProcessingState processingState) {
EmbeddableLoadingLogger.INSTANCE.debugf(
"Initializing composite instance [%s]",
navigablePath
);
handleParentInjection( processingState );
extractRowState( processingState );
if ( !createEmptyCompositesEnabled && allValuesNull == TRUE ) {
compositeInstance = null;
}
else {
notifyResolutionListeners( compositeInstance );
if ( compositeInstance instanceof HibernateProxy ) {
final Initializer parentInitializer = processingState.resolveInitializer( navigablePath.getParent() );
if ( parentInitializer != this ) {
( (FetchParentAccess) parentInitializer ).registerResolutionListener(
entity -> setPropertyValuesOnTarget( entity, processingState.getSession() )
);
}
else {
Object target = representationStrategy
.getInstantiator()
.instantiate( null, sessionFactory );
setPropertyValuesOnTarget( target, processingState.getSession() );
( (HibernateProxy) compositeInstance ).getHibernateLazyInitializer().setImplementation( target );
}
}
// At this point, createEmptyCompositesEnabled is always true.
// We can only set the property values on the compositeInstance though if there is at least one non null value.
// If the values are all null, we would normally not create a composite instance at all because no values exist.
// Setting all properties to null could cause IllegalArgumentExceptions though when the component has primitive properties.
// To avoid this exception and align with what Hibernate 5 did, we skip setting properties if all values are null.
// A possible alternative could be to initialize the resolved values for primitive fields to their default value,
// but that might cause unexpected outcomes for Hibernate 5 users that use createEmptyCompositesEnabled when updating.
// You can see the need for this by running EmptyCompositeEquivalentToNullTest
else if ( allValuesNull == FALSE ) {
setPropertyValuesOnTarget( compositeInstance, processingState.getSession() );
}
}
}
private void extractRowState(RowProcessingState processingState) { private void extractRowState(RowProcessingState processingState) {
allValuesNull = true; allValuesNull = true;
for ( int i = 0; i < assemblers.size(); i++ ) { for ( int i = 0; i < assemblers.size(); i++ ) {

View File

@ -27,39 +27,6 @@ import static org.assertj.core.api.Assertions.assertThat;
public class NestedIdClassTests { public class NestedIdClassTests {
@Test @Test
public void smokeTest(SessionFactoryScope scope) { public void smokeTest(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final String qry = "from Payment p join fetch p.id.order o join fetch o.id.customer";
session.createQuery( qry ).list();
});
scope.inTransaction( (session) -> {
final String qry = "from Payment p join fetch p.order o join fetch o.customer";
session.createQuery( qry ).list();
});
}
@Test
public void smokeTest2(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final String qry = "from Payment p";
final Payment payment = session.createQuery( qry, Payment.class ).uniqueResult();
assertThat( payment ).isNotNull();
assertThat( payment.accountNumber ).isNotNull();
assertThat( payment.order ).isNotNull();
assertThat( payment.order.orderNumber ).isNotNull();
assertThat( payment.order.customer ).isNotNull();
assertThat( payment.order.customer.id ).isNotNull();
assertThat( payment.order.customer.name ).isNotNull();
});
scope.inTransaction( (session) -> {
final String qry = "from Payment p join fetch p.order o join fetch o.customer";
session.createQuery( qry ).list();
});
}
@Test
public void smokeTest3(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
final Payment payment = session.get( Payment.class, new PaymentId( new OrderId( 1, 1 ), "123" ) ); final Payment payment = session.get( Payment.class, new PaymentId( new OrderId( 1, 1 ), "123" ) );
assertThat( payment ).isNotNull(); assertThat( payment ).isNotNull();