diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index b3e6cc0a2915..e32c2aa4d364 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -144,7 +144,7 @@ public ReflectionOptimizer getReflectionOptimizer( final Method[] getters = new Method[getterNames.length]; final Method[] setters = new Method[setterNames.length]; try { - findAccessors( clazz, getterNames, setterNames, types, getters, setters ); + unwrapPropertyInfos( clazz, getterNames, setterNames, types, getters, setters ); } catch (InvalidPropertyAccessorException ex) { LOG.unableToGenerateReflectionOptimizer( clazz.getName(), ex.getMessage() ); @@ -179,6 +179,18 @@ public ReflectionOptimizer getReflectionOptimizer( @Override public @Nullable ReflectionOptimizer getReflectionOptimizer(Class clazz, Map propertyAccessMap) { + final PropertyInfo[] propertyInfos = new PropertyInfo[propertyAccessMap.size()]; + int i = 0; + for ( Map.Entry entry : propertyAccessMap.entrySet() ) { + final String propertyName = entry.getKey(); + final PropertyAccess propertyAccess = entry.getValue(); + propertyInfos[i++] = new PropertyInfo( propertyName, propertyAccess ); + } + return getReflectionOptimizer( clazz, propertyInfos ); + } + + @Override + public @Nullable ReflectionOptimizer getReflectionOptimizer(Class clazz, PropertyInfo[] propertyInfos) { final Class fastClass; if ( !clazz.isInterface() && !Modifier.isAbstract( clazz.getModifiers() ) ) { // we only provide a fast class instantiator if the class can be instantiated @@ -203,17 +215,17 @@ public ReflectionOptimizer getReflectionOptimizer( fastClass = null; } - final Member[] getters = new Member[propertyAccessMap.size()]; - final Member[] setters = new Member[propertyAccessMap.size()]; + final Member[] getters = new Member[propertyInfos.length]; + final Member[] setters = new Member[propertyInfos.length]; + final String[] propertyNames = new String[propertyInfos.length]; try { - findAccessors( clazz, propertyAccessMap, getters, setters ); + unwrapPropertyInfos( clazz, propertyInfos, propertyNames, getters, setters ); } catch (InvalidPropertyAccessorException ex) { LOG.unableToGenerateReflectionOptimizer( clazz.getName(), ex.getMessage() ); return null; } - final String[] propertyNames = propertyAccessMap.keySet().toArray( new String[0] ); final Class superClass = determineAccessOptimizerSuperClass( clazz, propertyNames, getters, setters ); final String className = clazz.getName() + "$" + OPTIMIZER_PROXY_NAMING_SUFFIX + NameEncodeHelper.encodeName( propertyNames, getters, setters ); @@ -391,7 +403,7 @@ public ByteBuddyProxyHelper getByteBuddyProxyHelper() { return byteBuddyProxyHelper; } - private static void findAccessors( + private static void unwrapPropertyInfos( Class clazz, String[] getterNames, String[] setterNames, @@ -432,14 +444,17 @@ else if ( Modifier.isPrivate( setters[i].getModifiers() ) ) { } } - private static void findAccessors( + private static void unwrapPropertyInfos( Class clazz, - Map propertyAccessMap, + PropertyInfo[] propertyInfos, + String[] propertyNames, Member[] getters, Member[] setters) { int i = 0; - for ( Map.Entry entry : propertyAccessMap.entrySet() ) { - final PropertyAccess propertyAccess = entry.getValue(); + for ( PropertyInfo propertyInfo : propertyInfos ) { + final String propertyName = propertyInfo.propertyName(); + final PropertyAccess propertyAccess = propertyInfo.propertyAccess(); + propertyNames[i] = propertyName; if ( propertyAccess instanceof PropertyAccessEmbeddedImpl ) { getters[i] = EMBEDDED_MEMBER; setters[i] = EMBEDDED_MEMBER; @@ -448,14 +463,14 @@ private static void findAccessors( } final Getter getter = propertyAccess.getGetter(); if ( getter == null ) { - throw new InvalidPropertyAccessorException( "invalid getter for property [" + entry.getKey() + "]" ); + throw new InvalidPropertyAccessorException( "invalid getter for property [" + propertyName + "]" ); } final Setter setter = propertyAccess.getSetter(); if ( setter == null ) { throw new InvalidPropertyAccessorException( String.format( "cannot find a setter for [%s] on type [%s]", - entry.getKey(), + propertyName, clazz.getName() ) ); @@ -471,7 +486,7 @@ else if ( getter instanceof GetterFieldImpl getterField ) { throw new InvalidPropertyAccessorException( String.format( "cannot find a getter for [%s] on type [%s]", - entry.getKey(), + propertyName, clazz.getName() ) ); @@ -490,7 +505,7 @@ else if ( setter instanceof SetterFieldImpl setterField ) { throw new InvalidPropertyAccessorException( String.format( "cannot find a setter for [%s] on type [%s]", - entry.getKey(), + propertyName, clazz.getName() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java index 73e0b09ea1db..89fe23d10638 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/spi/BytecodeProvider.java @@ -4,6 +4,7 @@ */ package org.hibernate.bytecode.spi; +import java.util.HashMap; import java.util.Map; import org.hibernate.bytecode.enhance.spi.EnhancementContext; @@ -55,9 +56,35 @@ public interface BytecodeProvider extends Service { * @param clazz The class to be reflected upon. * @param propertyAccessMap The ordered property access map * @return The reflection optimization delegate. + * @deprecated Use {@link #getReflectionOptimizer(Class, PropertyInfo[])} instead */ + @Deprecated(forRemoval = true) @Nullable ReflectionOptimizer getReflectionOptimizer(Class clazz, Map propertyAccessMap); + /** + * Retrieve the ReflectionOptimizer delegate for this provider + * capable of generating reflection optimization components. + * + * @param clazz The class to be reflected upon. + * @param propertyInfos The ordered property infos + * @return The reflection optimization delegate. + */ + default @Nullable ReflectionOptimizer getReflectionOptimizer(Class clazz, PropertyInfo[] propertyInfos) { + final Map map = new HashMap<>(); + for ( int i = 0; i < propertyInfos.length; i++ ) { + map.put( propertyInfos[i].propertyName(), propertyInfos[i].propertyAccess() ); + } + return getReflectionOptimizer( clazz, map ); + } + + /** + * Information about a property of a class, needed for generating reflection optimizers. + * + * @param propertyName The name of the property + * @param propertyAccess The property access + */ + record PropertyInfo(String propertyName, PropertyAccess propertyAccess) {} + /** * Returns a byte code enhancer that implements the enhancements described in the supplied enhancement context. * diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java index efe75d482a05..c50b7b2e9f08 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableRepresentationStrategyPojo.java @@ -6,7 +6,7 @@ import java.util.Collection; import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.Supplier; @@ -197,15 +197,15 @@ private static ReflectionOptimizer buildReflectionOptimizer( && bootDescriptor.getCustomInstantiator() == null && bootDescriptor.getInstantiator() == null && !bootDescriptor.isPolymorphic() ) { - final Map propertyAccessMap = new LinkedHashMap<>(); - int i = 0; - for ( Property property : bootDescriptor.getProperties() ) { - propertyAccessMap.put( property.getName(), propertyAccesses[i] ); - i++; + final List properties = bootDescriptor.getProperties(); + final BytecodeProvider.PropertyInfo[] propertyInfos = new BytecodeProvider.PropertyInfo[properties.size()]; + for ( int i = 0; i < properties.size(); i++ ) { + final Property property = properties.get( i ); + propertyInfos[i] = new BytecodeProvider.PropertyInfo( property.getName(), propertyAccesses[i] ); } return creationContext.getServiceRegistry() .requireService( BytecodeProvider.class ) - .getReflectionOptimizer( bootDescriptor.getComponentClass(), propertyAccessMap ); + .getReflectionOptimizer( bootDescriptor.getComponentClass(), propertyInfos ); } else { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java index 0284d4137c5e..bdb883d47edb 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EntityRepresentationStrategyPojoStandard.java @@ -5,8 +5,9 @@ package org.hibernate.metamodel.internal; import java.lang.reflect.Method; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -59,6 +60,7 @@ public class EntityRepresentationStrategyPojoStandard implements EntityRepresent private final String identifierPropertyName; private final PropertyAccess identifierPropertyAccess; + private final BytecodeProvider.PropertyInfo[] propertyInfos; private final Map propertyAccessMap; private final EmbeddableRepresentationStrategyPojo mapsIdRepresentationStrategy; @@ -119,7 +121,8 @@ public EntityRepresentationStrategyPojoStandard( creationContext ); - propertyAccessMap = buildPropertyAccessMap( bootDescriptor ); + propertyInfos = buildPropertyInfos( bootDescriptor ); + propertyAccessMap = buildPropertyAccessMap( propertyInfos ); reflectionOptimizer = resolveReflectionOptimizer( bytecodeProvider ); instantiator = determineInstantiator( bootDescriptor, runtimeDescriptor.getEntityMetamodel() ); @@ -155,12 +158,22 @@ private ProxyFactory resolveProxyFactory( } } - private Map buildPropertyAccessMap(PersistentClass bootDescriptor) { - final Map propertyAccessMap = new LinkedHashMap<>(); - for ( Property property : bootDescriptor.getPropertyClosure() ) { - propertyAccessMap.put( property.getName(), makePropertyAccess( property ) ); + private Map buildPropertyAccessMap(BytecodeProvider.PropertyInfo[] propertyInfos) { + final Map map = new HashMap<>( propertyInfos.length ); + for ( BytecodeProvider.PropertyInfo propertyInfo : propertyInfos ) { + map.put( propertyInfo.propertyName(), propertyInfo.propertyAccess() ); } - return propertyAccessMap; + return map; + } + + private BytecodeProvider.PropertyInfo[] buildPropertyInfos(PersistentClass bootDescriptor) { + final List properties = bootDescriptor.getPropertyClosure(); + final BytecodeProvider.PropertyInfo[] propertyInfos = new BytecodeProvider.PropertyInfo[properties.size()]; + for ( int i = 0; i < properties.size(); i++ ) { + final Property property = properties.get( i ); + propertyInfos[i] = new BytecodeProvider.PropertyInfo( property.getName(), makePropertyAccess( property ) ); + } + return propertyInfos; } private EntityInstantiator determineInstantiator(PersistentClass bootDescriptor, EntityMetamodel entityMetamodel) { @@ -303,11 +316,11 @@ private static ProxyFactory instantiateProxyFactory( } private ReflectionOptimizer resolveReflectionOptimizer(BytecodeProvider bytecodeProvider) { - return bytecodeProvider.getReflectionOptimizer( mappedJtd.getJavaTypeClass(), propertyAccessMap ); + return bytecodeProvider.getReflectionOptimizer( mappedJtd.getJavaTypeClass(), propertyInfos ); } private PropertyAccess makePropertyAccess(Property bootAttributeDescriptor) { - final Class mappedClass = mappedJtd.getJavaTypeClass(); + final Class mappedClass = bootAttributeDescriptor.getPersistentClass().getMappedClass(); final String descriptorName = bootAttributeDescriptor.getName(); final var strategy = propertyAccessStrategy( bootAttributeDescriptor, mappedClass, strategySelector ); if ( strategy == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index b711e4a918a3..72922b1b19ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -4822,26 +4822,27 @@ private void inheritSupertypeSpecialAttributeMappings() { final NonIdentifierAttribute[] properties = entityMetamodel.getProperties(); final AttributeMappingsMap.Builder mappingsBuilder = AttributeMappingsMap.builder(); int fetchableIndex = getFetchableIndexOffset(); - for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { - final NonIdentifierAttribute runtimeAttributeDefinition = properties[i]; - final String attributeName = runtimeAttributeDefinition.getName(); - final Property bootProperty = bootEntityDescriptor.getProperty( attributeName ); - if ( superMappingType == null - || superMappingType.findAttributeMapping( bootProperty.getName() ) == null ) { - mappingsBuilder.put( - attributeName, - generateNonIdAttributeMapping( - runtimeAttributeDefinition, - bootProperty, - stateArrayPosition++, - fetchableIndex++, - creationProcess - ) - ); - } - declaredAttributeMappings = mappingsBuilder.build(); - // otherwise, it's defined on the supertype, skip it here + // For every property that is "owned" by this persistent class, create a declared attribute mapping + final List bootProperties = bootEntityDescriptor.getProperties(); + // EntityMetamodel uses getPropertyClosure(), which includes the properties of the super type, + // so use that property span as offset when indexing into the EntityMetamodel NonIdentifierAttribute[] + final int superclassPropertiesOffset = superMappingType == null ? 0 + : superMappingType.getEntityPersister().getEntityMetamodel().getPropertySpan(); + for ( int i = 0; i < bootProperties.size(); i++ ) { + final Property bootProperty = bootProperties.get( i ); + assert properties[superclassPropertiesOffset + i].getName().equals( bootProperty.getName() ); + mappingsBuilder.put( + bootProperty.getName(), + generateNonIdAttributeMapping( + properties[superclassPropertiesOffset + i], + bootProperty, + stateArrayPosition++, + fetchableIndex++, + creationProcess + ) + ); } + declaredAttributeMappings = mappingsBuilder.build(); } private static BeforeExecutionGenerator createVersionGenerator diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/MultipleInheritanceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/MultipleInheritanceTest.java new file mode 100644 index 000000000000..88369b715eed --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/MultipleInheritanceTest.java @@ -0,0 +1,133 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.inheritance; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import java.io.Serializable; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class MultipleInheritanceTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + CarOptional.class, + CarPart.class, + BasicCar.class, + SuperCar.class, + Car.class + }; + } + + @Test + public void test() { + inTransaction( session -> { + + Car car = new Car(); + CarPart carPart = new CarPart(); + + + CarPK id = new CarPK(); + id.carId1 = "1"; + carPart.id = id; + session.persist( carPart ); + + car.id = id; + car.parts = carPart; + ((BasicCar) car).parts = carPart; + session.persist( car ); + session.flush(); + session.clear(); + + Car loadedCar = session.find( Car.class, id ); + assertNotNull( loadedCar.parts ); + assertNotNull( ( (BasicCar) loadedCar ).parts ); + } ); + } + + @Embeddable + public static class CarPK implements Serializable { + @Column(name = "CAR_ID_1") + protected String carId1; + } + + @Entity(name = "BasicCar") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class BasicCar { + @EmbeddedId + protected CarPK id; + + @OneToOne + @JoinColumn(name = "CAR_ID_1", referencedColumnName = "CAR_ID_1", insertable = false, updatable = false) + CarPart parts; + } + + @Entity(name = "SuperCar") + public static class SuperCar extends BasicCar { + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) +// @JoinColumn(name = "CAR_ID_1", referencedColumnName = "CAR_ID_1") + private List optionals; + } + + @MappedSuperclass + public static abstract class AbstractCar extends BasicCar { + @OneToOne + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "CAR_ID_1", referencedColumnName = "CAR_ID_1", insertable = false, updatable = false) + CarPart parts ; + } + + @Entity(name = "CarPart") + public static class CarPart { + @EmbeddedId + private CarPK id; + + String name; + } + + @Entity(name = "Car") + public static class Car extends AbstractCar { + + } + + + + @Entity(name = "CarOptional") + public static class CarOptional { + + @EmbeddedId + private CarOptionalPK id; + + private String name; + + @Embeddable + public class CarOptionalPK implements Serializable { + + @Column(name = "OPTIONAL_ID1") + private String id1; + + } + } +}