Skip to content

Commit f310270

Browse files
committed
HHH-19993 Allow custom UserType constructor accepts MemberDetails as parameter
Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
1 parent 8ca24d9 commit f310270

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.setMemberDetails( memberDetails );
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
@@ -15,6 +15,7 @@
1515
import org.hibernate.Incubating;
1616
import org.hibernate.Internal;
1717
import org.hibernate.MappingException;
18+
import org.hibernate.models.spi.MemberDetails;
1819
import org.hibernate.type.TimeZoneStorageStrategy;
1920
import org.hibernate.annotations.SoftDelete;
2021
import org.hibernate.annotations.SoftDeleteType;
@@ -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 memberDetails = getMemberDetails();
10831086
resolution = new UserTypeResolution<>(
10841087
new CustomType<>(
1085-
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation ),
1088+
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation, memberDetails ),
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, MemberDetails memberDetails) {
1111+
final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation, memberDetails );
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, MemberDetails memberDetails ) {
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( MemberDetails.class );
11351138
constructor.setAccessible( true );
1136-
return constructor.newInstance( typeAnnotation );
1139+
return constructor.newInstance( memberDetails );
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+
MemberDetails.class );
1156+
constructor.setAccessible( true );
1157+
return constructor.newInstance( typeAnnotation, memberDetails );
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public abstract class SimpleValue implements KeyValue {
8989
private String typeName;
9090
private Properties typeParameters;
9191
private Annotation typeAnnotation;
92+
private MemberDetails memberDetails;
9293
private boolean isVersion;
9394
private boolean isNationalized;
9495
private boolean isLob;
@@ -135,6 +136,7 @@ protected SimpleValue(SimpleValue original) {
135136
this.typeName = original.typeName;
136137
this.typeParameters = original.typeParameters == null ? null : new Properties( original.typeParameters );
137138
this.typeAnnotation = original.typeAnnotation;
139+
this.memberDetails = original.memberDetails;
138140
this.isVersion = original.isVersion;
139141
this.isNationalized = original.isNationalized;
140142
this.isLob = original.isLob;
@@ -809,6 +811,10 @@ public void setTypeAnnotation(Annotation typeAnnotation) {
809811
this.typeAnnotation = typeAnnotation;
810812
}
811813

814+
public void setMemberDetails(MemberDetails memberDetails) {
815+
this.memberDetails = memberDetails;
816+
}
817+
812818
public Properties getTypeParameters() {
813819
return typeParameters;
814820
}
@@ -817,6 +823,10 @@ public Annotation getTypeAnnotation() {
817823
return typeAnnotation;
818824
}
819825

826+
public MemberDetails getMemberDetails() {
827+
return memberDetails;
828+
}
829+
820830
public void copyTypeFrom(SimpleValue sourceValue ) {
821831
setTypeName( sourceValue.getTypeName() );
822832
setTypeParameters( sourceValue.getTypeParameters() );
@@ -840,6 +850,7 @@ public boolean isSame(SimpleValue other) {
840850
&& Objects.equals( typeName, other.typeName )
841851
&& Objects.equals( typeParameters, other.typeParameters )
842852
&& Objects.equals( typeAnnotation, other.typeAnnotation )
853+
&& Objects.equals( memberDetails, other.memberDetails )
843854
&& Objects.equals( table, other.table )
844855
&& Objects.equals( foreignKeyName, other.foreignKeyName )
845856
&& Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition );

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.hibernate.engine.jdbc.Size;
1717
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1818
import org.hibernate.metamodel.mapping.JdbcMapping;
19+
import org.hibernate.models.spi.MemberDetails;
1920
import org.hibernate.type.descriptor.WrapperOptions;
2021
import org.hibernate.type.descriptor.jdbc.JdbcType;
2122

@@ -214,8 +215,9 @@
214215
* }
215216
* </pre>
216217
* <p>
217-
* Every implementor of {@code UserType} must be immutable and must
218-
* declare a public default constructor.
218+
* Every implementor of {@code UserType} must be immutable and could
219+
* declare a constructor accepts the {@link MemberDetails},
220+
* or the annotation type, or the annotation type and the {@link MemberDetails}.
219221
* <p>
220222
* A custom type implemented as a {@code UserType} is treated as a
221223
* 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
@@ -9,6 +9,7 @@
99
import jakarta.persistence.GeneratedValue;
1010
import jakarta.persistence.Id;
1111
import org.hibernate.annotations.Type;
12+
import org.hibernate.models.spi.MemberDetails;
1213
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
1314
import org.hibernate.testing.orm.junit.Jpa;
1415
import org.hibernate.type.descriptor.WrapperOptions;
@@ -18,6 +19,7 @@
1819
import java.io.Serializable;
1920
import java.lang.annotation.Retention;
2021
import java.lang.annotation.Target;
22+
import java.lang.reflect.Field;
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(MemberDetails memberDetails) {
162+
super( ( (Field) memberDetails.toJavaMember() ).getAnnotation( SecondTimePeriod.class ).days() );
163+
}
164+
165+
}
166+
167+
static class ThirdPeriodType extends AbstractPeriodType {
168+
169+
ThirdPeriodType(ThirdTimePeriod timePeriod, MemberDetails memberDetails) {
170+
super(timePeriod.days());
171+
if ( !timePeriod.equals( ( (Field) memberDetails.toJavaMember() ).getAnnotation( ThirdTimePeriod.class ) )) {
172+
throw new IllegalArgumentException(memberDetails.toJavaMember() + " 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)