HHH-9955 - EnumType mapped via hbm.xml not always resolved properly as far as named/ordinal
This commit is contained in:
parent
6bb8d03595
commit
e678a5b8cf
|
@ -12,6 +12,7 @@ import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import javax.persistence.Enumerated;
|
import javax.persistence.Enumerated;
|
||||||
import javax.persistence.MapKeyEnumerated;
|
import javax.persistence.MapKeyEnumerated;
|
||||||
|
@ -61,138 +62,17 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType,Logg
|
||||||
|
|
||||||
private Class<? extends Enum> enumClass;
|
private Class<? extends Enum> enumClass;
|
||||||
private EnumValueMapper enumValueMapper;
|
private EnumValueMapper enumValueMapper;
|
||||||
private int sqlType = Types.INTEGER; // before any guessing
|
private int sqlType;
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] sqlTypes() {
|
|
||||||
return new int[] { sqlType };
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends Enum> returnedClass() {
|
|
||||||
return enumClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object x, Object y) throws HibernateException {
|
|
||||||
return x == y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode(Object x) throws HibernateException {
|
|
||||||
return x == null ? 0 : x.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
|
|
||||||
if ( enumValueMapper == null ) {
|
|
||||||
resolveEnumValueMapper( rs, names[0] );
|
|
||||||
}
|
|
||||||
return enumValueMapper.getValue( rs, names );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resolveEnumValueMapper(ResultSet rs, String name) {
|
|
||||||
if ( enumValueMapper == null ) {
|
|
||||||
try {
|
|
||||||
resolveEnumValueMapper( rs.getMetaData().getColumnType( rs.findColumn( name ) ) );
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// because some drivers do not implement this
|
|
||||||
LOG.debugf(
|
|
||||||
"JDBC driver threw exception calling java.sql.ResultSetMetaData.getColumnType; " +
|
|
||||||
"using fallback determination [%s] : %s",
|
|
||||||
enumClass.getName(),
|
|
||||||
e.getMessage()
|
|
||||||
);
|
|
||||||
// peek at the result value to guess type (this is legacy behavior)
|
|
||||||
try {
|
|
||||||
Object value = rs.getObject( name );
|
|
||||||
if ( Number.class.isInstance( value ) ) {
|
|
||||||
treatAsOrdinal();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
treatAsNamed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (SQLException ignore) {
|
|
||||||
treatAsOrdinal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resolveEnumValueMapper(int columnType) {
|
|
||||||
// fallback for cases where not enough parameter/parameterization information was passed in
|
|
||||||
if ( isOrdinal( columnType ) ) {
|
|
||||||
treatAsOrdinal();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
treatAsNamed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
|
|
||||||
if ( enumValueMapper == null ) {
|
|
||||||
resolveEnumValueMapper( st, index );
|
|
||||||
}
|
|
||||||
enumValueMapper.setValue( st, (Enum) value, index );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resolveEnumValueMapper(PreparedStatement st, int index) {
|
|
||||||
if ( enumValueMapper == null ) {
|
|
||||||
try {
|
|
||||||
resolveEnumValueMapper( st.getParameterMetaData().getParameterType( index ) );
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// because some drivers do not implement this
|
|
||||||
LOG.debugf(
|
|
||||||
"JDBC driver threw exception calling java.sql.ParameterMetaData#getParameterType; " +
|
|
||||||
"falling back to ordinal-based enum mapping [%s] : %s",
|
|
||||||
enumClass.getName(),
|
|
||||||
e.getMessage()
|
|
||||||
);
|
|
||||||
// Originally, this was simply treatAsOrdinal(). But, for DBs that do not implement the above, enums
|
|
||||||
// were treated as ordinal even when the *.hbm.xml explicitly define the type sqlCode. By default,
|
|
||||||
// this is essentially the same anyway, since sqlType is defaulted to Integer.
|
|
||||||
resolveEnumValueMapper( sqlType );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object deepCopy(Object value) throws HibernateException {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMutable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Serializable disassemble(Object value) throws HibernateException {
|
|
||||||
return ( Serializable ) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object assemble(Serializable cached, Object owner) throws HibernateException {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object replace(Object original, Object target, Object owner) throws HibernateException {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setParameterValues(Properties parameters) {
|
public void setParameterValues(Properties parameters) {
|
||||||
|
// IMPL NOTE: we handle 2 distinct cases here:
|
||||||
|
// 1) we are passed a ParameterType instance in the incoming Properties - generally
|
||||||
|
// speaking this indicates the annotation-binding case, and the passed ParameterType
|
||||||
|
// represents information about the attribute and annotation
|
||||||
|
// 2) we are not passed a ParameterType - generally this indicates a hbm.xml binding case.
|
||||||
final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE );
|
final ParameterType reader = (ParameterType) parameters.get( PARAMETER_TYPE );
|
||||||
|
|
||||||
// IMPL NOTE : be protective about not setting enumValueMapper (i.e. calling treatAsNamed/treatAsOrdinal)
|
|
||||||
// in cases where we do not have enough information. In such cases we do additional checks
|
|
||||||
// as part of nullSafeGet/nullSafeSet to query against the JDBC metadata to make the determination.
|
|
||||||
|
|
||||||
if ( reader != null ) {
|
if ( reader != null ) {
|
||||||
enumClass = reader.getReturnedClass().asSubclass( Enum.class );
|
enumClass = reader.getReturnedClass().asSubclass( Enum.class );
|
||||||
|
|
||||||
|
@ -212,52 +92,24 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType,Logg
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isOrdinal ) {
|
if ( isOrdinal ) {
|
||||||
treatAsOrdinal();
|
this.enumValueMapper = new OrdinalEnumValueMapper();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
treatAsNamed();
|
this.enumValueMapper = new NamedEnumValueMapper();
|
||||||
}
|
}
|
||||||
sqlType = enumValueMapper.getSqlType();
|
sqlType = enumValueMapper.getSqlType();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String enumClassName = (String) parameters.get( ENUM );
|
final String enumClassName = (String) parameters.get( ENUM );
|
||||||
try {
|
try {
|
||||||
enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class );
|
enumClass = ReflectHelper.classForName( enumClassName, this.getClass() ).asSubclass( Enum.class );
|
||||||
}
|
}
|
||||||
catch ( ClassNotFoundException exception ) {
|
catch ( ClassNotFoundException exception ) {
|
||||||
throw new HibernateException( "Enum class not found", exception );
|
throw new HibernateException( "Enum class not found: " + enumClassName, exception );
|
||||||
}
|
}
|
||||||
|
|
||||||
final Object useNamedSetting = parameters.get( NAMED );
|
this.enumValueMapper = interpretParameters( parameters );
|
||||||
if ( useNamedSetting != null ) {
|
this.sqlType = enumValueMapper.getSqlType();
|
||||||
final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters );
|
|
||||||
if ( useNamed ) {
|
|
||||||
treatAsNamed();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
treatAsOrdinal();
|
|
||||||
}
|
|
||||||
sqlType = enumValueMapper.getSqlType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final String type = (String) parameters.get( TYPE );
|
|
||||||
if ( type != null ) {
|
|
||||||
sqlType = Integer.decode( type );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void treatAsOrdinal() {
|
|
||||||
if ( enumValueMapper == null || ! OrdinalEnumValueMapper.class.isInstance( enumValueMapper ) ) {
|
|
||||||
enumValueMapper = new OrdinalEnumValueMapper();
|
|
||||||
sqlType = enumValueMapper.getSqlType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void treatAsNamed() {
|
|
||||||
if ( enumValueMapper == null || ! NamedEnumValueMapper.class.isInstance( enumValueMapper ) ) {
|
|
||||||
enumValueMapper = new NamedEnumValueMapper();
|
|
||||||
sqlType = enumValueMapper.getSqlType();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +139,131 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType,Logg
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EnumValueMapper interpretParameters(Properties parameters) {
|
||||||
|
if ( parameters.containsKey( NAMED ) ) {
|
||||||
|
final boolean useNamed = ConfigurationHelper.getBoolean( NAMED, parameters );
|
||||||
|
if ( useNamed ) {
|
||||||
|
return new NamedEnumValueMapper();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new OrdinalEnumValueMapper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( parameters.containsKey( TYPE ) ) {
|
||||||
|
final int type = Integer.decode( (String) parameters.get( TYPE ) );
|
||||||
|
if ( isNumericType( type ) ) {
|
||||||
|
return new OrdinalEnumValueMapper();
|
||||||
|
}
|
||||||
|
else if ( isCharacterType( type ) ) {
|
||||||
|
return new OrdinalEnumValueMapper();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new HibernateException(
|
||||||
|
String.format(
|
||||||
|
Locale.ENGLISH,
|
||||||
|
"Passed JDBC type code [%s] not recognized as numeric nor character",
|
||||||
|
type
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the fallback
|
||||||
|
return new OrdinalEnumValueMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCharacterType(int jdbcTypeCode) {
|
||||||
|
switch ( jdbcTypeCode ) {
|
||||||
|
case Types.CHAR:
|
||||||
|
case Types.LONGVARCHAR:
|
||||||
|
case Types.VARCHAR: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNumericType(int jdbcTypeCode) {
|
||||||
|
switch ( jdbcTypeCode ) {
|
||||||
|
case Types.INTEGER:
|
||||||
|
case Types.NUMERIC:
|
||||||
|
case Types.SMALLINT:
|
||||||
|
case Types.TINYINT:
|
||||||
|
case Types.BIGINT:
|
||||||
|
case Types.DECIMAL:
|
||||||
|
case Types.DOUBLE:
|
||||||
|
case Types.FLOAT: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] sqlTypes() {
|
||||||
|
return new int[] { sqlType };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Enum> returnedClass() {
|
||||||
|
return enumClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object x, Object y) throws HibernateException {
|
||||||
|
return x == y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode(Object x) throws HibernateException {
|
||||||
|
return x == null ? 0 : x.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
|
||||||
|
if ( enumValueMapper == null ) {
|
||||||
|
throw new AssertionFailure( "EnumType (" + enumClass.getName() + ") not properly, fully configured" );
|
||||||
|
}
|
||||||
|
return enumValueMapper.getValue( rs, names );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
|
||||||
|
if ( enumValueMapper == null ) {
|
||||||
|
throw new AssertionFailure( "EnumType (" + enumClass.getName() + ") not properly, fully configured" );
|
||||||
|
}
|
||||||
|
enumValueMapper.setValue( st, (Enum) value, index );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object deepCopy(Object value) throws HibernateException {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMutable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Serializable disassemble(Object value) throws HibernateException {
|
||||||
|
return ( Serializable ) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object assemble(Serializable cached, Object owner) throws HibernateException {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object replace(Object original, Object target, Object owner) throws HibernateException {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String objectToSQLString(Object value) {
|
public String objectToSQLString(Object value) {
|
||||||
return enumValueMapper.objectToSQLString( (Enum) value );
|
return enumValueMapper.objectToSQLString( (Enum) value );
|
||||||
|
@ -310,14 +287,18 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType,Logg
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static interface EnumValueMapper extends Serializable {
|
public boolean isOrdinal() {
|
||||||
public int getSqlType();
|
return enumValueMapper instanceof OrdinalEnumValueMapper;
|
||||||
public Enum getValue(ResultSet rs, String[] names) throws SQLException;
|
}
|
||||||
public void setValue(PreparedStatement st, Enum value, int index) throws SQLException;
|
|
||||||
|
|
||||||
public String objectToSQLString(Enum value);
|
private interface EnumValueMapper extends Serializable {
|
||||||
public String toXMLString(Enum value);
|
int getSqlType();
|
||||||
public Enum fromXMLString(String xml);
|
Enum getValue(ResultSet rs, String[] names) throws SQLException;
|
||||||
|
void setValue(PreparedStatement st, Enum value, int index) throws SQLException;
|
||||||
|
|
||||||
|
String objectToSQLString(Enum value);
|
||||||
|
String toXMLString(Enum value);
|
||||||
|
Enum fromXMLString(String xml);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class EnumValueMapperSupport implements EnumValueMapper {
|
public abstract class EnumValueMapperSupport implements EnumValueMapper {
|
||||||
|
@ -479,27 +460,4 @@ public class EnumType implements EnhancedUserType, DynamicParameterizedType,Logg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOrdinal() {
|
|
||||||
return isOrdinal( sqlType );
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isOrdinal(int paramType) {
|
|
||||||
switch ( paramType ) {
|
|
||||||
case Types.INTEGER:
|
|
||||||
case Types.NUMERIC:
|
|
||||||
case Types.SMALLINT:
|
|
||||||
case Types.TINYINT:
|
|
||||||
case Types.BIGINT:
|
|
||||||
case Types.DECIMAL: //for Oracle Driver
|
|
||||||
case Types.DOUBLE: //for Oracle Driver
|
|
||||||
case Types.FLOAT: //for Oracle Driver
|
|
||||||
return true;
|
|
||||||
case Types.CHAR:
|
|
||||||
case Types.LONGVARCHAR:
|
|
||||||
case Types.VARCHAR:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
throw new HibernateException( "Unable to persist an Enum in a column of SQL Type: " + paramType );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue