HHH-16761 Throw error for identifier properties not found in `@IdClass`

This commit is contained in:
Marco Belladelli 2023-05-29 15:46:27 +02:00
parent 4317215ee1
commit ecc7dc1880
3 changed files with 140 additions and 28 deletions

View File

@ -143,6 +143,7 @@ import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations; import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity; import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass; import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.hasIdAnnotation;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations; import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder; import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle; import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
@ -960,46 +961,61 @@ public class EntityBinder {
Set<String> idPropertiesIfIdClass, Set<String> idPropertiesIfIdClass,
ElementsToProcess elementsToProcess, ElementsToProcess elementsToProcess,
Map<XClass, InheritanceState> inheritanceStates) { Map<XClass, InheritanceState> inheritanceStates) {
final Set<String> missingIdProperties = new HashSet<>( idPropertiesIfIdClass ); final Set<String> missingIdProperties = new HashSet<>( idPropertiesIfIdClass );
final Set<String> missingEntityProperties = new HashSet<>();
for ( PropertyData propertyAnnotatedElement : elementsToProcess.getElements() ) { for ( PropertyData propertyAnnotatedElement : elementsToProcess.getElements() ) {
final String propertyName = propertyAnnotatedElement.getPropertyName(); final String propertyName = propertyAnnotatedElement.getPropertyName();
if ( !idPropertiesIfIdClass.contains( propertyName ) ) { if ( !idPropertiesIfIdClass.contains( propertyName ) ) {
boolean subclassAndSingleTableStrategy = if ( !idPropertiesIfIdClass.isEmpty() && !isIgnoreIdAnnotations()
inheritanceState.getType() == InheritanceType.SINGLE_TABLE && hasIdAnnotation( propertyAnnotatedElement.getProperty() ) ) {
&& inheritanceState.hasParents(); missingEntityProperties.add( propertyName );
processElementAnnotations( }
propertyHolder, else {
subclassAndSingleTableStrategy boolean subclassAndSingleTableStrategy =
? Nullability.FORCED_NULL inheritanceState.getType() == InheritanceType.SINGLE_TABLE
: Nullability.NO_CONSTRAINT, && inheritanceState.hasParents();
propertyAnnotatedElement, processElementAnnotations(
generators, propertyHolder,
this, subclassAndSingleTableStrategy
false, ? Nullability.FORCED_NULL
false, : Nullability.NO_CONSTRAINT,
false, propertyAnnotatedElement,
context, generators,
inheritanceStates this,
); false,
false,
false,
context,
inheritanceStates
);
}
} }
else { else {
missingIdProperties.remove( propertyName ); missingIdProperties.remove( propertyName );
} }
} }
if ( missingIdProperties.size() != 0 ) { if ( !missingIdProperties.isEmpty() ) {
final StringBuilder missings = new StringBuilder();
for ( String property : missingIdProperties ) {
if ( missings.length() > 0 ) {
missings.append(", ");
}
missings.append("'").append( property ).append( "'" );
}
throw new AnnotationException( "Entity '" + persistentClass.getEntityName() throw new AnnotationException( "Entity '" + persistentClass.getEntityName()
+ "' has an '@IdClass' with properties " + missings + "' has an '@IdClass' with properties " + getMissingPropertiesString( missingIdProperties )
+ " which do not match properties of the entity class" ); + " which do not match properties of the entity class" );
} }
else if ( !missingEntityProperties.isEmpty() ) {
throw new AnnotationException( "Entity '" + persistentClass.getEntityName()
+ "' has '@Id' annotated properties " + getMissingPropertiesString( missingEntityProperties )
+ " which do not match properties of the specified '@IdClass'" );
}
}
private static String getMissingPropertiesString(Set<String> propertyNames) {
final StringBuilder sb = new StringBuilder();
for ( String property : propertyNames ) {
if ( sb.length() > 0 ) {
sb.append( ", " );
}
sb.append( "'" ).append( property ).append( "'" );
}
return sb.toString();
} }
private static PersistentClass makePersistentClass( private static PersistentClass makePersistentClass(

View File

@ -637,7 +637,7 @@ public class PropertyBinder {
return false; return false;
} }
private static boolean hasIdAnnotation(XAnnotatedElement element) { static boolean hasIdAnnotation(XAnnotatedElement element) {
return element.isAnnotationPresent( Id.class ) return element.isAnnotationPresent( Id.class )
|| element.isAnnotationPresent( EmbeddedId.class ); || element.isAnnotationPresent( EmbeddedId.class );
} }

View File

@ -0,0 +1,96 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.idclass;
import java.io.Serializable;
import org.hibernate.AnnotationException;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.testing.orm.junit.Jira;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Marco Belladelli
*/
@Jira( "https://hibernate.atlassian.net/browse/HHH-16761" )
public class IdClassPropertiesTest {
@Test
public void testRight() {
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
final MetadataSources metadataSources = new MetadataSources( ssr )
.addAnnotatedClass( RightEntity.class );
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
}
}
@Test
public void testWrongLess() {
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
final MetadataSources metadataSources = new MetadataSources( ssr )
.addAnnotatedClass( WrongEntityLess.class );
final AnnotationException thrown = assertThrows( AnnotationException.class, metadataSources::buildMetadata );
assertTrue( thrown.getMessage().contains( "childId' belongs to an '@IdClass' but has no matching property in entity class" ) );
}
}
@Test
public void testWrongMore() {
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
final MetadataSources metadataSources = new MetadataSources( ssr )
.addAnnotatedClass( WrongEntityMore.class );
final AnnotationException thrown = assertThrows( AnnotationException.class, metadataSources::buildMetadata );
assertTrue( thrown.getMessage().contains( "'anotherId' which do not match properties of the specified '@IdClass'" ) );
}
}
public static class ParentPK implements Serializable {
private Long parentId;
}
public static class ChildPK extends ParentPK {
private String childId;
}
@Entity( name = "RightEntity" )
@IdClass( ChildPK.class )
public static class RightEntity {
@Id
private Long parentId;
@Id
private String childId;
private String nonIdProp;
}
@Entity( name = "WrongEntityLess" )
@IdClass( ChildPK.class )
public static class WrongEntityLess {
@Id
private Long parentId;
}
@Entity( name = "WrongEntityMore" )
@IdClass( ChildPK.class )
public static class WrongEntityMore {
@Id
private Long parentId;
@Id
private String childId;
@Id
private Integer anotherId;
}
}