diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java index 1e94e35a6a..43c0d2dea9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java @@ -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,18 +97,26 @@ 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 ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index 5b2c03ee4b..4d071819aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -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 ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreationContext.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreationContext.java index 21da21bd62..3012afe5a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/LobCreationContext.java @@ -17,6 +17,7 @@ public interface LobCreationContext { /** * The callback contract for making use of the JDBC {@link Connection}. */ + @FunctionalInterface interface Callback { /** * 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 execute(Callback callback); + + default T fromContext(Callback callback) { + return execute( callback ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ContextualLobCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/BlobAndClobCreator.java similarity index 57% rename from hibernate-core/src/main/java/org/hibernate/engine/jdbc/ContextualLobCreator.java rename to hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/BlobAndClobCreator.java index c2460b9fbf..1aa615c8e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/ContextualLobCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/BlobAndClobCreator.java @@ -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 . + * 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 CREATE_BLOB_CALLBACK = Connection::createBlob; + + /** + * Callback for performing contextual CLOB creation + */ + public static final LobCreationContext.Callback 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) { + final Blob blob = createBlob(); try { - final Blob blob = createBlob(); 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 CREATE_BLOB_CALLBACK = Connection::createBlob; - - /** - * Callback for performing contextual CLOB creation - */ - public static final LobCreationContext.Callback CREATE_CLOB_CALLBACK = Connection::createClob; - - /** - * Callback for performing contextual NCLOB creation - */ - public static final LobCreationContext.Callback CREATE_NCLOB_CALLBACK = Connection::createNClob; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreationHelper.java new file mode 100644 index 0000000000..64a04b05a9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreationHelper.java @@ -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 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 getSupportedContextualLobTypes(Dialect dialect, Map 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; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreationLogging.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreationLogging.java new file mode 100644 index 0000000000..78a261d052 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreationLogging.java @@ -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); +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreatorBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreatorBuilderImpl.java index deffc99a7b..3f3a65a6cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreatorBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobCreatorBuilderImpl.java @@ -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 supportedContextualLobTypes; - private final boolean useContextualLobCreation; - - private LobCreatorBuilderImpl(boolean useContextualLobCreation) { - this.useContextualLobCreation = useContextualLobCreation; + public LobCreatorBuilderImpl(EnumSet 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 configValues, Connection jdbcConnection) { - return new LobCreatorBuilderImpl( useContextualLobCreation( dialect, configValues, jdbcConnection ) ); + public static LobCreatorBuilderImpl makeLobCreatorBuilder( + Dialect dialect, + Map configValues, + Connection jdbcConnection) { + final EnumSet 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 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; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobTypes.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobTypes.java new file mode 100644 index 0000000000..3cfa6cae7f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/LobTypes.java @@ -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; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/StandardLobCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/StandardLobCreator.java new file mode 100644 index 0000000000..e430986277 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/StandardLobCreator.java @@ -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 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 ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LobCreatorBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LobCreatorBuilder.java deleted file mode 100644 index e33e95330c..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/LobCreatorBuilder.java +++ /dev/null @@ -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 . - */ -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 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 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 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; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 914c9cffae..5752436413 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -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) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/LobCreatorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/LobCreatorTest.java index 3d7795528c..c9476ac3bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/LobCreatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/LobCreatorTest.java @@ -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 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 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 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 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 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 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 supportedTypes; - private JdbcLobBuilderImpl(boolean isSupported) { - this.isSupported = isSupported; + private JdbcLobBuilderImpl(LobTypes... supportedTypes) { + this.supportedTypes = convert( supportedTypes ); } + + private static Set convert(LobTypes... supportedTypes) { + final Set 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(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/LobUnfetchedPropertyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/LobUnfetchedPropertyTest.java index ab9174de59..8650d59b0d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/type/LobUnfetchedPropertyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/LobUnfetchedPropertyTest.java @@ -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; + } + } }