Skip to content

Commit e678a5b

Browse files
committed
HHH-9955 - EnumType mapped via hbm.xml not always resolved properly as far as named/ordinal
1 parent 6bb8d03 commit e678a5b

File tree

1 file changed

+149
-191
lines changed

1 file changed

+149
-191
lines changed

hibernate-core/src/main/java/org/hibernate/type/EnumType.java

Lines changed: 149 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.sql.ResultSet;
1313
import java.sql.SQLException;
1414
import java.sql.Types;
15+
import java.util.Locale;
1516
import java.util.Properties;
1617
import javax.persistence.Enumerated;
1718
import javax.persistence.MapKeyEnumerated;
@@ -61,138 +62,17 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType,Logg
6162

6263
private Class<? extends Enum> enumClass;
6364
private EnumValueMapper enumValueMapper;
64-
private int sqlType = Types.INTEGER; // before any guessing
65-
66-
@Override
67-
public int[] sqlTypes() {
68-
return new int[] { sqlType };
69-
}
70-
71-
@Override
72-
public Class<? extends Enum> returnedClass() {
73-
return enumClass;
74-
}
75-
76-
@Override
77-
public boolean equals(Object x, Object y) throws HibernateException {
78-
return x == y;
79-
}
80-
81-
@Override
82-
public int hashCode(Object x) throws HibernateException {
83-
return x == null ? 0 : x.hashCode();
84-
}
85-
86-
@Override
87-
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
88-
if ( enumValueMapper == null ) {
89-
resolveEnumValueMapper( rs, names[0] );
90-
}
91-
return enumValueMapper.getValue( rs, names );
92-
}
93-
94-
private void resolveEnumValueMapper(ResultSet rs, String name) {
95-
if ( enumValueMapper == null ) {
96-
try {
97-
resolveEnumValueMapper( rs.getMetaData().getColumnType( rs.findColumn( name ) ) );
98-
}
99-
catch (Exception e) {
100-
// because some drivers do not implement this
101-
LOG.debugf(
102-
"JDBC driver threw exception calling java.sql.ResultSetMetaData.getColumnType; " +
103-
"using fallback determination [%s] : %s",
104-
enumClass.getName(),
105-
e.getMessage()
106-
);
107-
// peek at the result value to guess type (this is legacy behavior)
108-
try {
109-
Object value = rs.getObject( name );
110-
if ( Number.class.isInstance( value ) ) {
111-
treatAsOrdinal();
112-
}
113-
else {
114-
treatAsNamed();
115-
}
116-
}
117-
catch (SQLException ignore) {
118-
treatAsOrdinal();
119-
}
120-
}
121-
}
122-
}
123-
124-
private void resolveEnumValueMapper(int columnType) {
125-
// fallback for cases where not enough parameter/parameterization information was passed in
126-
if ( isOrdinal( columnType ) ) {
127-
treatAsOrdinal();
128-
}
129-
else {
130-
treatAsNamed();
131-
}
132-
}
133-
134-
@Override
135-
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
136-
if ( enumValueMapper == null ) {
137-
resolveEnumValueMapper( st, index );
138-
}
139-
enumValueMapper.setValue( st, (Enum) value, index );
140-
}
141-
142-
private void resolveEnumValueMapper(PreparedStatement st, int index) {
143-
if ( enumValueMapper == null ) {
144-
try {
145-
resolveEnumValueMapper( st.getParameterMetaData().getParameterType( index ) );
146-
}
147-
catch (Exception e) {
148-
// because some drivers do not implement this
149-
LOG.debugf(
150-
"JDBC driver threw exception calling java.sql.ParameterMetaData#getParameterType; " +
151-
"falling back to ordinal-based enum mapping [%s] : %s",
152-
enumClass.getName(),
153-
e.getMessage()
154-
);
155-
// Originally, this was simply treatAsOrdinal(). But, for DBs that do not implement the above, enums
156-
// were treated as ordinal even when the *.hbm.xml explicitly define the type sqlCode. By default,
157-
// this is essentially the same anyway, since sqlType is defaulted to Integer.
158-
resolveEnumValueMapper( sqlType );
159-
}
160-
}
161-
}
162-
163-
@Override
164-
public Object deepCopy(Object value) throws HibernateException {
165-
return value;
166-
}
167-
168-
@Override
169-
public boolean isMutable() {
170-
return false;
171-
}
172-
173-
@Override
174-
public Serializable disassemble(Object value) throws HibernateException {
175-
return ( Serializable ) value;
176-
}
177-
178-
@Override
179-
public Object assemble(Serializable cached, Object owner) throws HibernateException {
180-
return cached;
181-
}
182-
183-
@Override
184-
public Object replace(Object original, Object target, Object owner) throws HibernateException {
185-
return original;
186-
}
65+
private int sqlType;
18766

