HHH-12555 Fix merging of lazy loaded blobs/clobs/nclobs
It's better to avoid pushing UNFETCHED_PROPERTY to the types as it requires all the types to take it into account. TypeHelper looks like the only sensible caller that needs change. (cherry picked from commit1af878166f
) HHH-12555 Add tests (cherry picked from commit4e05953240
) HHH-12555 : Update test to check results (cherry picked from commitca6dc226eb
) HHH-12555 : Remove code in LobMergeStrategy implementations that copied original Lob when target is null (cherry picked from commit4d0b5dc184
) HHH-12555 Add a DialectCheck for NClob support (cherry picked from commit855f34c771
) HHH-12555 Disable NClob test for dialects not supporting NClob (cherry picked from commit98249af058
)
This commit is contained in:
parent
6f636a8c0b
commit
91ede032c9
|
@ -71,11 +71,7 @@ public abstract class AbstractStandardBasicType<T>
|
|||
}
|
||||
|
||||
protected T getReplacement(T original, T target, SharedSessionContractImplementor session) {
|
||||
if ( original == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
|
||||
return target;
|
||||
}
|
||||
else if ( !isMutable() ||
|
||||
( target != LazyPropertyInitializer.UNFETCHED_PROPERTY && isEqual( original, target ) ) ) {
|
||||
if ( !isMutable() || ( target != null && isEqual( original, target ) ) ) {
|
||||
return original;
|
||||
}
|
||||
else {
|
||||
|
@ -351,6 +347,10 @@ public abstract class AbstractStandardBasicType<T>
|
|||
@Override
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public final Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) {
|
||||
if ( original == null && target == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getReplacement( (T) original, (T) target, session );
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ public class ClobType extends AbstractSingleColumnStandardBasicType<Clob> {
|
|||
|
||||
@Override
|
||||
protected Clob getReplacement(Clob original, Clob target, SharedSessionContractImplementor session) {
|
||||
return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( original, target, session );
|
||||
return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeClob( (Clob) original, (Clob) target, session );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|||
import org.hibernate.type.descriptor.java.NClobTypeDescriptor;
|
||||
|
||||
/**
|
||||
* A type that maps between {@link java.sql.Types#CLOB CLOB} and {@link java.sql.Clob}
|
||||
* A type that maps between {@link java.sql.Types#NCLOB NCLOB} and {@link java.sql.NClob}
|
||||
*
|
||||
* @author Gavin King
|
||||
* @author Steve Ebersole
|
||||
|
@ -38,5 +38,4 @@ public class NClobType extends AbstractSingleColumnStandardBasicType<NClob> {
|
|||
protected NClob getReplacement(NClob original, NClob target, SharedSessionContractImplementor session) {
|
||||
return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeNClob( original, target, session );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -159,6 +159,9 @@ public class TypeHelper {
|
|||
if ( original[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY || original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) {
|
||||
copied[i] = target[i];
|
||||
}
|
||||
else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
|
||||
copied[i] = types[i].replace( original[i], null, session, owner, copyCache );
|
||||
}
|
||||
else {
|
||||
copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache );
|
||||
}
|
||||
|
@ -193,6 +196,9 @@ public class TypeHelper {
|
|||
|| original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) {
|
||||
copied[i] = target[i];
|
||||
}
|
||||
else if ( target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
|
||||
copied[i] = types[i].replace( original[i], null, session, owner, copyCache, foreignKeyDirection );
|
||||
}
|
||||
else {
|
||||
copied[i] = types[i].replace( original[i], target[i], session, owner, copyCache, foreignKeyDirection );
|
||||
}
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
*/
|
||||
package org.hibernate.test.nationalized;
|
||||
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Lob;
|
||||
|
@ -15,30 +20,18 @@ import org.hibernate.Session;
|
|||
import org.hibernate.annotations.Nationalized;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.dialect.PostgreSQL81Dialect;
|
||||
import org.hibernate.dialect.SybaseDialect;
|
||||
import org.hibernate.resource.transaction.spi.TransactionStatus;
|
||||
|
||||
import org.hibernate.testing.SkipForDialect;
|
||||
import org.hibernate.testing.DialectChecks;
|
||||
import org.hibernate.testing.RequiresDialectFeature;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-10364")
|
||||
@SkipForDialect(value = DB2Dialect.class, comment = "DB2 jdbc driver doesn't support getNClob")
|
||||
@SkipForDialect(value = MySQLDialect.class, comment = "MySQL/MariadB doesn't support nclob")
|
||||
@SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL doesn't support nclob")
|
||||
@SkipForDialect(value = SybaseDialect.class, comment = "Sybase doesn't support nclob")
|
||||
@RequiresDialectFeature(DialectChecks.SupportsNClob.class)
|
||||
public class NationalizedLobFieldTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.test.type;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.sql.NClob;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Lob;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
import org.hibernate.testing.DialectChecks;
|
||||
import org.hibernate.testing.RequiresDialectFeature;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@TestForIssue(jiraKey = "HHH-12555")
|
||||
@RequiresDialectFeature(DialectChecks.SupportsExpectedLobUsagePattern.class)
|
||||
@RunWith(BytecodeEnhancerRunner.class)
|
||||
public class LobUnfetchedPropertyTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[]{ FileBlob.class, FileClob.class, FileNClob.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlob() throws SQLException {
|
||||
final int id = doInHibernate( this::sessionFactory, s -> {
|
||||
FileBlob file = new FileBlob();
|
||||
file.setBlob( s.getLobHelper().createBlob( "TEST CASE".getBytes() ) );
|
||||
// merge transient entity
|
||||
file = (FileBlob) s.merge( file );
|
||||
return file.getId();
|
||||
} );
|
||||
|
||||
doInHibernate( this::sessionFactory, s -> {
|
||||
FileBlob file = s.get( FileBlob.class, id );
|
||||
assertFalse( Hibernate.isPropertyInitialized( file, "blob" ) );
|
||||
Blob blob = file.getBlob();
|
||||
try {
|
||||
assertTrue(
|
||||
Arrays.equals( "TEST CASE".getBytes(), blob.getBytes( 1, (int) file.getBlob().length() ) )
|
||||
);
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
fail( "could not determine Lob length" );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClob() {
|
||||
final int id = doInHibernate( this::sessionFactory, s -> {
|
||||
FileClob file = new FileClob();
|
||||
file.setClob( s.getLobHelper().createClob( "TEST CASE" ) );
|
||||
// merge transient entity
|
||||
file = (FileClob) s.merge( file );
|
||||
return file.getId();
|
||||
} );
|
||||
|
||||
doInHibernate( this::sessionFactory, s -> {
|
||||
FileClob file = s.get( FileClob.class, id );
|
||||
assertFalse( Hibernate.isPropertyInitialized( file, "clob" ) );
|
||||
Clob clob = file.getClob();
|
||||
try {
|
||||
final char[] chars = new char[(int) file.getClob().length()];
|
||||
clob.getCharacterStream().read( chars );
|
||||
assertTrue( Arrays.equals( "TEST CASE".toCharArray(), chars ) );
|
||||
}
|
||||
catch (SQLException ex ) {
|
||||
fail( "could not determine Lob length" );
|
||||
}
|
||||
catch (IOException ex) {
|
||||
fail( "could not read Lob" );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(DialectChecks.SupportsNClob.class)
|
||||
public void testNClob() {
|
||||
final int id = doInHibernate( this::sessionFactory, s -> {
|
||||
FileNClob file = new FileNClob();
|
||||
file.setClob( s.getLobHelper().createNClob( "TEST CASE" ) );
|
||||
// merge transient entity
|
||||
file = (FileNClob) s.merge( file );
|
||||
return file.getId();
|
||||
});
|
||||
|
||||
doInHibernate( this::sessionFactory, s -> {
|
||||
FileNClob file = s.get( FileNClob.class, id );
|
||||
assertFalse( Hibernate.isPropertyInitialized( file, "clob" ) );
|
||||
NClob nClob = file.getClob();
|
||||
try {
|
||||
final char[] chars = new char[(int) file.getClob().length()];
|
||||
nClob.getCharacterStream().read( chars );
|
||||
assertTrue( Arrays.equals( "TEST CASE".toCharArray(), chars ) );
|
||||
}
|
||||
catch (SQLException ex ) {
|
||||
fail( "could not determine Lob length" );
|
||||
}
|
||||
catch (IOException ex) {
|
||||
fail( "could not read Lob" );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Entity(name = "FileBlob")
|
||||
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy")
|
||||
public static class FileBlob {
|
||||
|
||||
private int id;
|
||||
|
||||
private Blob blob;
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Column(name = "filedata", length = 1024 * 1024)
|
||||
@Lob
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
public Blob getBlob() {
|
||||
return blob;
|
||||
}
|
||||
|
||||
public void setBlob(Blob blob) {
|
||||
this.blob = blob;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "FileClob")
|
||||
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy")
|
||||
public static class FileClob {
|
||||
|
||||
private int id;
|
||||
|
||||
private Clob clob;
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Column(name = "filedata", length = 1024 * 1024)
|
||||
@Lob
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
public Clob getClob() {
|
||||
return clob;
|
||||
}
|
||||
|
||||
public void setClob(Clob clob) {
|
||||
this.clob = clob;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "FileNClob")
|
||||
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, include = "non-lazy")
|
||||
public static class FileNClob {
|
||||
|
||||
private int id;
|
||||
|
||||
private NClob clob;
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Column(name = "filedata", length = 1024 * 1024)
|
||||
@Lob
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
public NClob getClob() {
|
||||
return clob;
|
||||
}
|
||||
|
||||
public void setClob(NClob clob) {
|
||||
this.clob = clob;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,23 @@
|
|||
*/
|
||||
package org.hibernate.test.type;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Calendar;
|
||||
import java.util.Currency;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
import org.hibernate.internal.util.SerializationHelper;
|
||||
|
@ -42,26 +59,11 @@ import org.hibernate.type.TimeType;
|
|||
import org.hibernate.type.TimeZoneType;
|
||||
import org.hibernate.type.TimestampType;
|
||||
import org.hibernate.type.TrueFalseType;
|
||||
import org.hibernate.type.Type;
|
||||
import org.hibernate.type.TypeHelper;
|
||||
import org.hibernate.type.YesNoType;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Calendar;
|
||||
import java.util.Currency;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -344,8 +346,11 @@ public class TypeTest extends BaseUnitTestCase {
|
|||
assertTrue( original == type.replace( original, copy, null, null, null ) );
|
||||
|
||||
// following tests assert that types work with properties not yet loaded in bytecode enhanced entities
|
||||
assertSame( copy, type.replace( LazyPropertyInitializer.UNFETCHED_PROPERTY, copy, null, null, null ) );
|
||||
assertNotEquals( LazyPropertyInitializer.UNFETCHED_PROPERTY, type.replace( original, LazyPropertyInitializer.UNFETCHED_PROPERTY, null, null, null ) );
|
||||
Type[] types = new Type[]{ type };
|
||||
assertSame( copy, TypeHelper.replace( new Object[]{ LazyPropertyInitializer.UNFETCHED_PROPERTY },
|
||||
new Object[]{ copy }, types, null, null, null )[0] );
|
||||
assertNotEquals( LazyPropertyInitializer.UNFETCHED_PROPERTY, TypeHelper.replace( new Object[]{ original },
|
||||
new Object[]{ LazyPropertyInitializer.UNFETCHED_PROPERTY }, types, null, null, null )[0] );
|
||||
|
||||
assertTrue( type.isSame( original, copy ) );
|
||||
assertTrue( type.isEqual( original, copy ) );
|
||||
|
|
|
@ -8,6 +8,9 @@ package org.hibernate.testing;
|
|||
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.dialect.PostgreSQL81Dialect;
|
||||
import org.hibernate.dialect.SybaseDialect;
|
||||
|
||||
/**
|
||||
* Container class for different implementation of the {@link DialectCheck} interface.
|
||||
|
@ -153,13 +156,13 @@ abstract public class DialectChecks {
|
|||
return dialect.supportsExistsInSelect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class SupportsLobValueChangePropogation implements DialectCheck {
|
||||
public boolean isMatch(Dialect dialect) {
|
||||
return dialect.supportsLobValueChangePropogation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class SupportsLockTimeouts implements DialectCheck {
|
||||
public boolean isMatch(Dialect dialect) {
|
||||
return dialect.supportsLockTimeouts();
|
||||
|
@ -256,4 +259,16 @@ abstract public class DialectChecks {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SupportsNClob implements DialectCheck {
|
||||
@Override
|
||||
public boolean isMatch(Dialect dialect) {
|
||||
return !(
|
||||
dialect instanceof DB2Dialect ||
|
||||
dialect instanceof PostgreSQL81Dialect ||
|
||||
dialect instanceof SybaseDialect ||
|
||||
dialect instanceof MySQLDialect
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue