HHH-16321 - Hibernate maps NCLOB to ntext on Sybase

This commit is contained in:
Steve Ebersole 2023-04-25 20:13:13 -05:00
parent 48c59392f6
commit 06381d2dd2
13 changed files with 549 additions and 327 deletions

View File

@ -47,6 +47,7 @@ import jakarta.persistence.TemporalType;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.DATE;
import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
@ -96,20 +97,28 @@ public class SybaseASEDialect extends SybaseDialect {
@Override
protected String columnType(int sqlTypeCode) {
switch ( sqlTypeCode ) {
case BOOLEAN:
case BOOLEAN: {
// On Sybase ASE, the 'bit' type cannot be null,
// and cannot have indexes (while we don't use
// tinyint to store signed bytes, we can use it
// to store boolean values)
return "tinyint";
case DATE:
}
case DATE: {
return "date";
case TIME:
}
case TIME: {
return "time";
default:
}
case NCLOB: {
// Sybase uses `unitext` instead of the T-SQL `ntext` type name
return "unitext";
}
default: {
return super.columnType( sqlTypeCode );
}
}
}
@Override
protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {

View File

@ -179,8 +179,12 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
jdbcTypeRegistry.addDescriptor( Types.NVARCHAR, ClobJdbcType.CLOB_BINDING );
}
else {
// Some Sybase drivers cannot support getClob. See HHH-7889
// jConnect driver only conditionally supports getClob/getNClob depending on a server setting. See
// - https://help.sap.com/doc/e3cb6844decf441e85e4670e1cf48c9b/16.0.3.6/en-US/SAP_jConnect_Programmers_Reference_en.pdf
// - https://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc20155.1570/html/OS_SDK_nf/CIHJFDDH.htm
// - HHH-7889
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.STREAM_BINDING_EXTRACTING );
jdbcTypeRegistry.addDescriptor( Types.NCLOB, ClobJdbcType.STREAM_BINDING_EXTRACTING );
}
jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.PRIMITIVE_ARRAY_BINDING );

View File

@ -17,6 +17,7 @@ public interface LobCreationContext {
/**
* The callback contract for making use of the JDBC {@link Connection}.
*/
@FunctionalInterface
interface Callback<T> {
/**
* Perform whatever actions are necessary using the provided JDBC {@link Connection}.
@ -28,6 +29,10 @@ public interface LobCreationContext {
* @throws SQLException Indicates trouble accessing the JDBC driver to create the LOB
*/
T executeOnConnection(Connection connection) throws SQLException;
default T from(Connection connection) throws SQLException {
return executeOnConnection( connection );
}
}
/**
@ -40,4 +45,8 @@ public interface LobCreationContext {
* @return The LOB created by the callback.
*/
<T> T execute(Callback<T> callback);
default <T> T fromContext(Callback<T> callback) {
return execute( callback );
}
}

View File

@ -2,9 +2,9 @@
* 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>.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.engine.jdbc;
package org.hibernate.engine.jdbc.env.internal;
import java.io.InputStream;
import java.io.Reader;
@ -15,23 +15,35 @@ import java.sql.NClob;
import java.sql.SQLException;
import org.hibernate.JDBCException;
import org.hibernate.engine.jdbc.AbstractLobCreator;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NClobProxy;
import org.hibernate.engine.jdbc.NonContextualLobCreator;
/**
* {@link LobCreator} implementation using contextual creation against the JDBC {@link Connection} class's LOB creation
* methods.
* LobCreator which can use {@link Connection#createBlob} and {@link Connection#createClob},
* but {@link java.sql.NClob} references are created locally.
*
* @see NClobProxy
*
* @author Steve Ebersole
* @author Gail Badner
*/
public class ContextualLobCreator extends AbstractLobCreator implements LobCreator {
private final LobCreationContext lobCreationContext;
public class BlobAndClobCreator extends AbstractLobCreator implements LobCreator {
/**
* Constructs a ContextualLobCreator
*
* @param lobCreationContext The context for performing LOB creation
* Callback for performing contextual BLOB creation
*/
public ContextualLobCreator(LobCreationContext lobCreationContext) {
public static final LobCreationContext.Callback<Blob> CREATE_BLOB_CALLBACK = Connection::createBlob;
/**
* Callback for performing contextual CLOB creation
*/
public static final LobCreationContext.Callback<Clob> CREATE_CLOB_CALLBACK = Connection::createClob;
protected final LobCreationContext lobCreationContext;
public BlobAndClobCreator(LobCreationContext lobCreationContext) {
this.lobCreationContext = lobCreationContext;
}
@ -41,13 +53,13 @@ public class ContextualLobCreator extends AbstractLobCreator implements LobCreat
* @return The created BLOB reference.
*/
public Blob createBlob() {
return lobCreationContext.execute( CREATE_BLOB_CALLBACK );
return lobCreationContext.fromContext( CREATE_BLOB_CALLBACK );
}
@Override
public Blob createBlob(byte[] bytes) {
try {
final Blob blob = createBlob();
try {
blob.setBytes( 1, bytes );
return blob;
}
@ -57,10 +69,10 @@ public class ContextualLobCreator extends AbstractLobCreator implements LobCreat
}
@Override
public Blob createBlob(InputStream inputStream, long length) {
public Blob createBlob(InputStream stream, long length) {
// IMPL NOTE : it is inefficient to use JDBC LOB locator creation to create a LOB
// backed by a given stream. So just wrap the stream (which is what the NonContextualLobCreator does).
return NonContextualLobCreator.INSTANCE.createBlob( inputStream, length );
return NonContextualLobCreator.INSTANCE.createBlob( stream, length );
}
/**
@ -69,7 +81,7 @@ public class ContextualLobCreator extends AbstractLobCreator implements LobCreat
* @return The created CLOB reference.
*/
public Clob createClob() {
return lobCreationContext.execute( CREATE_CLOB_CALLBACK );
return lobCreationContext.fromContext( CREATE_CLOB_CALLBACK );
}
@Override
@ -91,46 +103,13 @@ public class ContextualLobCreator extends AbstractLobCreator implements LobCreat
return NonContextualLobCreator.INSTANCE.createClob( reader, length );
}
/**
* Create the basic contextual NCLOB reference.
*
* @return The created NCLOB reference.
*/
public NClob createNClob() {
return lobCreationContext.execute( CREATE_NCLOB_CALLBACK );
}
@Override
public NClob createNClob(String string) {
try {
final NClob nclob = createNClob();
nclob.setString( 1, string );
return nclob;
}
catch ( SQLException e ) {
throw new JDBCException( "Unable to set NCLOB string after creation", e );
}
return NonContextualLobCreator.INSTANCE.createNClob( string );
}
@Override
public NClob createNClob(Reader reader, long length) {
// IMPL NOTE : it is inefficient to use JDBC LOB locator creation to create a LOB
// backed by a given stream. So just wrap the stream (which is what the NonContextualLobCreator does).
return NonContextualLobCreator.INSTANCE.createNClob( reader, length );
}
/**
* Callback for performing contextual BLOB creation
*/
public static final LobCreationContext.Callback<Blob> CREATE_BLOB_CALLBACK = Connection::createBlob;
/**
* Callback for performing contextual CLOB creation
*/
public static final LobCreationContext.Callback<Clob> CREATE_CLOB_CALLBACK = Connection::createClob;
/**
* Callback for performing contextual NCLOB creation
*/
public static final LobCreationContext.Callback<NClob> CREATE_NCLOB_CALLBACK = Connection::createNClob;
}

View File

@ -0,0 +1,118 @@
/*
* 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.engine.jdbc.env.internal;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Map;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.config.ConfigurationHelper;
import static org.hibernate.engine.jdbc.env.internal.LobCreationLogging.LOB_LOGGER;
import static org.hibernate.engine.jdbc.env.internal.LobCreationLogging.LOB_MESSAGE_LOGGER;
/**
* Utilities for LOB creation
*
* @author Steve Ebersole
*/
public class LobCreationHelper {
public static final EnumSet<LobTypes> NONE = EnumSet.noneOf( LobTypes.class );
/**
* Basically here we are simply checking whether we can call the {@link Connection} methods for
* LOB creation added in JDBC 4. We not only check whether the {@link Connection} declares these methods,
* but also whether the actual {@link Connection} instance implements them (i.e. can be called without simply
* throwing an exception).
*
* @param dialect The {@link Dialect} in use
* @param configValues The map of settings
* @param jdbcConnection The connection which can be used in level-of-support testing.
*/
public static EnumSet<LobTypes> getSupportedContextualLobTypes(Dialect dialect, Map<String,Object> configValues, Connection jdbcConnection) {
if ( ConfigurationHelper.getBoolean( Environment.NON_CONTEXTUAL_LOB_CREATION, configValues ) ) {
LOB_MESSAGE_LOGGER.disablingContextualLOBCreation( Environment.NON_CONTEXTUAL_LOB_CREATION );
return NONE;
}
if ( jdbcConnection == null ) {
LOB_MESSAGE_LOGGER.disablingContextualLOBCreationSinceConnectionNull();
return NONE;
}
try {
final DatabaseMetaData meta = jdbcConnection.getMetaData();
// if the jdbc driver version is less than 4, it shouldn't have createClob
if ( meta.getJDBCMajorVersion() < 4 ) {
LOB_MESSAGE_LOGGER.nonContextualLobCreationJdbcVersion( meta.getJDBCMajorVersion() );
return NONE;
}
if ( !dialect.supportsJdbcConnectionLobCreation( meta ) ) {
LOB_MESSAGE_LOGGER.nonContextualLobCreationDialect();
return NONE;
}
}
catch (SQLException ignore) {
// ignore exception and continue
}
// NOTE : for the time being we assume that the ability to call
// `createClob` implies the ability to call `#createBlob`
if ( canCreateClob( jdbcConnection ) ) {
if ( canCreateNClob( jdbcConnection ) ) {
return EnumSet.of( LobTypes.BLOB, LobTypes.CLOB, LobTypes.NCLOB );
}
else {
return EnumSet.of( LobTypes.BLOB, LobTypes.CLOB );
}
}
return NONE;
}
private static boolean canCreateClob(Connection jdbcConnection) {
try {
// we just want to see if the driver can create one. we can immediately free it.
final Clob clob = jdbcConnection.createClob();
try {
clob.free();
}
catch (Throwable e) {
LOB_LOGGER.tracef( "Unable to free CLOB created to test createClob() implementation : %s", e );
}
return true;
}
catch (SQLException e) {
LOB_MESSAGE_LOGGER.contextualClobCreationFailed( e );
return false;
}
}
private static boolean canCreateNClob(Connection jdbcConnection) {
try {
// we just want to see if the driver can create one. we can immediately free it.
final Clob clob = jdbcConnection.createNClob();
try {
clob.free();
}
catch (Throwable e) {
LOB_LOGGER.tracef( "Unable to free NCLOB created to test createNClob() implementation : %s", e );
}
return true;
}
catch (SQLException e) {
LOB_MESSAGE_LOGGER.contextualNClobCreationFailed( e );
return false;
}
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.engine.jdbc.env.internal;
import org.hibernate.boot.BootLogging;
import org.hibernate.engine.jdbc.JdbcLogging;
import org.hibernate.internal.log.SubSystemLogging;
import org.jboss.logging.BasicLogger;
import org.jboss.logging.Logger;
import org.jboss.logging.annotations.LogMessage;
import org.jboss.logging.annotations.Message;
import org.jboss.logging.annotations.MessageLogger;
import org.jboss.logging.annotations.ValidIdRange;
import static org.jboss.logging.Logger.Level.DEBUG;
/**
* @author Steve Ebersole
*/
@SubSystemLogging(
name = BootLogging.NAME,
description = "Logging related to "
)
@MessageLogger( projectCode = "HHH" )
@ValidIdRange( min = 10010001, max = 10010050 )
public interface LobCreationLogging extends BasicLogger {
String NAME = JdbcLogging.NAME + ".lob";
Logger LOB_LOGGER = Logger.getLogger( NAME );
LobCreationLogging LOB_MESSAGE_LOGGER = Logger.getMessageLogger( LobCreationLogging.class, NAME );
boolean LOB_TRACE_ENABLED = LOB_LOGGER.isTraceEnabled();
boolean LOB_DEBUG_ENABLED = LOB_LOGGER.isDebugEnabled();
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as %s is true", id = 10010001)
void disablingContextualLOBCreation(String settingName);
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as connection was null", id = 10010002)
void disablingContextualLOBCreationSinceConnectionNull();
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as JDBC driver reported JDBC version [%s] less than 4", id = 10010003)
void nonContextualLobCreationJdbcVersion(int jdbcMajorVersion);
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as Dialect reported it is not supported", id = 10010004)
void nonContextualLobCreationDialect();
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as createClob() method threw error : %s", id = 10010005)
void contextualClobCreationFailed(Throwable t);
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual NCLOB creation as createNClob() method threw error : %s", id = 10010006)
void contextualNClobCreationFailed(Throwable t);
}

View File

@ -6,24 +6,20 @@
*/
package org.hibernate.engine.jdbc.env.internal;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Map;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.ContextualLobCreator;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NonContextualLobCreator;
import org.hibernate.engine.jdbc.env.spi.LobCreatorBuilder;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.jboss.logging.Logger;
import static org.hibernate.engine.jdbc.env.internal.LobCreationHelper.NONE;
import static org.hibernate.engine.jdbc.env.internal.LobCreationHelper.getSupportedContextualLobTypes;
import static org.hibernate.engine.jdbc.env.internal.LobCreationLogging.LOB_LOGGER;
import static org.hibernate.engine.jdbc.env.internal.LobCreationLogging.LOB_MESSAGE_LOGGER;
/**
* Builds {@link LobCreator} instances based on the capabilities of the environment.
@ -31,15 +27,10 @@ import org.jboss.logging.Logger;
* @author Steve Ebersole
*/
public class LobCreatorBuilderImpl implements LobCreatorBuilder {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
LobCreatorBuilderImpl.class.getName()
);
private final EnumSet<LobTypes> supportedContextualLobTypes;
private final boolean useContextualLobCreation;
private LobCreatorBuilderImpl(boolean useContextualLobCreation) {
this.useContextualLobCreation = useContextualLobCreation;
public LobCreatorBuilderImpl(EnumSet<LobTypes> supportedContextualLobTypes) {
this.supportedContextualLobTypes = supportedContextualLobTypes;
}
// factory methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -53,8 +44,17 @@ public class LobCreatorBuilderImpl implements LobCreatorBuilder {
* @param jdbcConnection A JDBC {@link Connection} which can be used to gauge the drivers level of support,
* specifically for creating LOB references.
*/
public static LobCreatorBuilderImpl makeLobCreatorBuilder(Dialect dialect, Map<String,Object> configValues, Connection jdbcConnection) {
return new LobCreatorBuilderImpl( useContextualLobCreation( dialect, configValues, jdbcConnection ) );
public static LobCreatorBuilderImpl makeLobCreatorBuilder(
Dialect dialect,
Map<String,Object> configValues,
Connection jdbcConnection) {
final EnumSet<LobTypes> supportedContextualLobTypes = getSupportedContextualLobTypes(
dialect,
configValues,
jdbcConnection
);
return new LobCreatorBuilderImpl( supportedContextualLobTypes );
}
/**
@ -63,80 +63,8 @@ public class LobCreatorBuilderImpl implements LobCreatorBuilder {
* @return Appropriate LobCreatorBuilder
*/
public static LobCreatorBuilderImpl makeLobCreatorBuilder() {
LOG.disablingContextualLOBCreationSinceConnectionNull();
return new LobCreatorBuilderImpl( false );
}
private static final Class<?>[] NO_ARG_SIG = ArrayHelper.EMPTY_CLASS_ARRAY;
private static final Object[] NO_ARGS = ArrayHelper.EMPTY_OBJECT_ARRAY;
/**
* Basically here we are simply checking whether we can call the {@link Connection} methods for
* LOB creation added in JDBC 4. We not only check whether the {@link Connection} declares these methods,
* but also whether the actual {@link Connection} instance implements them (i.e. can be called without simply
* throwing an exception).
*
* @param dialect The {@link Dialect} in use
* @param configValues The map of settings
* @param jdbcConnection The connection which can be used in level-of-support testing.
*
* @return True if the connection can be used to create LOBs; false otherwise.
*/
private static boolean useContextualLobCreation(Dialect dialect, Map<String,Object> configValues, Connection jdbcConnection) {
final boolean isNonContextualLobCreationRequired =
ConfigurationHelper.getBoolean( Environment.NON_CONTEXTUAL_LOB_CREATION, configValues );
if ( isNonContextualLobCreationRequired ) {
LOG.disablingContextualLOBCreation( Environment.NON_CONTEXTUAL_LOB_CREATION );
return false;
}
if ( jdbcConnection == null ) {
LOG.disablingContextualLOBCreationSinceConnectionNull();
return false;
}
try {
try {
final DatabaseMetaData meta = jdbcConnection.getMetaData();
// if the jdbc driver version is less than 4, it shouldn't have createClob
if ( meta.getJDBCMajorVersion() < 4 ) {
LOG.disablingContextualLOBCreationSinceOldJdbcVersion( meta.getJDBCMajorVersion() );
return false;
}
if ( !dialect.supportsJdbcConnectionLobCreation( meta ) ) {
return false;
}
}
catch ( SQLException ignore ) {
// ignore exception and continue
}
final Class<?> connectionClass = Connection.class;
final Method createClobMethod = connectionClass.getMethod( "createClob", NO_ARG_SIG );
if ( createClobMethod.getDeclaringClass().equals( Connection.class ) ) {
// If we get here we are running in a jdk 1.6 (jdbc 4) environment:
// Further check to make sure the driver actually implements the LOB creation methods.
// We check against createClob() as indicative of all; should we check against all 3 explicitly?
try {
final Object clob = createClobMethod.invoke( jdbcConnection, NO_ARGS );
try {
final Method freeMethod = clob.getClass().getMethod( "free", NO_ARG_SIG );
freeMethod.invoke( clob, NO_ARGS );
}
catch ( Throwable e ) {
LOG.tracef( "Unable to free CLOB created to test createClob() implementation : %s", e );
}
return true;
}
catch ( Throwable t ) {
LOG.disablingContextualLOBCreationSinceCreateClobFailed( t );
}
}
}
catch ( NoSuchMethodException ignore ) {
}
return false;
LOB_MESSAGE_LOGGER.disablingContextualLOBCreationSinceConnectionNull();
return new LobCreatorBuilderImpl( NONE );
}
/**
@ -147,8 +75,20 @@ public class LobCreatorBuilderImpl implements LobCreatorBuilder {
* @return The LobCreator
*/
public LobCreator buildLobCreator(LobCreationContext lobCreationContext) {
return useContextualLobCreation
? new ContextualLobCreator( lobCreationContext )
: NonContextualLobCreator.INSTANCE;
if ( supportedContextualLobTypes.isEmpty() ) {
return NonContextualLobCreator.INSTANCE;
}
if ( supportedContextualLobTypes.contains( LobTypes.BLOB )
&& supportedContextualLobTypes.contains( LobTypes.CLOB ) ){
if ( !supportedContextualLobTypes.contains( LobTypes.NCLOB ) ) {
return new BlobAndClobCreator( lobCreationContext );
}
return new StandardLobCreator( lobCreationContext );
}
LOB_LOGGER.debug( "Unexpected condition resolving type of LobCreator to use. Falling back to NonContextualLobCreator" );
return NonContextualLobCreator.INSTANCE;
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.engine.jdbc.env.internal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
import org.hibernate.type.SqlTypes;
/**
* Enumeration of the JDBC LOB locator types
*
* @author Steve Ebersole
*/
public enum LobTypes {
BLOB( SqlTypes.BLOB, Blob.class ),
CLOB( SqlTypes.CLOB, Clob.class ),
NCLOB( SqlTypes.NCLOB, NClob.class );
private final int jdbcTypeCode;
private final Class<?> jdbcTypeClass;
LobTypes(int jdbcTypeCode, Class<?> jdbcTypeClass) {
this.jdbcTypeCode = jdbcTypeCode;
this.jdbcTypeClass = jdbcTypeClass;
}
public int getJdbcTypeCode() {
return jdbcTypeCode;
}
public Class<?> getJdbcTypeClass() {
return jdbcTypeClass;
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.engine.jdbc.env.internal;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.NClob;
import java.sql.SQLException;
import org.hibernate.JDBCException;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NonContextualLobCreator;
/**
* {@linkplain LobCreator} implementation using {@linkplain Connection#createBlob},
* {@linkplain Connection#createClob} and {@linkplain Connection#createNClob} to
* create the LOB references.
*
* @author Steve Ebersole
* @author Gail Badner
*/
public class StandardLobCreator extends BlobAndClobCreator {
/**
* Callback for performing contextual NCLOB creation
*/
public static final LobCreationContext.Callback<NClob> CREATE_NCLOB_CALLBACK = Connection::createNClob;
public StandardLobCreator(LobCreationContext lobCreationContext) {
super( lobCreationContext );
}
/**
* Create the basic contextual NCLOB reference.
*
* @return The created NCLOB reference.
*/
public NClob createNClob() {
return lobCreationContext.fromContext( CREATE_NCLOB_CALLBACK );
}
@Override
public NClob createNClob(String string) {
try {
final NClob nclob = createNClob();
nclob.setString( 1, string );
return nclob;
}
catch ( SQLException e ) {
throw new JDBCException( "Unable to set NCLOB string after creation", e );
}
}
@Override
public NClob createNClob(Reader reader, long length) {
// IMPL NOTE : it is inefficient to use JDBC LOB locator creation to create a LOB
// backed by a given stream. So just wrap the stream (which is what the NonContextualLobCreator does).
return NonContextualLobCreator.INSTANCE.createNClob( reader, length );
}
}

View File

@ -1,134 +0,0 @@
/*
* 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.engine.jdbc.internal;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.ContextualLobCreator;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NonContextualLobCreator;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.PropertiesHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.jboss.logging.Logger;
/**
* Builds {@link LobCreator} instances based on the capabilities of the environment.
*
* @author Steve Ebersole
*/
public class LobCreatorBuilder {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
LobCreatorBuilder.class.getName()
);
private final boolean useContextualLobCreation;
/**
* The public factory method for obtaining the appropriate according to given JDBC {@link Connection}.
*
* @param configValues The map of settings
* @param jdbcConnection A JDBC {@link Connection} which can be used to gauge the drivers level of support,
* specifically for creating LOB references.
*/
public LobCreatorBuilder(Map<String,Object> configValues, Connection jdbcConnection) {
this.useContextualLobCreation = useContextualLobCreation( configValues, jdbcConnection );
}
public LobCreatorBuilder(Properties configValues, Connection jdbcConnection) {
this( PropertiesHelper.map(configValues), jdbcConnection );
}
private static final Class<?>[] NO_ARG_SIG = ArrayHelper.EMPTY_CLASS_ARRAY;
private static final Object[] NO_ARGS = ArrayHelper.EMPTY_OBJECT_ARRAY;
/**
* Basically here we are simply checking whether we can call the {@link Connection} methods for
* LOB creation added in JDBC 4. We not only check whether the {@link Connection} declares these methods,
* but also whether the actual {@link Connection} instance implements them (i.e. can be called without simply
* throwing an exception).
*
* @param jdbcConnection The connection which can be used in level-of-support testing.
*
* @return True if the connection can be used to create LOBs; false otherwise.
*/
private static boolean useContextualLobCreation(Map<String,Object> configValues, Connection jdbcConnection) {
final boolean isNonContextualLobCreationRequired =
ConfigurationHelper.getBoolean( Environment.NON_CONTEXTUAL_LOB_CREATION, configValues );
if ( isNonContextualLobCreationRequired ) {
LOG.disablingContextualLOBCreation( Environment.NON_CONTEXTUAL_LOB_CREATION );
return false;
}
if ( jdbcConnection == null ) {
LOG.disablingContextualLOBCreationSinceConnectionNull();
return false;
}
try {
try {
final DatabaseMetaData meta = jdbcConnection.getMetaData();
// if the jdbc driver version is less than 4, it shouldn't have createClob
if ( meta.getJDBCMajorVersion() < 4 ) {
LOG.disablingContextualLOBCreationSinceOldJdbcVersion( meta.getJDBCMajorVersion() );
return false;
}
}
catch ( SQLException ignore ) {
// ignore exception and continue
}
final Class<Connection> connectionClass = Connection.class;
final Method createClobMethod = connectionClass.getMethod( "createClob", NO_ARG_SIG );
if ( createClobMethod.getDeclaringClass().equals( Connection.class ) ) {
// If we get here we are running in a jdk 1.6 (jdbc 4) environment:
// Further check to make sure the driver actually implements the LOB creation methods.
// We check against createClob() as indicative of all; should we check against all 3 explicitly?
try {
final Object clob = createClobMethod.invoke( jdbcConnection, NO_ARGS );
try {
final Method freeMethod = clob.getClass().getMethod( "free", NO_ARG_SIG );
freeMethod.invoke( clob, NO_ARGS );
}
catch ( Throwable e ) {
LOG.tracef( "Unable to free CLOB created to test createClob() implementation : %s", e );
}
return true;
}
catch ( Throwable t ) {
LOG.disablingContextualLOBCreationSinceCreateClobFailed( t );
}
}
}
catch ( NoSuchMethodException ignore ) {
}
return false;
}
/**
* Build a LobCreator using the given context
*
* @param lobCreationContext The LOB creation context
*
* @return The LobCreator
*/
public LobCreator buildLobCreator(LobCreationContext lobCreationContext) {
return useContextualLobCreation
? new ContextualLobCreator( lobCreationContext )
: NonContextualLobCreator.INSTANCE;
}
}

View File

@ -26,6 +26,7 @@ import org.hibernate.cache.CacheException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver;
import org.hibernate.engine.jdbc.env.internal.LobCreationLogging;
import org.hibernate.engine.jndi.JndiException;
import org.hibernate.engine.jndi.JndiNameException;
import org.hibernate.engine.spi.CollectionKey;
@ -1425,21 +1426,40 @@ public interface CoreMessageLogger extends BasicLogger {
@Message(value = "Closing un-released batch", id = 420)
void closingUnreleasedBatch();
/**
* @deprecated Use {@link LobCreationLogging#disablingContextualLOBCreation} instead
*/
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as %s is true", id = 421)
@Deprecated
void disablingContextualLOBCreation(String nonContextualLobCreation);
/**
* @deprecated Use {@link LobCreationLogging#disablingContextualLOBCreationSinceConnectionNull} instead
*/
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as connection was null", id = 422)
@Deprecated
void disablingContextualLOBCreationSinceConnectionNull();
/**
* @deprecated Use {@link LobCreationLogging#nonContextualLobCreationJdbcVersion} instead
*/
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as JDBC driver reported JDBC version [%s] less than 4",
id = 423)
@Message(value = "Disabling contextual LOB creation as JDBC driver reported JDBC version [%s] less than 4", id = 423)
@Deprecated
void disablingContextualLOBCreationSinceOldJdbcVersion(int jdbcMajorVersion);
/**
* @deprecated Use {@link LobCreationLogging#contextualClobCreationFailed} instead
*
* @see LobCreationLogging#contextualClobCreationFailed
* @see LobCreationLogging#contextualNClobCreationFailed
*/
@LogMessage(level = DEBUG)
@Message(value = "Disabling contextual LOB creation as createClob() method threw error : %s", id = 424)
@Deprecated
void disablingContextualLOBCreationSinceCreateClobFailed(Throwable t);
@LogMessage(level = INFO)

View File

@ -19,80 +19,142 @@ import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.SQLException;
import java.util.Properties;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.engine.jdbc.BlobImplementer;
import org.hibernate.engine.jdbc.ClobImplementer;
import org.hibernate.engine.jdbc.ContextualLobCreator;
import org.hibernate.engine.jdbc.LobCreationContext;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NClobImplementer;
import org.hibernate.engine.jdbc.NonContextualLobCreator;
import org.hibernate.engine.jdbc.WrappedBlob;
import org.hibernate.engine.jdbc.WrappedClob;
import org.hibernate.engine.jdbc.internal.LobCreatorBuilder;
import org.hibernate.engine.jdbc.env.internal.BlobAndClobCreator;
import org.hibernate.engine.jdbc.env.internal.LobCreationHelper;
import org.hibernate.engine.jdbc.env.internal.LobCreatorBuilderImpl;
import org.hibernate.engine.jdbc.env.internal.LobTypes;
import org.hibernate.engine.jdbc.env.internal.StandardLobCreator;
import static org.junit.Assert.assertSame;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
public class LobCreatorTest extends org.hibernate.testing.junit4.BaseUnitTestCase {
public class LobCreatorTest {
@Test
public void testConnectedLobCreator() throws SQLException {
final Connection connection = createConnectionProxy( 4, new JdbcLobBuilderImpl( true ) );
LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final Connection connection = createConnectionProxy( 4, new JdbcLobBuilderImpl( LobTypes.BLOB, LobTypes.CLOB, LobTypes.NCLOB ) );
final H2Dialect dialect = new H2Dialect();
final EnumSet<LobTypes> supportedContextualLobTypes = LobCreationHelper.getSupportedContextualLobTypes(
dialect,
Collections.emptyMap(),
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
assertThat( lobCreator ).isInstanceOf( StandardLobCreator.class );
LobCreator lobCreator =
new LobCreatorBuilder( new Properties(), connection )
.buildLobCreator( lobCreationContext );
assertTrue( lobCreator instanceof ContextualLobCreator );
testLobCreation( lobCreator );
connection.close();
}
@Test
public void testJdbc3LobCreator() throws SQLException {
final Connection connection = createConnectionProxy( 3, new JdbcLobBuilderImpl( false) );
LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final Connection connection = createConnectionProxy( 3, new JdbcLobBuilderImpl() );
final H2Dialect dialect = new H2Dialect();
LobCreator lobCreator =
new LobCreatorBuilder( new Properties(), connection )
.buildLobCreator( lobCreationContext );
assertSame( NonContextualLobCreator.INSTANCE, lobCreator );
final EnumSet<LobTypes> supportedContextualLobTypes = LobCreationHelper.getSupportedContextualLobTypes(
dialect,
Collections.emptyMap(),
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
assertThat( lobCreator ).isSameAs( NonContextualLobCreator.INSTANCE );
testLobCreation( lobCreator );
connection.close();
}
@Test
public void testJdbc4UnsupportedLobCreator() throws SQLException {
final Connection connection = createConnectionProxy( 4, new JdbcLobBuilderImpl( false ) );
LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final Connection connection = createConnectionProxy( 4, new JdbcLobBuilderImpl() );
final H2Dialect dialect = new H2Dialect();
LobCreator lobCreator =
new LobCreatorBuilder( new Properties(), connection )
.buildLobCreator( lobCreationContext );
assertSame( NonContextualLobCreator.INSTANCE, lobCreator );
final EnumSet<LobTypes> supportedContextualLobTypes = LobCreationHelper.getSupportedContextualLobTypes(
dialect,
Collections.emptyMap(),
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
assertThat( lobCreator ).isSameAs( NonContextualLobCreator.INSTANCE );
testLobCreation( lobCreator );
connection.close();
}
@Test
public void testConfiguredNonContextualLobCreator() throws SQLException {
final Connection connection = createConnectionProxy( 4, new JdbcLobBuilderImpl( true ) );
LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final Connection connection = createConnectionProxy( 4, new JdbcLobBuilderImpl( LobTypes.BLOB, LobTypes.CLOB, LobTypes.NCLOB ) );
final H2Dialect dialect = new H2Dialect();
final Map<String,Object> props = new HashMap<>();
props.put( Environment.NON_CONTEXTUAL_LOB_CREATION, "true" );
Properties props = new Properties();
props.setProperty( Environment.NON_CONTEXTUAL_LOB_CREATION, "true" );
LobCreator lobCreator =
new LobCreatorBuilder( props, connection )
.buildLobCreator( lobCreationContext );
assertSame( NonContextualLobCreator.INSTANCE, lobCreator );
final EnumSet<LobTypes> supportedContextualLobTypes = LobCreationHelper.getSupportedContextualLobTypes(
dialect,
props,
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
assertThat( lobCreator ).isSameAs( NonContextualLobCreator.INSTANCE );
testLobCreation( lobCreator );
connection.close();
}
@Test
public void testBlobAndClob() throws SQLException {
// no NCLOB
final Connection connection = createConnectionProxy( 4, new JdbcLobBuilderImpl( LobTypes.BLOB, LobTypes.CLOB ) );
final SybaseDialect dialect = new SybaseDialect();
final EnumSet<LobTypes> supportedContextualLobTypes = LobCreationHelper.getSupportedContextualLobTypes(
dialect,
Collections.emptyMap(),
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
assertThat( lobCreator ).isInstanceOf( BlobAndClobCreator.class );
testLobCreation( lobCreator );
connection.close();
@ -124,7 +186,7 @@ public class LobCreatorTest extends org.hibernate.testing.junit4.BaseUnitTestCas
assertTrue( nclob instanceof NClobImplementer );
}
else {
assertTrue( nclob instanceof JdbcNClob );
assertTrue( nclob instanceof NClob );
}
// assertTrue( nclob instanceof NClob );
nclob = lobCreator.wrap( nclob );
@ -159,27 +221,34 @@ public class LobCreatorTest extends org.hibernate.testing.junit4.BaseUnitTestCas
}
private static class JdbcLobBuilderImpl implements JdbcLobBuilder {
private final boolean isSupported;
private final Set<LobTypes> supportedTypes;
private JdbcLobBuilderImpl(boolean isSupported) {
this.isSupported = isSupported;
private JdbcLobBuilderImpl(LobTypes... supportedTypes) {
this.supportedTypes = convert( supportedTypes );
}
private static Set<LobTypes> convert(LobTypes... supportedTypes) {
final Set<LobTypes> result = new HashSet<>();
result.addAll( Arrays.asList( supportedTypes ) );
return result;
}
public Blob createBlob() throws SQLException {
if ( ! isSupported ) {
if ( ! supportedTypes.contains( LobTypes.BLOB ) ) {
throw new SQLException( "not supported!" );
}
return new JdbcBlob();
}
public Clob createClob() throws SQLException {
if ( ! isSupported ) {
if ( ! supportedTypes.contains( LobTypes.CLOB ) ) {
throw new SQLException( "not supported!" );
}
return new JdbcClob();
}
public NClob createNClob() throws SQLException {
if ( ! isSupported ) {
if ( ! supportedTypes.contains( LobTypes.NCLOB ) ) {
throw new SQLException( "not supported!" );
}
return new JdbcNClob();

View File

@ -29,11 +29,16 @@ import jakarta.persistence.Lob;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -102,6 +107,9 @@ public class LobUnfetchedPropertyTest extends BaseCoreFunctionalTestCase {
@Test
@RequiresDialectFeature(DialectChecks.SupportsNClob.class)
@SkipForDialect(
dialectClass = SybaseDialect.class, matchSubTypes = true,
reason = "jConnect does not support Connection#createNClob which is ultimately used by LobHelper#createNClob" )
public void testNClob() {
final int id = doInHibernate( this::sessionFactory, s -> {
FileNClob file = new FileNClob();
@ -219,4 +227,34 @@ public class LobUnfetchedPropertyTest extends BaseCoreFunctionalTestCase {
this.clob = clob;
}
}
@Entity(name = "FileNClob2")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, includeLazy = false)
public static class FileNClob2 {
private int id;
private String clob;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name = "filedata", length = 1024 * 1024)
@Lob
@Basic(fetch = FetchType.LAZY)
public String getClob() {
return clob;
}
public void setClob(String clob) {
this.clob = clob;
}
}
}