Skip to content

Commit bd69ddf

Browse files
committed
HHH-19993 Allow custom UserType constructor accepts Member as parameter
Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
1 parent 93c4950 commit bd69ddf

File tree

5 files changed

+143
-18
lines changed

5 files changed

+143
-18
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,9 @@ else if ( aggregateComponent != null ) {
11931193
}
11941194

11951195
public void fillSimpleValue() {
1196+
1197+
basicValue.setMember( memberDetails.toJavaMember() );
1198+
11961199
basicValue.setExplicitTypeParams( explicitLocalCustomTypeParams );
11971200

11981201
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.lang.annotation.Annotation;
88
import java.lang.reflect.InvocationTargetException;
9+
import java.lang.reflect.Member;
910
import java.util.HashMap;
1011
import java.util.Map;
1112
import java.util.Properties;
@@ -85,6 +86,7 @@
8586

8687
/**
8788
* @author Steve Ebersole
89+
* @author Yanming Zhou
8890
*/
8991
public class BasicValue extends SimpleValue
9092
implements JdbcTypeIndicators, Resolvable, JpaAttributeConverterCreationContext {
@@ -1080,9 +1082,10 @@ public void setExplicitCustomType(Class<? extends UserType<?>> explicitCustomTyp
10801082
else {
10811083
final var typeProperties = getCustomTypeProperties();
10821084
final var typeAnnotation = getTypeAnnotation();
1085+
final var member = getMember();
10831086
resolution = new UserTypeResolution<>(
10841087
new CustomType<>(
1085-
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation ),
1088+
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation, member ),
10861089
getTypeConfiguration()
10871090
),
10881091
null,
@@ -1104,8 +1107,8 @@ private Properties getCustomTypeProperties() {
11041107
}
11051108

11061109
private UserType<?> getConfiguredUserTypeBean(
1107-
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation) {
1108-
final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation );
1110+
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation, Member member) {
1111+
final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation, member );
11091112

11101113
if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
11111114
configurationAware.setTypeConfiguration( getTypeConfiguration() );
@@ -1127,21 +1130,41 @@ private UserType<?> getConfiguredUserTypeBean(
11271130
}
11281131

11291132
private <T extends UserType<?>> T instantiateUserType(
1130-
Class<T> customType, Properties properties, Annotation typeAnnotation) {
1131-
if ( typeAnnotation != null ) {
1132-
// attempt to instantiate it with the annotation as a constructor argument
1133+
Class<T> customType, Properties properties, Annotation typeAnnotation, Member member ) {
1134+
try {
1135+
// attempt to instantiate it with the member as a constructor argument
11331136
try {
1134-
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() );
1137+
final var constructor = customType.getDeclaredConstructor( Member.class );
11351138
constructor.setAccessible( true );
1136-
return constructor.newInstance( typeAnnotation );
1139+
return constructor.newInstance( member );
11371140
}
1138-
catch ( NoSuchMethodException ignored ) {
1139-
// no such constructor, instantiate it the old way
1141+
catch (NoSuchMethodException ignored) {
11401142
}
1141-
catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
1142-
throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e );
1143+
1144+
if ( typeAnnotation != null ) {
1145+
// attempt to instantiate it with the annotation as a constructor argument
1146+
try {
1147+
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() );
1148+
constructor.setAccessible( true );
1149+
return constructor.newInstance( typeAnnotation );
1150+
}
1151+
catch (NoSuchMethodException ignored) {
1152+
// attempt to instantiate it with the annotation and member as constructor arguments
1153+
try {
1154+
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType(),
1155+
Member.class );
1156+
constructor.setAccessible( true );
1157+
return constructor.newInstance( typeAnnotation, member );
1158+
}
1159+
catch (NoSuchMethodException ignored_) {
1160+
// no such constructor, instantiate it the old way
1161+
}
1162+
}
11431163
}
11441164
}
1165+
catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
1166+
throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e );
1167+
}
11451168

