From: <hib...@li...> - 2006-03-24 18:10:12
|
Author: ste...@jb... Date: 2006-03-24 13:10:04 -0500 (Fri, 24 Mar 2006) New Revision: 9681 Added: trunk/Hibernate3/src/org/hibernate/id/insert/ trunk/Hibernate3/src/org/hibernate/id/insert/AbstractReturningDelegate.java trunk/Hibernate3/src/org/hibernate/id/insert/AbstractSelectingDelegate.java trunk/Hibernate3/src/org/hibernate/id/insert/Binder.java trunk/Hibernate3/src/org/hibernate/id/insert/IdentifierGeneratingInsert.java trunk/Hibernate3/src/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java trunk/Hibernate3/src/org/hibernate/id/insert/InsertSelectIdentityInsert.java Modified: trunk/Hibernate3/src/org/hibernate/id/AbstractPostInsertGenerator.java trunk/Hibernate3/src/org/hibernate/id/IdentityGenerator.java trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentifierGenerator.java trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentityPersister.java trunk/Hibernate3/src/org/hibernate/id/SelectGenerator.java trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java trunk/Hibernate3/src/org/hibernate/sql/Insert.java Log: HHH-1590 : changed how PostInsertIdentifierGenerators are handled Modified: trunk/Hibernate3/src/org/hibernate/id/AbstractPostInsertGenerator.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/AbstractPostInsertGenerator.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/AbstractPostInsertGenerator.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -2,65 +2,14 @@ package org.hibernate.id; import java.io.Serializable; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import org.hibernate.HibernateException; import org.hibernate.engine.SessionImplementor; -import org.hibernate.exception.JDBCExceptionHelper; -import org.hibernate.pretty.MessageHelper; /** * @author Gavin King */ public abstract class AbstractPostInsertGenerator implements PostInsertIdentifierGenerator{ - public Serializable generate(SessionImplementor s, Object obj) { return IdentifierGeneratorFactory.POST_INSERT_INDICATOR; } - - protected abstract String getSQL(PostInsertIdentityPersister persister); - - protected void bindParameters(SessionImplementor session, PreparedStatement ps, Object object, PostInsertIdentityPersister persister) - throws SQLException {} - - protected abstract Serializable getResult(SessionImplementor session, ResultSet rs, Object object, PostInsertIdentityPersister persister) - throws SQLException; - - public Serializable getGenerated(SessionImplementor session, Object object, PostInsertIdentityPersister persister) - throws HibernateException { - - final String sql = getSQL(persister); - - try { - - //fetch the generated id in a separate query - PreparedStatement idSelect = session.getBatcher().prepareStatement(sql); - try { - bindParameters(session, idSelect, object, persister); - ResultSet rs = idSelect.executeQuery(); - try { - return getResult(session, rs, object, persister); - } - finally { - rs.close(); - } - } - finally { - session.getBatcher().closeStatement(idSelect); - } - - } - catch ( SQLException sqle ) { - throw JDBCExceptionHelper.convert( - session.getFactory().getSQLExceptionConverter(), - sqle, - "could not insert: " + MessageHelper.infoString( persister ), - sql - ); - } - - } - } Modified: trunk/Hibernate3/src/org/hibernate/id/IdentityGenerator.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/IdentityGenerator.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/IdentityGenerator.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -4,11 +4,22 @@ import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.PreparedStatement; import org.hibernate.engine.SessionImplementor; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.id.insert.IdentifierGeneratingInsert; +import org.hibernate.id.insert.AbstractSelectingDelegate; +import org.hibernate.id.insert.AbstractReturningDelegate; +import org.hibernate.id.insert.InsertSelectIdentityInsert; +import org.hibernate.dialect.Dialect; +import org.hibernate.HibernateException; +import org.hibernate.AssertionFailure; +import org.hibernate.util.GetGeneratedKeysHelper; /** + * A generator for use with ANSI-SQL IDENTITY columns used as the primary key. * The IdentityGenerator for autoincrement/identity key generation. * <br><br> * Indicates to the <tt>Session</tt> that identity (ie. identity/autoincrement @@ -18,18 +29,133 @@ */ public class IdentityGenerator extends AbstractPostInsertGenerator { - protected String getSQL(PostInsertIdentityPersister persister) { - return persister.getIdentitySelectString(); + public InsertGeneratedIdentifierDelegate getInsertGeneratedIdentifierDelegate( + PostInsertIdentityPersister persister, + Dialect dialect, + boolean isGetGeneratedKeysEnabled) throws HibernateException { + if ( isGetGeneratedKeysEnabled ) { + return new GetGeneratedKeysDelegate( persister, dialect ); + } + else if ( dialect.supportsInsertSelectIdentity() ) { + return new InsertSelectDelegate( persister, dialect ); + } + else { + return new BasicDelegate( persister, dialect ); + } } - - protected Serializable getResult(SessionImplementor session, ResultSet rs, Object object, PostInsertIdentityPersister persister) - throws SQLException { - return IdentifierGeneratorFactory.getGeneratedIdentity( rs, persister.getIdentifierType() ); + + /** + * Delegate for dealing with IDENTITY columns using JDBC3 getGeneratedKeys + */ + public static class GetGeneratedKeysDelegate + extends AbstractReturningDelegate + implements InsertGeneratedIdentifierDelegate { + private final PostInsertIdentityPersister persister; + private final Dialect dialect; + + public GetGeneratedKeysDelegate(PostInsertIdentityPersister persister, Dialect dialect) { + super( persister ); + this.persister = persister; + this.dialect = dialect; + } + + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + IdentifierGeneratingInsert insert = new IdentifierGeneratingInsert( dialect ); + insert.addIdentityColumn( persister.getRootTableKeyColumnNames()[0] ); + return insert; + } + + protected PreparedStatement prepare(String insertSQL, SessionImplementor session) throws SQLException { + return session.getBatcher().prepareStatement( insertSQL, true ); + } + + public Serializable executeAndExtract(PreparedStatement insert) throws SQLException { + insert.executeUpdate(); + return IdentifierGeneratorFactory.getGeneratedIdentity( + GetGeneratedKeysHelper.getGeneratedKey( insert ), + persister.getIdentifierType() + ); + } } -} + /** + * Delegate for dealing with IDENTITY columns where the dialect supports returning + * the generated IDENTITY value directly from the insert statement. + */ + public static class InsertSelectDelegate + extends AbstractReturningDelegate + implements InsertGeneratedIdentifierDelegate { + private final PostInsertIdentityPersister persister; + private final Dialect dialect; + public InsertSelectDelegate(PostInsertIdentityPersister persister, Dialect dialect) { + super( persister ); + this.persister = persister; + this.dialect = dialect; + } + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + InsertSelectIdentityInsert insert = new InsertSelectIdentityInsert( dialect ); + insert.addIdentityColumn( persister.getRootTableKeyColumnNames()[0] ); + return insert; + } + protected PreparedStatement prepare(String insertSQL, SessionImplementor session) throws SQLException { + return session.getBatcher().prepareStatement( insertSQL, false ); + } + public Serializable executeAndExtract(PreparedStatement insert) throws SQLException { + if ( !insert.execute() ) { + while ( !insert.getMoreResults() && insert.getUpdateCount() != -1 ) { + // do nothing until we hit the rsult set containing the generated id + } + } + ResultSet rs = insert.getResultSet(); + try { + return IdentifierGeneratorFactory.getGeneratedIdentity( rs, persister.getIdentifierType() ); + } + finally { + rs.close(); + } + } + public Serializable determineGeneratedIdentifier(SessionImplementor session, Object entity) { + throw new AssertionFailure( "insert statement returns generated value" ); + } + } + + /** + * Delegate for dealing with IDENTITY columns where the dialect requires an + * additional command execution to retrieve the generated IDENTITY value + */ + public static class BasicDelegate + extends AbstractSelectingDelegate + implements InsertGeneratedIdentifierDelegate { + private final PostInsertIdentityPersister persister; + private final Dialect dialect; + + public BasicDelegate(PostInsertIdentityPersister persister, Dialect dialect) { + super( persister ); + this.persister = persister; + this.dialect = dialect; + } + + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + IdentifierGeneratingInsert insert = new IdentifierGeneratingInsert( dialect ); + insert.addIdentityColumn( persister.getRootTableKeyColumnNames()[0] ); + return insert; + } + + protected String getSelectSQL() { + return persister.getIdentitySelectString(); + } + + protected Serializable getResult( + SessionImplementor session, + ResultSet rs, + Object object) throws SQLException { + return IdentifierGeneratorFactory.getGeneratedIdentity( rs, persister.getIdentifierType() ); + } + } + +} Modified: trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentifierGenerator.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentifierGenerator.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentifierGenerator.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -1,15 +1,16 @@ //$Id$ package org.hibernate.id; -import java.io.Serializable; - import org.hibernate.HibernateException; -import org.hibernate.engine.SessionImplementor; +import org.hibernate.dialect.Dialect; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; /** * @author Gavin King */ public interface PostInsertIdentifierGenerator extends IdentifierGenerator { - public Serializable getGenerated(SessionImplementor session, Object object, PostInsertIdentityPersister persister) - throws HibernateException; + public InsertGeneratedIdentifierDelegate getInsertGeneratedIdentifierDelegate( + PostInsertIdentityPersister persister, + Dialect dialect, + boolean isGetGeneratedKeysEnabled) throws HibernateException; } Modified: trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentityPersister.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentityPersister.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/PostInsertIdentityPersister.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -6,12 +6,32 @@ /** * A persister that may have an identity assigned by execution of * a SQL <tt>INSERT</tt>. + * * @author Gavin King */ public interface PostInsertIdentityPersister extends EntityPersister { - + /** + * Get a SQL select string that performs a select based on a unique + * key determined by the given property name). + * + * @param propertyName The name of the property which maps to the + * column(s) to use in the select statement restriction. + * @return The SQL select string + */ public String getSelectByUniqueKeyString(String propertyName); + + /** + * Get the database-specific SQL command to retrieve the last + * generated IDENTITY value. + * + * @return The SQL command string + */ public String getIdentitySelectString(); + + /** + * The names of the primary key columns in the root table. + * + * @return The primary key column names. + */ public String[] getRootTableKeyColumnNames(); - } Modified: trunk/Hibernate3/src/org/hibernate/id/SelectGenerator.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/SelectGenerator.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/SelectGenerator.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -8,92 +8,129 @@ import java.util.Properties; import org.hibernate.MappingException; +import org.hibernate.HibernateException; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.id.insert.IdentifierGeneratingInsert; +import org.hibernate.id.insert.AbstractSelectingDelegate; import org.hibernate.dialect.Dialect; import org.hibernate.engine.SessionImplementor; import org.hibernate.type.Type; /** - * An IdentityGenerator that selects the inserted row, to determine - * an identifier value assigned by the database. The correct row is - * located using a unique key. - * <br><br> - * One mapping parameter is required: key. + * A generator that selects the just inserted row to determine the identifier + * value assigned by the database. The correct row is located using a unique + * key. + * <p/> + * One mapping parameter is required: key (unless a natural-id is defined in the mapping). * * @author Gavin King */ public class SelectGenerator extends AbstractPostInsertGenerator implements Configurable { private String uniqueKeyPropertyName; - private Type idType; - private String entityName; public void configure(Type type, Properties params, Dialect d) throws MappingException { - uniqueKeyPropertyName = params.getProperty("key"); - entityName = params.getProperty(ENTITY_NAME); - this.idType = type; + uniqueKeyPropertyName = params.getProperty( "key" ); } - - protected String getSQL(PostInsertIdentityPersister persister) { - if ( entityName == null ) { - entityName = persister.getEntityName(); + + public InsertGeneratedIdentifierDelegate getInsertGeneratedIdentifierDelegate( + PostInsertIdentityPersister persister, + Dialect dialect, + boolean isGetGeneratedKeysEnabled) throws HibernateException { + return new SelectGeneratorDelegate( persister, dialect, uniqueKeyPropertyName ); + } + + private static String determineNameOfPropertyToUse(PostInsertIdentityPersister persister, String supplied) { + if ( supplied != null ) { + return supplied; } - if ( uniqueKeyPropertyName == null ) { - int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties(); - if ( naturalIdPropertyIndices == null ){ - throw new IdentifierGenerationException( - "no natural-id property defined; need to specify [key] in " + - "generator parameters" - ); - } - if ( naturalIdPropertyIndices.length > 1 ) { - throw new IdentifierGenerationException( - "select generator does not currently support composite " + - "natural-id properties; need to specify [key] in generator parameters" - ); - } - if ( persister.getPropertyInsertGeneration() [ naturalIdPropertyIndices[0] ] ) { - throw new IdentifierGenerationException( - "natural-id also defined as insert-generated; need to specify [key] " + - "in generator parameters" - ); - } - uniqueKeyPropertyName = persister.getPropertyNames() [ naturalIdPropertyIndices[0] ]; + int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties(); + if ( naturalIdPropertyIndices == null ){ + throw new IdentifierGenerationException( + "no natural-id property defined; need to specify [key] in " + + "generator parameters" + ); } - return persister.getSelectByUniqueKeyString( uniqueKeyPropertyName ); + if ( naturalIdPropertyIndices.length > 1 ) { + throw new IdentifierGenerationException( + "select generator does not currently support composite " + + "natural-id properties; need to specify [key] in generator parameters" + ); + } + if ( persister.getPropertyInsertGeneration() [ naturalIdPropertyIndices[0] ] ) { + throw new IdentifierGenerationException( + "natural-id also defined as insert-generated; need to specify [key] " + + "in generator parameters" + ); + } + return persister.getPropertyNames() [ naturalIdPropertyIndices[0] ]; } - protected void bindParameters( - SessionImplementor session, - PreparedStatement ps, - Object object, - PostInsertIdentityPersister persister) throws SQLException { - Type uniqueKeyPropertyType = session.getFactory() - .getClassMetadata( entityName ) - .getPropertyType( uniqueKeyPropertyName ); - Object uniqueKeyValue = persister.getPropertyValue( - object, - uniqueKeyPropertyName, - session.getEntityMode() - ); - uniqueKeyPropertyType.nullSafeSet( ps, uniqueKeyValue, 1, session ); - } - protected Serializable getResult( - SessionImplementor session, - ResultSet rs, - Object object, - PostInsertIdentityPersister persister) throws SQLException { - if ( !rs.next() ) { - throw new IdentifierGenerationException( - "the inserted row could not be located by the unique key: " + - uniqueKeyPropertyName + /** + * The delegate for the select generation strategy. + */ + public static class SelectGeneratorDelegate + extends AbstractSelectingDelegate + implements InsertGeneratedIdentifierDelegate { + private final PostInsertIdentityPersister persister; + private final Dialect dialect; + + private final String uniqueKeyPropertyName; + private final Type uniqueKeyType; + private final Type idType; + + private final String idSelectString; + + private SelectGeneratorDelegate( + PostInsertIdentityPersister persister, + Dialect dialect, + String suppliedUniqueKeyPropertyName) { + super( persister ); + this.persister = persister; + this.dialect = dialect; + this.uniqueKeyPropertyName = determineNameOfPropertyToUse( persister, suppliedUniqueKeyPropertyName ); + + idSelectString = persister.getSelectByUniqueKeyString( uniqueKeyPropertyName ); + uniqueKeyType = persister.getPropertyType( uniqueKeyPropertyName ); + idType = persister.getIdentifierType(); + } + + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert() { + return new IdentifierGeneratingInsert( dialect ); + } + + + // AbstractSelectingDelegate impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + protected String getSelectSQL() { + return idSelectString; + } + + protected void bindParameters( + SessionImplementor session, + PreparedStatement ps, + Object entity) throws SQLException { + Object uniqueKeyValue = persister.getPropertyValue( entity, uniqueKeyPropertyName, session.getEntityMode() ); + uniqueKeyType.nullSafeSet( ps, uniqueKeyValue, 1, session ); + } + + protected Serializable getResult( + SessionImplementor session, + ResultSet rs, + Object entity) throws SQLException { + if ( !rs.next() ) { + throw new IdentifierGenerationException( + "the inserted row could not be located by the unique key: " + + uniqueKeyPropertyName + ); + } + return ( Serializable ) idType.nullSafeGet( + rs, + persister.getRootTableKeyColumnNames(), + session, + entity ); } - return ( Serializable ) idType.nullSafeGet( - rs, - persister.getRootTableKeyColumnNames(), - session, - object - ); } } Added: trunk/Hibernate3/src/org/hibernate/id/insert/AbstractReturningDelegate.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/insert/AbstractReturningDelegate.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/insert/AbstractReturningDelegate.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -0,0 +1,52 @@ +package org.hibernate.id.insert; + +import org.hibernate.id.PostInsertIdentityPersister; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.exception.JDBCExceptionHelper; +import org.hibernate.pretty.MessageHelper; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Abstract InsertGeneratedIdentifierDelegate implementation where the + * underlying strategy causes the enerated identitifer to be returned as an + * effect of performing the insert statement. Thus, there is no need for an + * additional sql statement to determine the generated identitifer. + * + * @author Steve Ebersole + */ +public abstract class AbstractReturningDelegate implements InsertGeneratedIdentifierDelegate { + private final PostInsertIdentityPersister persister; + + public AbstractReturningDelegate(PostInsertIdentityPersister persister) { + this.persister = persister; + } + + public final Serializable performInsert(String insertSQL, SessionImplementor session, Binder binder) { + try { + // prepare and execute the insert + PreparedStatement insert = prepare( insertSQL, session ); + try { + binder.bindValues( insert ); + return executeAndExtract( insert ); + } + finally { + session.getBatcher().closeStatement( insert ); + } + } + catch ( SQLException sqle ) { + throw JDBCExceptionHelper.convert( + session.getFactory().getSQLExceptionConverter(), + sqle, + "could not insert: " + MessageHelper.infoString( persister ), + insertSQL + ); + } + } + + protected abstract PreparedStatement prepare(String insertSQL, SessionImplementor session) throws SQLException; + + protected abstract Serializable executeAndExtract(PreparedStatement insert) throws SQLException; +} Added: trunk/Hibernate3/src/org/hibernate/id/insert/AbstractSelectingDelegate.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/insert/AbstractSelectingDelegate.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/insert/AbstractSelectingDelegate.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -0,0 +1,113 @@ +package org.hibernate.id.insert; + +import org.hibernate.exception.JDBCExceptionHelper; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.id.PostInsertIdentityPersister; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.ResultSet; +import java.io.Serializable; + +/** + * Abstract InsertGeneratedIdentifierDelegate implementation where the + * underlying strategy requires an subsequent select after the insert + * to determine the generated identifier. + * + * @author Steve Ebersole + */ +public abstract class AbstractSelectingDelegate implements InsertGeneratedIdentifierDelegate { + private final PostInsertIdentityPersister persister; + + protected AbstractSelectingDelegate(PostInsertIdentityPersister persister) { + this.persister = persister; + } + + public final Serializable performInsert(String insertSQL, SessionImplementor session, Binder binder) { + try { + // prepare and execute the insert + PreparedStatement insert = session.getBatcher().prepareStatement( insertSQL, false ); + try { + binder.bindValues( insert ); + insert.executeUpdate(); + } + finally { + session.getBatcher().closeStatement( insert ); + } + } + catch ( SQLException sqle ) { + throw JDBCExceptionHelper.convert( + session.getFactory().getSQLExceptionConverter(), + sqle, + "could not insert: " + MessageHelper.infoString( persister ), + insertSQL + ); + } + + final String selectSQL = getSelectSQL(); + + try { + //fetch the generated id in a separate query + PreparedStatement idSelect = session.getBatcher().prepareStatement( selectSQL ); + try { + bindParameters( session, idSelect, binder.getEntity() ); + ResultSet rs = idSelect.executeQuery(); + try { + return getResult( session, rs, binder.getEntity() ); + } + finally { + rs.close(); + } + } + finally { + session.getBatcher().closeStatement( idSelect ); + } + + } + catch ( SQLException sqle ) { + throw JDBCExceptionHelper.convert( + session.getFactory().getSQLExceptionConverter(), + sqle, + "could not retrieve generated id after insert: " + MessageHelper.infoString( persister ), + insertSQL + ); + } + } + + /** + * Get the SQL statement to be used to retrieve generated key values. + * + * @return The SQL command string + */ + protected abstract String getSelectSQL(); + + /** + * Bind any required parameter values into the SQL command {@link #getSelectSQL}. + * + * @param session The session + * @param ps The prepared {@link #getSelectSQL SQL} command + * @param entity The entity being saved. + * @throws SQLException + */ + protected void bindParameters( + SessionImplementor session, + PreparedStatement ps, + Object entity) throws SQLException { + } + + /** + * Extract the generated key value from the given result set. + * + * @param session The session + * @param rs The result set containing the generated primay key values. + * @param entity The entity being saved. + * @return The generated identifier + * @throws SQLException + */ + protected abstract Serializable getResult( + SessionImplementor session, + ResultSet rs, + Object entity) throws SQLException; + +} Added: trunk/Hibernate3/src/org/hibernate/id/insert/Binder.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/insert/Binder.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/insert/Binder.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -0,0 +1,12 @@ +package org.hibernate.id.insert; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @author Steve Ebersole + */ +public interface Binder { + public void bindValues(PreparedStatement ps) throws SQLException; + public Object getEntity(); +} Added: trunk/Hibernate3/src/org/hibernate/id/insert/IdentifierGeneratingInsert.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/insert/IdentifierGeneratingInsert.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/insert/IdentifierGeneratingInsert.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -0,0 +1,17 @@ +package org.hibernate.id.insert; + +import org.hibernate.sql.Insert; +import org.hibernate.dialect.Dialect; + +/** + * Nothing more than a distinguishing subclass of Insert used to indicate + * intent. Some subclasses of this also provided some additional + * functionality or semantic to the genernated SQL statement string. + * + * @author Steve Ebersole + */ +public class IdentifierGeneratingInsert extends Insert { + public IdentifierGeneratingInsert(Dialect dialect) { + super( dialect ); + } +} Added: trunk/Hibernate3/src/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/insert/InsertGeneratedIdentifierDelegate.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -0,0 +1,39 @@ +package org.hibernate.id.insert; + +import org.hibernate.engine.SessionImplementor; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Responsible for handling delegation relating to variants in how + * insert-generated-identifier generator strategies dictate processing:<ul> + * <li>building the sql insert statement + * <li>determination of the generated identifier value + * </ul> + * + * @author Steve Ebersole + */ +public interface InsertGeneratedIdentifierDelegate { + + /** + * Build a {@link org.hibernate.sql.Insert} specific to the delegate's mode + * of handling generated key values. + * + * @return The insert object. + */ + public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(); + + /** + * Perform the indicated insert SQL statement and determine the identifier value + * generated. + * + * @param insertSQL + * @param session + * @param binder + * @return The generated identifier value. + */ + public Serializable performInsert(String insertSQL, SessionImplementor session, Binder binder); + +} Added: trunk/Hibernate3/src/org/hibernate/id/insert/InsertSelectIdentityInsert.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/id/insert/InsertSelectIdentityInsert.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/id/insert/InsertSelectIdentityInsert.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -0,0 +1,20 @@ +package org.hibernate.id.insert; + +import org.hibernate.dialect.Dialect; + +/** + * Specialized IdentifierGeneratingInsert which appends the database + * specific clause which signifies to return generated IDENTITY values + * to the end of the insert statement. + * + * @author Steve Ebersole + */ +public class InsertSelectIdentityInsert extends IdentifierGeneratingInsert { + public InsertSelectIdentityInsert(Dialect dialect) { + super( dialect ); + } + + public String toStatementString() { + return getDialect().appendIdentitySelectToInsert( super.toStatementString() ); + } +} Modified: trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -27,7 +27,6 @@ import org.hibernate.QueryException; import org.hibernate.StaleObjectStateException; import org.hibernate.dialect.lock.LockingStrategy; -import org.hibernate.dialect.lock.SelectLockingStrategy; import org.hibernate.cache.CacheConcurrencyStrategy; import org.hibernate.cache.CacheKey; import org.hibernate.cache.entry.CacheEntry; @@ -43,9 +42,10 @@ import org.hibernate.engine.Versioning; import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.id.IdentifierGenerator; -import org.hibernate.id.IdentifierGeneratorFactory; import org.hibernate.id.PostInsertIdentifierGenerator; import org.hibernate.id.PostInsertIdentityPersister; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.id.insert.Binder; import org.hibernate.intercept.LazyPropertyInitializer; import org.hibernate.intercept.FieldInterceptionHelper; import org.hibernate.intercept.FieldInterceptor; @@ -82,7 +82,6 @@ import org.hibernate.util.ArrayHelper; import org.hibernate.util.CollectionHelper; import org.hibernate.util.FilterHelper; -import org.hibernate.util.GetGeneratedKeysHelper; import org.hibernate.util.StringHelper; /** @@ -197,6 +196,8 @@ protected String[] customSQLUpdate; protected String[] customSQLDelete; + private InsertGeneratedIdentifierDelegate identityDelegate; + private boolean[] tableHasColumns; private final String loaderName; @@ -1289,93 +1290,6 @@ return getRootTableKeyColumnNames(); } -// /** -// * Generate the SQL that pessimistic locks a row by id (and version) -// */ -// protected String generateLockString(LockMode lockMode) { -// SimpleSelect select = new SimpleSelect( getFactory().getDialect() ) -// .setLockMode( lockMode ) -// .setTableName( getVersionedTableName() ) -// .addColumn( rootTableKeyColumnNames[0] ) -// .addCondition( rootTableKeyColumnNames, "=?" ); -// if ( isVersioned() ) { -// select.addCondition( getVersionColumnName(), "=?" ); -// } -// if ( getFactory().getSettings().isCommentsEnabled() ) { -// select.setComment( "lock " + getEntityName() ); -// } -// return select.toStatementString(); -// } -// -// protected void initLockers() { -// lockers.put( LockMode.READ, generateLockString( LockMode.READ ) ); -// lockers.put( LockMode.UPGRADE, generateLockString( LockMode.UPGRADE ) ); -// lockers.put( LockMode.UPGRADE_NOWAIT, generateLockString( LockMode.UPGRADE_NOWAIT ) ); -// } -// -// private String getLockString(LockMode lockMode) { -// return ( String ) lockers.get( lockMode ); -// } -// -// /** -// * Do a version check -// */ -// public void lock(Serializable id, Object version, Object object, LockMode lockMode, SessionImplementor session) -// throws HibernateException { -// -// if ( lockMode != LockMode.NONE ) { -// -// if ( log.isTraceEnabled() ) { -// log.trace( "Locking entity: " + MessageHelper.infoString( this, id, getFactory() ) ); -// if ( isVersioned() ) { -// log.trace( "Version: " + version ); -// } -// } -// -// final String sql = getLockString( lockMode ); -// -// try { -// -// PreparedStatement st = session.getBatcher().prepareSelectStatement( sql ); -// try { -// getIdentifierType().nullSafeSet( st, id, 1, session ); -// if ( isVersioned() ) { -// getVersionType().nullSafeSet( st, version, getIdentifierColumnSpan() + 1, session ); -// } -// -// ResultSet rs = st.executeQuery(); -// try { -// if ( !rs.next() ) { -// if ( getFactory().getStatistics().isStatisticsEnabled() ) { -// getFactory().getStatisticsImplementor() -// .optimisticFailure( getEntityName() ); -// } -// throw new StaleObjectStateException( getEntityName(), id ); -// } -// } -// finally { -// rs.close(); -// } -// } -// finally { -// session.getBatcher().closeStatement( st ); -// } -// -// } -// catch ( SQLException sqle ) { -// throw JDBCExceptionHelper.convert( -// getFactory().getSQLExceptionConverter(), -// sqle, -// "could not lock: " + -// MessageHelper.infoString( this, id, getFactory() ), -// sql -// ); -// } -// -// } -// -// } - public String[] toColumns(String alias, String propertyName) throws QueryException { return propertyMapping.toColumns( alias, propertyName ); } @@ -1872,6 +1786,9 @@ */ protected String generateInsertString(boolean identityInsert, boolean[] includeProperty, int j) { + // todo : remove the identityInsert param and variations; + // identity-insert strings are now generated from generateIdentityInsertString() + Insert insert = new Insert( getFactory().getDialect() ) .setTableName( getTableName( j ) ); @@ -1911,6 +1828,39 @@ } /** + * Used to generate an insery statement against the root table in the + * case of identifier generation strategies where the insert statement + * executions actually generates the identifier value. + * + * @param includeProperty indices of the properties to include in the + * insert statement. + * @return The insert SQL statement string + */ + protected String generateIdentityInsertString(boolean[] includeProperty) { + Insert insert = identityDelegate.prepareIdentifierGeneratingInsert(); + insert.setTableName( getTableName( 0 ) ); + + // add normal properties + for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { + if ( includeProperty[i] && isPropertyOfTable( i, 0 ) ) { + // this property belongs on the table and is to be inserted + insert.addColumns( getPropertyColumnNames(i), propertyColumnInsertable[i] ); + } + } + + // add the discriminator + addDiscriminatorToInsert( insert ); + + // delegate already handles PK columns + + if ( getFactory().getSettings().isCommentsEnabled() ) { + insert.setComment( "insert " + getEntityName() ); + } + + return insert.toStatementString(); + } + + /** * Generate the SQL that deletes a row by id (and version) */ protected String generateDeleteString(int j) { @@ -1933,24 +1883,23 @@ boolean[][] includeColumns, int j, PreparedStatement st, - SessionImplementor session) - throws HibernateException, SQLException { + SessionImplementor session) throws HibernateException, SQLException { return dehydrate( id, fields, null, includeProperty, includeColumns, j, st, session, 1 ); } /** * Marshall the fields of a persistent instance to a prepared statement */ - protected int dehydrate(final Serializable id, - final Object[] fields, - final Object rowId, - final boolean[] includeProperty, - final boolean[][] includeColumns, - final int j, - final PreparedStatement ps, - final SessionImplementor session, - int index) - throws SQLException, HibernateException { + protected int dehydrate( + final Serializable id, + final Object[] fields, + final Object rowId, + final boolean[] includeProperty, + final boolean[][] includeColumns, + final int j, + final PreparedStatement ps, + final SessionImplementor session, + int index) throws SQLException, HibernateException { if ( log.isTraceEnabled() ) { log.trace( "Dehydrating entity: " + MessageHelper.infoString( this, id, getFactory() ) ); @@ -1982,14 +1931,14 @@ * without resolving associations or collections. Question: should * this really be here, or should it be sent back to Loader? */ - public Object[] hydrate(final ResultSet rs, - final Serializable id, - final Object object, - final Loadable rootLoadable, - final String[][] suffixedPropertyColumns, - final boolean allProperties, - final SessionImplementor session) - throws SQLException, HibernateException { + public Object[] hydrate( + final ResultSet rs, + final Serializable id, + final Object object, + final Loadable rootLoadable, + final String[][] suffixedPropertyColumns, + final boolean allProperties, + final SessionImplementor session) throws SQLException, HibernateException { if ( log.isTraceEnabled() ) { log.trace( "Hydrating entity: " + MessageHelper.infoString( this, id, getFactory() ) ); @@ -2065,14 +2014,17 @@ } /** - * Perform an SQL INSERT, and then retrieve a generated identifier + * Perform an SQL INSERT, and then retrieve a generated identifier. + * <p/> + * This form is used for PostInsertIdentifierGenerator-style ids (IDENTITY, + * select, etc). */ - protected Serializable insert(final Object[] fields, - final boolean[] notNull, - String sql, - final Object object, - final SessionImplementor session) - throws HibernateException { + protected Serializable insert( + final Object[] fields, + final boolean[] notNull, + String sql, + final Object object, + final SessionImplementor session) throws HibernateException { if ( log.isTraceEnabled() ) { log.trace( "Inserting entity: " + getEntityName() + " (native id)" ); @@ -2081,57 +2033,15 @@ } } - try { - - //do the insert - PreparedStatement insert = session.getBatcher().prepareStatement( sql, useGetGeneratedKeys() ); - try { - dehydrate( null, fields, notNull, propertyColumnInsertable, 0, insert, session ); - - if ( useInsertSelectIdentity() ) { - if ( !insert.execute() ) { - while ( !insert.getMoreResults() && insert.getUpdateCount() != -1 ) { - continue; // Do nothing (but stop checkstyle from complaining). - } - } - //note early exit! - ResultSet rs = insert.getResultSet(); - try { - return IdentifierGeneratorFactory.getGeneratedIdentity( rs, getIdentifierType() ); - } - finally { - rs.close(); - } - } - else if ( useGetGeneratedKeys() ) { - insert.executeUpdate(); - //note early exit! - return IdentifierGeneratorFactory.getGeneratedIdentity( - GetGeneratedKeysHelper.getGeneratedKey(insert), - getIdentifierType() - ); - } - else { - insert.executeUpdate(); - } - + Binder binder = new Binder() { + public void bindValues(PreparedStatement ps) throws SQLException { + dehydrate( null, fields, notNull, propertyColumnInsertable, 0, ps, session ); } - finally { - session.getBatcher().closeStatement( insert ); + public Object getEntity() { + return object; } - - } - catch ( SQLException sqle ) { - throw JDBCExceptionHelper.convert( - getFactory().getSQLExceptionConverter(), - sqle, - "could not insert: " + MessageHelper.infoString(this), - sql - ); - } - - return ( (PostInsertIdentifierGenerator) getIdentifierGenerator() ).getGenerated(session, object, this); - + }; + return identityDelegate.performInsert( sql, session, binder ); } public String getIdentitySelectString() { @@ -2152,16 +2062,19 @@ } /** - * Perform an SQL INSERT + * Perform an SQL INSERT. + * <p/> + * This for is used for all non-root tables as well as the root table + * in cases where the identifier value is known before the insert occurs. */ - protected void insert(final Serializable id, - final Object[] fields, - final boolean[] notNull, - final int j, - final String sql, - final Object object, - final SessionImplementor session) - throws HibernateException { + protected void insert( + final Serializable id, + final Object[] fields, + final boolean[] notNull, + final int j, + final String sql, + final Object object, + final SessionImplementor session) throws HibernateException { if ( isInverseTable( j ) ) { return; @@ -2237,17 +2150,17 @@ /** * Perform an SQL UPDATE or SQL INSERT */ - protected void updateOrInsert(final Serializable id, - final Object[] fields, - final Object[] oldFields, - final Object rowId, - final boolean[] includeProperty, - final int j, - final Object oldVersion, - final Object object, - final String sql, - final SessionImplementor session) - throws HibernateException { + protected void updateOrInsert( + final Serializable id, + final Object[] fields, + final Object[] oldFields, + final Object rowId, + final boolean[] includeProperty, + final int j, + final Object oldVersion, + final Object object, + final String sql, + final SessionImplementor session) throws HibernateException { if ( !isInverseTable( j ) ) { @@ -2278,17 +2191,17 @@ } - protected boolean update(final Serializable id, - final Object[] fields, - final Object[] oldFields, - final Object rowId, - final boolean[] includeProperty, - final int j, - final Object oldVersion, - final Object object, - final String sql, - final SessionImplementor session) - throws HibernateException { + protected boolean update( + final Serializable id, + final Object[] fields, + final Object[] oldFields, + final Object rowId, + final boolean[] includeProperty, + final int j, + final Object oldVersion, + final Object object, + final String sql, + final SessionImplementor session) throws HibernateException { final boolean useVersion = j == 0 && isVersioned(); final boolean callable = isUpdateCallable( j ); @@ -2386,13 +2299,13 @@ /** * Perform an SQL DELETE */ - protected void delete(final Serializable id, - final Object version, - final int j, - final Object object, - final String sql, - final SessionImplementor session) - throws HibernateException { + protected void delete( + final Serializable id, + final Object version, + final int j, + final Object object, + final String sql, + final SessionImplementor session) throws HibernateException { if ( isInverseTable( j ) ) { return; @@ -2492,16 +2405,16 @@ /** * Update an object */ - public void update(final Serializable id, - final Object[] fields, - final int[] dirtyFields, - final boolean hasDirtyCollection, - final Object[] oldFields, - final Object oldVersion, - final Object object, - final Object rowId, - final SessionImplementor session) - throws HibernateException { + public void update( + final Serializable id, + final Object[] fields, + final int[] dirtyFields, + final boolean hasDirtyCollection, + final Object[] oldFields, + final Object oldVersion, + final Object object, + final Object rowId, + final SessionImplementor session) throws HibernateException { //note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-before-update // oldFields==null just means we had no snapshot to begin with (we might have used select-before-update to get the dirtyFields) @@ -2747,9 +2660,10 @@ StringHelper.qualify( alias, getSubclassTableKeyColumns( tableNumber ) ) ) + "=?"; } - protected String renderSelect(final int[] tableNumbers, - final int[] columnNumbers, - final int[] formulaNumbers) { + protected String renderSelect( + final int[] tableNumbers, + final int[] columnNumbers, + final int[] formulaNumbers) { Arrays.sort( tableNumbers ); //get 'em in the right order (not that it really matters) @@ -2829,9 +2743,9 @@ sqlUpdateGeneratedValuesSelectString = generateUpdateGeneratedValuesSelectString(); } if ( isIdentifierAssignedByInsert() ) { - sqlIdentityInsertString = customSQLInsert[0] == null ? - generateInsertString( true, getPropertyInsertability() ) : - customSQLInsert[0]; + identityDelegate = ( ( PostInsertIdentifierGenerator ) getIdentifierGenerator() ) + .getInsertGeneratedIdentifierDelegate( this, getFactory().getDialect(), useGetGeneratedKeys() ); + sqlIdentityInsertString = generateIdentityInsertString( getPropertyInsertability() ); } else { sqlIdentityInsertString = null; Modified: trunk/Hibernate3/src/org/hibernate/sql/Insert.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/sql/Insert.java 2006-03-22 23:47:31 UTC (rev 9680) +++ trunk/Hibernate3/src/org/hibernate/sql/Insert.java 2006-03-24 18:10:04 UTC (rev 9681) @@ -14,22 +14,24 @@ * @author Gavin King */ public class Insert { + private Dialect dialect; + private String tableName; + private String comment; + private Map columns = new SequencedHashMap(); public Insert(Dialect dialect) { this.dialect = dialect; } - private String comment; + protected Dialect getDialect() { + return dialect; + } + public Insert setComment(String comment) { this.comment = comment; return this; } - private Dialect dialect; - private String tableName; - - private Map columns = new SequencedHashMap(); - public Insert addColumn(String columnName) { return addColumn(columnName, "?"); } @@ -43,7 +45,9 @@ public Insert addColumns(String[] columnNames, boolean[] insertable) { for ( int i=0; i<columnNames.length; i++ ) { - if ( insertable[i] ) addColumn( columnNames[i] ); + if ( insertable[i] ) { + addColumn( columnNames[i] ); + } } return this; } @@ -59,7 +63,9 @@ public Insert addIdentityColumn(String columnName) { String value = dialect.getIdentityInsertString(); - if (value!=null) addColumn(columnName, value); + if ( value != null ) { + addColumn( columnName, value ); + } return this; } @@ -70,7 +76,9 @@ public String toStatementString() { StringBuffer buf = new StringBuffer( columns.size()*15 + tableName.length() + 10 ); - if (comment!=null) buf.append("/* ").append(comment).append(" */ "); + if ( comment != null ) { + buf.append( "/* " ).append( comment ).append( " */ " ); + } buf.append("insert into ") .append(tableName); if ( columns.size()==0 ) { @@ -81,13 +89,17 @@ Iterator iter = columns.keySet().iterator(); while ( iter.hasNext() ) { buf.append( iter.next() ); - if ( iter.hasNext() ) buf.append(", "); + if ( iter.hasNext() ) { + buf.append( ", " ); + } } buf.append(") values ("); iter = columns.values().iterator(); while ( iter.hasNext() ) { buf.append( iter.next() ); - if ( iter.hasNext() ) buf.append(", "); + if ( iter.hasNext() ) { + buf.append( ", " ); + } } buf.append(')'); } |