HHH-2680 - Blobs not updated on Session.merge() for detached instances
This commit is contained in:
parent
fd08540859
commit
a491f64570
|
@ -23,7 +23,12 @@
|
|||
*/
|
||||
package org.hibernate.dialect;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.sql.Blob;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.Clob;
|
||||
import java.sql.NClob;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
@ -50,6 +55,8 @@ import org.hibernate.dialect.lock.PessimisticForceIncrementLockingStrategy;
|
|||
import org.hibernate.dialect.lock.PessimisticReadSelectLockingStrategy;
|
||||
import org.hibernate.dialect.lock.PessimisticWriteSelectLockingStrategy;
|
||||
import org.hibernate.dialect.lock.SelectLockingStrategy;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.engine.jdbc.LobCreator;
|
||||
import org.hibernate.exception.SQLExceptionConverter;
|
||||
import org.hibernate.exception.SQLStateConverter;
|
||||
import org.hibernate.exception.ViolatedConstraintNameExtracter;
|
||||
|
@ -58,6 +65,8 @@ import org.hibernate.id.SequenceGenerator;
|
|||
import org.hibernate.id.TableHiLoGenerator;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.internal.util.io.StreamCopier;
|
||||
import org.hibernate.mapping.Column;
|
||||
import org.hibernate.persister.entity.Lockable;
|
||||
import org.hibernate.sql.ANSICaseFragment;
|
||||
|
@ -357,6 +366,123 @@ public abstract class Dialect {
|
|||
return descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge strategy based on transferring contents based on streams.
|
||||
*/
|
||||
protected static final LobMergeStrategy STREAM_XFER_LOB_MERGE_STRATEGY = new LobMergeStrategy() {
|
||||
@Override
|
||||
public Blob mergeBlob(Blob original, Blob target, SessionImplementor session) {
|
||||
if ( original != target ) {
|
||||
try {
|
||||
OutputStream connectedStream = target.setBinaryStream( 1L ); // the BLOB just read during the load phase of merge
|
||||
InputStream detachedStream = original.getBinaryStream(); // the BLOB from the detached state
|
||||
StreamCopier.copy( detachedStream, connectedStream );
|
||||
return target;
|
||||
}
|
||||
catch (SQLException e ) {
|
||||
throw session.getFactory().getSQLExceptionHelper().convert( e, "unable to merge BLOB data" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
return NEW_LOCATOR_LOB_MERGE_STRATEGY.mergeBlob( original, target, session );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clob mergeClob(Clob original, Clob target, SessionImplementor session) {
|
||||
if ( original != target ) {
|
||||
try {
|
||||
OutputStream connectedStream = target.setAsciiStream( 1L ); // the CLOB just read during the load phase of merge
|
||||
InputStream detachedStream = original.getAsciiStream(); // the CLOB from the detached state
|
||||
StreamCopier.copy( detachedStream, connectedStream );
|
||||
return target;
|
||||
}
|
||||
catch (SQLException e ) {
|
||||
throw session.getFactory().getSQLExceptionHelper().convert( e, "unable to merge CLOB data" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
return NEW_LOCATOR_LOB_MERGE_STRATEGY.mergeClob( original, target, session );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NClob mergeNClob(NClob original, NClob target, SessionImplementor session) {
|
||||
if ( original != target ) {
|
||||
try {
|
||||
OutputStream connectedStream = target.setAsciiStream( 1L ); // the NCLOB just read during the load phase of merge
|
||||
InputStream detachedStream = original.getAsciiStream(); // the NCLOB from the detached state
|
||||
StreamCopier.copy( detachedStream, connectedStream );
|
||||
return target;
|
||||
}
|
||||
catch (SQLException e ) {
|
||||
throw session.getFactory().getSQLExceptionHelper().convert( e, "unable to merge NCLOB data" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
return NEW_LOCATOR_LOB_MERGE_STRATEGY.mergeNClob( original, target, session );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge strategy based on creating a new LOB locator.
|
||||
*/
|
||||
protected static final LobMergeStrategy NEW_LOCATOR_LOB_MERGE_STRATEGY = new LobMergeStrategy() {
|
||||
@Override
|
||||
public Blob mergeBlob(Blob original, Blob target, SessionImplementor session) {
|
||||
if ( original == null && target == null ) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
LobCreator lobCreator = session.getFactory().getJdbcServices().getLobCreator( session );
|
||||
return original == null
|
||||
? lobCreator.createBlob( ArrayHelper.EMPTY_BYTE_ARRAY )
|
||||
: lobCreator.createBlob( original.getBinaryStream(), original.length() );
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw session.getFactory().getSQLExceptionHelper().convert( e, "unable to merge BLOB data" );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clob mergeClob(Clob original, Clob target, SessionImplementor session) {
|
||||
if ( original == null && target == null ) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
LobCreator lobCreator = session.getFactory().getJdbcServices().getLobCreator( session );
|
||||
return original == null
|
||||
? lobCreator.createClob( "" )
|
||||
: lobCreator.createClob( original.getCharacterStream(), original.length() );
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw session.getFactory().getSQLExceptionHelper().convert( e, "unable to merge CLOB data" );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NClob mergeNClob(NClob original, NClob target, SessionImplementor session) {
|
||||
if ( original == null && target == null ) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
LobCreator lobCreator = session.getFactory().getJdbcServices().getLobCreator( session );
|
||||
return original == null
|
||||
? lobCreator.createNClob( "" )
|
||||
: lobCreator.createNClob( original.getCharacterStream(), original.length() );
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw session.getFactory().getSQLExceptionHelper().convert( e, "unable to merge NCLOB data" );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public LobMergeStrategy getLobMergeStrategy() {
|
||||
return NEW_LOCATOR_LOB_MERGE_STRATEGY;
|
||||
}
|
||||
|
||||
|
||||
// hibernate type mapping support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
/**
|
||||
|
@ -1907,6 +2033,7 @@ public abstract class Dialect {
|
|||
* @since 3.2
|
||||
*/
|
||||
public boolean supportsLobValueChangePropogation() {
|
||||
// todo : pretty sure this is the same as the java.sql.DatabaseMetaData.locatorsUpdateCopy method added in JDBC 4, see HHH-6046
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.dialect;
|
||||
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.sql.NClob;
|
||||
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
|
||||
/**
|
||||
* Strategy for how dialects need {@code LOB} values to be merged.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface LobMergeStrategy {
|
||||
/**
|
||||
* Perform merge on {@link Blob} values.
|
||||
*
|
||||
* @param original The detached {@code BLOB} state
|
||||
* @param target The managed {@code BLOB} state
|
||||
* @param session The session
|
||||
*
|
||||
* @return The merged {@code BLOB} state
|
||||
*/
|
||||
public Blob mergeBlob(Blob original, Blob target, SessionImplementor session);
|
||||
|
||||
/**
|
||||
* Perform merge on {@link Clob} values.
|
||||
*
|
||||
* @param original The detached {@code CLOB} state
|
||||
* @param target The managed {@code CLOB} state
|
||||
* @param session The session
|
||||
*
|
||||
* @return The merged {@code CLOB} state
|
||||
*/
|
||||
public Clob mergeClob(Clob original, Clob target, SessionImplementor session);
|
||||
|
||||
/**
|
||||
* Perform merge on {@link NClob} values.
|
||||
*
|
||||
* @param original The detached {@code NCLOB} state
|
||||
* @param target The managed {@code NCLOB} state
|
||||
* @param session The session
|
||||
*
|
||||
* @return The merged {@code NCLOB} state
|
||||
*/
|
||||
public NClob mergeNClob(NClob original, NClob target, SessionImplementor session);
|
||||
}
|
|
@ -38,6 +38,7 @@ import org.hibernate.Query;
|
|||
import org.hibernate.ScrollMode;
|
||||
import org.hibernate.ScrollableResults;
|
||||
import org.hibernate.collection.PersistentCollection;
|
||||
import org.hibernate.engine.jdbc.LobCreationContext;
|
||||
import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
|
||||
import org.hibernate.engine.transaction.spi.TransactionCoordinator;
|
||||
import org.hibernate.event.EventListeners;
|
||||
|
@ -55,7 +56,7 @@ import org.hibernate.type.Type;
|
|||
* @see org.hibernate.impl.SessionImpl the actual implementation
|
||||
* @author Gavin King
|
||||
*/
|
||||
public interface SessionImplementor extends Serializable {
|
||||
public interface SessionImplementor extends Serializable, LobCreationContext {
|
||||
|
||||
/**
|
||||
* Retrieves the interceptor currently in use by this event source.
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.engine.jdbc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
|
@ -31,6 +32,7 @@ import java.lang.reflect.Method;
|
|||
import java.lang.reflect.Proxy;
|
||||
import java.sql.Clob;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.hibernate.type.descriptor.java.DataHelper;
|
||||
|
||||
/**
|
||||
|
@ -173,7 +175,7 @@ public class ClobProxy implements InvocationHandler {
|
|||
}
|
||||
}
|
||||
catch ( IOException ioe ) {
|
||||
throw new SQLException( "could not reset reader" );
|
||||
throw new SQLException( "could not reset reader", ioe );
|
||||
}
|
||||
needsReset = true;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
package org.hibernate.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.MappingException;
|
||||
|
@ -37,11 +39,14 @@ import org.hibernate.engine.NamedSQLQueryDefinition;
|
|||
import org.hibernate.engine.QueryParameters;
|
||||
import org.hibernate.engine.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.engine.jdbc.LobCreationContext;
|
||||
import org.hibernate.engine.query.HQLQueryPlan;
|
||||
import org.hibernate.engine.query.NativeSQLQueryPlan;
|
||||
import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
|
||||
import org.hibernate.engine.transaction.spi.TransactionContext;
|
||||
import org.hibernate.engine.transaction.spi.TransactionEnvironment;
|
||||
import org.hibernate.jdbc.WorkExecutor;
|
||||
import org.hibernate.jdbc.WorkExecutorVisitable;
|
||||
|
||||
/**
|
||||
* Functionality common to stateless and stateful sessions
|
||||
|
@ -66,6 +71,26 @@ public abstract class AbstractSessionImpl implements Serializable, SessionImplem
|
|||
return factory.getTransactionEnvironment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T execute(final LobCreationContext.Callback<T> callback) {
|
||||
return getTransactionCoordinator().getJdbcCoordinator().coordinateWork(
|
||||
new WorkExecutorVisitable<T>() {
|
||||
@Override
|
||||
public T accept(WorkExecutor<T> workExecutor, Connection connection) throws SQLException {
|
||||
try {
|
||||
return callback.executeOnConnection( connection );
|
||||
}
|
||||
catch ( SQLException e ) {
|
||||
throw getFactory().getSQLExceptionHelper().convert(
|
||||
e,
|
||||
"Error creating contextual LOB : " + e.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
|
|
@ -2051,25 +2051,6 @@ public final class SessionImpl
|
|||
oos.writeObject( childSessionsByEntityMode );
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Object execute(LobCreationContext.Callback callback) {
|
||||
Connection connection = transactionCoordinator.getJdbcCoordinator().getLogicalConnection().getConnection();
|
||||
try {
|
||||
return callback.executeOnConnection( connection );
|
||||
}
|
||||
catch ( SQLException e ) {
|
||||
throw getFactory().getSQLExceptionHelper().convert(
|
||||
e,
|
||||
"Error creating contextual LOB : " + e.getMessage()
|
||||
);
|
||||
}
|
||||
finally {
|
||||
transactionCoordinator.getJdbcCoordinator().getLogicalConnection().afterStatementExecution();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -258,6 +258,7 @@ public final class ArrayHelper {
|
|||
public static final Class[] EMPTY_CLASS_ARRAY = {};
|
||||
public static final Object[] EMPTY_OBJECT_ARRAY = {};
|
||||
public static final Type[] EMPTY_TYPE_ARRAY = {};
|
||||
public static final byte[] EMPTY_BYTE_ARRAY = {};
|
||||
|
||||
public static int[] getBatchSizes(int maxBatchSize) {
|
||||
int batchSize = maxBatchSize;
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.internal.util.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
||||
/**
|
||||
* Utilities for copying I/O streams.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class StreamCopier {
|
||||
public static final int BUFFER_SIZE = 1024 * 4;
|
||||
public static final byte[] BUFFER = new byte[ BUFFER_SIZE ];
|
||||
|
||||
public static long copy(InputStream from, OutputStream into) {
|
||||
try {
|
||||
long totalRead = 0;
|
||||
while ( true ) {
|
||||
synchronized ( BUFFER ) {
|
||||
int amountRead = from.read( BUFFER );
|
||||
if ( amountRead == -1 ) {
|
||||
break;
|
||||
}
|
||||
into.write( BUFFER, 0, amountRead );
|
||||
totalRead += amountRead;
|
||||
if ( amountRead < BUFFER_SIZE ) {
|
||||
// should mean there is no more data in the stream, no need for next read
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalRead;
|
||||
}
|
||||
catch (IOException e ) {
|
||||
throw new HibernateException( "Unable to copy stream content", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ public abstract class AbstractStandardBasicType<T>
|
|||
return javaTypeDescriptor.getMutabilityPlan();
|
||||
}
|
||||
|
||||
protected T getReplacement(T original, T target) {
|
||||
protected T getReplacement(T original, T target, SessionImplementor session) {
|
||||
if ( !isMutable() ) {
|
||||
return original;
|
||||
}
|
||||
|
@ -375,7 +375,7 @@ public abstract class AbstractStandardBasicType<T>
|
|||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public final Object replace(Object original, Object target, SessionImplementor session, Object owner, Map copyCache) {
|
||||
return getReplacement( (T) original, (T) target );
|
||||
return getReplacement( (T) original, (T) target, session );
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
|
@ -387,7 +387,7 @@ public abstract class AbstractStandardBasicType<T>
|
|||
Map copyCache,
|
||||
ForeignKeyDirection foreignKeyDirection) {
|
||||
return ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT == foreignKeyDirection
|
||||
? getReplacement( (T) original, (T) target )
|
||||
? getReplacement( (T) original, (T) target, session )
|
||||
: target;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
*/
|
||||
package org.hibernate.type;
|
||||
import java.sql.Blob;
|
||||
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.type.descriptor.java.BlobTypeDescriptor;
|
||||
|
||||
/**
|
||||
|
@ -51,8 +53,8 @@ public class BlobType extends AbstractSingleColumnStandardBasicType<Blob> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Blob getReplacement(Blob original, Blob target) {
|
||||
return target;
|
||||
protected Blob getReplacement(Blob original, Blob target, SessionImplementor session) {
|
||||
return session.getFactory().getDialect().getLobMergeStrategy().mergeBlob( original, target, session );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
*/
|
||||
package org.hibernate.type;
|
||||
import java.sql.Clob;
|
||||
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.type.descriptor.java.ClobTypeDescriptor;
|
||||
|
||||
/**
|
||||
|
@ -48,8 +50,8 @@ public class ClobType extends AbstractSingleColumnStandardBasicType<Clob> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Clob getReplacement(Clob original, Clob target) {
|
||||
return target;
|
||||
protected Clob getReplacement(Clob original, Clob target, SessionImplementor session) {
|
||||
return session.getFactory().getDialect().getLobMergeStrategy().mergeClob( original, target, session );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -56,8 +56,8 @@ public class BlobLocatorTest extends BaseCoreFunctionalTestCase {
|
|||
|
||||
@Test
|
||||
public void testBoundedBlobLocatorAccess() throws Throwable {
|
||||
byte[] original = buildRecursively( BLOB_SIZE, true );
|
||||
byte[] changed = buildRecursively( BLOB_SIZE, false );
|
||||
byte[] original = buildByteArray( BLOB_SIZE, true );
|
||||
byte[] changed = buildByteArray( BLOB_SIZE, false );
|
||||
byte[] empty = new byte[] {};
|
||||
|
||||
Session s = openSession();
|
||||
|
@ -142,7 +142,7 @@ public class BlobLocatorTest extends BaseCoreFunctionalTestCase {
|
|||
// unsupported; most databases would not allow such a construct anyway.
|
||||
// Thus here we are only testing materialization...
|
||||
|
||||
byte[] original = buildRecursively( BLOB_SIZE, true );
|
||||
byte[] original = buildByteArray( BLOB_SIZE, true );
|
||||
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
@ -170,12 +170,12 @@ public class BlobLocatorTest extends BaseCoreFunctionalTestCase {
|
|||
s.close();
|
||||
}
|
||||
|
||||
private byte[] extractData(Blob blob) throws Throwable {
|
||||
public static byte[] extractData(Blob blob) throws Exception {
|
||||
return blob.getBytes( 1, ( int ) blob.length() );
|
||||
}
|
||||
|
||||
|
||||
private byte[] buildRecursively(long size, boolean on) {
|
||||
public static byte[] buildByteArray(long size, boolean on) {
|
||||
byte[] data = new byte[(int)size];
|
||||
data[0] = mask( on );
|
||||
for ( int i = 0; i < size; i++ ) {
|
||||
|
@ -185,11 +185,11 @@ public class BlobLocatorTest extends BaseCoreFunctionalTestCase {
|
|||
return data;
|
||||
}
|
||||
|
||||
private byte mask(boolean on) {
|
||||
private static byte mask(boolean on) {
|
||||
return on ? ( byte ) 1 : ( byte ) 0;
|
||||
}
|
||||
|
||||
private static void assertEquals(byte[] val1, byte[] val2) {
|
||||
public static void assertEquals(byte[] val1, byte[] val2) {
|
||||
if ( !ArrayHelper.isEquals( val1, val2 ) ) {
|
||||
throw new AssertionFailedError( "byte arrays did not match" );
|
||||
}
|
||||
|
|
|
@ -57,8 +57,8 @@ public class ClobLocatorTest extends BaseCoreFunctionalTestCase {
|
|||
|
||||
@Test
|
||||
public void testBoundedClobLocatorAccess() throws Throwable {
|
||||
String original = buildRecursively( CLOB_SIZE, 'x' );
|
||||
String changed = buildRecursively( CLOB_SIZE, 'y' );
|
||||
String original = buildString( CLOB_SIZE, 'x' );
|
||||
String changed = buildString( CLOB_SIZE, 'y' );
|
||||
String empty = "";
|
||||
|
||||
Session s = openSession();
|
||||
|
@ -143,7 +143,7 @@ public class ClobLocatorTest extends BaseCoreFunctionalTestCase {
|
|||
// unsupported; most databases would not allow such a construct anyway.
|
||||
// Thus here we are only testing materialization...
|
||||
|
||||
String original = buildRecursively( CLOB_SIZE, 'x' );
|
||||
String original = buildString( CLOB_SIZE, 'x' );
|
||||
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
@ -171,12 +171,11 @@ public class ClobLocatorTest extends BaseCoreFunctionalTestCase {
|
|||
s.close();
|
||||
}
|
||||
|
||||
private String extractData(Clob clob) throws Throwable {
|
||||
public static String extractData(Clob clob) throws Exception {
|
||||
return DataHelper.extractString( clob.getCharacterStream() );
|
||||
}
|
||||
|
||||
|
||||
private String buildRecursively(int size, char baseChar) {
|
||||
public static String buildString(int size, char baseChar) {
|
||||
StringBuffer buff = new StringBuffer();
|
||||
for( int i = 0; i < size; i++ ) {
|
||||
buff.append( baseChar );
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.lob;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.DialectChecks;
|
||||
import org.hibernate.testing.RequiresDialectFeature;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@TestForIssue( jiraKey = "HHH-2680" )
|
||||
@RequiresDialectFeature( DialectChecks.SupportsExpectedLobUsagePattern.class )
|
||||
public class LobMergeTest extends BaseCoreFunctionalTestCase {
|
||||
private static final int LOB_SIZE = 10000;
|
||||
|
||||
public String[] getMappings() {
|
||||
return new String[] { "lob/LobMappings.hbm.xml" };
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergingBlobData() throws Exception {
|
||||
final byte[] original = BlobLocatorTest.buildByteArray( LOB_SIZE, true );
|
||||
final byte[] updated = BlobLocatorTest.buildByteArray( LOB_SIZE, false );
|
||||
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
||||
LobHolder entity = new LobHolder();
|
||||
entity.setBlobLocator( s.getLobHelper().createBlob( original ) );
|
||||
s.save( entity );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
// entity still detached...
|
||||
entity.setBlobLocator( s.getLobHelper().createBlob( updated ) );
|
||||
entity = (LobHolder) s.merge( entity );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
entity = (LobHolder) s.get( LobHolder.class, entity.getId() );
|
||||
assertEquals( "blob sizes did not match after merge", LOB_SIZE, entity.getBlobLocator().length() );
|
||||
assertTrue(
|
||||
"blob contents did not match after merge",
|
||||
ArrayHelper.isEquals( updated, BlobLocatorTest.extractData( entity.getBlobLocator() ) )
|
||||
);
|
||||
s.delete( entity );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergingClobData() throws Exception {
|
||||
final String original = ClobLocatorTest.buildString( LOB_SIZE, 'a' );
|
||||
final String updated = ClobLocatorTest.buildString( LOB_SIZE, 'z' );
|
||||
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
||||
LobHolder entity = new LobHolder();
|
||||
entity.setClobLocator( s.getLobHelper().createClob( original ) );
|
||||
s.save( entity );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
// entity still detached...
|
||||
entity.setClobLocator( s.getLobHelper().createClob( updated ) );
|
||||
entity = (LobHolder) s.merge( entity );
|
||||
s.flush();
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
entity = (LobHolder) s.get( LobHolder.class, entity.getId() );
|
||||
assertEquals( "clob sizes did not match after merge", LOB_SIZE, entity.getClobLocator().length() );
|
||||
assertEquals(
|
||||
"clob contents did not match after merge",
|
||||
updated,
|
||||
ClobLocatorTest.extractData( entity.getClobLocator() )
|
||||
);
|
||||
s.delete( entity );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue