diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java index efa83ac68f..c0b8b58403 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java @@ -1045,6 +1045,11 @@ public class CockroachLegacyDialect extends Dialect { return false; } + @Override + public boolean useConnectionToCreateLob() { + return false; + } + @Override public boolean supportsOffsetInSubquery() { return true; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java index 65eb290918..3da7ba4005 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java @@ -335,6 +335,11 @@ public class MimerSQLDialect extends Dialect { return false; } + @Override + public boolean useConnectionToCreateLob() { + return false; + } + @Override public IdentityColumnSupport getIdentityColumnSupport() { return MimerSQLIdentityColumnSupport.INSTANCE; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index e47e3a073a..616199bb60 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -1563,4 +1563,11 @@ public class OracleLegacyDialect extends Dialect { public boolean supportsFromClauseInUpdate() { return true; } + + @Override + public boolean useInputStreamToInsertBlob() { + // see HHH-18206 + return false; + } + } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index 65c309e95c..401e41d0c5 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -849,6 +849,11 @@ public class PostgreSQLLegacyDialect extends Dialect { return false; } + @Override + public boolean useConnectionToCreateLob() { + return false; + } + @Override public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) { // Workaround for postgres bug #1453 diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java index 65212d6337..69b0687d32 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TeradataDialect.java @@ -138,6 +138,11 @@ public class TeradataDialect extends Dialect { return getVersion().isSameOrAfter( 14 ); } + @Override + public boolean useConnectionToCreateLob() { + return false; + } + @Override public int getMaxVarcharLength() { //for the unicode server character set diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index dfce4648f0..c9d3c00c55 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -1014,6 +1014,11 @@ public class CockroachDialect extends Dialect { return false; } + @Override + public boolean useConnectionToCreateLob() { + return false; + } + @Override public boolean supportsOffsetInSubquery() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index bb1ab31b0b..8d39b5495a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -3647,6 +3647,20 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun return true; } + /** + * Should BLOB, CLOB, and NCLOB be created solely using respectively + * {@link Connection#createBlob()}, {@link Connection#createClob()}, + * and {@link Connection#createNClob()}. + * + * @return True if BLOB, CLOB, and NCLOB should be created using JDBC + * {@link Connection}. + * + * @since 6.6 + */ + public boolean useConnectionToCreateLob() { + return !useInputStreamToInsertBlob(); + } + /** * Does this dialect support parameters within the {@code SELECT} clause * of {@code INSERT ... SELECT ...} statements? diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java b/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java index 40976252bb..e048ad1d6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DialectDelegateWrapper.java @@ -1041,6 +1041,11 @@ public class DialectDelegateWrapper extends Dialect { return wrapped.useInputStreamToInsertBlob(); } + @Override + public boolean useConnectionToCreateLob() { + return wrapped.useConnectionToCreateLob(); + } + @Override @Deprecated(since = "6", forRemoval = true) public boolean supportsParametersInInsertSelect() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 03d9587c6a..da7f6926b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1676,4 +1676,11 @@ public class OracleDialect extends Dialect { public String[] getDropEnumTypeCommand(String name) { return new String[] { "drop domain if exists " + name + " force" }; } + + @Override + public boolean useInputStreamToInsertBlob() { + // see HHH-18206 + return false; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 4ea90b6f68..90be854ecd 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -903,6 +903,11 @@ public class PostgreSQLDialect extends Dialect { return false; } + @Override + public boolean useConnectionToCreateLob() { + return false; + } + @Override public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) { // TODO: adapt this to handle named enum types! diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java index 2053239704..bc1190d4fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/NClobProxy.java @@ -41,7 +41,7 @@ public class NClobProxy extends ClobProxy { * @return The generated proxy. */ public static NClob generateProxy(String string) { - return (NClob) Proxy.newProxyInstance( getProxyClassLoader(), PROXY_INTERFACES, new ClobProxy( string ) ); + return (NClob) Proxy.newProxyInstance( getProxyClassLoader(), PROXY_INTERFACES, new NClobProxy( string ) ); } /** @@ -53,7 +53,7 @@ public class NClobProxy extends ClobProxy { * @return The generated proxy. */ public static NClob generateProxy(Reader reader, long length) { - return (NClob) Proxy.newProxyInstance( getProxyClassLoader(), PROXY_INTERFACES, new ClobProxy( reader, length ) ); + return (NClob) Proxy.newProxyInstance( getProxyClassLoader(), PROXY_INTERFACES, new NClobProxy( reader, length ) ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java index faf334194d..3149e7ad70 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java @@ -6,6 +6,7 @@ */ package org.hibernate.type.descriptor; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -34,6 +35,13 @@ public interface WrapperOptions { */ SessionFactoryImplementor getSessionFactory(); + /** + * Access to the current dialect. + */ + default Dialect getDialect() { + return getSessionFactory().getJdbcServices().getDialect(); + } + /** * Determines whether streams should be used for binding LOB values. * diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java index ae358cdfc8..57ee42fd64 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BlobJavaType.java @@ -32,6 +32,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; * * @author Steve Ebersole * @author Brett Meyer + * @author Loïc Lefèvre */ public class BlobJavaType extends AbstractClassJavaType { public static final BlobJavaType INSTANCE = new BlobJavaType(); @@ -133,7 +134,7 @@ public class BlobJavaType extends AbstractClassJavaType { else if (Blob.class.isAssignableFrom( type )) { final Blob blob = value instanceof WrappedBlob ? ( (WrappedBlob) value ).getWrappedBlob() - : value; + : getOrCreateBlob(value, options); return (X) blob; } } @@ -144,6 +145,21 @@ public class BlobJavaType extends AbstractClassJavaType { throw unknownUnwrap( type ); } + private Blob getOrCreateBlob(Blob value, WrapperOptions options) throws SQLException { + if(options.getDialect().useConnectionToCreateLob()) { + if(value.length() == 0) { + // empty Blob + return options.getLobCreator().createBlob(new byte[0]); + } + else { + return options.getLobCreator().createBlob(value.getBytes(1, (int) value.length())); + } + } + else { + return value; + } + } + @Override public Blob wrap(X value, WrapperOptions options) { if ( value == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java index 7badc04ae2..d1637fdfd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayJavaType.java @@ -9,6 +9,7 @@ package org.hibernate.type.descriptor.java; import java.io.Reader; import java.io.StringReader; import java.sql.Clob; +import java.sql.NClob; import java.util.Arrays; import org.hibernate.engine.jdbc.CharacterStream; @@ -80,6 +81,9 @@ public class CharacterArrayJavaType extends AbstractClassJavaType { if ( String.class.isAssignableFrom( type ) ) { return (X) new String( unwrapChars( value ) ); } + if ( NClob.class.isAssignableFrom( type ) ) { + return (X) options.getLobCreator().createNClob( new String( unwrapChars( value ) ) ); + } if ( Clob.class.isAssignableFrom( type ) ) { return (X) options.getLobCreator().createClob( new String( unwrapChars( value ) ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java index 655d55fd5c..b1248a76da 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClobJavaType.java @@ -24,7 +24,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; -import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; /** * Descriptor for {@link Clob} handling. @@ -33,6 +32,7 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; * But we treat them as immutable because we simply have no way to dirty check nor deep copy them. * * @author Steve Ebersole + * @author Loïc Lefèvre */ public class ClobJavaType extends AbstractClassJavaType { public static final ClobJavaType INSTANCE = new ClobJavaType(); @@ -107,7 +107,7 @@ public class ClobJavaType extends AbstractClassJavaType { else if (Clob.class.isAssignableFrom( type )) { final Clob clob = value instanceof WrappedClob ? ( (WrappedClob) value ).getWrappedClob() - : value; + : getOrCreateClob(value, options); return (X) clob; } else if ( String.class.isAssignableFrom( type ) ) { @@ -128,6 +128,21 @@ public class ClobJavaType extends AbstractClassJavaType { throw unknownUnwrap( type ); } + private Clob getOrCreateClob(Clob value, WrapperOptions options) throws SQLException { + if(options.getDialect().useConnectionToCreateLob()) { + if(value.length() == 0) { + // empty Clob + return options.getLobCreator().createClob(""); + } + else { + return options.getLobCreator().createClob(value.getSubString(1, (int) value.length())); + } + } + else { + return value; + } + } + public Clob wrap(X value, WrapperOptions options) { if ( value == null ) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobJavaType.java index 66e50a518b..48d4c9c2b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/NClobJavaType.java @@ -28,6 +28,7 @@ import org.hibernate.type.descriptor.WrapperOptions; * treat them as immutable because we cannot properly check them for changes nor deep copy them. * * @author Steve Ebersole + * @author Loïc Lefèvre */ public class NClobJavaType extends AbstractClassJavaType { public static final NClobJavaType INSTANCE = new NClobJavaType(); @@ -105,7 +106,7 @@ public class NClobJavaType extends AbstractClassJavaType { else if (NClob.class.isAssignableFrom( type )) { final NClob nclob = value instanceof WrappedNClob ? ( (WrappedNClob) value ).getWrappedNClob() - : value; + : getOrCreateNClob(value, options); return (X) nclob; } } @@ -116,6 +117,22 @@ public class NClobJavaType extends AbstractClassJavaType { throw unknownUnwrap( type ); } + private NClob getOrCreateNClob(NClob value, WrapperOptions options) throws SQLException { + if(options.getDialect().useConnectionToCreateLob()) { + if(value.length() == 0) { + // empty NClob + return options.getLobCreator().createNClob(""); + } + else { + return options.getLobCreator().createNClob(value.getSubString(1, (int) value.length())); + } + } + else { + return value; + } + } + + public NClob wrap(X value, WrapperOptions options) { if ( value == null ) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java index 46e6339c6f..cdcfa34172 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java @@ -9,6 +9,7 @@ package org.hibernate.type.descriptor.java; import java.io.Reader; import java.io.StringReader; import java.sql.Clob; +import java.sql.NClob; import java.util.Arrays; import org.hibernate.engine.jdbc.CharacterStream; @@ -62,6 +63,9 @@ public class PrimitiveCharacterArrayJavaType extends AbstractClassJavaType { if ( DataHelper.isNClob( type ) ) { return (X) options.getLobCreator().createNClob( value ); } + if ( NClob.class.isAssignableFrom( type ) ) { + return (X) options.getLobCreator().createNClob( value ); + } if ( Clob.class.isAssignableFrom( type ) ) { return (X) options.getLobCreator().createClob( value ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/java/AbstractDescriptorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/java/AbstractDescriptorTest.java index b512eb46d8..a7082b7554 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/java/AbstractDescriptorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/java/AbstractDescriptorTest.java @@ -10,6 +10,8 @@ import java.sql.Blob; import java.sql.Clob; import java.util.TimeZone; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.NonContextualLobCreator; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -78,6 +80,18 @@ public abstract class AbstractDescriptorTest extends BaseUnitTestCase { public TimeZone getJdbcTimeZone() { return null; } + + private final Dialect dialect = new H2Dialect() { + @Override + public boolean useConnectionToCreateLob() { + return false; + } + }; + + @Override + public Dialect getDialect() { + return dialect; + } }; public AbstractDescriptorTest(JavaType typeDescriptor) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/sql/StringValueMappingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/sql/StringValueMappingTest.java index 68a277d3ba..3da882b0f1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/sql/StringValueMappingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/descriptor/sql/StringValueMappingTest.java @@ -12,6 +12,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.TimeZone; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; import org.hibernate.engine.jdbc.LobCreator; import org.hibernate.engine.jdbc.NonContextualLobCreator; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -75,6 +77,18 @@ public class StringValueMappingTest { public TimeZone getJdbcTimeZone() { return null; } + + private final Dialect dialect = new H2Dialect() { + @Override + public boolean useConnectionToCreateLob() { + return false; + } + }; + + @Override + public Dialect getDialect() { + return dialect; + } }; public static final int COLUMN_POSITION = 0;