mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-09 12:44:49 +00:00
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 1af878166fe0883ceb3d9130afa2790850492624) HHH-12555 Add tests (cherry picked from commit 4e05953240264d32fe67f6e0f1810d8168c2725f) HHH-12555 : Update test to check results (cherry picked from commit ca6dc226eb53a0f40978847d2d251b64c5e49f41) HHH-12555 : Remove code in LobMergeStrategy implementations that copied original Lob when target is null (cherry picked from commit 4d0b5dc184f7b48a493da31edfe4acea30c8e975) HHH-12555 Add a DialectCheck for NClob support (cherry picked from commit 855f34c77181fd9ff103a8d2bf9c9bfb37360f2c) HHH-12555 Disable NClob test for dialects not supporting NClob (cherry picked from commit 98249af058a1f6408fcfca5a61d675af192458a1)
This commit is contained in:
parent
6f636a8c0b
commit
91ede032c9
@ -71,11 +71,7 @@ protected MutabilityPlan<T> getMutabilityPlan() {
|
||||
}
|
||||
|
||||
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 final Type getSemiResolvedType(SessionFactoryImplementor factory) {
|
||||
@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 @@ protected boolean registerUnderJavaType() {
|
||||
|
||||
@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.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 @@ protected boolean registerUnderJavaType() {
|
||||
protected NClob getReplacement(NClob original, NClob target, SharedSessionContractImplementor session) {
|
||||
return session.getJdbcServices().getJdbcEnvironment().getDialect().getLobMergeStrategy().mergeNClob( original, target, session );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -159,6 +159,9 @@ public static Object[] replace(
|
||||
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 static Object[] replace(
|
||||
|| 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.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.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 @@ protected <T> void runBasicTests(AbstractSingleColumnStandardBasicType<T> type,
|
||||
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 @@
|
||||
|
||||
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 @@ public boolean isMatch(Dialect dialect) {
|
||||
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 @@ public boolean isMatch(Dialect dialect) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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…
x
Reference in New Issue
Block a user