HHH-18829 Main:

- Hibernate Processor - create ID class in meta data class if entity class with more than
          one ID component is not annotated with @IdClass; property order must be preserved
    - Hibernate Core - if entity class has more than one ID component, but is not annotated with @IdClass
          entity binder should try to use ID class from metamodel if generated
This commit is contained in:
Čedomir Igaly 2024-11-08 14:00:00 +01:00 committed by Gavin King
parent b9351cffc6
commit 445f95fb62
5 changed files with 137 additions and 4 deletions

View File

@ -481,10 +481,22 @@ public class EntityBinder {
final ClassDetails classWithIdClass = inheritanceState.getClassWithIdClass( false ); final ClassDetails classWithIdClass = inheritanceState.getClassWithIdClass( false );
if ( classWithIdClass != null ) { if ( classWithIdClass != null ) {
final IdClass idClassAnn = classWithIdClass.getDirectAnnotationUsage( IdClass.class ); final IdClass idClassAnn = classWithIdClass.getDirectAnnotationUsage( IdClass.class );
final Class<?> idClassValue = idClassAnn.value(); final ClassDetails compositeClass;
final ClassDetails compositeClass = if ( idClassAnn == null ) {
getMetadataCollector().getSourceModelBuildingContext().getClassDetailsRegistry() try {
.resolveClassDetails( idClassValue.getName() ); compositeClass = getMetadataCollector().getSourceModelBuildingContext()
.getClassDetailsRegistry()
.resolveClassDetails( inheritanceState.getClassDetails().getClassName() + "_$Id" );
}
catch (RuntimeException e) {
return false;
}
}
else {
final Class<?> idClassValue = idClassAnn.value();
compositeClass = getMetadataCollector().getSourceModelBuildingContext()
.getClassDetailsRegistry().resolveClassDetails( idClassValue.getName() );
}
final TypeDetails compositeType = new ClassTypeDetailsImpl( compositeClass, TypeDetails.Kind.CLASS ); final TypeDetails compositeType = new ClassTypeDetailsImpl( compositeClass, TypeDetails.Kind.CLASS );
final TypeDetails classWithIdType = new ClassTypeDetailsImpl( classWithIdClass, TypeDetails.Kind.CLASS ); final TypeDetails classWithIdType = new ClassTypeDetailsImpl( classWithIdClass, TypeDetails.Kind.CLASS );

View File

@ -7,6 +7,7 @@ package org.hibernate.boot.model.internal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.AccessType;
@ -183,6 +184,13 @@ public class InheritanceState {
return classDetails; return classDetails;
} }
else { else {
final long count = Stream.concat(
classDetails.getFields().stream(),
classDetails.getMethods().stream()
).filter( t -> t.hasDirectAnnotationUsage( Id.class ) ).count();
if ( count > 1 ) {
return classDetails;
}
final InheritanceState state = getSuperclassInheritanceState( classDetails, inheritanceStatePerClass ); final InheritanceState state = getSuperclassInheritanceState( classDetails, inheritanceStatePerClass );
if ( state != null ) { if ( state != null ) {
return state.getClassWithIdClass( true ); return state.getClassWithIdClass( true );

View File

@ -108,6 +108,8 @@ import static org.hibernate.processor.util.TypeUtils.propertyName;
*/ */
public class AnnotationMetaEntity extends AnnotationMeta { public class AnnotationMetaEntity extends AnnotationMeta {
private static final String ID_CLASS_MEMBER_NAME = "<ID_CLASS>";
private final ImportContext importContext; private final ImportContext importContext;
private final TypeElement element; private final TypeElement element;
private final Map<String, MetaAttribute> members; private final Map<String, MetaAttribute> members;
@ -438,6 +440,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
addPersistentMembers( fieldsOfClass, AccessType.FIELD ); addPersistentMembers( fieldsOfClass, AccessType.FIELD );
addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY ); addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY );
addIdClassIfNeeded( fieldsOfClass, gettersAndSettersOfClass );
} }
addAuxiliaryMembers(); addAuxiliaryMembers();
@ -451,6 +455,33 @@ public class AnnotationMetaEntity extends AnnotationMeta {
initialized = true; initialized = true;
} }
private void addIdClassIfNeeded(List<? extends Element> fields, List<? extends Element> methods) {
if ( hasAnnotation( element, ID_CLASS ) ) {
return;
}
final List<MetaAttribute> components = new ArrayList<>();
for ( Element field : fields ) {
if ( hasAnnotation( field, ID ) && isPersistent( field, AccessType.FIELD ) ) {
final String propertyName = propertyName( this, field );
if ( members.containsKey( propertyName ) ) {
components.add( members.get( propertyName ) );
}
}
}
for ( Element method : methods ) {
if ( hasAnnotation( method, ID ) && isPersistent( method, AccessType.PROPERTY ) ) {
final String propertyName = propertyName( this, method );
if ( members.containsKey( propertyName ) ) {
components.add( members.get( propertyName ) );
}
}
}
if ( components.size() < 2 ) {
return;
}
putMember( ID_CLASS_MEMBER_NAME, new IdClassMetaAttribute( this, components ) );
}
private boolean checkEntities(List<ExecutableElement> lifecycleMethods) { private boolean checkEntities(List<ExecutableElement> lifecycleMethods) {
boolean foundPersistenceEntity = false; boolean foundPersistenceEntity = false;
VariableElement nonPersistenceParameter = null; VariableElement nonPersistenceParameter = null;

View File

@ -4,11 +4,15 @@
*/ */
package org.hibernate.processor.annotation; package org.hibernate.processor.annotation;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import org.hibernate.processor.model.MetaSingleAttribute; import org.hibernate.processor.model.MetaSingleAttribute;
import org.hibernate.processor.util.Constants; import org.hibernate.processor.util.Constants;
import java.util.ArrayList;
import java.util.List;
/** /**
* @author Max Andersen * @author Max Andersen
* @author Hardy Ferentschik * @author Hardy Ferentschik
@ -24,4 +28,9 @@ public class AnnotationMetaSingleAttribute extends AnnotationMetaAttribute imple
public final String getMetaType() { public final String getMetaType() {
return Constants.SINGULAR_ATTRIBUTE; return Constants.SINGULAR_ATTRIBUTE;
} }
@Override
public List<AnnotationMirror> inheritedAnnotations() {
return new ArrayList<>(element.getAnnotationMirrors());
}
} }

View File

@ -0,0 +1,73 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.processor.annotation;
import org.hibernate.processor.model.MetaAttribute;
import org.hibernate.processor.model.Metamodel;
import java.util.List;
public class IdClassMetaAttribute implements MetaAttribute {
private final Metamodel parent;
private final List<MetaAttribute> components;
public IdClassMetaAttribute(Metamodel parent, List<MetaAttribute> components) {
this.parent = parent;
this.components = components;
}
@Override
public boolean hasTypedAttribute() {
return true;
}
@Override
public boolean hasStringAttribute() {
return false;
}
@Override
public String getAttributeDeclarationString() {
final StringBuilder decl = new StringBuilder()
.append("\n/**\n * Static ID class for {@link ")
.append( parent.getQualifiedName() )
.append( "}\n **/\n" )
.append( "public record Id" );
String delimiter = "(";
for ( MetaAttribute component : components ) {
decl.append( delimiter ).append( parent.importType( component.getTypeDeclaration() ) )
.append( ' ' ).append( component.getPropertyName() );
delimiter = ", ";
}
return decl.append( ") {}" ).toString();
}
@Override
public String getAttributeNameDeclarationString() {
return "";
}
@Override
public String getMetaType() {
return "";
}
@Override
public String getPropertyName() {
return "";
}
@Override
public String getTypeDeclaration() {
return "";
}
@Override
public Metamodel getHostingEntity() {
return parent;
}
}