HHH-18839 Validate property type of `@IdClass` at bind time

This commit is contained in:
Yanming Zhou 2024-11-11 12:05:22 +08:00 committed by Gavin King
parent 82060f282d
commit ff99ddbf14
2 changed files with 77 additions and 1 deletions

View File

@ -835,8 +835,9 @@ public class EmbeddableBinder {
for ( int i = 0; i < classElements.size(); i++ ) {
final PropertyData idClassPropertyData = classElements.get( i );
final String propertyName = idClassPropertyData.getPropertyName();
final PropertyData entityPropertyData =
baseClassElementsByName.get( idClassPropertyData.getPropertyName() );
baseClassElementsByName.get( propertyName );
if ( propertyHolder.isInIdClass() ) {
if ( entityPropertyData == null ) {
throw new AnnotationException(
@ -852,11 +853,38 @@ public class EmbeddableBinder {
//the annotation overriding will be dealt with by a mechanism similar to @MapsId
continue;
}
if ( !hasCompatibleType( idClassPropertyData.getTypeName(), entityPropertyData.getTypeName() ) ) {
throw new AnnotationException(
"Property '" + propertyName + "' in @IdClass '" + idClassPropertyData.getDeclaringClass().getName()
+ "' doesn't match type in entity class '" + baseInferredData.getPropertyType().getName()
+ "' (expected '" + entityPropertyData.getTypeName() + "' but was '" + idClassPropertyData.getTypeName() + "')"
);
}
}
classElements.set( i, entityPropertyData ); //this works since they are in the same order
}
}
private static boolean hasCompatibleType(String typeNameInIdClass, String typeNameInEntityClass) {
return typeNameInIdClass.equals( typeNameInEntityClass )
|| canonicalize( typeNameInIdClass ).equals( typeNameInEntityClass )
|| typeNameInIdClass.equals( canonicalize( typeNameInEntityClass ) );
}
private static String canonicalize(String typeName) {
return switch (typeName) {
case "boolean" -> Boolean.class.getName();
case "char" -> Character.class.getName();
case "int" -> Integer.class.getName();
case "long" -> Long.class.getName();
case "short" -> Short.class.getName();
case "byte" -> Byte.class.getName();
case "float" -> Float.class.getName();
case "double" -> Double.class.getName();
default -> typeName;
};
}
static Component createEmbeddable(
PropertyHolder propertyHolder,
PropertyData inferredData,

View File

@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.annotations.cid;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import org.hibernate.AnnotationException;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Yanming Zhou
*/
public class CompositeIdTypeMismatchTest {
@Test
public void test() {
try ( StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistryBuilder().build() ) {
final MetadataSources metadataSources = new MetadataSources( ssr );
metadataSources.addAnnotatedClass( TestEntity.class );
assertThatExceptionOfType( AnnotationException.class).isThrownBy( metadataSources::buildMetadata ).withMessageContaining( "doesn't match type" );
}
}
@IdClass(TestEntity.ID.class)
@Entity
static class TestEntity {
@Id
String code;
@Id
Integer type;
record ID(String code, String type) {
}
}
}