HHH-16544 add support for Oracle nested tables

... and clean up of some stuff about array type initialization
This commit is contained in:
Gavin 2023-05-02 11:42:26 +03:00 committed by Gavin King
parent 18ddbe15d6
commit 7c22a537d1
19 changed files with 589 additions and 210 deletions

View File

@ -74,6 +74,7 @@ import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.UserType;
@ -152,6 +153,7 @@ public class BasicValueBinder implements JdbcTypeIndicators {
private TemporalType temporalPrecision;
private TimeZoneStorageType timeZoneStorageType;
private boolean partitionKey;
private Integer jdbcTypeCode;
private Table table;
private AnnotatedColumns columns;
@ -940,7 +942,13 @@ public class BasicValueBinder implements JdbcTypeIndicators {
if ( jdbcTypeCodeAnn != null ) {
final int jdbcTypeCode = jdbcTypeCodeAnn.value();
if ( jdbcTypeCode != Integer.MIN_VALUE ) {
return typeConfiguration.getJdbcTypeRegistry().getDescriptor( jdbcTypeCode );
final JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry();
if ( jdbcTypeRegistry.getConstructor( jdbcTypeCode ) != null ) {
return null;
}
else {
return jdbcTypeRegistry.getDescriptor( jdbcTypeCode );
}
}
}
@ -1052,15 +1060,15 @@ public class BasicValueBinder implements JdbcTypeIndicators {
private void normalSupplementalDetails(XProperty attributeXProperty) {
explicitJavaTypeAccess = typeConfiguration -> {
final org.hibernate.annotations.JavaType javaTypeAnn = findAnnotation( attributeXProperty, org.hibernate.annotations.JavaType.class );
if ( javaTypeAnn != null ) {
final Class<? extends BasicJavaType<?>> javaTypeClass = normalizeJavaType( javaTypeAnn.value() );
final org.hibernate.annotations.JavaType javaType =
findAnnotation( attributeXProperty, org.hibernate.annotations.JavaType.class );
if ( javaType != null ) {
final Class<? extends BasicJavaType<?>> javaTypeClass = normalizeJavaType( javaType.value() );
if ( javaTypeClass != null ) {
if ( buildingContext.getBuildingOptions().disallowExtensionsInCdi() ) {
return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( javaTypeClass );
}
final ManagedBean<? extends BasicJavaType<?>> jtdBean = getManagedBeanRegistry().getBean( javaTypeClass );
return jtdBean.getBeanInstance();
return getManagedBeanRegistry().getBean( javaTypeClass ).getBeanInstance();
}
}
@ -1072,22 +1080,28 @@ public class BasicValueBinder implements JdbcTypeIndicators {
return null;
};
final org.hibernate.annotations.JdbcTypeCode jdbcType =
findAnnotation( attributeXProperty, org.hibernate.annotations.JdbcTypeCode.class );
if ( jdbcType != null ) {
jdbcTypeCode = jdbcType.value();
}
normalJdbcTypeDetails( attributeXProperty);
normalMutabilityDetails( attributeXProperty );
final Enumerated enumeratedAnn = attributeXProperty.getAnnotation( Enumerated.class );
if ( enumeratedAnn != null ) {
enumType = enumeratedAnn.value();
final Enumerated enumerated = attributeXProperty.getAnnotation( Enumerated.class );
if ( enumerated != null ) {
enumType = enumerated.value();
}
final Temporal temporalAnn = attributeXProperty.getAnnotation( Temporal.class );
if ( temporalAnn != null ) {
temporalPrecision = temporalAnn.value();
final Temporal temporal = attributeXProperty.getAnnotation( Temporal.class );
if ( temporal != null ) {
temporalPrecision = temporal.value();
}
final TimeZoneStorage timeZoneStorageAnn = attributeXProperty.getAnnotation( TimeZoneStorage.class );
if ( timeZoneStorageAnn != null ) {
timeZoneStorageType = timeZoneStorageAnn.value();
final TimeZoneStorage timeZoneStorage = attributeXProperty.getAnnotation( TimeZoneStorage.class );
if ( timeZoneStorage != null ) {
timeZoneStorageType = timeZoneStorage.value();
final TimeZoneColumn timeZoneColumnAnn = attributeXProperty.getAnnotation( TimeZoneColumn.class );
if ( timeZoneColumnAnn != null ) {
if ( timeZoneStorageType != TimeZoneStorageType.AUTO && timeZoneStorageType != TimeZoneStorageType.COLUMN ) {
@ -1223,6 +1237,10 @@ public class BasicValueBinder implements JdbcTypeIndicators {
basicValue.setTemporalPrecision( temporalPrecision );
}
if ( jdbcTypeCode != null ) {
basicValue.setExplicitJdbcTypeCode( jdbcTypeCode );
}
linkWithValue();
boolean isInSecondPass = buildingContext.getMetadataCollector().isInSecondPass();

View File

@ -72,7 +72,7 @@ public class InferredBasicValueResolver {
final BasicType<T> jdbcMapping;
if (explicitJavaType != null) {
if ( explicitJavaType != null ) {
// we have an explicit JavaType
if ( isTemporal( explicitJavaType ) ) {
return fromTemporal(
@ -85,26 +85,21 @@ public class InferredBasicValueResolver {
}
else if ( explicitJdbcType != null ) {
// we also have an explicit JdbcType
jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve(
explicitJavaType,
explicitJdbcType
);
jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve( explicitJavaType, explicitJdbcType );
}
else {
// we need to infer the JdbcType and use that to build the value-mapping
final JdbcType inferredJdbcType = explicitJavaType.getRecommendedJdbcType( stdIndicators );
if ( inferredJdbcType instanceof ObjectJdbcType && ( explicitJavaType instanceof SerializableJavaType
|| explicitJavaType.getJavaType() instanceof Serializable ) ) {
if ( inferredJdbcType instanceof ObjectJdbcType
&& ( explicitJavaType instanceof SerializableJavaType
|| explicitJavaType.getJavaType() instanceof Serializable ) ) {
// Use the SerializableType if possible since ObjectJdbcType is our fallback
jdbcMapping = new SerializableType( explicitJavaType );
}
else {
jdbcMapping = resolveSqlTypeIndicators(
stdIndicators,
typeConfiguration.getBasicTypeRegistry().resolve(
explicitJavaType,
inferredJdbcType
),
typeConfiguration.getBasicTypeRegistry().resolve( explicitJavaType, inferredJdbcType ),
explicitJavaType
);
}
@ -201,10 +196,7 @@ public class InferredBasicValueResolver {
if ( recommendedJdbcType != null ) {
jdbcMapping = resolveSqlTypeIndicators(
stdIndicators,
typeConfiguration.getBasicTypeRegistry().resolve(
reflectedJtd,
recommendedJdbcType
),
typeConfiguration.getBasicTypeRegistry().resolve( reflectedJtd, recommendedJdbcType ),
reflectedJtd
);
}
@ -241,18 +233,11 @@ public class InferredBasicValueResolver {
}
}
final JavaType<T> recommendedJtd = explicitJdbcType.getJdbcRecommendedJavaTypeMapping(
length,
scale,
typeConfiguration
);
final JavaType<T> recommendedJtd =
explicitJdbcType.getJdbcRecommendedJavaTypeMapping( length, scale, typeConfiguration );
jdbcMapping = resolveSqlTypeIndicators(
stdIndicators,
typeConfiguration.getBasicTypeRegistry().resolve(
recommendedJtd,
explicitJdbcType
),
typeConfiguration.getBasicTypeRegistry().resolve( recommendedJtd, explicitJdbcType ),
recommendedJtd
);
}

View File

@ -223,13 +223,6 @@ public class OracleArrayJdbcType implements JdbcType {
return EMPTY_STRING_ARRAY;
}
// @Override
// public String getExtraCreateTableInfo(JavaType<?> javaType, String columnName, String tableName, Database database) {
// final Dialect dialect = database.getDialect();
// final BasicPluralJavaType<?> pluralJavaType = (BasicPluralJavaType<?>) javaType;
// String elementTypeName = getTypeName( pluralJavaType.getElementJavaType(), dialect );
// return " nested table " + columnName + " store as " + tableName + columnName + elementTypeName;
// }
@Override
public String getFriendlyName() {
return typeName;

View File

@ -100,6 +100,7 @@ import static org.hibernate.LockOptions.SKIP_LOCKED;
import static org.hibernate.LockOptions.WAIT_FOREVER;
import static org.hibernate.cfg.AvailableSettings.BATCH_VERSIONED_DATA;
import static org.hibernate.dialect.OracleJdbcHelper.getArrayJdbcTypeConstructor;
import static org.hibernate.dialect.OracleJdbcHelper.getNestedTableJdbcTypeConstructor;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.query.sqm.TemporalUnit.DAY;
@ -125,6 +126,7 @@ import static org.hibernate.type.SqlTypes.REAL;
import static org.hibernate.type.SqlTypes.SMALLINT;
import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.STRUCT;
import static org.hibernate.type.SqlTypes.TABLE;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
@ -689,6 +691,7 @@ public class OracleDialect extends Dialect {
}
ddlTypeRegistry.addDescriptor( new ArrayDdlTypeImpl( this ) );
ddlTypeRegistry.addDescriptor( TABLE, new ArrayDdlTypeImpl( this ) );
}
@Override
@ -833,6 +836,7 @@ public class OracleDialect extends Dialect {
if ( OracleJdbcHelper.isUsable( serviceRegistry ) ) {
typeContributions.contributeJdbcTypeConstructor( getArrayJdbcTypeConstructor( serviceRegistry ) );
typeContributions.contributeJdbcTypeConstructor( getNestedTableJdbcTypeConstructor( serviceRegistry ) );
}
else {
typeContributions.contributeJdbcType( OracleReflectionStructJdbcType.INSTANCE );

View File

@ -38,11 +38,15 @@ public class OracleJdbcHelper {
return create( serviceRegistry, "org.hibernate.dialect.OracleArrayJdbcTypeConstructor" );
}
public static JdbcTypeConstructor getNestedTableJdbcTypeConstructor(ServiceRegistry serviceRegistry) {
return create( serviceRegistry, "org.hibernate.dialect.OracleNestedTableJdbcTypeConstructor" );
}
public static JdbcType getStructJdbcType(ServiceRegistry serviceRegistry) {
return create( serviceRegistry, "org.hibernate.dialect.OracleStructJdbcType" );
}
public static <X> X create(ServiceRegistry serviceRegistry, String className) {
private static <X> X create(ServiceRegistry serviceRegistry, String className) {
final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class );
try {
return classLoaderService.<X>classForName( className ).getConstructor().newInstance();

View File

@ -0,0 +1,252 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.dialect;
import oracle.jdbc.OracleConnection;
import org.hibernate.HibernateException;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.NamedAuxiliaryDatabaseObject;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.internal.BasicTypeImpl;
import org.hibernate.type.spi.TypeConfiguration;
import java.lang.reflect.Array;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Locale;
import static java.sql.Types.ARRAY;
import static java.util.Collections.emptySet;
import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_STRING_ARRAY;
/**
* Descriptor for {@link Types#ARRAY ARRAY} handling.
*
* @author Christian Beikov
* @author Jordan Gigov
*/
public class OracleNestedTableJdbcType implements JdbcType {
private final JdbcType elementJdbcType;
private final String typeName;
public OracleNestedTableJdbcType(JdbcType elementJdbcType, String typeName) {
this.elementJdbcType = elementJdbcType;
this.typeName = typeName;
}
@Override
public int getJdbcTypeCode() {
return Types.ARRAY;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.TABLE;
}
public JdbcType getElementJdbcType() {
return elementJdbcType;
}
@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer precision,
Integer scale,
TypeConfiguration typeConfiguration) {
final JavaType<Object> elementJavaType = elementJdbcType.getJdbcRecommendedJavaTypeMapping(
precision,
scale,
typeConfiguration
);
return typeConfiguration.getJavaTypeRegistry().resolveDescriptor(
Array.newInstance( elementJavaType.getJavaTypeClass(), 0 ).getClass()
);
}
@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaTypeDescriptor) {
return null;
}
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return java.sql.Array.class;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
//noinspection unchecked
final BasicPluralJavaType<X> containerJavaType = (BasicPluralJavaType<X>) javaTypeDescriptor;
return new BasicBinder<>( javaTypeDescriptor, this ) {
private String typeName(WrapperOptions options) {
return ( typeName == null ? getTypeName( options, containerJavaType ) : typeName )
.toUpperCase(Locale.ROOT);
}
@Override
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
st.setNull( index, ARRAY, typeName( options ) );
}
@Override
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
st.setNull( name, ARRAY, typeName( options ) );
}
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setArray( index, getArray( value, containerJavaType, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final java.sql.Array arr = getArray( value, containerJavaType, options );
try {
st.setObject( name, arr, ARRAY );
}
catch (SQLException ex) {
throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex );
}
}
private java.sql.Array getArray(X value, BasicPluralJavaType<X> containerJavaType, WrapperOptions options)
throws SQLException {
//noinspection unchecked
final Class<Object[]> arrayClass = (Class<Object[]>) Array.newInstance(
getElementJdbcType().getPreferredJavaTypeClass( options ),
0
).getClass();
final Object[] objects = javaTypeDescriptor.unwrap( value, arrayClass, options );
final String arrayTypeName = typeName( options ).toUpperCase(Locale.ROOT);
final OracleConnection oracleConnection = options.getSession()
.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
.unwrap( OracleConnection.class );
try {
return oracleConnection.createOracleArray( arrayTypeName, objects );
}
catch (Exception e) {
throw new HibernateException( "Couldn't create a java.sql.Array", e );
}
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaTypeDescriptor) {
return new BasicExtractor<>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getArray( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getArray( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getArray( name ), options );
}
};
}
static String getTypeName(WrapperOptions options, BasicPluralJavaType<?> containerJavaType) {
Dialect dialect = options.getSessionFactory().getJdbcServices().getDialect();
return getTypeName( containerJavaType.getElementJavaType(), dialect );
}
static String getTypeName(JavaType<?> elementJavaType, Dialect dialect) {
return dialect.getArrayTypeName(
elementJavaType.getJavaTypeClass().getSimpleName(),
null, // not needed by OracleDialect.getArrayTypeName(),
null // not needed by OracleDialect.getArrayTypeName()
);
}
@Override
public void addAuxiliaryDatabaseObjects(
JavaType<?> javaType,
Size columnSize,
Database database,
TypeConfiguration typeConfiguration) {
final Dialect dialect = database.getDialect();
final BasicPluralJavaType<?> pluralJavaType = (BasicPluralJavaType<?>) javaType;
final JavaType<?> elementJavaType = pluralJavaType.getElementJavaType();
final String elementTypeName = typeName==null ? getTypeName( elementJavaType, dialect ) : typeName;
final String elementType =
typeConfiguration.getDdlTypeRegistry().getTypeName(
getElementJdbcType().getDdlTypeCode(),
dialect.getSizeStrategy().resolveSize(
getElementJdbcType(),
elementJavaType,
columnSize.getPrecision(),
columnSize.getScale(),
columnSize.getLength()
),
new BasicTypeImpl<>( elementJavaType, getElementJdbcType() )
);
database.addAuxiliaryDatabaseObject(
new NamedAuxiliaryDatabaseObject(
elementTypeName,
database.getDefaultNamespace(),
getCreateArrayTypeCommand( elementTypeName, elementType ),
getDropArrayTypeCommand( elementTypeName ),
emptySet(),
true
)
);
}
String[] getCreateArrayTypeCommand(String elementTypeName, String elementType) {
return new String[]{
"create or replace type " + elementTypeName
+ " as table of " + elementType
};
}
String[] getDropArrayTypeCommand(String elementTypeName) {
// for some weird reason dropping the type declarations causes problem in the test suite
// return new String[] { "drop type " + elementTypeName };
return EMPTY_STRING_ARRAY;
}
@Override
public String getExtraCreateTableInfo(JavaType<?> javaType, String columnName, String tableName, Database database) {
final Dialect dialect = database.getDialect();
final BasicPluralJavaType<?> pluralJavaType = (BasicPluralJavaType<?>) javaType;
String elementTypeName = getTypeName( pluralJavaType.getElementJavaType(), dialect );
return " nested table " + columnName
+ " store as \"" + tableName + " " + columnName + " " + elementTypeName + "\"";
}
@Override
public String getFriendlyName() {
return typeName;
}
@Override
public String toString() {
return "OracleArrayTypeDescriptor(" + typeName + ")";
}
}

View File

@ -0,0 +1,50 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.dialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Factory for {@link OracleNestedTableJdbcType}.
*
* @see OracleJdbcHelper#getArrayJdbcTypeConstructor
*
* @author Gavin King
*/
public class OracleNestedTableJdbcTypeConstructor implements JdbcTypeConstructor {
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect, BasicType<?> elementType,
ColumnTypeInformation columnTypeInformation) {
String typeName = columnTypeInformation.getTypeName();
if ( typeName == null || typeName.isBlank() ) {
typeName = OracleArrayJdbcType.getTypeName( elementType.getJavaTypeDescriptor(), dialect );
}
return new OracleNestedTableJdbcType( elementType.getJdbcType(), typeName );
}
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
JdbcType elementType,
ColumnTypeInformation columnTypeInformation) {
// a bit wrong, since columnTypeInformation.getTypeName() is typically null!
return new OracleNestedTableJdbcType( elementType, columnTypeInformation.getTypeName() );
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.TABLE;
}
}

View File

@ -12,6 +12,7 @@ import java.util.Properties;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.Incubating;
import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.TimeZoneStorageStrategy;
@ -98,6 +99,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Resolved state - available after `#resolve`
private Resolution<?> resolution;
private Integer jdbcTypeCode;
public BasicValue(MetadataBuildingContext buildingContext) {
@ -389,8 +391,8 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
protected Resolution<?> buildResolution() {
Properties typeParameters = getTypeParameters();
if (typeParameters != null
&& parseBoolean(typeParameters.getProperty(DynamicParameterizedType.IS_DYNAMIC))
if ( typeParameters != null
&& parseBoolean( typeParameters.getProperty(DynamicParameterizedType.IS_DYNAMIC) )
&& typeParameters.get(DynamicParameterizedType.PARAMETER_TYPE) == null ) {
createParameterImpl();
}
@ -410,14 +412,9 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
if ( isVersion() ) {
return VersionResolution.from(
implicitJavaTypeAccess,
timeZoneStorageType,
getBuildingContext()
);
return VersionResolution.from( implicitJavaTypeAccess, timeZoneStorageType, getBuildingContext() );
}
// determine JavaType if we can
final BasicJavaType explicitJavaType = explicitJavaTypeAccess == null ? null
@ -451,8 +448,8 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
if ( javaType instanceof BasicPluralJavaType<?>
&& !attributeConverterDescriptor.getDomainValueResolvedType()
.getErasedType()
.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
.getErasedType()
.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
// In this case, the converter applies to the element of a BasicPluralJavaType
final BasicPluralJavaType<?> containerJtd = (BasicPluralJavaType<?>) javaType;
final BasicType registeredElementType = converterResolution.getLegacyResolvedBasicType();
@ -620,7 +617,6 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
if ( name.startsWith( BasicTypeImpl.EXTERNALIZED_PREFIX ) ) {
final BasicTypeImpl<Object> basicType = context.getBootstrapContext().resolveAdHocBasicType( name );
return new NamedBasicTypeResolution<>(
basicType.getJavaTypeDescriptor(),
basicType,
@ -896,6 +892,16 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
return javaTypeClass == Byte[].class || javaTypeClass == Character[].class;
}
@Incubating
public void setExplicitJdbcTypeCode(Integer jdbcTypeCode) {
this.jdbcTypeCode = jdbcTypeCode;
}
@Override
public Integer getExplicitJdbcTypeCode() {
return jdbcTypeCode == null ? getPreferredSqlTypeCodeForArray() : jdbcTypeCode;
}
/**
* Resolved form of {@link BasicValue} as part of interpreting the
* boot-time model into the run-time model

View File

@ -287,9 +287,10 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
throw new MappingException(
String.format(
Locale.ROOT,
"Unable to determine SQL type name for column '%s' of table '%s'",
"Unable to determine SQL type name for column '%s' of table '%s': %s",
getName(),
getValue().getTable().getName()
getValue().getTable().getName(),
cause.getMessage()
),
cause
);

View File

@ -16,21 +16,21 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
* @author Jordan Gigov
* @author Christian Beikov
*/
public class BasicArrayType<T>
extends AbstractSingleColumnStandardBasicType<T[]>
implements AdjustableBasicType<T[]>, BasicPluralType<T[], T> {
public class BasicArrayType<T,E>
extends AbstractSingleColumnStandardBasicType<T>
implements AdjustableBasicType<T>, BasicPluralType<T, E> {
private final BasicType<T> baseDescriptor;
private final BasicType<E> baseDescriptor;
private final String name;
public BasicArrayType(BasicType<T> baseDescriptor, JdbcType arrayJdbcType, JavaType<T[]> arrayTypeDescriptor) {
public BasicArrayType(BasicType<E> baseDescriptor, JdbcType arrayJdbcType, JavaType<T> arrayTypeDescriptor) {
super( arrayJdbcType, arrayTypeDescriptor );
this.baseDescriptor = baseDescriptor;
this.name = baseDescriptor.getName() + "[]";
}
@Override
public BasicType<T> getElementType() {
public BasicType<E> getElementType() {
return baseDescriptor;
}

View File

@ -12,34 +12,70 @@ import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
/**
* A converted basic array type.
* Given a {@link BasicValueConverter} for an array type,
*
* @param <E> the unconverted element type
* @param <T> the unconverted array type
* @param <S> the converted array type
*
* @author Christian Beikov
*/
public class ConvertedBasicArrayType<T> extends BasicArrayType<T> {
public class ConvertedBasicArrayType<T,S,E>
extends AbstractSingleColumnStandardBasicType<T>
implements AdjustableBasicType<T>, BasicPluralType<T, E> {
private final BasicValueConverter<T[], ?> converter;
private final ValueExtractor<T[]> jdbcValueExtractor;
private final ValueBinder<T[]> jdbcValueBinder;
private final JdbcLiteralFormatter<T[]> jdbcLiteralFormatter;
private final BasicType<E> baseDescriptor;
private final String name;
private final BasicValueConverter<T, S> converter;
private final ValueExtractor<T> jdbcValueExtractor;
private final ValueBinder<T> jdbcValueBinder;
private final JdbcLiteralFormatter<T> jdbcLiteralFormatter;
@SuppressWarnings("unchecked")
public ConvertedBasicArrayType(
BasicType<T> baseDescriptor,
BasicType<E> baseDescriptor,
JdbcType arrayJdbcType,
JavaType<T[]> arrayTypeDescriptor,
BasicValueConverter<T[], ?> converter) {
super( baseDescriptor, arrayJdbcType, arrayTypeDescriptor );
JavaType<T> arrayTypeDescriptor,
BasicValueConverter<T, S> converter) {
super( arrayJdbcType, arrayTypeDescriptor );
this.converter = converter;
this.jdbcValueBinder = (ValueBinder<T[]>) arrayJdbcType.getBinder( converter.getRelationalJavaType() );
this.jdbcValueExtractor = (ValueExtractor<T[]>) arrayJdbcType.getExtractor( converter.getRelationalJavaType() );
this.jdbcLiteralFormatter = (JdbcLiteralFormatter<T[]>) arrayJdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() );
//TODO: these type casts look completely bogus (T==E[] and S are distinct array types)
this.jdbcValueBinder = (ValueBinder<T>) arrayJdbcType.getBinder( converter.getRelationalJavaType() );
this.jdbcValueExtractor = (ValueExtractor<T>) arrayJdbcType.getExtractor( converter.getRelationalJavaType() );
this.jdbcLiteralFormatter = (JdbcLiteralFormatter<T>) arrayJdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() );
this.baseDescriptor = baseDescriptor;
this.name = baseDescriptor.getName() + "[]";
}
@Override
public BasicValueConverter<T[], ?> getValueConverter() {
public BasicType<E> getElementType() {
return baseDescriptor;
}
@Override
public String getName() {
return name;
}
@Override
protected boolean registerUnderJavaType() {
return true;
}
@Override
public <X> BasicType<X> resolveIndicatedType(JdbcTypeIndicators indicators, JavaType<X> domainJtd) {
// TODO: maybe fallback to some encoding by default if the DB doesn't support arrays natively?
// also, maybe move that logic into the ArrayJdbcType
//noinspection unchecked
return (BasicType<X>) this;
}
@Override
public BasicValueConverter<T, ?> getValueConverter() {
return converter;
}
@ -49,17 +85,17 @@ public class ConvertedBasicArrayType<T> extends BasicArrayType<T> {
}
@Override
public ValueExtractor<T[]> getJdbcValueExtractor() {
public ValueExtractor<T> getJdbcValueExtractor() {
return jdbcValueExtractor;
}
@Override
public ValueBinder<T[]> getJdbcValueBinder() {
public ValueBinder<T> getJdbcValueBinder() {
return jdbcValueBinder;
}
@Override
public JdbcLiteralFormatter<T[]> getJdbcLiteralFormatter() {
public JdbcLiteralFormatter<T> getJdbcLiteralFormatter() {
return jdbcLiteralFormatter;
}
}

View File

@ -277,6 +277,13 @@ public class SqlTypes {
*/
public final static int ARRAY = Types.ARRAY;
/**
* A type code representing an Oracle-style nested table.
*
* @see org.hibernate.dialect.OracleNestedTableJdbcType
*/
public final static int TABLE = 4000;
/**
* A type code representing the generic SQL type {@code BLOB}.
*
@ -527,6 +534,8 @@ public class SqlTypes {
* {@link org.hibernate.dialect.MySQLDialect MySQL} where {@code ENUM}
* types do not have names.
*
* @see org.hibernate.dialect.MySQLEnumJdbcType
*
* @since 6.3
*/
public static final int ENUM = 6000;
@ -536,6 +545,8 @@ public class SqlTypes {
* {@link org.hibernate.dialect.PostgreSQLDialect PostgreSQL} where
* {@code ENUM} types must have names.
*
* @see org.hibernate.dialect.PostgreSQLEnumJdbcType
*
* @since 6.3
*/
public static final int NAMED_ENUM = 6001;

View File

@ -12,72 +12,94 @@ import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.JavaType;
/**
* Handles conversion to/from an array of a converted element type.
* Given a {@link BasicValueConverter} for array elements, handles conversion
* to and from an array of the converted element type.
*
* @param <E> the unconverted element type
* @param <F> the converted element type
* @param <T> the unconverted array type
* @param <S> the converted array type
*/
public class ArrayConverter<X, Y> implements BasicValueConverter<X, Y> {
public class ArrayConverter<T, S, E, F> implements BasicValueConverter<T, S> {
private final BasicValueConverter<Object, Object> elementConverter;
private final JavaType<X> domainJavaType;
private final JavaType<Y> relationalJavaType;
private final BasicValueConverter<E, F> elementConverter;
private final JavaType<T> domainJavaType;
private final JavaType<S> relationalJavaType;
public ArrayConverter(
BasicValueConverter<Object, Object> elementConverter,
JavaType<X> domainJavaType,
JavaType<Y> relationalJavaType) {
BasicValueConverter<E, F> elementConverter,
JavaType<T> domainJavaType,
JavaType<S> relationalJavaType) {
this.elementConverter = elementConverter;
this.domainJavaType = domainJavaType;
this.relationalJavaType = relationalJavaType;
}
@Override
public X toDomainValue(Y relationalForm) {
public T toDomainValue(S relationalForm) {
if ( relationalForm == null ) {
return null;
}
if ( relationalForm.getClass().getComponentType() == elementConverter.getDomainJavaType().getJavaTypeClass() ) {
//noinspection unchecked
return (X) relationalForm;
else {
final Class<E> elementClass = elementConverter.getDomainJavaType().getJavaTypeClass();
if ( relationalForm.getClass().getComponentType() == elementClass) {
//noinspection unchecked
return (T) relationalForm;
}
else {
//noinspection unchecked
return convertTo( (F[]) relationalForm, elementClass );
}
}
final Object[] relationalArray = (Object[]) relationalForm;
final Object[] domainArray = (Object[]) Array.newInstance(
elementConverter.getDomainJavaType().getJavaTypeClass(),
relationalArray.length
);
}
private T convertTo(F[] relationalArray, Class<E> elementClass) {
//TODO: the following implementation only handles conversion between non-primitive arrays!
//noinspection unchecked
final E[] domainArray = (E[]) Array.newInstance( elementClass, relationalArray.length );
for ( int i = 0; i < relationalArray.length; i++ ) {
domainArray[i] = elementConverter.toDomainValue( relationalArray[i] );
}
//noinspection unchecked
return (X) domainArray;
return (T) domainArray;
}
@Override
public Y toRelationalValue(X domainForm) {
public S toRelationalValue(T domainForm) {
if ( domainForm == null ) {
return null;
}
if ( domainForm.getClass().getComponentType() == elementConverter.getRelationalJavaType().getJavaTypeClass() ) {
//noinspection unchecked
return (Y) domainForm;
else {
final Class<F> elementClass = elementConverter.getRelationalJavaType().getJavaTypeClass();
if ( domainForm.getClass().getComponentType() == elementClass) {
//noinspection unchecked
return (S) domainForm;
}
else {
//noinspection unchecked
return convertFrom((E[]) domainForm, elementClass);
}
}
final Object[] domainArray = (Object[]) domainForm;
final Object[] relationalArray = (Object[]) Array.newInstance(
elementConverter.getRelationalJavaType().getJavaTypeClass(),
domainArray.length
);
}
private S convertFrom(E[] domainArray, Class<F> elementClass) {
//TODO: the following implementation only handles conversion between non-primitive arrays!
//noinspection unchecked
final F[] relationalArray = (F[]) Array.newInstance( elementClass, domainArray.length );
for ( int i = 0; i < domainArray.length; i++ ) {
relationalArray[i] = elementConverter.toRelationalValue( domainArray[i] );
}
//noinspection unchecked
return (Y) relationalArray;
return (S) relationalArray;
}
@Override
public JavaType<X> getDomainJavaType() {
public JavaType<T> getDomainJavaType() {
return domainJavaType;
}
@Override
public JavaType<Y> getRelationalJavaType() {
public JavaType<S> getRelationalJavaType() {
return relationalJavaType;
}

View File

@ -7,8 +7,6 @@
package org.hibernate.type.descriptor.java;
import java.lang.reflect.Array;
import java.sql.Types;
import java.util.function.Function;
import org.hibernate.dialect.Dialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
@ -30,10 +28,6 @@ public abstract class AbstractArrayJavaType<T, E> extends AbstractClassJavaType<
private final JavaType<E> componentJavaType;
public AbstractArrayJavaType(Class<T> clazz, BasicType<E> baseDescriptor, MutabilityPlan<T> mutabilityPlan) {
this( clazz, baseDescriptor.getJavaTypeDescriptor(), mutabilityPlan );
}
public AbstractArrayJavaType(Class<T> clazz, JavaType<E> baseDescriptor, MutabilityPlan<T> mutabilityPlan) {
super( clazz, mutabilityPlan );
this.componentJavaType = baseDescriptor;
@ -64,39 +58,76 @@ public abstract class AbstractArrayJavaType<T, E> extends AbstractClassJavaType<
ColumnTypeInformation columnTypeInformation,
JdbcTypeIndicators stdIndicators) {
final Class<?> elementJavaTypeClass = elementType.getJavaTypeDescriptor().getJavaTypeClass();
if ( elementType instanceof BasicPluralType<?, ?> || elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) {
if ( elementType instanceof BasicPluralType<?, ?>
|| elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) {
return null;
}
final BasicValueConverter<E, ?> valueConverter = elementType.getValueConverter();
if ( valueConverter == null ) {
final Function<JavaType<T>, BasicType<T>> creator = javaType -> {
final JdbcType arrayJdbcType =
getArrayJdbcType( typeConfiguration, dialect, Types.ARRAY, elementType, columnTypeInformation );
//noinspection unchecked,rawtypes
return new BasicArrayType( elementType, arrayJdbcType, javaType );
};
if ( typeConfiguration.getBasicTypeRegistry().getRegisteredType( elementType.getName() ) == elementType ) {
return typeConfiguration.standardBasicTypeForJavaType( getJavaType(), creator );
}
else {
return creator.apply( this );
}
}
else {
final JavaType<Object> relationalJavaType = typeConfiguration.getJavaTypeRegistry().getDescriptor(
Array.newInstance( valueConverter.getRelationalJavaType().getJavaTypeClass(), 0 ).getClass()
);
//noinspection unchecked,rawtypes
return new ConvertedBasicArrayType(
elementType,
getArrayJdbcType( typeConfiguration, dialect, Types.ARRAY, elementType, columnTypeInformation ),
this,
new ArrayConverter( valueConverter, this, relationalJavaType )
);
}
return valueConverter == null
? createType( typeConfiguration, dialect, this, elementType, columnTypeInformation, stdIndicators )
: createTypeUsingConverter( typeConfiguration, dialect, elementType, columnTypeInformation, stdIndicators, valueConverter );
}
private static JdbcType getArrayJdbcType(
<F> BasicType<T> createTypeUsingConverter(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<E> elementType,
ColumnTypeInformation columnTypeInformation,
JdbcTypeIndicators stdIndicators,
BasicValueConverter<E, F> valueConverter) {
final Class<F> convertedElementClass = valueConverter.getRelationalJavaType().getJavaTypeClass();
final Class<?> convertedArrayClass = Array.newInstance( convertedElementClass, 0 ).getClass();
final JavaType<?> relationalJavaType = typeConfiguration.getJavaTypeRegistry().getDescriptor( convertedArrayClass );
return new ConvertedBasicArrayType<>(
elementType,
getArrayJdbcType(
typeConfiguration,
dialect,
stdIndicators.getExplicitJdbcTypeCode(),
elementType,
columnTypeInformation
),
this,
new ArrayConverter<>( valueConverter, this, relationalJavaType )
);
}
BasicType<T> createType(
TypeConfiguration typeConfiguration,
Dialect dialect,
AbstractArrayJavaType<T,E> arrayJavaType,
BasicType<E> elementType,
ColumnTypeInformation columnTypeInformation,
JdbcTypeIndicators stdIndicators) {
return typeConfiguration.getBasicTypeRegistry().getRegisteredType( elementType.getName() ) == elementType
? typeConfiguration.standardBasicTypeForJavaType(
arrayJavaType.getJavaType(),
javaType -> basicArrayType( typeConfiguration, dialect, elementType, columnTypeInformation, stdIndicators, arrayJavaType )
)
: basicArrayType( typeConfiguration, dialect, elementType, columnTypeInformation, stdIndicators, arrayJavaType );
}
BasicType<T> basicArrayType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<E> elementType,
ColumnTypeInformation columnTypeInformation,
JdbcTypeIndicators stdIndicators,
JavaType<T> javaType) {
return new BasicArrayType<>(
elementType,
getArrayJdbcType(
typeConfiguration,
dialect,
stdIndicators.getExplicitJdbcTypeCode(),
elementType,
columnTypeInformation
),
javaType
);
}
static JdbcType getArrayJdbcType(
TypeConfiguration typeConfiguration,
Dialect dialect,
int preferredSqlTypeCodeForArray,

View File

@ -9,8 +9,6 @@ package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.sql.Types;
import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
@ -19,17 +17,11 @@ import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.descriptor.converter.internal.ArrayConverter;
import org.hibernate.type.BasicArrayType;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.ConvertedBasicArrayType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
/**
@ -75,7 +67,8 @@ public class ArrayJavaType<T> extends AbstractArrayJavaType<T[], T> {
}
}
final Class<?> elementJavaTypeClass = elementType.getJavaTypeDescriptor().getJavaTypeClass();
if ( elementType instanceof BasicPluralType<?, ?> || elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) {
if ( elementType instanceof BasicPluralType<?, ?>
|| elementJavaTypeClass != null && elementJavaTypeClass.isArray() ) {
return null;
}
final ArrayJavaType<T> arrayJavaType;
@ -88,52 +81,9 @@ public class ArrayJavaType<T> extends AbstractArrayJavaType<T[], T> {
typeConfiguration.getJavaTypeRegistry().addDescriptor( arrayJavaType );
}
final BasicValueConverter<T, ?> valueConverter = elementType.getValueConverter();
if ( valueConverter == null ) {
final Function<JavaType<T[]>, BasicType<T[]>> creator = javaType -> {
final JdbcType arrayJdbcType =
getArrayJdbcType( typeConfiguration, dialect, Types.ARRAY, elementType, columnTypeInformation );
return new BasicArrayType<>( elementType, arrayJdbcType, javaType );
};
if ( typeConfiguration.getBasicTypeRegistry().getRegisteredType( elementType.getName() ) == elementType ) {
return typeConfiguration.standardBasicTypeForJavaType( arrayJavaType.getJavaType(), creator );
}
return creator.apply( arrayJavaType );
}
else {
final JavaType<Object> relationalJavaType = typeConfiguration.getJavaTypeRegistry().getDescriptor(
Array.newInstance( valueConverter.getRelationalJavaType().getJavaTypeClass(), 0 ).getClass()
);
//noinspection unchecked,rawtypes
return new ConvertedBasicArrayType(
elementType,
getArrayJdbcType( typeConfiguration, dialect, Types.ARRAY, elementType, columnTypeInformation ),
arrayJavaType,
new ArrayConverter( valueConverter, arrayJavaType, relationalJavaType )
);
}
}
//TODO: copy/pasted from AbstractArrayJavaType
private static JdbcType getArrayJdbcType(
TypeConfiguration typeConfiguration,
Dialect dialect,
int preferredSqlTypeCodeForArray,
BasicType<?> elementType,
ColumnTypeInformation columnTypeInformation) {
final JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry();
final JdbcTypeConstructor arrayJdbcTypeConstructor =
jdbcTypeRegistry.getConstructor( preferredSqlTypeCodeForArray );
if ( arrayJdbcTypeConstructor != null ) {
return arrayJdbcTypeConstructor.resolveType(
typeConfiguration,
dialect,
elementType,
columnTypeInformation
);
}
else {
return jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForArray );
}
return valueConverter == null
? createType( typeConfiguration, dialect, arrayJavaType, elementType, columnTypeInformation, stdIndicators )
: createTypeUsingConverter( typeConfiguration, dialect, elementType, columnTypeInformation, stdIndicators, valueConverter );
}
@Override

View File

@ -74,14 +74,17 @@ public class ArrayJdbcType implements JdbcType {
//noinspection unchecked
elementJavaType = (JavaType<T>) ByteJavaType.INSTANCE;
}
else {
else if (javaTypeDescriptor instanceof BasicPluralJavaType) {
//noinspection unchecked
elementJavaType = javaTypeDescriptor instanceof BasicPluralJavaType
? ( (BasicPluralJavaType<T>) javaTypeDescriptor ).getElementJavaType()
: null; //TODO: what should really happen here?
elementJavaType = ((BasicPluralJavaType<T>) javaTypeDescriptor).getElementJavaType();
}
final JdbcLiteralFormatter<T> elementFormatter = elementJdbcType.getJdbcLiteralFormatter( elementJavaType );
return new JdbcLiteralFormatterArray<>( javaTypeDescriptor, elementFormatter );
else {
throw new IllegalArgumentException("not a BasicPluralJavaType");
}
return new JdbcLiteralFormatterArray<>(
javaTypeDescriptor,
elementJdbcType.getJdbcLiteralFormatter( elementJavaType )
);
}
@Override

View File

@ -10,6 +10,7 @@ import jakarta.persistence.EnumType;
import jakarta.persistence.TemporalType;
import org.hibernate.AssertionFailure;
import org.hibernate.Incubating;
import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.SqlTypes;
@ -151,6 +152,17 @@ public interface JdbcTypeIndicators {
return NO_COLUMN_SCALE;
}
/**
* Used (for now) only to choose a container {@link JdbcType} for
* SQL arrays.
*
* @since 6.3
*/
@Incubating
default Integer getExplicitJdbcTypeCode() {
return getPreferredSqlTypeCodeForArray();
}
/**
* The default {@link TimeZoneStorageStrategy}.
*

View File

@ -88,6 +88,7 @@ public class ConvertedBasicTypeImpl<J> implements ConvertedBasicType<J>,
this.converter = converter;
this.jdbcType = jdbcType;
this.sqlTypes = new int[] { jdbcType.getDdlTypeCode() };
//TODO: these type casts look completely bogus
this.jdbcValueBinder = (ValueBinder<J>) jdbcType.getBinder( converter.getRelationalJavaType() );
this.jdbcValueExtractor = (ValueExtractor<J>) jdbcType.getExtractor( converter.getRelationalJavaType() );
this.jdbcLiteralFormatter = (JdbcLiteralFormatter<J>) jdbcType.getJdbcLiteralFormatter( converter.getRelationalJavaType() );

View File

@ -157,7 +157,7 @@ public class SessionFactoryExtension
return sessionFactory;
}
catch (Exception e) {
throw new RuntimeException( "Could not build SessionFactory", e );
throw new RuntimeException( "Could not build SessionFactory: " + e.getMessage(), e );
}
};
}