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 commit 1af878166f)

HHH-12555 Add tests

(cherry picked from commit 4e05953240)

HHH-12555 : Update test to check results

(cherry picked from commit ca6dc226eb)

HHH-12555 : Remove code in LobMergeStrategy implementations that copied original Lob when target is null

(cherry picked from commit 4d0b5dc184)

HHH-12555 Add a DialectCheck for NClob support

(cherry picked from commit 855f34c771)

HHH-12555 Disable NClob test for dialects not supporting NClob

(cherry picked from commit 98249af058)
This commit is contained in:
Guillaume Smet 2018-05-28 12:35:37 +02:00 committed by Gail Badner
parent 6f636a8c0b
commit 91ede032c9
8 changed files with 283 additions and 45 deletions

View File

@ -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 );
}

View File

@ -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 );
}
}

View File

@ -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 );
}
}

View File

@ -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 );
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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 ) );

View File

@ -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.
@ -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
);
}
}
}