Introduce `VirtualIdEmbeddable` and `IdClassEmbeddable` + instantiators

Move all component instantiations to use the new mapping model EmbeddableInstantiator

Still need to
  - 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
  - clean up ComponentType, esp wrt its use of ComponentTuplizer
This commit is contained in:
Steve Ebersole 2021-12-01 07:06:22 -06:00
parent 5b44aa5d44
commit eb5afb0427
7 changed files with 87 additions and 139 deletions

View File

@ -5,6 +5,7 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.tuple.component;
import java.lang.reflect.Method;
import java.util.Iterator;
@ -13,7 +14,6 @@ import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.tuple.Instantiator;
/**
* Support for tuplizers relating to components.
@ -25,10 +25,8 @@ public abstract class AbstractComponentTuplizer implements ComponentTuplizer {
protected final Getter[] getters;
protected final Setter[] setters;
protected final int propertySpan;
protected final Instantiator instantiator;
protected final boolean hasCustomAccessors;
protected abstract Instantiator buildInstantiator(Component component);
protected abstract Getter buildGetter(Component component, Property prop);
protected abstract Setter buildSetter(Component component, Property prop);
@ -51,7 +49,6 @@ public abstract class AbstractComponentTuplizer implements ComponentTuplizer {
i++;
}
hasCustomAccessors = foundCustomAccessor;
instantiator = buildInstantiator( component );
}
public Object getPropertyValue(Object component, int i) throws HibernateException {
@ -72,13 +69,6 @@ public abstract class AbstractComponentTuplizer implements ComponentTuplizer {
}
}
/**
* This method does not populate the component parent
*/
public Object instantiate() throws HibernateException {
return instantiator.instantiate();
}
public boolean isMethodOf(Method method) {
return false;
}

View File

