Skip to content

Commit c959c0b

Browse files
authored
HHH-19140 Reproducer test and issue fix (#9758)
* HHH-19140 Add test case * HHH-19140 Fix for issue
1 parent d776191 commit c959c0b

File tree

2 files changed

+157
-86
lines changed

2 files changed

+157
-86
lines changed

hibernate-core/src/main/java/org/hibernate/property/access/internal/AccessStrategyHelper.java

Lines changed: 4 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44
*/
55
package org.hibernate.property.access.internal;
66

7-
import java.beans.Introspector;
87
import java.lang.reflect.AnnotatedElement;
98
import java.lang.reflect.Field;
109
import java.lang.reflect.Method;
1110
import java.lang.reflect.Modifier;
12-
import java.util.Locale;
1311

14-
import org.hibernate.MappingException;
1512
import org.hibernate.PropertyNotFoundException;
1613
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
1714
import org.hibernate.engine.spi.CompositeOwner;
@@ -31,6 +28,7 @@
3128
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptableType;
3229
import static org.hibernate.internal.util.ReflectHelper.NO_PARAM_SIGNATURE;
3330
import static org.hibernate.internal.util.ReflectHelper.findField;
31+
import static org.hibernate.internal.util.ReflectHelper.getterMethodOrNull;
3432
import static org.hibernate.internal.util.ReflectHelper.isRecord;
3533

3634
/**
@@ -85,89 +83,9 @@ public static AccessType getAccessType(Class<?> containerJavaType, String proper
8583
return AccessType.FIELD;
8684
}
8785

88-
for ( Method method : containerClass.getDeclaredMethods() ) {
89-
// if the method has parameters, skip it
90-
if ( method.getParameterCount() != 0 ) {
91-
continue;
92-
}
93-
94-
// if the method is a "bridge", skip it
95-
if ( method.isBridge() ) {
96-
continue;
97-
}
98-
99-
if ( method.isAnnotationPresent( Transient.class ) ) {
100-
continue;
101-
}
102-
103-
if ( Modifier.isStatic( method.getModifiers() ) ) {
104-
continue;
105-
}
106-
107-
final String methodName = method.getName();
108-
109-
// try "get"
110-
if ( methodName.startsWith( "get" ) ) {
111-
final String stemName = methodName.substring( 3 );
112-
final String decapitalizedStemName = Introspector.decapitalize( stemName );
113-
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
114-
if ( method.isAnnotationPresent( Access.class ) ) {
115-
return AccessType.PROPERTY;
116-
}
117-
else {
118-
checkIsMethodVariant( containerClass, propertyName, method, stemName );
119-
}
120-
}
121-
}
122-
123-
// if not "get", then try "is"
124-
if ( methodName.startsWith( "is" ) ) {
125-
final String stemName = methodName.substring( 2 );
126-
String decapitalizedStemName = Introspector.decapitalize( stemName );
127-
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
128-
if ( method.isAnnotationPresent( Access.class ) ) {
129-
return AccessType.PROPERTY;
130-
}
131-
}
132-
}
133-
}
134-
135-
return null;
136-
}
137-
138-
private static void checkIsMethodVariant(
139-
Class<?> containerClass,
140-
String propertyName,
141-
Method method,
142-
String stemName) {
143-
final Method isMethodVariant = findIsMethodVariant( containerClass, stemName );
144-
if ( isMethodVariant == null ) {
145-
return;
146-
}
147-
148-
if ( !isMethodVariant.isAnnotationPresent( Access.class ) ) {
149-
throw new MappingException(
150-
String.format(
151-
Locale.ROOT,
152-
"Class '%s' declares both 'get' [%s] and 'is' [%s] variants of getter for property '%s'",
153-
containerClass.getName(),
154-
method.toString(),
155-
isMethodVariant,
156-
propertyName
157-
)
158-
);
159-
}
160-
}
161-
162-
public static @Nullable Method findIsMethodVariant(Class<?> containerClass, String stemName) {
163-
// verify that the Class does not also define a method with the same stem name with 'is'
164-
try {
165-
final Method isMethod = containerClass.getDeclaredMethod( "is" + stemName );
166-
if ( !Modifier.isStatic( isMethod.getModifiers() ) && isMethod.getAnnotation( Transient.class ) == null ) {
167-
return isMethod;
168-
}
169-
}
170-
catch (NoSuchMethodException ignore) {
86+
final Method getter = getterMethodOrNull( containerClass, propertyName );
87+
if ( getter != null && getter.isAnnotationPresent( Access.class ) ) {
88+
return AccessType.PROPERTY;
17189
}
17290

17391
return null;
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.bytecode.enhancement.access;
6+
7+
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
8+
import org.hibernate.testing.orm.junit.DomainModel;
9+
import org.hibernate.testing.orm.junit.JiraKey;
10+
import org.hibernate.testing.orm.junit.SessionFactory;
11+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
12+
import org.junit.jupiter.api.AfterEach;
13+
import org.junit.jupiter.api.Test;
14+
15+
import jakarta.persistence.Access;
16+
import jakarta.persistence.AccessType;
17+
import jakarta.persistence.Basic;
18+
import jakarta.persistence.DiscriminatorColumn;
19+
import jakarta.persistence.DiscriminatorValue;
20+
import jakarta.persistence.Entity;
21+
import jakarta.persistence.Id;
22+
import jakarta.persistence.Inheritance;
23+
import jakarta.persistence.Table;
24+
import jakarta.persistence.Transient;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
@DomainModel(
29+
annotatedClasses = {
30+
HierarchyPropertyAccessTest.ChildEntity.class,
31+
}
32+
)
33+
@SessionFactory
34+
@JiraKey("HHH-19140")
35+
@BytecodeEnhanced
36+
public class HierarchyPropertyAccessTest {
37+
38+
39+
@Test
40+
public void testParent(SessionFactoryScope scope) {
41+
scope.inTransaction( session -> {
42+
session.persist( new ParentEntity( 1L, "field", "transient: property" ) );
43+
} );
44+
45+
scope.inTransaction( session -> {
46+
ParentEntity entity = session.get( ParentEntity.class, 1L );
47+
assertThat( entity.persistProperty ).isEqualTo( "property" );
48+
assertThat( entity.property ).isEqualTo( "transient: property" );
49+
50+
entity.setProperty( "transient: updated" );
51+
} );
52+
53+
scope.inTransaction( session -> {
54+
ParentEntity entity = session.get( ParentEntity.class, 1L );
55+
assertThat( entity.persistProperty ).isEqualTo( "updated" );
56+
assertThat( entity.property ).isEqualTo( "transient: updated" );
57+
} );
58+
}
59+
60+
@Test
61+
public void testChild(SessionFactoryScope scope) {
62+
scope.inTransaction( session -> {
63+
session.persist( new ChildEntity( 2L, "field", "transient: property" ) );
64+
} );
65+
66+
scope.inTransaction( session -> {
67+
ChildEntity entity = session.get( ChildEntity.class, 2L );
68+
assertThat( entity.persistProperty ).isEqualTo( "property" );
69+
assertThat( entity.property ).isEqualTo( "transient: property" );
70+
71+
entity.setProperty( "transient: updated" );
72+
} );
73+
74+
scope.inTransaction( session -> {
75+
ChildEntity entity = session.get( ChildEntity.class, 2L );
76+
assertThat( entity.persistProperty ).isEqualTo( "updated" );
77+
assertThat( entity.property ).isEqualTo( "transient: updated" );
78+
} );
79+
}
80+
81+
@AfterEach
82+
public void cleanup(SessionFactoryScope scope) {
83+
scope.inTransaction( session -> {
84+
ParentEntity parentEntity = session.get( ParentEntity.class, 1L );
85+
if (parentEntity != null) {
86+
session.remove( parentEntity );
87+
}
88+
ChildEntity childEntity = session.get( ChildEntity.class, 2L );
89+
if (childEntity != null) {
90+
session.remove( childEntity );
91+
}
92+
} );
93+
}
94+
95+
@Entity
96+
@Table(name = "PARENT_ENTITY")
97+
@Inheritance
98+
@DiscriminatorColumn(name = "type")
99+
@DiscriminatorValue("Parent")
100+
static class ParentEntity {
101+
@Id
102+
Long id;
103+
104+
@Basic
105+
String field;
106+
107+
String persistProperty;
108+
109+
@Transient
110+
String property;
111+
112+
public ParentEntity() {
113+
}
114+
115+
public ParentEntity(Long id, String field, String property) {
116+
this.id = id;
117+
this.field = field;
118+
this.property = property;
119+
}
120+
121+
@Basic
122+
@Access(AccessType.PROPERTY)
123+
public String getPersistProperty() {
124+
this.persistProperty = this.property.substring( 11 );
125+
return this.persistProperty;
126+
}
127+
128+
public void setPersistProperty(String persistProperty) {
129+
this.property = "transient: " + persistProperty;
130+
this.persistProperty = persistProperty;
131+
}
132+
133+
public String getProperty() {
134+
return this.property;
135+
}
136+
137+
public void setProperty(String property) {
138+
this.property = property;
139+
}
140+
}
141+
142+
@Entity
143+
@DiscriminatorValue("Child")
144+
static class ChildEntity extends ParentEntity {
145+
146+
public ChildEntity() {
147+
}
148+
149+
public ChildEntity(Long id, String field, String property) {
150+
super(id, field, property);
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)