18867
@Override
18968
public void setParameterValues(Properties parameters) {
69+
// IMPL NOTE: we handle 2 distinct cases here:
70+
// 1) we are passed a ParameterType instance in the incoming Properties - generally
71+
// speaking this indicates the annotation-binding case, and the passed ParameterType
72+
// represents information about the attribute and annotation
73+
// 2) we are not passed a ParameterType - generally this indicates a hbm.xml binding case.
19074
final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE );
19175

192-
// IMPL NOTE : be protective about not setting enumValueMapper (i.e. calling treatAsNamed/treatAsOrdinal)
193-
// in cases where we do not have enough information. In such cases we do additional checks
194-
// as part of nullSafeGet/nullSafeSet to query against the JDBC metadata to make the determination.
195-
19676
if ( reader != null ) {
19777
enumClass = reader.getReturnedClass().asSubclass( Enum.class );
19878

@@ -212,52 +92,24 @@ else if ( javax.persistence.EnumType.STRING.equals( enumType ) ) {
21292
}
21393

21494
if ( isOrdinal ) {
215-
treatAsOrdinal();
95+
this.enumValueMapper = new OrdinalEnumValueMapper();
21696
}
21797
else {
218-
treatAsNamed();
98+
this.enumValueMapper = new NamedEnumValueMapper();
21999
}
220100
sqlType = enumValueMapper.getSqlType();
221101
}
222102
else {
223-
String enumClassName = (String) parameters.get( ENUM );
103+
final String enumClassName = (String) parameters.get( ENUM );
224104
try {
225105
enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class );
226106
}
227107
catch ( ClassNotFoundException exception ) {
228-
throw new HibernateException( "Enum class not found", exception );
108+
throw new HibernateException( "Enum class not found: " + enumClassName, exception );
229109
}
230110

231-
final Object useNamedSetting = parameters.get( NAMED );
232-
if ( useNamedSetting != null ) {
233-
final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters );
234-
if ( useNamed ) {
235-
treatAsNamed();
236-
}
237-
else {
238-
treatAsOrdinal();
239-
}
240-
sqlType = enumValueMapper.getSqlType();
241-
}
242-
}
243-
244-
final String type = (String) parameters.get( TYPE );
245-
if ( type != null ) {
246-
sqlType = Integer.decode( type );
247-
}
248-
}
249-
250-
private void treatAsOrdinal() {
251-
if ( enumValueMapper == null || ! OrdinalEnumValueMapper.class.isInstance( enumValueMapper ) ) {
252-
enumValueMapper = new OrdinalEnumValueMapper();
253-
sqlType = enumValueMapper.getSqlType();
254-
}
255-
}
256-
257-
private void treatAsNamed() {
258-
if ( enumValueMapper == null || ! NamedEnumValueMapper.class.isInstance( enumValueMapper ) ) {
259-
enumValueMapper = new NamedEnumValueMapper();
260-
sqlType = enumValueMapper.getSqlType();
111+
this.enumValueMapper = interpretParameters( parameters );
112+
this.sqlType = enumValueMapper.getSqlType();
261113
}
262114
}
263115

@@ -287,6 +139,131 @@ private <T extends Annotation> T getAnnotation(Annotation[] annotations, Class<T
287139
return null;
288140
}
289141