11461169
return getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi()
11471170
? getUserTypeBean( customType, properties ).getBeanInstance()

hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.io.Serializable;
88
import java.lang.annotation.Annotation;
9+
import java.lang.reflect.Member;
910
import java.sql.Types;
1011
import java.util.ArrayList;
1112
import java.util.List;
@@ -89,6 +90,7 @@ public abstract class SimpleValue implements KeyValue {
8990
private String typeName;
9091
private Properties typeParameters;
9192
private Annotation typeAnnotation;
93+
private Member member;
9294
private boolean isVersion;
9395
private boolean isNationalized;
9496
private boolean isLob;
@@ -135,6 +137,7 @@ protected SimpleValue(SimpleValue original) {
135137
this.typeName = original.typeName;
136138
this.typeParameters = original.typeParameters == null ? null : new Properties( original.typeParameters );
137139
this.typeAnnotation = original.typeAnnotation;
140+
this.member = original.member;
138141
this.isVersion = original.isVersion;
139142
this.isNationalized = original.isNationalized;
140143
this.isLob = original.isLob;
@@ -804,6 +807,10 @@ public void setTypeAnnotation(Annotation typeAnnotation) {
804807
this.typeAnnotation = typeAnnotation;
805808
}
806809

810+
public void setMember(Member member) {
811+
this.member = member;
812+
}
813+
807814
public Properties getTypeParameters() {
808815
return typeParameters;
809816
}
@@ -812,6 +819,10 @@ public Annotation getTypeAnnotation() {
812819
return typeAnnotation;
813820
}
814821

822+
public Member getMember() {
823+
return member;
824+
}
825+
815826
public void copyTypeFrom(SimpleValue sourceValue ) {
816827
setTypeName( sourceValue.getTypeName() );
817828
setTypeParameters( sourceValue.getTypeParameters() );
@@ -835,6 +846,7 @@ public boolean isSame(SimpleValue other) {
835846
&& Objects.equals( typeName, other.typeName )
836847
&& Objects.equals( typeParameters, other.typeParameters )
837848
&& Objects.equals( typeAnnotation, other.typeAnnotation )
849+
&& Objects.equals( member, other.member )
838850
&& Objects.equals( table, other.table )
839851
&& Objects.equals( foreignKeyName, other.foreignKeyName )
840852
&& Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition );

hibernate-core/src/main/java/org/hibernate/usertype/UserType.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@
214214
* }
215215
* </pre>
216216
* <p>
217-
* Every implementor of {@code UserType} must be immutable and must
218-
* declare a public default constructor.
217+
* Every implementor of {@code UserType} must be immutable and could
218+
* declare a constructor accepts the {@link java.lang.reflect.Member},
219+
* or the annotation type, or the annotation type and the member.
219220
* <p>
220221
* A custom type implemented as a {@code UserType} is treated as a
221222
* non-composite value, and does not have persistent attributes which

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/MetaUserTypeTest.java

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.io.Serializable;
1919
import java.lang.annotation.Retention;
2020
import java.lang.annotation.Target;
21+
import java.lang.reflect.Field;
22+
import java.lang.reflect.Member;
2123
import java.sql.PreparedStatement;
2224
import java.sql.ResultSet;
2325
import java.sql.SQLException;
@@ -33,7 +35,8 @@
3335
import static java.sql.Types.VARCHAR;
3436
import static org.junit.jupiter.api.Assertions.assertEquals;
3537

36-
@Jpa(annotatedClasses = {MetaUserTypeTest.Thing.class, MetaUserTypeTest.Things.class})
38+
@Jpa(annotatedClasses = {MetaUserTypeTest.Thing.class, MetaUserTypeTest.SecondThing.class,
39+
MetaUserTypeTest.ThirdThing.class, MetaUserTypeTest.Things.class})
3740
public class MetaUserTypeTest {
3841

3942
@Test void test(EntityManagerFactoryScope scope) {
@@ -48,6 +51,30 @@ public class MetaUserTypeTest {
4851
assertEquals( Period.of( 1, 2, 3 ), thing.period );
4952
assertEquals( Period.ofDays( 42 ), thing.days );
5053
} );
54+
55+
scope.inTransaction( em -> {
56+
SecondThing thing = new SecondThing();
57+
thing.period = Period.of( 1, 2, 3 );
58+
thing.days = Period.ofDays( 42 );
59+
em.persist( thing );
60+
} );
61+
scope.inTransaction( em -> {
62+
SecondThing thing = em.find( SecondThing.class, 1 );
63+
assertEquals( Period.of( 1, 2, 3 ), thing.period );
64+
assertEquals( Period.ofDays( 42 ), thing.days );
65+
} );
66+
67+
scope.inTransaction( em -> {
68+
ThirdThing thing = new ThirdThing();
69+
thing.period = Period.of( 1, 2, 3 );
70+
thing.days = Period.ofDays( 42 );
71+
em.persist( thing );
72+
} );
73+
scope.inTransaction( em -> {
74+
ThirdThing thing = em.find( ThirdThing.class, 1 );
75+
assertEquals( Period.of( 1, 2, 3 ), thing.period );
76+
assertEquals( Period.ofDays( 42 ), thing.days );
77+
} );
5178
}
5279

5380
@Test void testCollection(EntityManagerFactoryScope scope) {
@@ -73,6 +100,24 @@ public class MetaUserTypeTest {
73100
Period days;
74101
}
75102

103+
@Entity static class SecondThing {
104+
@Id @GeneratedValue
105+
long id;
106+
@SecondTimePeriod
107+
Period period;
108+
@SecondTimePeriod(days = true)
109+
Period days;
110+
}
111+
112+
@Entity static class ThirdThing {
113+
@Id @GeneratedValue
114+
long id;
115+
@ThirdTimePeriod
116+
Period period;
117+
@ThirdTimePeriod(days = true)
118+
Period days;
119+
}
120+
76121
@Entity static class Things {
77122
@Id @GeneratedValue
78123
long id;
@@ -89,11 +134,52 @@ public class MetaUserTypeTest {
89134
boolean days() default false;
90135
}
91136

92-
static class PeriodType implements UserType<Period> {
93-
private final boolean days;
137+
@Type(SecondPeriodType.class)
138+
@Target({METHOD, FIELD})
139+
@Retention(RUNTIME)
140+
public @interface SecondTimePeriod {
141+
boolean days() default false;
142+
}
143+
144+
@Type(ThirdPeriodType.class)
145+
@Target({METHOD, FIELD})
146+
@Retention(RUNTIME)
147+
public @interface ThirdTimePeriod {
148+
boolean days() default false;
149+
}
150+
151+
static class PeriodType extends AbstractPeriodType {
94152

95153
PeriodType(TimePeriod timePeriod) {
96-
days = timePeriod.days();
154+
super(timePeriod.days());
155+
}
156+
157+
}
158+
159+
static class SecondPeriodType extends AbstractPeriodType {
160+
161+
SecondPeriodType(Member member) {
162+
super( ( (Field) member ).getAnnotation( SecondTimePeriod.class ).days() );
163+
}
164+
165+
}
166+
167+
static class ThirdPeriodType extends AbstractPeriodType {
168+
169+
ThirdPeriodType(ThirdTimePeriod timePeriod, Member member) {
170+
super(timePeriod.days());
171+
if ( !timePeriod.equals( ( (Field) member ).getAnnotation( ThirdTimePeriod.class ) )) {
172+
throw new IllegalArgumentException(member + " should be annotated with " + timePeriod);
173+
}
174+
}
175+
176+
}
177+
178+
static abstract class AbstractPeriodType implements UserType<Period> {
179+
private final boolean days;
180+
181+
AbstractPeriodType(boolean days) {
182+
this.days = days;
97183
}
98184

99185
@Override

0 commit comments

Comments
 (0)