diff --git a/hibernate-core/src/main/java/org/hibernate/engine/TransactionHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/TransactionHelper.java deleted file mode 100644 index d820594a9a..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/engine/TransactionHelper.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as - * indicated by the @author tags or express copyright attribution - * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Inc. - * - * This copyrighted material is made available to anyone wishing to use, modify, - * copy, or redistribute it subject to the terms and conditions of the GNU - * Lesser General Public License, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this distribution; if not, write to: - * Free Software Foundation, Inc. - * 51 Franklin Street, Fifth Floor - * Boston, MA 02110-1301 USA - */ -package org.hibernate.engine; - -import org.hibernate.HibernateException; -import org.hibernate.jdbc.Work; - -import java.io.Serializable; -import java.sql.Connection; -import java.sql.SQLException; - -/** - * Allows work to be done outside the current transaction, by suspending it, - * and performing work in a new transaction - * - * @author Emmanuel Bernard - */ -public abstract class TransactionHelper { - - // todo : remove this and just have subclasses use IsolationDelegate directly... - - /** - * The work to be done - */ - protected abstract Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException; - - /** - * Suspend the current transaction and perform work in a new transaction - */ - public Serializable doWorkInNewTransaction(final SessionImplementor session) throws HibernateException { - class WorkToDo implements Work { - Serializable generatedValue; - - @Override - public void execute(Connection connection) throws SQLException { - String sql = null; - try { - generatedValue = doWorkInCurrentTransaction( connection, sql ); - } - catch( SQLException e ) { - throw session.getFactory().getSQLExceptionHelper().convert( - e, - "could not get or update next value", - sql - ); - } - } - } - WorkToDo work = new WorkToDo(); - session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork( work, true ); - return work.generatedValue; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java index 241530c1a6..dbd92ad62b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java @@ -36,6 +36,7 @@ import org.hibernate.engine.transaction.internal.TransactionCoordinatorImpl; import org.hibernate.engine.transaction.spi.TransactionContext; import org.hibernate.engine.transaction.spi.TransactionCoordinator; import org.hibernate.engine.transaction.spi.TransactionEnvironment; +import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -204,6 +205,29 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { } } + @Override + public T coordinateWork(ReturningWork work) { + Connection connection = getLogicalConnection().getDistinctConnectionProxy(); + try { + T result = work.execute( connection ); + getLogicalConnection().afterStatementExecution(); + return result; + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "error executing work" ); + } + finally { + try { + if ( ! connection.isClosed() ) { + connection.close(); + } + } + catch (SQLException e) { + log.debug( "Error closing connection proxy", e ); + } + } + } + public void executeBatch() { if ( currentBatch != null ) { currentBatch.execute(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java index d1d9a143de..b693081470 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcCoordinator.java @@ -26,6 +26,8 @@ package org.hibernate.engine.jdbc.spi; import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.batch.spi.BatchKey; import org.hibernate.engine.transaction.spi.TransactionCoordinator; +import org.hibernate.id.IntegralDataTypeHolder; +import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; import java.io.Serializable; @@ -88,9 +90,12 @@ public interface JdbcCoordinator extends Serializable { public void coordinateWork(Work work); + public T coordinateWork(ReturningWork work); + public void executeBatch(); public void cancelLastQuery(); public void setTransactionTimeOut(int timeout); + } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SQLStatementLogger.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SQLStatementLogger.java index 141c14ce79..5be6dd39ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SQLStatementLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SQLStatementLogger.java @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.hibernate.jdbc.util.FormatStyle; +import org.hibernate.jdbc.util.Formatter; /** * Centralize logging for SQL statements. @@ -90,9 +91,13 @@ public class SQLStatementLogger { */ public void logStatement(String statement) { // for now just assume a DML log for formatting + logStatement( statement, FormatStyle.BASIC.getFormatter() ); + } + + public void logStatement(String statement, Formatter formatter) { if ( format ) { if ( logToStdout || log.isDebugEnabled() ) { - statement = FormatStyle.BASIC.getFormatter().format( statement ); + statement = formatter.format( statement ); } } log.debug( statement ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java index 01a4e611d2..f00fbf55ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jdbc/JdbcIsolationDelegate.java @@ -34,6 +34,7 @@ import org.hibernate.HibernateException; import org.hibernate.engine.jdbc.spi.SQLExceptionHelper; import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.engine.transaction.spi.TransactionCoordinator; +import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; @@ -120,4 +121,68 @@ public class JdbcIsolationDelegate implements IsolationDelegate { throw sqlExceptionHelper().convert( sqle, "unable to obtain isolated JDBC connection" ); } } + + @Override + public T delegateWork(ReturningWork work, boolean transacted) throws HibernateException { + boolean wasAutoCommit = false; + try { + // todo : should we use a connection proxy here? + Connection connection = connectionProvider().getConnection(); + try { + if ( transacted ) { + if ( connection.getAutoCommit() ) { + wasAutoCommit = true; + connection.setAutoCommit( false ); + } + } + + T result = work.execute( connection ); + + if ( transacted ) { + connection.commit(); + } + + return result; + } + catch ( Exception e ) { + try { + if ( transacted && !connection.isClosed() ) { + connection.rollback(); + } + } + catch ( Exception ignore ) { + log.info( "unable to rollback connection on exception [" + ignore + "]" ); + } + + if ( e instanceof HibernateException ) { + throw (HibernateException) e; + } + else if ( e instanceof SQLException ) { + throw sqlExceptionHelper().convert( (SQLException) e, "error performing isolated work" ); + } + else { + throw new HibernateException( "error performing isolated work", e ); + } + } + finally { + if ( transacted && wasAutoCommit ) { + try { + connection.setAutoCommit( true ); + } + catch ( Exception ignore ) { + log.trace( "was unable to reset connection back to auto-commit" ); + } + } + try { + connectionProvider().closeConnection( connection ); + } + catch ( Exception ignore ) { + log.info( "Unable to release isolated connection [" + ignore + "]" ); + } + } + } + catch ( SQLException sqle ) { + throw sqlExceptionHelper().convert( sqle, "unable to obtain isolated JDBC connection" ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jta/JtaIsolationDelegate.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jta/JtaIsolationDelegate.java index 7e071428fe..42bcfa9966 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jta/JtaIsolationDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/internal/jta/JtaIsolationDelegate.java @@ -37,6 +37,7 @@ import org.hibernate.HibernateException; import org.hibernate.engine.jdbc.spi.SQLExceptionHelper; import org.hibernate.engine.transaction.spi.IsolationDelegate; import org.hibernate.engine.transaction.spi.TransactionCoordinator; +import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; @@ -181,5 +182,113 @@ public class JtaIsolationDelegate implements IsolationDelegate { } } + @Override + public T delegateWork(ReturningWork work, boolean transacted) throws HibernateException { + TransactionManager transactionManager = transactionManager(); + + try { + // First we suspend any current JTA transaction + Transaction surroundingTransaction = transactionManager.suspend(); + if ( log.isDebugEnabled() ) { + log.debug( "surrounding JTA transaction suspended [" + surroundingTransaction + "]" ); + } + + boolean hadProblems = false; + try { + // then perform the requested work + if ( transacted ) { + return doTheWorkInNewTransaction( work, transactionManager ); + } + else { + return doTheWorkInNoTransaction( work ); + } + } + catch ( HibernateException e ) { + hadProblems = true; + throw e; + } + finally { + try { + transactionManager.resume( surroundingTransaction ); + if ( log.isDebugEnabled() ) { + log.debug( "surrounding JTA transaction resumed [" + surroundingTransaction + "]" ); + } + } + catch( Throwable t ) { + // if the actually work had an error use that, otherwise error based on t + if ( !hadProblems ) { + //noinspection ThrowFromFinallyBlock + throw new HibernateException( "Unable to resume previously suspended transaction", t ); + } + } + } + } + catch ( SystemException e ) { + throw new HibernateException( "Unable to suspend current JTA transaction", e ); + } + } + + private T doTheWorkInNewTransaction(ReturningWork work, TransactionManager transactionManager) { + T result = null; + try { + // start the new isolated transaction + transactionManager.begin(); + + try { + result = doTheWork( work ); + // if everything went ok, commit the isolated transaction + transactionManager.commit(); + } + catch ( Exception e ) { + try { + transactionManager.rollback(); + } + catch ( Exception ignore ) { + log.info( "Unable to rollback isolated transaction on error [" + e + "] : [" + ignore + "]" ); + } + } + } + catch ( SystemException e ) { + throw new HibernateException( "Unable to start isolated transaction", e ); + } + catch ( NotSupportedException e ) { + throw new HibernateException( "Unable to start isolated transaction", e ); + } + return result; + } + + private T doTheWorkInNoTransaction(ReturningWork work) { + return doTheWork( work ); + } + + private T doTheWork(ReturningWork work) { + try { + // obtain our isolated connection + Connection connection = connectionProvider().getConnection(); + try { + // do the actual work + return work.execute( connection ); + } + catch ( HibernateException e ) { + throw e; + } + catch ( Exception e ) { + throw new HibernateException( "Unable to perform isolated work", e ); + } + finally { + try { + // no matter what, release the connection (handle) + connectionProvider().closeConnection( connection ); + } + catch ( Throwable ignore ) { + log.info( "Unable to release isolated connection [" + ignore + "]" ); + } + } + } + catch ( SQLException e ) { + throw sqlExceptionHelper().convert( e, "unable to obtain isolated JDBC connection" ); + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/IsolationDelegate.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/IsolationDelegate.java index 68935e95f4..e744b0d00a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/IsolationDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/spi/IsolationDelegate.java @@ -24,6 +24,7 @@ package org.hibernate.engine.transaction.spi; import org.hibernate.HibernateException; +import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; /** @@ -41,4 +42,16 @@ public interface IsolationDelegate { * @throws HibernateException Indicates a problem performing the work. */ public void delegateWork(Work work, boolean transacted) throws HibernateException; + + /** + * Perform the given work in isolation from current transaction. + * + * @param work The work to be performed. + * @param transacted Should the work itself be done in a (isolated) transaction? + * + * @return The work result + * + * @throws HibernateException Indicates a problem performing the work. + */ + public T delegateWork(ReturningWork work, boolean transacted) throws HibernateException; } diff --git a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java index bad7b90643..265c0ae216 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/MultipleHiLoPerTableGenerator.java @@ -23,6 +23,24 @@ */ package org.hibernate.id; +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.MappingException; +import org.hibernate.cfg.ObjectNameNormalizer; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.SQLStatementLogger; +import org.hibernate.id.enhanced.AccessCallback; +import org.hibernate.id.enhanced.OptimizerFactory; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.util.FormatStyle; +import org.hibernate.mapping.Table; +import org.hibernate.type.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; @@ -31,22 +49,6 @@ import java.sql.SQLException; import java.sql.Types; import java.util.Properties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.MappingException; -import org.hibernate.cfg.ObjectNameNormalizer; -import org.hibernate.id.enhanced.AccessCallback; -import org.hibernate.id.enhanced.OptimizerFactory; -import org.hibernate.internal.util.config.ConfigurationHelper; -import org.hibernate.jdbc.util.FormatStyle; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.SessionImplementor; -import org.hibernate.engine.TransactionHelper; -import org.hibernate.mapping.Table; -import org.hibernate.type.Type; - /** * * A hilo IdentifierGenerator that returns a Long, constructed using @@ -78,11 +80,8 @@ import org.hibernate.type.Type; * @author Emmanuel Bernard * @author Klaus Richarz. */ -public class MultipleHiLoPerTableGenerator - extends TransactionHelper - implements PersistentIdentifierGenerator, Configurable { - - private static final Logger log = LoggerFactory.getLogger(MultipleHiLoPerTableGenerator.class); +public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenerator, Configurable { + private static final Logger log = LoggerFactory.getLogger( MultipleHiLoPerTableGenerator.class ); public static final String ID_TABLE = "table"; public static final String PK_COLUMN_NAME = "primary_key_column"; @@ -146,66 +145,73 @@ public class MultipleHiLoPerTableGenerator return tableName; } - public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException { - IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( returnClass ); - int rows; - do { - SQL_STATEMENT_LOGGER.logStatement( query, FormatStyle.BASIC ); - PreparedStatement qps = conn.prepareStatement( query ); - PreparedStatement ips = null; - try { - ResultSet rs = qps.executeQuery(); - boolean isInitialized = rs.next(); - if ( !isInitialized ) { - value.initialize( 0 ); - SQL_STATEMENT_LOGGER.logStatement( insert, FormatStyle.BASIC ); - ips = conn.prepareStatement( insert ); - value.bind( ips, 1 ); - ips.execute(); - } - else { - value.initialize( rs, 0 ); - } - rs.close(); - } - catch (SQLException sqle) { - log.error("could not read or init a hi value", sqle); - throw sqle; - } - finally { - if (ips != null) { - ips.close(); - } - qps.close(); - } + public synchronized Serializable generate(final SessionImplementor session, Object obj) { + final ReturningWork work = new ReturningWork() { + @Override + public IntegralDataTypeHolder execute(Connection connection) throws SQLException { + IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( returnClass ); + SQLStatementLogger statementLogger = session + .getFactory() + .getServiceRegistry() + .getService( JdbcServices.class ) + .getSqlStatementLogger(); + int rows; + do { + statementLogger.logStatement( query, FormatStyle.BASIC.getFormatter() ); + PreparedStatement qps = connection.prepareStatement( query ); + PreparedStatement ips = null; + try { + ResultSet rs = qps.executeQuery(); + boolean isInitialized = rs.next(); + if ( !isInitialized ) { + value.initialize( 0 ); + statementLogger.logStatement( insert, FormatStyle.BASIC.getFormatter() ); + ips = connection.prepareStatement( insert ); + value.bind( ips, 1 ); + ips.execute(); + } + else { + value.initialize( rs, 0 ); + } + rs.close(); + } + catch (SQLException sqle) { + log.error("could not read or init a hi value", sqle); + throw sqle; + } + finally { + if (ips != null) { + ips.close(); + } + qps.close(); + } - SQL_STATEMENT_LOGGER.logStatement( update, FormatStyle.BASIC ); - PreparedStatement ups = conn.prepareStatement( update ); - try { - value.copy().increment().bind( ups, 1 ); - value.bind( ups, 2 ); - rows = ups.executeUpdate(); - } - catch (SQLException sqle) { - log.error("could not update hi value in: " + tableName, sqle); - throw sqle; - } - finally { - ups.close(); - } - } while ( rows==0 ); + statementLogger.logStatement( update, FormatStyle.BASIC.getFormatter() ); + PreparedStatement ups = connection.prepareStatement( update ); + try { + value.copy().increment().bind( ups, 1 ); + value.bind( ups, 2 ); + rows = ups.executeUpdate(); + } + catch (SQLException sqle) { + log.error("could not update hi value in: " + tableName, sqle); + throw sqle; + } + finally { + ups.close(); + } + } while ( rows==0 ); - return value; - } + return value; + } + }; - public synchronized Serializable generate(final SessionImplementor session, Object obj) - throws HibernateException { // maxLo < 1 indicates a hilo generator with no hilo :? if ( maxLo < 1 ) { //keep the behavior consistent even for boundary usages IntegralDataTypeHolder value = null; while ( value == null || value.lt( 1 ) ) { - value = (IntegralDataTypeHolder) doWorkInNewTransaction( session ); + value = session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork( work, true ); } return value.makeValue(); } @@ -213,7 +219,7 @@ public class MultipleHiLoPerTableGenerator return hiloOptimizer.generate( new AccessCallback() { public IntegralDataTypeHolder getNextValue() { - return (IntegralDataTypeHolder) doWorkInNewTransaction( session ); + return session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork( work, true ); } } ); diff --git a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java index 6a33796fba..44d4760607 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PersistentIdentifierGenerator.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -20,13 +20,11 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.id; import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; -import org.hibernate.jdbc.util.SQLStatementLogger; /** * An IdentifierGenerator that requires creation of database objects. @@ -99,8 +97,6 @@ public interface PersistentIdentifierGenerator extends IdentifierGenerator { */ public Object generatorKey(); - static final SQLStatementLogger SQL_STATEMENT_LOGGER = new SQLStatementLogger( false, false ); - } diff --git a/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java index 8cff01a2a3..1c8019b5df 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/TableGenerator.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -20,10 +20,24 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.id; +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.cfg.ObjectNameNormalizer; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.SQLStatementLogger; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.util.FormatStyle; +import org.hibernate.mapping.Table; +import org.hibernate.type.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; @@ -32,19 +46,6 @@ import java.sql.SQLException; import java.sql.Types; import java.util.Properties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.cfg.ObjectNameNormalizer; -import org.hibernate.internal.util.config.ConfigurationHelper; -import org.hibernate.jdbc.util.FormatStyle; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.SessionImplementor; -import org.hibernate.engine.TransactionHelper; -import org.hibernate.mapping.Table; -import org.hibernate.type.Type; - /** * An IdentifierGenerator that uses a database * table to store the last generated value. It is not @@ -70,8 +71,7 @@ import org.hibernate.type.Type; * @see TableHiLoGenerator * @author Gavin King */ -public class TableGenerator extends TransactionHelper - implements PersistentIdentifierGenerator, Configurable { +public class TableGenerator implements PersistentIdentifierGenerator, Configurable { /* COLUMN and TABLE should be renamed but it would break the public API */ /** The column parameter */ public static final String COLUMN = "column"; @@ -139,7 +139,63 @@ public class TableGenerator extends TransactionHelper } protected IntegralDataTypeHolder generateHolder(SessionImplementor session) { - return (IntegralDataTypeHolder) doWorkInNewTransaction( session ); + final SQLStatementLogger statementLogger = session + .getFactory() + .getServiceRegistry() + .getService( JdbcServices.class ) + .getSqlStatementLogger(); + return session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork( + new ReturningWork() { + @Override + public IntegralDataTypeHolder execute(Connection connection) throws SQLException { + IntegralDataTypeHolder value = buildHolder(); + int rows; + do { + // The loop ensures atomicity of the + // select + update even for no transaction + // or read committed isolation level + + statementLogger.logStatement( query, FormatStyle.BASIC.getFormatter() ); + PreparedStatement qps = connection.prepareStatement( query ); + try { + ResultSet rs = qps.executeQuery(); + if ( !rs.next() ) { + String err = "could not read a hi value - you need to populate the table: " + tableName; + log.error(err); + throw new IdentifierGenerationException(err); + } + value.initialize( rs, 1 ); + rs.close(); + } + catch (SQLException e) { + log.error("could not read a hi value", e); + throw e; + } + finally { + qps.close(); + } + + statementLogger.logStatement( update, FormatStyle.BASIC.getFormatter() ); + PreparedStatement ups = connection.prepareStatement(update); + try { + value.copy().increment().bind( ups, 1 ); + value.bind( ups, 2 ); + rows = ups.executeUpdate(); + } + catch (SQLException sqle) { + log.error("could not update hi value in: " + tableName, sqle); + throw sqle; + } + finally { + ups.close(); + } + } + while (rows==0); + return value; + } + }, + true + ); } public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { @@ -165,66 +221,6 @@ public class TableGenerator extends TransactionHelper return tableName; } - /** - * Get the next value. - * - * @param conn The sql connection to use. - * @param sql n/a - * - * @return Prior to 3.5 this method returned an {@link Integer}. Since 3.5 it now - * returns a {@link IntegralDataTypeHolder} - * - * @throws SQLException - */ - public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException { - IntegralDataTypeHolder value = buildHolder(); - int rows; - do { - // The loop ensures atomicity of the - // select + update even for no transaction - // or read committed isolation level - - sql = query; - SQL_STATEMENT_LOGGER.logStatement( sql, FormatStyle.BASIC ); - PreparedStatement qps = conn.prepareStatement(query); - try { - ResultSet rs = qps.executeQuery(); - if ( !rs.next() ) { - String err = "could not read a hi value - you need to populate the table: " + tableName; - log.error(err); - throw new IdentifierGenerationException(err); - } - value.initialize( rs, 1 ); - rs.close(); - } - catch (SQLException sqle) { - log.error("could not read a hi value", sqle); - throw sqle; - } - finally { - qps.close(); - } - - sql = update; - SQL_STATEMENT_LOGGER.logStatement( sql, FormatStyle.BASIC ); - PreparedStatement ups = conn.prepareStatement(update); - try { - value.copy().increment().bind( ups, 1 ); - value.bind( ups, 2 ); - rows = ups.executeUpdate(); - } - catch (SQLException sqle) { - log.error("could not update hi value in: " + tableName, sqle); - throw sqle; - } - finally { - ups.close(); - } - } - while (rows==0); - return value; - } - protected IntegralDataTypeHolder buildHolder() { return IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java index a1d4db56b4..b2bc06083d 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java @@ -23,37 +23,38 @@ */ package org.hibernate.id.enhanced; -import java.sql.Types; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.Properties; -import java.util.Collections; -import java.util.Map; -import java.io.Serializable; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.MappingException; import org.hibernate.cfg.Environment; -import org.hibernate.engine.TransactionHelper; +import org.hibernate.cfg.ObjectNameNormalizer; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.SQLStatementLogger; +import org.hibernate.id.Configurable; import org.hibernate.id.IdentifierGeneratorHelper; import org.hibernate.id.IntegralDataTypeHolder; import org.hibernate.id.PersistentIdentifierGenerator; -import org.hibernate.id.Configurable; import org.hibernate.internal.util.config.ConfigurationHelper; -import org.hibernate.type.Type; -import org.hibernate.dialect.Dialect; -import org.hibernate.HibernateException; -import org.hibernate.MappingException; -import org.hibernate.LockOptions; -import org.hibernate.LockMode; -import org.hibernate.cfg.ObjectNameNormalizer; +import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.util.FormatStyle; import org.hibernate.mapping.Table; +import org.hibernate.type.Type; import org.hibernate.util.StringHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; /** * An enhanced version of table-based id generation. @@ -128,7 +129,7 @@ import org.hibernate.util.StringHelper; * * @author Steve Ebersole */ -public class TableGenerator extends TransactionHelper implements PersistentIdentifierGenerator, Configurable { +public class TableGenerator implements PersistentIdentifierGenerator, Configurable { private static final Logger log = LoggerFactory.getLogger( TableGenerator.class ); public static final String CONFIG_PREFER_SEGMENT_PER_ENTITY = "prefer_entity_table_as_segment_value"; @@ -176,9 +177,7 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent private Optimizer optimizer; private long accessCount = 0; - /** - * {@inheritDoc} - */ + @Override public Object generatorKey() { return tableName; } @@ -283,9 +282,7 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent return accessCount; } - /** - * {@inheritDoc} - */ + @Override public void configure(Type type, Properties params, Dialect dialect) throws MappingException { identifierType = type; @@ -456,93 +453,96 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent return "insert into " + tableName + " (" + segmentColumnName + ", " + valueColumnName + ") " + " values (?,?)"; } - /** - * {@inheritDoc} - */ + @Override public synchronized Serializable generate(final SessionImplementor session, Object obj) { + final SQLStatementLogger statementLogger = session + .getFactory() + .getServiceRegistry() + .getService( JdbcServices.class ) + .getSqlStatementLogger(); return optimizer.generate( new AccessCallback() { + @Override public IntegralDataTypeHolder getNextValue() { - return ( IntegralDataTypeHolder ) doWorkInNewTransaction( session ); + return session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork( + new ReturningWork() { + @Override + public IntegralDataTypeHolder execute(Connection connection) throws SQLException { + IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() ); + int rows; + do { + statementLogger.logStatement( selectQuery, FormatStyle.BASIC.getFormatter() ); + PreparedStatement selectPS = connection.prepareStatement( selectQuery ); + try { + selectPS.setString( 1, segmentValue ); + ResultSet selectRS = selectPS.executeQuery(); + if ( !selectRS.next() ) { + value.initialize( initialValue ); + PreparedStatement insertPS = null; + try { + statementLogger.logStatement( insertQuery, FormatStyle.BASIC.getFormatter() ); + insertPS = connection.prepareStatement( insertQuery ); + insertPS.setString( 1, segmentValue ); + value.bind( insertPS, 2 ); + insertPS.execute(); + } + finally { + if ( insertPS != null ) { + insertPS.close(); + } + } + } + else { + value.initialize( selectRS, 1 ); + } + selectRS.close(); + } + catch ( SQLException e ) { + log.error( "could not read or init a hi value", e ); + throw e; + } + finally { + selectPS.close(); + } + + statementLogger.logStatement( updateQuery, FormatStyle.BASIC.getFormatter() ); + PreparedStatement updatePS = connection.prepareStatement( updateQuery ); + try { + final IntegralDataTypeHolder updateValue = value.copy(); + if ( optimizer.applyIncrementSizeToSourceValues() ) { + updateValue.add( incrementSize ); + } + else { + updateValue.increment(); + } + updateValue.bind( updatePS, 1 ); + value.bind( updatePS, 2 ); + updatePS.setString( 3, segmentValue ); + rows = updatePS.executeUpdate(); + } + catch ( SQLException e ) { + log.error( "could not updateQuery hi value in: " + tableName, e ); + throw e; + } + finally { + updatePS.close(); + } + } + while ( rows == 0 ); + + accessCount++; + + return value; + } + }, + true + ); } } ); } - /** - * {@inheritDoc} - */ - public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException { - IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() ); - int rows; - do { - SQL_STATEMENT_LOGGER.logStatement( selectQuery, FormatStyle.BASIC ); - PreparedStatement selectPS = conn.prepareStatement( selectQuery ); - try { - selectPS.setString( 1, segmentValue ); - ResultSet selectRS = selectPS.executeQuery(); - if ( !selectRS.next() ) { - value.initialize( initialValue ); - PreparedStatement insertPS = null; - try { - SQL_STATEMENT_LOGGER.logStatement( insertQuery, FormatStyle.BASIC ); - insertPS = conn.prepareStatement( insertQuery ); - insertPS.setString( 1, segmentValue ); - value.bind( insertPS, 2 ); - insertPS.execute(); - } - finally { - if ( insertPS != null ) { - insertPS.close(); - } - } - } - else { - value.initialize( selectRS, 1 ); - } - selectRS.close(); - } - catch ( SQLException sqle ) { - log.error( "could not read or init a hi value", sqle ); - throw sqle; - } - finally { - selectPS.close(); - } - - SQL_STATEMENT_LOGGER.logStatement( updateQuery, FormatStyle.BASIC ); - PreparedStatement updatePS = conn.prepareStatement( updateQuery ); - try { - final IntegralDataTypeHolder updateValue = value.copy(); - if ( optimizer.applyIncrementSizeToSourceValues() ) { - updateValue.add( incrementSize ); - } - else { - updateValue.increment(); - } - updateValue.bind( updatePS, 1 ); - value.bind( updatePS, 2 ); - updatePS.setString( 3, segmentValue ); - rows = updatePS.executeUpdate(); - } - catch ( SQLException sqle ) { - log.error( "could not updateQuery hi value in: " + tableName, sqle ); - throw sqle; - } - finally { - updatePS.close(); - } - } - while ( rows == 0 ); - - accessCount++; - - return value; - } - - /** - * {@inheritDoc} - */ + @Override public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { return new String[] { new StringBuffer() @@ -565,9 +565,7 @@ public class TableGenerator extends TransactionHelper implements PersistentIdent }; } - /** - * {@inheritDoc} - */ + @Override public String[] sqlDropStrings(Dialect dialect) throws HibernateException { StringBuffer sqlDropString = new StringBuffer().append( "drop table " ); if ( dialect.supportsIfExistsBeforeTableName() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java index 931128d136..cbad28cb17 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableStructure.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -20,39 +20,36 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.id.enhanced; -import java.io.Serializable; +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.SQLStatementLogger; +import org.hibernate.id.IdentifierGenerationException; +import org.hibernate.id.IdentifierGeneratorHelper; +import org.hibernate.id.IntegralDataTypeHolder; +import org.hibernate.jdbc.ReturningWork; +import org.hibernate.jdbc.util.FormatStyle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.SessionImplementor; -import org.hibernate.engine.TransactionHelper; -import org.hibernate.id.IdentifierGenerationException; -import org.hibernate.id.IdentifierGeneratorHelper; -import org.hibernate.id.IntegralDataTypeHolder; -import org.hibernate.jdbc.util.FormatStyle; -import org.hibernate.jdbc.util.SQLStatementLogger; - /** * Describes a table used to mimic sequence behavior * * @author Steve Ebersole */ -public class TableStructure extends TransactionHelper implements DatabaseStructure { +public class TableStructure implements DatabaseStructure { private static final Logger log = LoggerFactory.getLogger( TableStructure.class ); - private static final SQLStatementLogger SQL_STATEMENT_LOGGER = new SQLStatementLogger( false, false ); private final String tableName; private final String valueColumnName; @@ -87,55 +84,98 @@ public class TableStructure extends TransactionHelper implements DatabaseStructu " where " + valueColumnName + "=?"; } - /** - * {@inheritDoc} - */ + @Override public String getName() { return tableName; } - /** - * {@inheritDoc} - */ + @Override public int getInitialValue() { return initialValue; } - /** - * {@inheritDoc} - */ + @Override public int getIncrementSize() { return incrementSize; } - /** - * {@inheritDoc} - */ + @Override public int getTimesAccessed() { return accessCounter; } - /** - * {@inheritDoc} - */ + @Override public void prepare(Optimizer optimizer) { applyIncrementSizeToSourceValues = optimizer.applyIncrementSizeToSourceValues(); } - /** - * {@inheritDoc} - */ + @Override public AccessCallback buildCallback(final SessionImplementor session) { return new AccessCallback() { + @Override public IntegralDataTypeHolder getNextValue() { - return ( IntegralDataTypeHolder ) doWorkInNewTransaction( session ); + return session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork( + new ReturningWork() { + @Override + public IntegralDataTypeHolder execute(Connection connection) throws SQLException { + final SQLStatementLogger statementLogger = session + .getFactory() + .getServiceRegistry() + .getService( JdbcServices.class ) + .getSqlStatementLogger(); + IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( numberType ); + int rows; + do { + statementLogger.logStatement( selectQuery, FormatStyle.BASIC.getFormatter() ); + PreparedStatement selectStatement = connection.prepareStatement( selectQuery ); + try { + ResultSet selectRS = selectStatement.executeQuery(); + if ( !selectRS.next() ) { + String err = "could not read a hi value - you need to populate the table: " + tableName; + log.error( err ); + throw new IdentifierGenerationException( err ); + } + value.initialize( selectRS, 1 ); + selectRS.close(); + } + catch ( SQLException sqle ) { + log.error( "could not read a hi value", sqle ); + throw sqle; + } + finally { + selectStatement.close(); + } + + statementLogger.logStatement( updateQuery, FormatStyle.BASIC.getFormatter() ); + PreparedStatement updatePS = connection.prepareStatement( updateQuery ); + try { + final int increment = applyIncrementSizeToSourceValues ? incrementSize : 1; + final IntegralDataTypeHolder updateValue = value.copy().add( increment ); + updateValue.bind( updatePS, 1 ); + value.bind( updatePS, 2 ); + rows = updatePS.executeUpdate(); + } + catch ( SQLException e ) { + log.error( "could not updateQuery hi value in: " + tableName, e ); + throw e; + } + finally { + updatePS.close(); + } + } while ( rows == 0 ); + + accessCounter++; + + return value; + } + }, + true + ); } }; } - /** - * {@inheritDoc} - */ + @Override public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { return new String[] { dialect.getCreateTableString() + " " + tableName + " ( " + valueColumnName + " " + dialect.getTypeName( Types.BIGINT ) + " )", @@ -143,9 +183,7 @@ public class TableStructure extends TransactionHelper implements DatabaseStructu }; } - /** - * {@inheritDoc} - */ + @Override public String[] sqlDropStrings(Dialect dialect) throws HibernateException { StringBuffer sqlDropString = new StringBuffer().append( "drop table " ); if ( dialect.supportsIfExistsBeforeTableName() ) { @@ -157,55 +195,4 @@ public class TableStructure extends TransactionHelper implements DatabaseStructu } return new String[] { sqlDropString.toString() }; } - - /** - * {@inheritDoc} - */ - protected Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException { - IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( numberType ); - int rows; - do { - SQL_STATEMENT_LOGGER.logStatement( selectQuery, FormatStyle.BASIC ); - PreparedStatement selectPS = conn.prepareStatement( selectQuery ); - try { - ResultSet selectRS = selectPS.executeQuery(); - if ( !selectRS.next() ) { - String err = "could not read a hi value - you need to populate the table: " + tableName; - log.error( err ); - throw new IdentifierGenerationException( err ); - } - value.initialize( selectRS, 1 ); - selectRS.close(); - } - catch ( SQLException sqle ) { - log.error( "could not read a hi value", sqle ); - throw sqle; - } - finally { - selectPS.close(); - } - - SQL_STATEMENT_LOGGER.logStatement( updateQuery, FormatStyle.BASIC ); - PreparedStatement updatePS = conn.prepareStatement( updateQuery ); - try { - final int increment = applyIncrementSizeToSourceValues ? incrementSize : 1; - final IntegralDataTypeHolder updateValue = value.copy().add( increment ); - updateValue.bind( updatePS, 1 ); - value.bind( updatePS, 2 ); - rows = updatePS.executeUpdate(); - } - catch ( SQLException sqle ) { - log.error( "could not updateQuery hi value in: " + tableName, sqle ); - throw sqle; - } - finally { - updatePS.close(); - } - } while ( rows == 0 ); - - accessCounter++; - - return value; - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/jdbc/ReturningWork.java b/hibernate-core/src/main/java/org/hibernate/jdbc/ReturningWork.java new file mode 100644 index 0000000000..83b9d4ced9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jdbc/ReturningWork.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2011, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A discrete piece of work following the lines of {@link Work} but returning a result. + * + * @author Steve Ebersole + */ +public interface ReturningWork { + /** + * Execute the discrete work encapsulated by this work instance using the supplied connection. + * + * @param connection The connection on which to perform the work. + * + * @return The work result + * + * @throws SQLException Thrown during execution of the underlying JDBC interaction. + * @throws org.hibernate.HibernateException Generally indicates a wrapped SQLException. + */ + public T execute(Connection connection) throws SQLException; +}