142+
private EnumValueMapper interpretParameters(Properties parameters) {
143+
if ( parameters.containsKey( NAMED ) ) {
144+
final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters );
145+
if ( useNamed ) {
146+
return new NamedEnumValueMapper();
147+
}
148+
else {
149+
return new OrdinalEnumValueMapper();
150+
}
151+
}
152+
153+
if ( parameters.containsKey( TYPE ) ) {
154+
final int type = Integer.decode( (String) parameters.get( TYPE ) );
155+
if ( isNumericType( type ) ) {
156+
return new OrdinalEnumValueMapper();
157+
}
158+
else if ( isCharacterType( type ) ) {
159+
return new OrdinalEnumValueMapper();
160+
}
161+
else {
162+
throw new HibernateException(
163+
String.format(
164+
Locale.ENGLISH,
165+
"Passed JDBC type code [%s] not recognized as numeric nor character",
166+
type
167+
)
168+
);
169+
}
170+
}
171+
172+
// the fallback
173+
return new OrdinalEnumValueMapper();
174+
}
175+
176+
private boolean isCharacterType(int jdbcTypeCode) {
177+
switch ( jdbcTypeCode ) {
178+
case Types.CHAR:
179+
case Types.LONGVARCHAR:
180+
case Types.VARCHAR: {
181+
return true;
182+
}
183+
default: {
184+
return false;
185+
}
186+
}
187+
}
188+
189+
private boolean isNumericType(int jdbcTypeCode) {
190+
switch ( jdbcTypeCode ) {
191+
case Types.INTEGER:
192+
case Types.NUMERIC:
193+
case Types.SMALLINT:
194+
case Types.TINYINT:
195+
case Types.BIGINT:
196+
case Types.DECIMAL:
197+
case Types.DOUBLE:
198+
case Types.FLOAT: {
199+
return true;
200+
}
201+
default:
202+
return false;
203+
}
204+
}
205+
206+
@Override
207+
public int[] sqlTypes() {
208+
return new int[] { sqlType };
209+
}
210+
211+
@Override
212+
public Class<? extends Enum> returnedClass() {
213+
return enumClass;
214+
}
215+
216+
@Override
217+
public boolean equals(Object x, Object y) throws HibernateException {
218+
return x == y;
219+
}
220+
221+
@Override
222+
public int hashCode(Object x) throws HibernateException {
223+
return x == null ? 0 : x.hashCode();
224+
}
225+
226+
@Override
227+
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
228+
if ( enumValueMapper == null ) {
229+
throw new AssertionFailure( "EnumType (" + enumClass.getName() + ") not properly, fully configured" );
230+
}
231+
return enumValueMapper.getValue( rs, names );
232+
}
233+
234+
@Override
235+
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
236+
if ( enumValueMapper == null ) {
237+
throw new AssertionFailure( "EnumType (" + enumClass.getName() + ") not properly, fully configured" );
238+
}
239+
enumValueMapper.setValue( st, (Enum) value, index );
240+
}
241+
242+
@Override
243+
public Object deepCopy(Object value) throws HibernateException {
244+
return value;
245+
}
246+
247+
@Override
248+
public boolean isMutable() {
249+
return false;
250+
}
251+
252+
@Override
253+
public Serializable disassemble(Object value) throws HibernateException {
254+
return ( Serializable ) value;
255+
}
256+
257+
@Override
258+
public Object assemble(Serializable cached, Object owner) throws HibernateException {
259+
return cached;
260+
}
261+
262+
@Override
263+
public Object replace(Object original, Object target, Object owner) throws HibernateException {
264+
return original;
265+
}
266+
290267
@Override
291268
public String objectToSQLString(Object value) {
292269
return enumValueMapper.objectToSQLString( (Enum) value );
@@ -310,14 +287,18 @@ public String toLoggableString(Object value, SessionFactoryImplementor factory)
310287
return value.toString();
311288
}
312289

313-
private static interface EnumValueMapper extends Serializable {
314-
public int getSqlType();
315-
public Enum getValue(ResultSet rs, String[] names) throws SQLException;
316-
public void setValue(PreparedStatement st, Enum value, int index) throws SQLException;
290+
public boolean isOrdinal() {
291+
return enumValueMapper instanceof OrdinalEnumValueMapper;
292+
}
293+
294+
private interface EnumValueMapper extends Serializable {
295+
int getSqlType();
296+
Enum getValue(ResultSet rs, String[] names) throws SQLException;
297+
void setValue(PreparedStatement st, Enum value, int index) throws SQLException;
317298

318-
public String objectToSQLString(Enum value);
319-
public String toXMLString(Enum value);
320-
public Enum fromXMLString(String xml);
299+
String objectToSQLString(Enum value);
300+
String toXMLString(Enum value);
301+
Enum fromXMLString(String xml);
321302
}
322303

323304
public abstract class EnumValueMapperSupport implements EnumValueMapper {
@@ -479,27 +460,4 @@ protected Object extractJdbcValue(Enum value) {
479460
}
480461
}
481462

482-
public boolean isOrdinal() {
483-
return isOrdinal( sqlType );
484-
}
485-
486-
private boolean isOrdinal(int paramType) {
487-
switch ( paramType ) {
488-
case Types.INTEGER:
489-
case Types.NUMERIC:
490-
case Types.SMALLINT:
491-
case Types.TINYINT:
492-
case Types.BIGINT:
493-
case Types.DECIMAL: //for Oracle Driver
494-
case Types.DOUBLE: //for Oracle Driver
495-
case Types.FLOAT: //for Oracle Driver
496-
return true;
497-
case Types.CHAR:
498-
case Types.LONGVARCHAR:
499-
case Types.VARCHAR:
500-
return false;
501-
default:
502-
throw new HibernateException( "Unable to persist an Enum in a column of SQL Type: " + paramType );
503-
}
504-
}
505463
}

0 commit comments

Comments
 (0)