Skip to content

Commit ecc7dc1

Browse files
committed
HHH-16761 Throw error for identifier properties not found in @IdClass
1 parent 4317215 commit ecc7dc1

File tree

3 files changed

+140
-28
lines changed

3 files changed

+140
-28
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
144144
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
145145
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
146+
import static org.hibernate.boot.model.internal.PropertyBinder.hasIdAnnotation;
146147
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
147148
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
148149
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
@@ -960,46 +961,61 @@ private void processIdPropertiesIfNotAlready(
960961
Set<String> idPropertiesIfIdClass,
961962
ElementsToProcess elementsToProcess,
962963
Map<XClass, InheritanceState> inheritanceStates) {
963-
964964
final Set<String> missingIdProperties = new HashSet<>( idPropertiesIfIdClass );
965+
final Set<String> missingEntityProperties = new HashSet<>();
965966
for ( PropertyData propertyAnnotatedElement : elementsToProcess.getElements() ) {
966967
final String propertyName = propertyAnnotatedElement.getPropertyName();
967968
if ( !idPropertiesIfIdClass.contains( propertyName ) ) {
968-
boolean subclassAndSingleTableStrategy =
969-
inheritanceState.getType() == InheritanceType.SINGLE_TABLE
970-
&& inheritanceState.hasParents();
971-
processElementAnnotations(
972-
propertyHolder,
973-
subclassAndSingleTableStrategy
974-
? Nullability.FORCED_NULL
975-
: Nullability.NO_CONSTRAINT,
976-
propertyAnnotatedElement,
977-
generators,
978-
this,
979-
false,
980-
false,
981-
false,
982-
context,
983-
inheritanceStates
984-
);
969+
if ( !idPropertiesIfIdClass.isEmpty() && !isIgnoreIdAnnotations()
970+
&& hasIdAnnotation( propertyAnnotatedElement.getProperty() ) ) {
971+
missingEntityProperties.add( propertyName );
972+
}
973+
else {
974+
boolean subclassAndSingleTableStrategy =
975+
inheritanceState.getType() == InheritanceType.SINGLE_TABLE
976+
&& inheritanceState.hasParents();
977+
processElementAnnotations(
978+
propertyHolder,
979+
subclassAndSingleTableStrategy
980+
? Nullability.FORCED_NULL
981+
: Nullability.NO_CONSTRAINT,
982+
propertyAnnotatedElement,
983+
generators,
984+
this,
985+
false,
986+
false,
987+
false,
988+
context,
989+
inheritanceStates
990+
);
991+
}
985992
}
986993
else {
987994
missingIdProperties.remove( propertyName );
988995
}
989996
}
990997

991-
if ( missingIdProperties.size() != 0 ) {
992-
final StringBuilder missings = new StringBuilder();
993-
for ( String property : missingIdProperties ) {
994-
if ( missings.length() > 0 ) {
995-
missings.append(", ");
996-
}
997-
missings.append("'").append( property ).append( "'" );
998-
}
998+
if ( !missingIdProperties.isEmpty() ) {
999999
throw new AnnotationException( "Entity '" + persistentClass.getEntityName()
1000-
+ "' has an '@IdClass' with properties " + missings
1000+
+ "' has an '@IdClass' with properties " + getMissingPropertiesString( missingIdProperties )
10011001
+ " which do not match properties of the entity class" );
10021002
}
1003+
else if ( !missingEntityProperties.isEmpty() ) {
1004+
throw new AnnotationException( "Entity '" + persistentClass.getEntityName()
1005+
+ "' has '@Id' annotated properties " + getMissingPropertiesString( missingEntityProperties )
1006+
+ " which do not match properties of the specified '@IdClass'" );
1007+
}
1008+
}
1009+
1010+
private static String getMissingPropertiesString(Set<String> propertyNames) {
1011+
final StringBuilder sb = new StringBuilder();
1012+
for ( String property : propertyNames ) {
1013+
if ( sb.length() > 0 ) {
1014+
sb.append( ", " );
1015+
}
1016+
sb.append( "'" ).append( property ).append( "'" );
1017+
}
1018+
return sb.toString();
10031019
}
10041020

10051021
private static PersistentClass makePersistentClass(

hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ else if ( property.isAnnotationPresent( JoinColumns.class ) ) {
637637
return false;
638638
}
639639

640-
private static boolean hasIdAnnotation(XAnnotatedElement element) {
640+
static boolean hasIdAnnotation(XAnnotatedElement element) {
641641
return element.isAnnotationPresent( Id.class )
642642
|| element.isAnnotationPresent( EmbeddedId.class );
643643
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.orm.test.idclass;
8+
9+
import java.io.Serializable;
10+
11+
import org.hibernate.AnnotationException;
12+
import org.hibernate.boot.MetadataSources;
13+
import org.hibernate.boot.registry.StandardServiceRegistry;
14+
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
15+
16+
import org.hibernate.testing.orm.junit.Jira;
17+
import org.junit.jupiter.api.Test;
18+
19+
import jakarta.persistence.Entity;
20+
import jakarta.persistence.Id;
21+
import jakarta.persistence.IdClass;
22+
23+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
24+
import static org.junit.jupiter.api.Assertions.assertThrows;
25+
import static org.junit.jupiter.api.Assertions.assertTrue;
26+
27+
/**
28+
* @author Marco Belladelli
29+
*/
30+
@Jira( "https://hibernate.atlassian.net/browse/HHH-16761" )
31+
public class IdClassPropertiesTest {
32+
@Test
33+
public void testRight() {
34+
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
35+
final MetadataSources metadataSources = new MetadataSources( ssr )
36+
.addAnnotatedClass( RightEntity.class );
37+
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
38+
}
39+
}
40+
41+
@Test
42+
public void testWrongLess() {
43+
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
44+
final MetadataSources metadataSources = new MetadataSources( ssr )
45+
.addAnnotatedClass( WrongEntityLess.class );
46+
final AnnotationException thrown = assertThrows( AnnotationException.class, metadataSources::buildMetadata );
47+
assertTrue( thrown.getMessage().contains( "childId' belongs to an '@IdClass' but has no matching property in entity class" ) );
48+
}
49+
}
50+
51+
@Test
52+
public void testWrongMore() {
53+
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
54+
final MetadataSources metadataSources = new MetadataSources( ssr )
55+
.addAnnotatedClass( WrongEntityMore.class );
56+
final AnnotationException thrown = assertThrows( AnnotationException.class, metadataSources::buildMetadata );
57+
assertTrue( thrown.getMessage().contains( "'anotherId' which do not match properties of the specified '@IdClass'" ) );
58+
}
59+
}
60+
61+
public static class ParentPK implements Serializable {
62+
private Long parentId;
63+
}
64+
65+
public static class ChildPK extends ParentPK {
66+
private String childId;
67+
}
68+
69+
@Entity( name = "RightEntity" )
70+
@IdClass( ChildPK.class )
71+
public static class RightEntity {
72+
@Id
73+
private Long parentId;
74+
@Id
75+
private String childId;
76+
private String nonIdProp;
77+
}
78+
79+
@Entity( name = "WrongEntityLess" )
80+
@IdClass( ChildPK.class )
81+
public static class WrongEntityLess {
82+
@Id
83+
private Long parentId;
84+
}
85+
86+
@Entity( name = "WrongEntityMore" )
87+
@IdClass( ChildPK.class )
88+
public static class WrongEntityMore {
89+
@Id
90+
private Long parentId;
91+
@Id
92+
private String childId;
93+
@Id
94+
private Integer anotherId;
95+
}
96+
}

0 commit comments

Comments
 (0)