@ -35,13 +35,6 @@ public interface ComponentTuplizer extends Tuplizer, Serializable {
*/
public boolean isMethodOf(Method method);
/**
* Generate a new, empty entity.
*
* @return The new, empty entity instance.
*/
public Object instantiate();
/**
* Extract the current values contained on the given entity.
*

View File

@ -12,8 +12,6 @@ import org.hibernate.mapping.Property;
import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.tuple.DynamicMapInstantiator;
import org.hibernate.tuple.Instantiator;
/**
* A {@link ComponentTuplizer} specific to the dynamic-map entity mode.
@ -27,10 +25,6 @@ public class DynamicMapComponentTuplizer extends AbstractComponentTuplizer {
return Map.class;
}
protected Instantiator buildInstantiator(Component component) {
return new DynamicMapInstantiator();
}
public DynamicMapComponentTuplizer(Component component) {
super(component);
}

View File

@ -15,7 +15,6 @@ import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
import org.hibernate.bytecode.spi.ReflectionOptimizer;
import org.hibernate.cfg.Environment;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl;
@ -24,7 +23,6 @@ import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.tuple.Instantiator;
import org.hibernate.tuple.PojoInstantiator;
/**
* A {@link ComponentTuplizer} specific to the pojo entity mode.
@ -110,19 +108,6 @@ public class PojoComponentTuplizer extends AbstractComponentTuplizer {
return false;
}
protected Instantiator buildInstantiator(Component component) {
if ( component.isEmbedded() && ReflectHelper.isAbstractClass( this.componentClass ) ) {
ProxyFactoryFactory proxyFactoryFactory = component.getServiceRegistry().getService( ProxyFactoryFactory.class );
return new ProxiedInstantiator( this.componentClass, proxyFactoryFactory );
}
if ( optimizer == null ) {
return new PojoInstantiator( this.componentClass, null );
}
else {
return new PojoInstantiator( this.componentClass, optimizer.getInstantiationOptimizer() );
}
}
protected Getter buildGetter(Component component, Property prop) {
return prop.getGetter( this.componentClass );
}

View File

@ -29,6 +29,7 @@ import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.tuple.StandardProperty;
import org.hibernate.tuple.ValueGeneration;
@ -466,19 +467,20 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
}
@Override
public Object deepCopy(Object component, SessionFactoryImplementor factory)
throws HibernateException {
public Object deepCopy(Object component, SessionFactoryImplementor factory) {
if ( component == null ) {
return null;
}
Object[] values = getPropertyValues( component );
final Object[] values = getPropertyValues( component );
for ( int i = 0; i < propertySpan; i++ ) {
values[i] = propertyTypes[i].deepCopy( values[i], factory );
}
Object result = instantiate();
setPropertyValues( result, values );
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
.getRepresentationStrategy()
.getInstantiator();
Object result = instantiator.instantiate( () -> values, factory );
//not absolutely necessary, but helps for some
//equals()/hashCode() implementations
@ -496,29 +498,42 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
Object target,
SharedSessionContractImplementor session,
Object owner,
Map copyCache)
throws HibernateException {
Map copyCache) {
if ( original == null ) {
return null;
}
//if ( original == target ) return target;
final Object result = target == null
? instantiate( owner, session )
: target;
final Object[] originalValues = getPropertyValues( original );
final Object[] resultValues;
Object[] values = TypeHelper.replace(
getPropertyValues( original ),
getPropertyValues( result ),
if ( target == null ) {
resultValues = new Object[originalValues.length];
}
else {
resultValues = getPropertyValues( target );
}
final Object[] replacedValues = TypeHelper.replace(
originalValues,
resultValues,
propertyTypes,
session,
owner,
copyCache
);
setPropertyValues( result, values );
return result;
if ( target == null ) {
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
.getRepresentationStrategy()
.getInstantiator();
return instantiator.instantiate( () -> replacedValues, session.getSessionFactory() );
}
else {
setPropertyValues( target, replacedValues );
return target;
}
}
@Override
@ -528,21 +543,27 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
SharedSessionContractImplementor session,
Object owner,
Map copyCache,
ForeignKeyDirection foreignKeyDirection)
throws HibernateException {
ForeignKeyDirection foreignKeyDirection) {
if ( original == null ) {
return null;
}
//if ( original == target ) return target;
final Object result = target == null ?
instantiate( owner, session ) :
target;
Object[] values = TypeHelper.replace(
getPropertyValues( original ),
getPropertyValues( result ),
final Object[] originalValues = getPropertyValues( original );
final Object[] resultValues;
if ( target == null ) {
resultValues = new Object[originalValues.length];
}
else {
resultValues = getPropertyValues( target );
}
final Object[] replacedValues = TypeHelper.replace(
originalValues,
resultValues,
propertyTypes,
session,
owner,
@ -550,30 +571,16 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
foreignKeyDirection
);
setPropertyValues( result, values );
return result;
}
/**
* This method does not populate the component parent
*/
public Object instantiate() {
return componentTuplizer.instantiate();
}
public Object instantiate(Object parent, SharedSessionContractImplementor session) {
Object result = instantiate();
final PropertyAccess parentAccess = mappingModelPart().getParentInjectionAttributePropertyAccess();
if ( parentAccess != null && parent != null ) {
parentAccess.getSetter().set(
result,
session.getPersistenceContextInternal().proxyFor( parent ),
session.getFactory()
);
if ( target == null ) {
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
.getRepresentationStrategy()
.getInstantiator();
return instantiator.instantiate( () -> replacedValues, session.getSessionFactory() );
}
else {
setPropertyValues( target, replacedValues );
return target;
}
return result;
}
@Override
@ -615,9 +622,11 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
for ( int i = 0; i < propertyTypes.length; i++ ) {
assembled[i] = propertyTypes[i].assemble( (Serializable) values[i], session, owner );
}
Object result = instantiate( owner, session );
setPropertyValues( result, assembled );
return result;
final EmbeddableInstantiator instantiator = mappingModelPart.getEmbeddableTypeDescriptor()
.getRepresentationStrategy()
.getInstantiator();
return instantiator.instantiate( () -> assembled, session.getFactory() );
}
}

