HHH-18806 attempt to make handling of Clob/NClob more robust

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-11-01 22:26:30 +01:00
parent 1ef22c01f3
commit 2d9f58ae4b
32 changed files with 407 additions and 284 deletions

View File

@ -3777,12 +3777,12 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
}
/**
* Should BLOB, CLOB, and NCLOB be created solely using respectively
* {@link Connection#createBlob()}, {@link Connection#createClob()},
* and {@link Connection#createNClob()}.
* Should {@link Blob}, {@link Clob}, and {@link NClob} be created solely
* using {@link Connection#createBlob()}, {@link Connection#createClob()},
* and {@link Connection#createNClob()}, instead of allowing the use of
* our own implementations.
*
* @return True if BLOB, CLOB, and NCLOB should be created using JDBC
* {@link Connection}.
* @return True if these types should be instantiated using {@link Connection}.
*
* @since 6.6
*/

View File

@ -58,6 +58,7 @@ import org.hibernate.type.NullType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.NClobJdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.TinyIntAsSmallIntJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
@ -213,7 +214,7 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
// The jTDS driver doesn't support the JDBC4 signatures using 'long length' for stream bindings
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.NCLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.NCLOB, NClobJdbcType.NCLOB_BINDING );
}
else {
// jConnect driver only conditionally supports getClob/getNClob depending on a server setting. See

View File

@ -3,12 +3,14 @@
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.engine.jdbc;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
/**
* Convenient base class for proxy-based LobCreator for handling wrapping.
* Convenient base class for proxy-based {@link LobCreator} for handling wrapping.
*
* @author Steve Ebersole
*/
@ -20,16 +22,28 @@ public abstract class AbstractLobCreator implements LobCreator {
@Override
public Clob wrap(Clob clob) {
if ( clob instanceof NClob ) {
return wrap( (NClob) clob );
}
else {
return SerializableClobProxy.generateProxy( clob );
}
return clob instanceof NClob nclob
? wrap( nclob )
: SerializableClobProxy.generateProxy( clob );
}
@Override
public NClob wrap(NClob nclob) {
return SerializableNClobProxy.generateProxy( nclob );
}
@Override
public Blob createBlob(Blob blob) {
return blob;
}
@Override
public Clob createClob(Clob clob) {
return clob;
}
@Override
public NClob createNClob(NClob clob) {
return clob;
}
}

View File

@ -5,6 +5,7 @@
package org.hibernate.engine.jdbc;
import java.io.InputStream;
import java.sql.Blob;
/**
* Wraps a binary stream to also provide the length which is needed when binding.
@ -37,4 +38,12 @@ public interface BinaryStream {
* Release any underlying resources.
*/
void release();
/**
* Use the given {@link LobCreator} to create a {@link Blob}
* with the same data as this binary stream.
*
* @since 7.0
*/
Blob asBlob(LobCreator lobCreator);
}

View File

@ -11,7 +11,8 @@ import java.sql.Blob;
import java.sql.SQLException;
import org.hibernate.Internal;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.engine.jdbc.internal.StreamBackedBinaryStream;
import org.hibernate.type.descriptor.java.DataHelper;
/**
@ -45,7 +46,7 @@ public final class BlobProxy implements Blob, BlobImplementer {
* @see #generateProxy(byte[])
*/
private BlobProxy(byte[] bytes) {
binaryStream = new BinaryStreamImpl( bytes );
binaryStream = new ArrayBackedBinaryStream( bytes );
}
/**
@ -179,45 +180,6 @@ public final class BlobProxy implements Blob, BlobImplementer {
return DataHelper.subStream( getStream(), start-1, intLength );
}
private static class StreamBackedBinaryStream implements BinaryStream {
private final InputStream stream;
private final long length;
private byte[] bytes;
private StreamBackedBinaryStream(InputStream stream, long length) {
this.stream = stream;
this.length = length;
}
@Override
public InputStream getInputStream() {
return stream;
}
@Override
public byte[] getBytes() {
if ( bytes == null ) {
bytes = DataHelper.extractBytes( stream );
}
return bytes;
}
@Override
public long getLength() {
return length;
}
@Override
public void release() {
try {
stream.close();
}
catch (IOException ignore) {
}
}
}
private static UnsupportedOperationException notSupported() {
return new UnsupportedOperationException( "Blob may not be manipulated from creating session" );
}

View File

@ -100,4 +100,8 @@ public interface LobCreator {
* environments, also castable to java.sql.NClob
*/
NClob createNClob(Reader reader, long length);
Blob createBlob(Blob clob);
Clob createClob(Clob clob);
NClob createNClob(NClob clob);
}

View File

@ -20,7 +20,7 @@ import org.hibernate.engine.jdbc.NClobProxy;
import org.hibernate.engine.jdbc.NonContextualLobCreator;
/**
* LobCreator which can use {@link Connection#createBlob} and {@link Connection#createClob},
* {@link LobCreator} which can use {@link Connection#createBlob} and {@link Connection#createClob},
* but {@link java.sql.NClob} references are created locally.
*
* @see NClobProxy
@ -40,9 +40,11 @@ public class BlobAndClobCreator extends AbstractLobCreator implements LobCreator
public static final LobCreationContext.Callback<Clob> CREATE_CLOB_CALLBACK = Connection::createClob;
protected final LobCreationContext lobCreationContext;
protected final boolean useConnectionToCreateLob;
public BlobAndClobCreator(LobCreationContext lobCreationContext) {
public BlobAndClobCreator(LobCreationContext lobCreationContext, boolean useConnectionToCreateLob) {
this.lobCreationContext = lobCreationContext;
this.useConnectionToCreateLob = useConnectionToCreateLob;
}
/**
@ -68,8 +70,9 @@ public class BlobAndClobCreator extends AbstractLobCreator implements LobCreator
@Override
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).
// IMPL NOTE: it's 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( stream, length );
}
@ -96,8 +99,9 @@ public class BlobAndClobCreator extends AbstractLobCreator implements LobCreator
@Override
public Clob createClob(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).
// IMPL NOTE: it's 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.createClob( reader, length );
}
@ -110,4 +114,40 @@ public class BlobAndClobCreator extends AbstractLobCreator implements LobCreator
public NClob createNClob(Reader reader, long length) {
return NonContextualLobCreator.INSTANCE.createNClob( reader, length );
}
@Override
public Blob createBlob(Blob blob) {
try {
return useConnectionToCreateLob
? createBlob( blob.getBytes( 1, (int) blob.length() ) )
: blob;
}
catch (SQLException e) {
throw new JDBCException( "Could not create JDBC Clob", e );
}
}
@Override
public Clob createClob(Clob clob) {
try {
return useConnectionToCreateLob
? createClob( clob.getSubString( 1, (int) clob.length() ) )
: clob;
}
catch (SQLException e) {
throw new JDBCException( "Could not create JDBC Clob", e );
}
}
@Override
public NClob createNClob(NClob clob) {
try {
return useConnectionToCreateLob
? createNClob( clob.getSubString( 1, (int) clob.length() ) )
: clob;
}
catch (SQLException e) {
throw new JDBCException( "Could not create JDBC Clob", e );
}
}
}

View File

@ -35,6 +35,8 @@ import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.jboss.logging.Logger;
import static org.hibernate.engine.jdbc.env.internal.LobCreatorBuilderImpl.makeLobCreatorBuilder;
/**
* @author Steve Ebersole
*/
@ -100,7 +102,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
this.qualifiedObjectNameFormatter = new QualifiedObjectNameFormatterStandardImpl( nameQualifierSupport );
this.lobCreatorBuilder = LobCreatorBuilderImpl.makeLobCreatorBuilder();
this.lobCreatorBuilder = makeLobCreatorBuilder( dialect );
}
private IdentifierHelperBuilder identifierHelperBuilder(
@ -201,7 +203,7 @@ public class JdbcEnvironmentImpl implements JdbcEnvironment {
databaseMetaData
);
this.lobCreatorBuilder = LobCreatorBuilderImpl.makeLobCreatorBuilder();
this.lobCreatorBuilder = makeLobCreatorBuilder( dialect );
}
private IdentifierHelper identifierHelper(DatabaseMetaData databaseMetaData, Dialect dialect) {

View File

@ -25,9 +25,11 @@ import static org.hibernate.engine.jdbc.env.internal.LobCreationLogging.LOB_MESS
* @author Steve Ebersole
*/
public class LobCreatorBuilderImpl implements LobCreatorBuilder {
private final boolean useConnectionToCreateLob;
private final EnumSet<LobTypes> supportedContextualLobTypes;
public LobCreatorBuilderImpl(EnumSet<LobTypes> supportedContextualLobTypes) {
public LobCreatorBuilderImpl(boolean useConnectionToCreateLob, EnumSet<LobTypes> supportedContextualLobTypes) {
this.useConnectionToCreateLob = useConnectionToCreateLob;
this.supportedContextualLobTypes = supportedContextualLobTypes;
}
@ -46,13 +48,8 @@ public class LobCreatorBuilderImpl implements LobCreatorBuilder {
Dialect dialect,
Map<String,Object> configValues,
Connection jdbcConnection) {
final EnumSet<LobTypes> supportedContextualLobTypes = getSupportedContextualLobTypes(
dialect,
configValues,
jdbcConnection
);
return new LobCreatorBuilderImpl( supportedContextualLobTypes );
return new LobCreatorBuilderImpl( dialect.useConnectionToCreateLob(),
getSupportedContextualLobTypes( dialect, configValues, jdbcConnection ) );
}
/**
@ -60,9 +57,9 @@ public class LobCreatorBuilderImpl implements LobCreatorBuilder {
*
* @return Appropriate LobCreatorBuilder
*/
public static LobCreatorBuilderImpl makeLobCreatorBuilder() {
public static LobCreatorBuilderImpl makeLobCreatorBuilder(Dialect dialect) {
LOB_MESSAGE_LOGGER.disablingContextualLOBCreationSinceConnectionNull();
return new LobCreatorBuilderImpl( NONE );
return new LobCreatorBuilderImpl( dialect.useConnectionToCreateLob(), NONE );
}
/**
@ -76,17 +73,18 @@ public class LobCreatorBuilderImpl implements LobCreatorBuilder {
if ( supportedContextualLobTypes.isEmpty() ) {
return NonContextualLobCreator.INSTANCE;
}
if ( supportedContextualLobTypes.contains( LobTypes.BLOB )
else if ( supportedContextualLobTypes.contains( LobTypes.BLOB )
&& supportedContextualLobTypes.contains( LobTypes.CLOB ) ){
if ( !supportedContextualLobTypes.contains( LobTypes.NCLOB ) ) {
return new BlobAndClobCreator( lobCreationContext );
return new BlobAndClobCreator( lobCreationContext, useConnectionToCreateLob );
}
else {
return new StandardLobCreator( lobCreationContext, useConnectionToCreateLob );
}
return new StandardLobCreator( lobCreationContext );
}
LOB_LOGGER.debug( "Unexpected condition resolving type of LobCreator to use. Falling back to NonContextualLobCreator" );
return NonContextualLobCreator.INSTANCE;
else {
LOB_LOGGER.debug( "Unexpected condition resolving type of LobCreator to use. Falling back to NonContextualLobCreator" );
return NonContextualLobCreator.INSTANCE;
}
}
}

View File

@ -28,8 +28,8 @@ public class StandardLobCreator extends BlobAndClobCreator {
*/
public static final LobCreationContext.Callback<NClob> CREATE_NCLOB_CALLBACK = Connection::createNClob;
public StandardLobCreator(LobCreationContext lobCreationContext) {
super( lobCreationContext );
public StandardLobCreator(LobCreationContext lobCreationContext, boolean useConnectionToCreateLob) {
super( lobCreationContext, useConnectionToCreateLob );
}
/**

View File

@ -4,43 +4,53 @@
*/
package org.hibernate.engine.jdbc.internal;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.LobCreator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.hibernate.engine.jdbc.BinaryStream;
import java.sql.Blob;
/**
* Implementation of {@link BinaryStream}
* Implementation of {@link BinaryStream} backed by a {@code byte[]} array.
*
* @author Steve Ebersole
*/
public final class BinaryStreamImpl extends ByteArrayInputStream implements BinaryStream {
public class ArrayBackedBinaryStream extends ByteArrayInputStream implements BinaryStream {
private final int length;
/**
* Constructs a BinaryStreamImpl
* Constructs a ArrayBackedBinaryStream
*
* @param bytes The bytes to use backing the stream
*/
public BinaryStreamImpl(byte[] bytes) {
public ArrayBackedBinaryStream(byte[] bytes) {
super( bytes );
this.length = bytes.length;
}
@Override
public InputStream getInputStream() {
return this;
}
@Override
public byte[] getBytes() {
// from ByteArrayInputStream
return buf;
}
@Override
public long getLength() {
return length;
}
@Override
public Blob asBlob(LobCreator lobCreator) {
return lobCreator.createBlob( buf );
}
@Override
public void release() {
try {

View File

@ -0,0 +1,67 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.engine.jdbc.internal;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.LobCreator;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
/**
* Implementation of {@link BinaryStream} backed by an {@link InputStream}.
*
* @since 7.0
*/
public class StreamBackedBinaryStream implements BinaryStream {
private final InputStream stream;
private final long length;
private byte[] bytes;
public StreamBackedBinaryStream(InputStream stream, long length) {
this.stream = stream;
this.length = length;
}
@Override
public InputStream getInputStream() {
return stream;
}
@Override
public byte[] getBytes() {
if ( bytes == null ) {
try {
bytes = stream.readAllBytes();
}
catch (IOException e) {
throw new HibernateException( "IOException occurred reading a binary value", e );
}
}
return bytes;
}
@Override
public long getLength() {
return length;
}
@Override
public Blob asBlob(LobCreator lobCreator) {
return lobCreator.createBlob( stream, length );
}
@Override
public void release() {
try {
stream.close();
}
catch (IOException ignore) {
}
}
}

View File

@ -13,7 +13,7 @@ import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicPluralType;
@ -264,7 +264,7 @@ public class ArrayJavaType<T> extends AbstractArrayJavaType<T[], T> {
}
else if ( type == BinaryStream.class ) {
//noinspection unchecked
return (X) new BinaryStreamImpl( toBytes( value ) );
return (X) new ArrayBackedBinaryStream( toBytes( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();

View File

@ -16,12 +16,15 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.BlobImplementer;
import org.hibernate.engine.jdbc.BlobProxy;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.internal.StreamBackedBinaryStream;
import org.hibernate.engine.jdbc.WrappedBlob;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import static org.hibernate.type.descriptor.java.DataHelper.extractBytes;
/**
* Descriptor for {@link Blob} handling.
* <p>
@ -72,7 +75,7 @@ public class BlobJavaType extends AbstractClassJavaType<Blob> {
public String toString(Blob value) {
final byte[] bytes;
try {
bytes = DataHelper.extractBytes( value.getBinaryStream() );
bytes = extractBytes( value.getBinaryStream() );
}
catch ( SQLException e ) {
throw new HibernateException( "Unable to access blob stream", e );
@ -109,15 +112,12 @@ public class BlobJavaType extends AbstractClassJavaType<Blob> {
}
try {
if ( BinaryStream.class.isAssignableFrom( type ) ) {
if (value instanceof BlobImplementer blobImplementer) {
// if the incoming Blob is a wrapper, just pass along its BinaryStream
return (X) blobImplementer.getUnderlyingStream();
}
else {
// otherwise we need to build a BinaryStream...
return (X) new BinaryStreamImpl( DataHelper.extractBytes( value.getBinaryStream() ) );
if ( Blob.class.isAssignableFrom( type ) ) {
Blob blob = value;
if ( blob instanceof WrappedBlob wrappedBlob ) {
blob = wrappedBlob.getWrappedBlob();
}
return (X) options.getLobCreator().createBlob( blob );
}
else if ( byte[].class.isAssignableFrom( type )) {
if (value instanceof BlobImplementer blobImplementer) {
@ -125,12 +125,32 @@ public class BlobJavaType extends AbstractClassJavaType<Blob> {
return (X) blobImplementer.getUnderlyingStream().getBytes();
}
else {
// otherwise extract the bytes from the stream manually
return (X) DataHelper.extractBytes( value.getBinaryStream() );
try {
// otherwise extract the bytes from the stream manually
return (X) value.getBinaryStream().readAllBytes();
}
catch ( IOException e ) {
throw new HibernateException( "IOException occurred reading a binary value", e );
}
}
}
else if ( Blob.class.isAssignableFrom( type ) ) {
return (X) getOrCreateBlob( value, options );
else if ( BinaryStream.class.isAssignableFrom( type ) ) {
if (value instanceof BlobImplementer blobImplementer) {
return (X) blobImplementer.getUnderlyingStream();
}
else {
return (X) new StreamBackedBinaryStream( value.getBinaryStream(), value.length() );
}
}
else if ( InputStream.class.isAssignableFrom( type ) ) {
if (value instanceof BlobImplementer blobImplementer) {
// if the incoming Blob is a wrapper, just pass along its BinaryStream
return (X) blobImplementer.getUnderlyingStream().getInputStream();
}
else {
// otherwise we need to build a BinaryStream...
return (X) value.getBinaryStream();
}
}
}
catch ( SQLException e ) {
@ -140,45 +160,32 @@ public class BlobJavaType extends AbstractClassJavaType<Blob> {
throw unknownUnwrap( type );
}
private Blob getOrCreateBlob(Blob value, WrapperOptions options) throws SQLException {
if ( value instanceof WrappedBlob wrappedBlob ) {
value = wrappedBlob.getWrappedBlob();
}
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 <X> Blob wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
else if ( value instanceof Blob blob ) {
return options.getLobCreator().wrap( blob );
}
else if ( value instanceof byte[] bytes ) {
return options.getLobCreator().createBlob( bytes );
}
else if ( value instanceof InputStream inputStream ) {
try {
return options.getLobCreator().createBlob( inputStream, inputStream.available() );
else {
final LobCreator lobCreator = options.getLobCreator();
if ( value instanceof Blob blob ) {
return lobCreator.wrap( blob );
}
catch ( IOException e ) {
else if ( value instanceof byte[] bytes ) {
return lobCreator.createBlob( bytes );
}
else if ( value instanceof BinaryStream binaryStream) {
return binaryStream.asBlob( lobCreator );
}
else if ( value instanceof InputStream inputStream ) {
// A JDBC Blob object needs to know its length, but
// there's no way to get an accurate length from an
// InputStream without reading the whole stream
return lobCreator.createBlob( extractBytes( inputStream ) );
}
else {
throw unknownWrap( value.getClass() );
}
}
throw unknownWrap( value.getClass() );
}
@Override

View File

@ -15,7 +15,7 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
@ -118,7 +118,7 @@ public class BooleanPrimitiveArrayJavaType extends AbstractArrayJavaType<boolean
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();

View File

@ -12,7 +12,7 @@ import java.util.Arrays;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType;
@ -99,7 +99,7 @@ public class ByteArrayJavaType extends AbstractClassJavaType<Byte[]> {
return (X) new ByteArrayInputStream( unwrapBytes( value ) );
}
if ( BinaryStream.class.isAssignableFrom( type ) ) {
return (X) new BinaryStreamImpl( unwrapBytes( value ) );
return (X) new ArrayBackedBinaryStream( unwrapBytes( value ) );
}
if ( Blob.class.isAssignableFrom( type ) ) {
return (X) options.getLobCreator().createBlob( unwrapBytes( value ) );

View File

@ -16,13 +16,15 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.CharacterStream;
import org.hibernate.engine.jdbc.ClobImplementer;
import org.hibernate.engine.jdbc.ClobProxy;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.WrappedClob;
import org.hibernate.engine.jdbc.internal.CharacterStreamImpl;
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 static org.hibernate.type.descriptor.java.DataHelper.extractString;
/**
* Descriptor for {@link Clob} handling.
* <p>
@ -52,7 +54,7 @@ public class ClobJavaType extends AbstractClassJavaType<Clob> {
}
public String toString(Clob value) {
return DataHelper.extractString( value );
return extractString( value );
}
public Clob fromString(CharSequence string) {
@ -82,37 +84,41 @@ public class ClobJavaType extends AbstractClassJavaType<Clob> {
}
try {
if ( CharacterStream.class.isAssignableFrom( type ) ) {
if ( Clob.class.isAssignableFrom( type ) ) {
Clob clob = value;
if ( clob instanceof WrappedClob wrappedClob ) {
clob = wrappedClob.getWrappedClob();
}
return (X) options.getLobCreator().createClob( clob );
}
else if ( String.class.isAssignableFrom( type ) ) {
if (value instanceof ClobImplementer clobImplementer) {
// if the incoming Clob is a wrapper, just grab the string from its CharacterStream
return (X) clobImplementer.getUnderlyingStream().asString();
}
else {
// otherwise extract the bytes from the stream manually
return (X) extractString( value.getCharacterStream() );
}
}
else if ( Reader.class.isAssignableFrom( type ) ) {
if (value instanceof ClobImplementer clobImplementer) {
// if the incoming NClob is a wrapper, just pass along its BinaryStream
return (X) clobImplementer.getUnderlyingStream().asReader();
}
else {
// otherwise we need to build a CharacterStream...
return (X) value.getCharacterStream();
}
}
else if ( CharacterStream.class.isAssignableFrom( type ) ) {
if (value instanceof ClobImplementer clobImplementer) {
// if the incoming Clob is a wrapper, just pass along its CharacterStream
return (X) clobImplementer.getUnderlyingStream();
}
else {
// otherwise we need to build a CharacterStream...
return (X) new CharacterStreamImpl( DataHelper.extractString( value.getCharacterStream() ) );
}
}
else if ( String.class.isAssignableFrom( type ) ) {
if (value instanceof ClobImplementer clobImplementer) {
// if the incoming Clob is a wrapper, just grab the bytes from its BinaryStream
return (X) clobImplementer.getUnderlyingStream().asString();
}
else {
// otherwise extract the bytes from the stream manually
return (X) DataHelper.extractString( value.getCharacterStream() );
}
}
else if ( Clob.class.isAssignableFrom( type ) ) {
return (X) getOrCreateClob( value, options );
}
else if ( String.class.isAssignableFrom( type ) ) {
if (value instanceof ClobImplementer clobImplementer) {
// if the incoming Clob is a wrapper, just get the underlying String.
return (X) clobImplementer.getUnderlyingStream().asString();
}
else {
// otherwise we need to extract the String.
return (X) DataHelper.extractString( value.getCharacterStream() );
return (X) value.getCharacterStream();
}
}
}
@ -123,39 +129,28 @@ public class ClobJavaType extends AbstractClassJavaType<Clob> {
throw unknownUnwrap( type );
}
private Clob getOrCreateClob(Clob value, WrapperOptions options) throws SQLException {
if ( value instanceof WrappedClob wrappedClob ) {
value = wrappedClob.getWrappedClob();
}
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 <X> Clob wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
else if ( value instanceof Clob clob ) {
return options.getLobCreator().wrap( clob );
else {
final LobCreator lobCreator = options.getLobCreator();
if ( value instanceof Clob clob ) {
return lobCreator.wrap( clob );
}
else if ( value instanceof String string ) {
return lobCreator.createClob( string );
}
else if ( value instanceof Reader reader ) {
return lobCreator.createClob( extractString( reader ) );
}
else if ( value instanceof CharacterStream stream ) {
return lobCreator.createClob( stream.asReader(), stream.getLength() );
}
else {
throw unknownWrap( value.getClass() );
}
}
else if ( value instanceof String string ) {
return options.getLobCreator().createClob( string );
}
else if ( value instanceof Reader reader ) {
return options.getLobCreator().createClob( DataHelper.extractString( reader ) );
}
throw unknownWrap( value.getClass() );
}
@Override

View File

@ -17,7 +17,7 @@ import java.sql.SQLFeatureNotSupportedException;
import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.CoreMessageLogger;
import org.jboss.logging.Logger;
@ -246,7 +246,7 @@ public final class DataHelper {
* @return The extracted bytes as a stream
*/
public static InputStream subStream(InputStream inputStream, long start, int length) {
return new BinaryStreamImpl( extractBytes( inputStream, start, length ) );
return new ArrayBackedBinaryStream( extractBytes( inputStream, start, length ) );
}
/**

View File

@ -15,7 +15,7 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
@ -118,7 +118,7 @@ public class DoublePrimitiveArrayJavaType extends AbstractArrayJavaType<double[]
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();

View File

@ -15,7 +15,7 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
@ -118,7 +118,7 @@ public class FloatPrimitiveArrayJavaType extends AbstractArrayJavaType<float[],
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();

View File

@ -15,7 +15,7 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
@ -118,7 +118,7 @@ public class IntegerPrimitiveArrayJavaType extends AbstractArrayJavaType<int[],
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();

View File

@ -15,7 +15,7 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
@ -118,7 +118,7 @@ public class LongPrimitiveArrayJavaType extends AbstractArrayJavaType<long[], Lo
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();

View File

@ -12,13 +12,15 @@ import java.sql.SQLException;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.CharacterStream;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.NClobImplementer;
import org.hibernate.engine.jdbc.NClobProxy;
import org.hibernate.engine.jdbc.WrappedNClob;
import org.hibernate.engine.jdbc.internal.CharacterStreamImpl;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.WrapperOptions;
import static org.hibernate.type.descriptor.java.DataHelper.extractString;
/**
* Descriptor for {@link NClob} handling.
* <p>
@ -61,7 +63,7 @@ public class NClobJavaType extends AbstractClassJavaType<NClob> {
}
public String toString(NClob value) {
return DataHelper.extractString( value );
return extractString( value );
}
public NClob fromString(CharSequence string) {
@ -91,19 +93,43 @@ public class NClobJavaType extends AbstractClassJavaType<NClob> {
}
try {
if ( CharacterStream.class.isAssignableFrom( type ) ) {
if ( NClob.class.isAssignableFrom( type ) ) {
NClob clob = value;
if ( clob instanceof WrappedNClob wrappedNClob ) {
clob = wrappedNClob.getWrappedNClob();
}
return (X) options.getLobCreator().createNClob( clob );
}
else if ( String.class.isAssignableFrom( type ) ) {
if (value instanceof NClobImplementer clobImplementer) {
// if the incoming NClob is a wrapper, just pass along its BinaryStream
// if the incoming Clob is a wrapper, just get the underlying String.
return (X) clobImplementer.getUnderlyingStream().asString();
}
else {
// otherwise we need to extract the String.
return (X) extractString( value.getCharacterStream() );
}
}
else if ( Reader.class.isAssignableFrom( type ) ) {
if (value instanceof NClobImplementer clobImplementer) {
// if the incoming NClob is a wrapper, just pass along its CharacterStream
return (X) clobImplementer.getUnderlyingStream().asReader();
}
else {
// otherwise we need to build a Reader...
return (X) value.getCharacterStream();
}
}
else if ( CharacterStream.class.isAssignableFrom( type ) ) {
if (value instanceof NClobImplementer clobImplementer) {
// if the incoming NClob is a wrapper, just pass along its CharacterStream
return (X) clobImplementer.getUnderlyingStream();
}
else {
// otherwise we need to build a BinaryStream...
return (X) new CharacterStreamImpl( DataHelper.extractString( value.getCharacterStream() ) );
// otherwise we need to build a CharacterStream...
return (X) value.getCharacterStream();
}
}
else if ( NClob.class.isAssignableFrom( type ) ) {
return (X) getOrCreateNClob( value, options );
}
}
catch ( SQLException e ) {
throw new HibernateException( "Unable to access nclob stream", e );
@ -112,38 +138,27 @@ public class NClobJavaType extends AbstractClassJavaType<NClob> {
throw unknownUnwrap( type );
}
private NClob getOrCreateNClob(NClob value, WrapperOptions options) throws SQLException {
if ( value instanceof WrappedNClob wrappedNClob ) {
value = wrappedNClob.getWrappedNClob();
}
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 <X> NClob wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
// Support multiple return types from
// org.hibernate.type.descriptor.sql.ClobTypeDescriptor
if ( value instanceof NClob clob ) {
return options.getLobCreator().wrap( clob );
else {
final LobCreator lobCreator = options.getLobCreator();
if ( value instanceof NClob clob ) {
return lobCreator.wrap( clob );
}
else if ( value instanceof String string ) {
return lobCreator.createNClob( string );
}
else if ( value instanceof Reader reader ) {
return lobCreator.createNClob( extractString( reader ) );
}
else if ( value instanceof CharacterStream stream ) {
return lobCreator.createNClob( stream.asReader(), stream.getLength() );
}
else {
throw unknownWrap( value.getClass() );
}
}
else if ( value instanceof Reader reader ) {
return options.getLobCreator().createNClob( DataHelper.extractString( reader ) );
}
throw unknownWrap( value.getClass() );
}
}

View File

@ -12,7 +12,7 @@ import java.util.Arrays;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.compare.RowVersionComparator;
import org.hibernate.sql.ast.spi.SqlAppender;
@ -107,7 +107,7 @@ public class PrimitiveByteArrayJavaType extends AbstractClassJavaType<byte[]>
return (X) new ByteArrayInputStream( value );
}
if ( BinaryStream.class.isAssignableFrom( type ) ) {
return (X) new BinaryStreamImpl( value );
return (X) new ArrayBackedBinaryStream( value );
}
if ( Blob.class.isAssignableFrom( type ) ) {
return (X) options.getLobCreator().createBlob( value );

View File

@ -15,7 +15,7 @@ import java.util.Arrays;
import org.hibernate.HibernateException;
import org.hibernate.annotations.Immutable;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -107,7 +107,7 @@ public class SerializableJavaType<T extends Serializable> extends AbstractClassJ
return (X) new ByteArrayInputStream( toBytes( value ) );
}
else if ( BinaryStream.class.isAssignableFrom( type ) ) {
return (X) new BinaryStreamImpl( toBytes( value ) );
return (X) new ArrayBackedBinaryStream( toBytes( value ) );
}
else if ( Blob.class.isAssignableFrom( type ) ) {
return (X) options.getLobCreator().createBlob( toBytes( value ) );

View File

@ -15,7 +15,7 @@ import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.SharedSessionContract;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.type.descriptor.WrapperOptions;
@ -118,7 +118,7 @@ public class ShortPrimitiveArrayJavaType extends AbstractArrayJavaType<short[],
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( value ) );
return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( value ) );
}
else if ( type.isArray() ) {
final Class<?> preferredJavaTypeClass = type.getComponentType();

View File

@ -20,7 +20,7 @@ import org.hibernate.SharedSessionContract;
import org.hibernate.collection.spi.CollectionSemantics;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.BinaryStream;
import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
import org.hibernate.engine.jdbc.internal.ArrayBackedBinaryStream;
import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.CollectionClassification;
@ -341,7 +341,7 @@ public class BasicCollectionJavaType<C extends Collection<E>, E> extends Abstrac
else if ( type == BinaryStream.class ) {
// BinaryStream can only be requested if the value should be serialized
//noinspection unchecked
return (X) new BinaryStreamImpl( SerializationHelper.serialize( asArrayList( value ) ) );
return (X) new ArrayBackedBinaryStream( SerializationHelper.serialize( asArrayList( value ) ) );
}
else if ( type == Object[].class ) {
//noinspection unchecked

View File

@ -197,6 +197,7 @@ public abstract class BlobJdbcType implements JdbcType {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
// the use of BinaryStream here instead of InputStream seems to be only necessary on Oracle
final BinaryStream binaryStream = javaType.unwrap( value, BinaryStream.class, options );
st.setBinaryStream( index, binaryStream.getInputStream(), binaryStream.getLength() );
}
@ -204,6 +205,7 @@ public abstract class BlobJdbcType implements JdbcType {
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
// the use of BinaryStream here instead of InputStream seems to be only necessary on Oracle
final BinaryStream binaryStream = javaType.unwrap( value, BinaryStream.class, options );
st.setBinaryStream( name, binaryStream.getInputStream(), binaryStream.getLength() );
}

View File

@ -5,6 +5,7 @@
package org.hibernate.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@ -45,7 +46,12 @@ public abstract class NClobJdbcType implements JdbcType {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
if ( options.getDialect().supportsNationalizedMethods() ) {
return javaType.wrap( rs.getNClob( paramIndex ), options );
try {
return javaType.wrap( rs.getNClob( paramIndex ), options );
}
catch (AbstractMethodError e) {
return javaType.wrap( rs.getClob( paramIndex ), options );
}
}
else {
return javaType.wrap( rs.getClob( paramIndex ), options );
@ -115,15 +121,15 @@ public abstract class NClobJdbcType implements JdbcType {
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
getDescriptor( value, options ).getNClobBinder( javaType ).doBind( st, value, index, options );
}
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
getDescriptor( value, options ).getNClobBinder( javaType ).doBind( st, value, name, options );
}
};
}
};
}
};
public static final NClobJdbcType STRING_BINDING = new NClobJdbcType() {
@ -219,7 +225,12 @@ public abstract class NClobJdbcType implements JdbcType {
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
if ( options.getDialect().supportsNationalizedMethods() ) {
st.setNClob( index, javaType.unwrap( value, NClob.class, options ) );
try {
st.setNClob( index, javaType.unwrap( value, NClob.class, options ) );
}
catch (AbstractMethodError e) {
st.setClob( index, javaType.unwrap( value, Clob.class, options ) );
}
}
else {
st.setClob( index, javaType.unwrap( value, NClob.class, options ) );

View File

@ -63,7 +63,7 @@ public class LobCreatorTest {
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( dialect.useConnectionToCreateLob(), supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
@ -85,7 +85,7 @@ public class LobCreatorTest {
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( dialect.useConnectionToCreateLob(), supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
@ -106,7 +106,7 @@ public class LobCreatorTest {
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( dialect.useConnectionToCreateLob(), supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
@ -128,7 +128,7 @@ public class LobCreatorTest {
props,
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( dialect.useConnectionToCreateLob(), supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );
@ -148,7 +148,7 @@ public class LobCreatorTest {
Collections.emptyMap(),
connection
);
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( supportedContextualLobTypes );
final LobCreatorBuilderImpl creatorBuilder = new LobCreatorBuilderImpl( dialect.useConnectionToCreateLob(), supportedContextualLobTypes );
final LobCreationContext lobCreationContext = new LobCreationContextImpl( connection );
final LobCreator lobCreator = creatorBuilder.buildLobCreator( lobCreationContext );

View File

@ -9,8 +9,10 @@ import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import org.hibernate.annotations.Nationalized;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@ -19,6 +21,7 @@ import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
@SkipForDialect(dialectClass = SybaseASEDialect.class)
public class NClobStringTest extends BaseEntityManagerFunctionalTestCase {
@Override
@ -34,7 +37,7 @@ public class NClobStringTest extends BaseEntityManagerFunctionalTestCase {
final Product product = new Product();
product.setId(1);
product.setName("Mobile phone");
product.setWarranty("My product warranty");
product.setWarranty("My product®™ warranty 😍");
entityManager.persist(product);
return product.getId();
@ -42,7 +45,7 @@ public class NClobStringTest extends BaseEntityManagerFunctionalTestCase {
doInJPA(this::entityManagerFactory, entityManager -> {
Product product = entityManager.find(Product.class, productId);
assertEquals("My product warranty", product.getWarranty());
assertEquals("My product®™ warranty 😍", product.getWarranty());
});
}

View File

@ -4,22 +4,20 @@
*/
package org.hibernate.orm.test.mapping.basic;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.sql.NClob;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import org.hibernate.annotations.Nationalized;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.engine.jdbc.NClobProxy;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
@ -35,13 +33,14 @@ import static org.junit.Assert.fail;
"because we explicitly map this attribute to the `NClob` java type the database really" +
" has to support those types"
)
@SkipForDialect(dialectClass = SybaseASEDialect.class)
public class NClobTest {
@Test
public void test(EntityManagerFactoryScope scope) {
scope.inTransaction(
(entityManager) -> {
//tag::basic-nclob-persist-example[]
String warranty = "My product warranty";
String warranty = "My product®™ warranty 😍";
final Product product = new Product();
product.setId(1);
@ -60,33 +59,17 @@ public class NClobTest {
//tag::basic-nclob-find-example[]
Product product = entityManager.find(Product.class, 1);
try (Reader reader = product.getWarranty().getCharacterStream()) {
assertEquals("My product warranty", toString(reader));
}
NClob warranty = product.getWarranty();
assertEquals("My product®™ warranty 😍", warranty.getSubString( 1, (int) warranty.length() ) );
//end::basic-nclob-find-example[]
}
catch (Exception e) {
fail(e.getMessage());
}
}
);
);
}
private String toString(Reader reader) throws IOException {
BufferedReader bufferedReader = new BufferedReader(reader);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int result = bufferedReader.read();
while (result != -1) {
byteArrayOutputStream.write((byte) result);
result = bufferedReader.read();
}
return byteArrayOutputStream.toString();
}
//tag::basic-nclob-example[]
@Entity(name = "Product")
public static class Product {