diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java index 27e9235a89..0aa513d6b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java @@ -24,10 +24,10 @@ package org.hibernate.cfg.annotations; import java.util.Map; + import javax.persistence.EmbeddedId; import javax.persistence.Id; - -import org.jboss.logging.Logger; +import javax.persistence.Lob; import org.hibernate.AnnotationException; import org.hibernate.annotations.Generated; @@ -57,6 +57,7 @@ import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.ToOne; import org.hibernate.mapping.Value; +import org.jboss.logging.Logger; /** * @author Emmanuel Bernard @@ -264,6 +265,7 @@ public class PropertyBinder { prop.setLazy( lazy ); prop.setCascade( cascade ); prop.setPropertyAccessorName( accessType.getType() ); + Generated ann = property != null ? property.getAnnotation( Generated.class ) : null; @@ -286,6 +288,7 @@ public class PropertyBinder { prop.setGeneration( PropertyGeneration.parse( generated.toString().toLowerCase() ) ); } } + NaturalId naturalId = property != null ? property.getAnnotation( NaturalId.class ) : null; if ( naturalId != null ) { if ( ! entityBinder.isRootEntity() ) { @@ -296,6 +299,11 @@ public class PropertyBinder { } prop.setNaturalIdentifier( true ); } + + // HHH-4635 -- needed for dialect-specific property ordering + Lob lob = property != null ? property.getAnnotation( Lob.class ) : null; + prop.setLob( lob != null ); + prop.setInsertable( insertable ); prop.setUpdateable( updatable ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 6984d884c4..c6166d5b39 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -2290,4 +2290,15 @@ public abstract class Dialect implements ConversionContext { public int getInExpressionCountLimit() { return 0; } + + /** + * HHH-4635 + * Oracle expects all Lob values to be last in inserts and updates. + * + * @return boolean True of Lob values should be last, false if it + * does not matter. + */ + public boolean forceLobAsLastValue() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java index 22ab648724..6f77273d91 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java @@ -576,5 +576,10 @@ public class Oracle8iDialect extends Dialect { public boolean supportsNotNullUnique() { return false; } + + @Override + public boolean forceLobAsLastValue() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index cffdc68e2d..e90e583bd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -61,6 +61,7 @@ public class Property implements Serializable, MetaAttributable { private java.util.Map metaAttributes; private PersistentClass persistentClass; private boolean naturalIdentifier; + private boolean lob; public boolean isBackRef() { return false; @@ -338,4 +339,12 @@ public class Property implements Serializable, MetaAttributable { this.naturalIdentifier = naturalIdentifier; } + public boolean isLob() { + return lob; + } + + public void setLob(boolean lob) { + this.lob = lob; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 36dc800e8e..b3c8c71555 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -184,6 +184,8 @@ public abstract class AbstractEntityPersister private final boolean[][] propertyColumnInsertable; private final boolean[] propertyUniqueness; private final boolean[] propertySelectable; + + private final List lobProperties = new ArrayList(); //information about lazy properties of this class private final String[] lazyPropertyNames; @@ -630,6 +632,10 @@ public abstract class AbstractEntityPersister propertySelectable[i] = prop.isSelectable(); propertyUniqueness[i] = prop.getValue().isAlternateUniqueKey(); + + if (prop.isLob() && getFactory().getDialect().forceLobAsLastValue() ) { + lobProperties.add( i ); + } i++; @@ -942,6 +948,8 @@ public abstract class AbstractEntityPersister propertySelectable[i] = true; propertyUniqueness[i] = singularAttributeBinding.isAlternateUniqueKey(); + + // TODO: Does this need AttributeBindings wired into lobProperties? Currently in Property only. i++; @@ -2508,12 +2516,26 @@ public abstract class AbstractEntityPersister boolean hasColumns = false; for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { - if ( includeProperty[i] && isPropertyOfTable( i, j ) ) { + if ( includeProperty[i] && isPropertyOfTable( i, j ) + && !lobProperties.contains( i ) ) { // this is a property of the table, which we are updating - update.addColumns( getPropertyColumnNames(i), propertyColumnUpdateable[i], propertyColumnWriters[i] ); + update.addColumns( getPropertyColumnNames(i), + propertyColumnUpdateable[i], propertyColumnWriters[i] ); hasColumns = hasColumns || getPropertyColumnSpan( i ) > 0; } } + + // HHH-4635 + // Oracle expects all Lob properties to be last in inserts + // and updates. Insert them at the end. + for ( int i : lobProperties ) { + if ( includeProperty[i] && isPropertyOfTable( i, j ) ) { + // this property belongs on the table and is to be inserted + update.addColumns( getPropertyColumnNames(i), + propertyColumnUpdateable[i], propertyColumnWriters[i] ); + hasColumns = true; + } + } if ( j == 0 && isVersioned() && entityMetamodel.getOptimisticLockStyle() == OptimisticLockStyle.VERSION ) { // this is the root (versioned) table, and we are using version-based @@ -2579,7 +2601,8 @@ public abstract class AbstractEntityPersister /** * Generate the SQL that inserts a row */ - protected String generateInsertString(boolean identityInsert, boolean[] includeProperty, int j) { + protected String generateInsertString(boolean identityInsert, + boolean[] includeProperty, int j) { // todo : remove the identityInsert param and variations; // identity-insert strings are now generated from generateIdentityInsertString() @@ -2589,9 +2612,13 @@ public abstract class AbstractEntityPersister // add normal properties for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { - if ( includeProperty[i] && isPropertyOfTable( i, j ) ) { + + if ( includeProperty[i] && isPropertyOfTable( i, j ) + && !lobProperties.contains( i ) ) { // this property belongs on the table and is to be inserted - insert.addColumns( getPropertyColumnNames(i), propertyColumnInsertable[i], propertyColumnWriters[i] ); + insert.addColumns( getPropertyColumnNames(i), + propertyColumnInsertable[i], + propertyColumnWriters[i] ); } } @@ -2611,6 +2638,18 @@ public abstract class AbstractEntityPersister if ( getFactory().getSettings().isCommentsEnabled() ) { insert.setComment( "insert " + getEntityName() ); } + + // HHH-4635 + // Oracle expects all Lob properties to be last in inserts + // and updates. Insert them at the end. + for ( int i : lobProperties ) { + if ( includeProperty[i] && isPropertyOfTable( i, j ) ) { + // this property belongs on the table and is to be inserted + insert.addColumns( getPropertyColumnNames(i), + propertyColumnInsertable[i], + propertyColumnWriters[i] ); + } + } String result = insert.toStatementString(); @@ -2678,8 +2717,9 @@ public abstract class AbstractEntityPersister boolean[][] includeColumns, int j, PreparedStatement st, - SessionImplementor session) throws HibernateException, SQLException { - return dehydrate( id, fields, null, includeProperty, includeColumns, j, st, session, 1 ); + SessionImplementor session, + boolean isUpdate) throws HibernateException, SQLException { + return dehydrate( id, fields, null, includeProperty, includeColumns, j, st, session, 1, isUpdate ); } /** @@ -2694,32 +2734,58 @@ public abstract class AbstractEntityPersister final int j, final PreparedStatement ps, final SessionImplementor session, - int index) throws SQLException, HibernateException { + int index, + boolean isUpdate ) throws SQLException, HibernateException { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Dehydrating entity: {0}", MessageHelper.infoString( this, id, getFactory() ) ); } for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { - if ( includeProperty[i] && isPropertyOfTable( i, j ) ) { + if ( includeProperty[i] && isPropertyOfTable( i, j ) + && !lobProperties.contains( i )) { getPropertyTypes()[i].nullSafeSet( ps, fields[i], index, includeColumns[i], session ); - //index += getPropertyColumnSpan( i ); index += ArrayHelper.countTrue( includeColumns[i] ); //TODO: this is kinda slow... } } - - if ( rowId != null ) { - ps.setObject( index, rowId ); - index += 1; + + if ( !isUpdate ) { + index += dehydrateId( id, rowId, ps, session, index ); } - else if ( id != null ) { - getIdentifierType().nullSafeSet( ps, id, index, session ); - index += getIdentifierColumnSpan(); + + // HHH-4635 + // Oracle expects all Lob properties to be last in inserts + // and updates. Insert them at the end. + for ( int i : lobProperties ) { + if ( includeProperty[i] && isPropertyOfTable( i, j ) ) { + getPropertyTypes()[i].nullSafeSet( ps, fields[i], index, includeColumns[i], session ); + index += ArrayHelper.countTrue( includeColumns[i] ); //TODO: this is kinda slow... + } + } + + if ( isUpdate ) { + index += dehydrateId( id, rowId, ps, session, index ); } return index; } + + private int dehydrateId( + final Serializable id, + final Object rowId, + final PreparedStatement ps, + final SessionImplementor session, + int index ) throws SQLException { + if ( rowId != null ) { + ps.setObject( index, rowId ); + return 1; + } else if ( id != null ) { + getIdentifierType().nullSafeSet( ps, id, index, session ); + return getIdentifierColumnSpan(); + } + return 0; + } /** * Unmarshall the fields of a persistent instance from a result set, @@ -2860,7 +2926,7 @@ public abstract class AbstractEntityPersister Binder binder = new Binder() { public void bindValues(PreparedStatement ps) throws SQLException { - dehydrate( null, fields, notNull, propertyColumnInsertable, 0, ps, session ); + dehydrate( null, fields, notNull, propertyColumnInsertable, 0, ps, session, false ); } public Object getEntity() { return object; @@ -2956,7 +3022,7 @@ public abstract class AbstractEntityPersister // Write the values of fields onto the prepared statement - we MUST use the state at the time the // insert was issued (cos of foreign key constraints). Not necessarily the object's current state - dehydrate( id, fields, null, notNull, propertyColumnInsertable, j, insert, session, index ); + dehydrate( id, fields, null, notNull, propertyColumnInsertable, j, insert, session, index, false ); if ( useBatch ) { session.getTransactionCoordinator().getJdbcCoordinator().getBatch( inserBatchKey ).addToBatch(); @@ -3083,7 +3149,7 @@ public abstract class AbstractEntityPersister index+= expectation.prepare( update ); //Now write the values of fields onto the prepared statement - index = dehydrate( id, fields, rowId, includeProperty, propertyColumnUpdateable, j, update, session, index ); + index = dehydrate( id, fields, rowId, includeProperty, propertyColumnUpdateable, j, update, session, index, true ); // Write any appropriate versioning conditional parameters if ( useVersion && entityMetamodel.getOptimisticLockStyle() == OptimisticLockStyle.VERSION ) { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTestEntity.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTestEntity.java index 37e6231529..9938d652a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTestEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/lob/hhh4635/LobTestEntity.java @@ -5,6 +5,7 @@ import java.sql.Blob; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.Lob; import javax.persistence.Table; @Entity @@ -14,6 +15,7 @@ public class LobTestEntity { @Id private Long id; + @Lob private Blob lobValue; @Column( name = "qwerty", length = 4000 )