View File

@ -8,8 +8,6 @@ package org.hibernate.type;
import java.lang.reflect.Method;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.tuple.component.ComponentMetamodel;
import org.hibernate.type.spi.TypeConfiguration;
@ -28,15 +26,4 @@ public class EmbeddedComponentType extends ComponentType {
public boolean isMethodOf(Method method) {
return componentTuplizer.isMethodOf( method );
}
@Override
public Object instantiate(Object parent, SharedSessionContractImplementor session) throws HibernateException {
final boolean useParent = parent != null &&
//TODO: Yuck! This is not quite good enough, it's a quick
//hack around the problem of having a to-one association
//that refers to an embedded component:
super.getReturnedClass().isInstance( parent );
return useParent ? parent : super.instantiate( parent, session );
}
}

View File

@ -8,43 +8,40 @@ package org.hibernate.orm.test.component.proxy;
import java.util.List;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.type.ComponentType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.type.spi.CompositeTypeImplementor;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.NotImplementedYet;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Guillaume Smet
* @author Oliver Libutzki
*/
public class ComponentBasicProxyTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[]{
Person.class, Adult.class
};
}
@DomainModel( annotatedClasses = { Person.class, Adult.class } )
@SessionFactory
public class ComponentBasicProxyTest {
@Test
@TestForIssue(jiraKey = "HHH-12786")
public void testBasicProxyingWithProtectedMethodCalledInConstructor() {
doInJPA( this::entityManagerFactory, entityManager -> {
public void testBasicProxyingWithProtectedMethodCalledInConstructor(SessionFactoryScope scope) {
scope.inTransaction( (entityManager) -> {
Adult adult = new Adult();
adult.setName( "Arjun Kumar" );
entityManager.persist( adult );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
scope.inTransaction( (entityManager) -> {
List<Adult> adultsCalledArjun = entityManager
.createQuery( "SELECT a from Adult a WHERE a.name = :name", Adult.class )
.setParameter( "name", "Arjun Kumar" ).getResultList();
@ -55,25 +52,18 @@ public class ComponentBasicProxyTest extends BaseEntityManagerFunctionalTestCase
@Test
@TestForIssue(jiraKey = "HHH-12791")
public void testOnlyOneProxyClassGenerated() {
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build();
public void testOnlyOneProxyClassGenerated(DomainModelScope domainModelScope, SessionFactoryScope sfScope) {
final SessionFactoryImplementor sessionFactory = sfScope.getSessionFactory();
try {
Metadata metadata = new MetadataSources( ssr ).addAnnotatedClass( Person.class )
.getMetadataBuilder()
.build();
PersistentClass persistentClass = metadata.getEntityBinding( Person.class.getName() );
final PersistentClass personDescriptor = domainModelScope.getDomainModel().getEntityBinding( Person.class.getName() );
final CompositeTypeImplementor componentType = (CompositeTypeImplementor) personDescriptor.getIdentifierMapper().getType();
final EmbeddableValuedModelPart embedded = componentType.getMappingModelPart();
final EmbeddableInstantiator instantiator = embedded.getEmbeddableTypeDescriptor()
.getRepresentationStrategy()
.getInstantiator();
ComponentType componentType1 = (ComponentType) persistentClass.getIdentifierMapper().getType();
Object instance1 = componentType1.instantiate();
ComponentType componentType2 = (ComponentType) persistentClass.getIdentifierMapper().getType();
Object instance2 = componentType2.instantiate();
assertEquals( instance1.getClass(), instance2.getClass() );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
final Object instance1 = instantiator.instantiate( null, sessionFactory );
final Object instance2 = instantiator.instantiate( null, sessionFactory );
assertThat( instance1.getClass() ).isEqualTo( instance2.getClass() );
}
}