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:
parent
29ed0a0566
commit
142299e7a8
|
@ -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++ ) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue