diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java index 89575ca33..6f9f5ebea 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java @@ -32,91 +32,92 @@ import java.util.concurrent.atomic.AtomicLong; */ public class TransactionManager { - private static final Logger logger = LoggerFactory.getLogger(TransactionManager.class); - + private static final Logger logger = LoggerFactory.getLogger(TransactionManager.class); + public static final Logger SQL_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SQL"); - + public static final Logger SUM_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SUM"); - + public static final Logger TXN_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.TXN"); - protected final BeanDescriptorManager beanDescriptorManager; - - /** - * Prefix for transaction id's (logging). - */ - protected final String prefix; + protected final BeanDescriptorManager beanDescriptorManager; - protected final String externalTransPrefix; + /** + * Prefix for transaction id's (logging). + */ + protected final String prefix; - /** - * The dataSource of connections. - */ - protected final DataSource dataSource; + protected final String externalTransPrefix; - /** - * Flag to indicate the default Isolation is READ COMMITTED. This enables us - * to close queryOnly transactions rather than commit or rollback them. - */ - protected final OnQueryOnly onQueryOnly; + /** + * The dataSource of connections. + */ + protected final DataSource dataSource; - protected final BackgroundExecutor backgroundExecutor; - - protected final ClusterManager clusterManager; - - protected final String serverName; + /** + * Flag to indicate the default Isolation is READ COMMITTED. This enables us + * to close queryOnly transactions rather than commit or rollback them. + */ + protected final OnQueryOnly onQueryOnly; + + protected final BackgroundExecutor backgroundExecutor; + + protected final ClusterManager clusterManager; + + protected final String serverName; protected final PersistBatch persistBatch; protected final PersistBatch persistBatchOnCascade; - /** - * Id's for transaction logging. - */ - protected final AtomicLong transactionCounter = new AtomicLong(1000); + /** + * Id's for transaction logging. + */ + protected final AtomicLong transactionCounter = new AtomicLong(1000); - protected final BulkEventListenerMap bulkEventListenerMap; + protected final BulkEventListenerMap bulkEventListenerMap; - protected final TransactionEventListener[] transactionEventListeners; + protected final TransactionEventListener[] transactionEventListeners; - /** - * Create the TransactionManager - */ - public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config, - BeanDescriptorManager descMgr, BootupClasses bootupClasses) { + /** + * Create the TransactionManager + */ + public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config, + BeanDescriptorManager descMgr, BootupClasses bootupClasses) { this.persistBatch = config.getPersistBatch(); this.persistBatchOnCascade = config.appliedPersistBatchOnCascade(); - this.beanDescriptorManager = descMgr; - this.clusterManager = clusterManager; - this.serverName = config.getName(); - this.backgroundExecutor = backgroundExecutor; - this.dataSource = config.getDataSource(); - this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners()); + this.beanDescriptorManager = descMgr; + this.clusterManager = clusterManager; + this.serverName = config.getName(); + this.backgroundExecutor = backgroundExecutor; + this.dataSource = config.getDataSource(); + this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners()); List transactionEventListeners = bootupClasses.getTransactionEventListeners(); - this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]); + this.transactionEventListeners = transactionEventListeners.toArray(new + TransactionEventListener[transactionEventListeners.size()]); - this.prefix = ""; - this.externalTransPrefix = "e"; - - this.onQueryOnly = initOnQueryOnly(config.getDatabasePlatform().getOnQueryOnly(), dataSource); - - initialiseHeartbeat(); - } - - private void initialiseHeartbeat() { - if (dataSource instanceof DataSourcePool) { - DataSourcePool ds = (DataSourcePool)dataSource; - backgroundExecutor.executePeriodically(ds.getHeartbeatRunnable(), ds.getHeartbeatFreqSecs(), TimeUnit.SECONDS); - } - } - - public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) { - if (shutdownDataSource && (dataSource instanceof DataSourcePool)) { - ((DataSourcePool)dataSource).shutdown(deregisterDriver); - } - } + this.prefix = ""; + this.externalTransPrefix = "e"; + + this.onQueryOnly = initOnQueryOnly(config.getDatabasePlatform().getOnQueryOnly(), dataSource); + + initialiseHeartbeat(); + } + + private void initialiseHeartbeat() { + if (dataSource instanceof DataSourcePool) { + DataSourcePool ds = (DataSourcePool) dataSource; + backgroundExecutor.executePeriodically(ds.getHeartbeatRunnable(), ds.getHeartbeatFreqSecs(), TimeUnit.SECONDS); + } + } + + public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) { + if (shutdownDataSource && (dataSource instanceof DataSourcePool)) { + ((DataSourcePool) dataSource).shutdown(deregisterDriver); + } + } public BeanDescriptorManager getBeanDescriptorManager() { return beanDescriptorManager; @@ -135,74 +136,75 @@ public class TransactionManager { } /** - * Return the behaviour to use when a query only transaction is committed. - *

- * There is a potential optimisation available when read committed is the default - * isolation level. If it is, then Connections used only for queries do not require - * commit or rollback but instead can just be put back into the pool via close(). - *

- *

- * If the Isolation level is higher (say SERIALIZABLE) then Connections used - * just for queries do need to be committed or rollback after the query. - *

- */ - private OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) { + * Return the behaviour to use when a query only transaction is committed. + *

+ * There is a potential optimisation available when read committed is the default + * isolation level. If it is, then Connections used only for queries do not require + * commit or rollback but instead can just be put back into the pool via close(). + *

+ *

+ * If the Isolation level is higher (say SERIALIZABLE) then Connections used + * just for queries do need to be committed or rollback after the query. + *

+ */ + private OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) { - // first check for a system property 'override' - String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly"); - if (systemPropertyValue != null) { - return OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase()); - } + // first check for a system property 'override' + String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly"); + if (systemPropertyValue != null) { + return OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase()); + } - if (OnQueryOnly.CLOSE.equals(dbPlatformOnQueryOnly)) { - // check for read committed isolation level - if (!isReadCommitedIsolation(ds)){ - logger.warn("Ignoring DatabasePlatform.OnQueryOnly.CLOSE as the transaction Isolation Level is not READ_COMMITTED"); - // we will just use ROLLBACK and ignore the desired optimisation - return OnQueryOnly.ROLLBACK; - } else { - // will use the OnQueryOnly.CLOSE optimisation - return OnQueryOnly.CLOSE; - } - } - // default to rollback if not defined on the platform - return dbPlatformOnQueryOnly == null ? OnQueryOnly.ROLLBACK : dbPlatformOnQueryOnly; - } - - /** - * Return true if the isolation level is read committed. - */ - private boolean isReadCommitedIsolation(DataSource ds) { - - Connection c = null; - try { - c = ds.getConnection(); + if (OnQueryOnly.CLOSE.equals(dbPlatformOnQueryOnly)) { + // check for read committed isolation level + if (!isReadCommitedIsolation(ds)) { + logger.warn("Ignoring DatabasePlatform.OnQueryOnly.CLOSE as the transaction Isolation Level is not " + + "READ_COMMITTED"); + // we will just use ROLLBACK and ignore the desired optimisation + return OnQueryOnly.ROLLBACK; + } else { + // will use the OnQueryOnly.CLOSE optimisation + return OnQueryOnly.CLOSE; + } + } + // default to rollback if not defined on the platform + return dbPlatformOnQueryOnly == null ? OnQueryOnly.ROLLBACK : dbPlatformOnQueryOnly; + } - int isolationLevel = c.getTransactionIsolation(); - return (isolationLevel == Connection.TRANSACTION_READ_COMMITTED); + /** + * Return true if the isolation level is read committed. + */ + private boolean isReadCommitedIsolation(DataSource ds) { - } catch (SQLException ex) { - String m = "Errored trying to determine the default Isolation Level"; - throw new PersistenceException(m, ex); + Connection c = null; + try { + c = ds.getConnection(); - } finally { - try { - if (c != null) { - c.close(); - } - } catch (SQLException ex) { - logger.error("closing connection", ex); - } - } - } - - public String getServerName() { - return serverName; - } - - public DataSource getDataSource() { - return dataSource; - } + int isolationLevel = c.getTransactionIsolation(); + return (isolationLevel == Connection.TRANSACTION_READ_COMMITTED); + + } catch (SQLException ex) { + String m = "Errored trying to determine the default Isolation Level"; + throw new PersistenceException(m, ex); + + } finally { + try { + if (c != null) { + c.close(); + } + } catch (SQLException ex) { + logger.error("closing connection", ex); + } + } + } + + public String getServerName() { + return serverName; + } + + public DataSource getDataSource() { + return dataSource; + } /** * Defines the type of behavior to use when closing a transaction that was used to query data only. @@ -211,70 +213,70 @@ public class TransactionManager { return onQueryOnly; } - /** - * Wrap the externally supplied Connection. - */ - public SpiTransaction wrapExternalConnection(Connection c) { + /** + * Wrap the externally supplied Connection. + */ + public SpiTransaction wrapExternalConnection(Connection c) { - return wrapExternalConnection(externalTransPrefix + c.hashCode(), c); - } - - /** - * Wrap an externally supplied Connection with a known transaction id. - */ - public SpiTransaction wrapExternalConnection(String id, Connection c) { + return wrapExternalConnection(externalTransPrefix + c.hashCode(), c); + } - ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this); + /** + * Wrap an externally supplied Connection with a known transaction id. + */ + public SpiTransaction wrapExternalConnection(String id, Connection c) { - // set the default batch mode + ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this); + + // set the default batch mode t.setBatch(persistBatch); t.setBatchOnCascade(persistBatchOnCascade); - return t; - } - - /** - * Create a new Transaction. - */ - public SpiTransaction createTransaction(boolean explicit, int isolationLevel) { - Connection c = null; - try { - c = dataSource.getConnection(); - long id = transactionCounter.incrementAndGet(); + return t; + } - SpiTransaction t = createTransaction(explicit, c, id); - if (isolationLevel > -1) { - c.setTransactionIsolation(isolationLevel); - } - - if (explicit && TXN_LOGGER.isTraceEnabled()) { - TXN_LOGGER.trace(t.getLogPrefix()+"Begin"); - } - - return t; - - } catch (SQLException ex) { - // close connection on failed creation - try { - if (c != null){ - c.close(); - } - } catch (SQLException e) { - logger.error("Error closing failed connection", e); - } - throw new PersistenceException(ex); - } - } - - public SpiTransaction createQueryTransaction() { - Connection c = null; - try { + /** + * Create a new Transaction. + */ + public SpiTransaction createTransaction(boolean explicit, int isolationLevel) { + Connection c = null; + try { c = dataSource.getConnection(); - long id = transactionCounter.incrementAndGet(); + long id = transactionCounter.incrementAndGet(); - return createTransaction(false, c, id); + SpiTransaction t = createTransaction(explicit, c, id); + if (isolationLevel > -1) { + c.setTransactionIsolation(isolationLevel); + } - } catch (PersistenceException ex) { - // close the connection and re-throw the exception + if (explicit && TXN_LOGGER.isTraceEnabled()) { + TXN_LOGGER.trace(t.getLogPrefix() + "Begin"); + } + + return t; + + } catch (SQLException ex) { + // close connection on failed creation + try { + if (c != null) { + c.close(); + } + } catch (SQLException e) { + logger.error("Error closing failed connection", e); + } + throw new PersistenceException(ex); + } + } + + public SpiTransaction createQueryTransaction() { + Connection c = null; + try { + c = dataSource.getConnection(); + long id = transactionCounter.incrementAndGet(); + + return createTransaction(false, c, id); + + } catch (PersistenceException ex) { + // close the connection and re-throw the exception try { if (c != null) { c.close(); @@ -283,42 +285,42 @@ public class TransactionManager { logger.error("Error closing failed connection", e); } throw ex; - - } catch (SQLException ex) { - // don't need to close connection in this case - throw new PersistenceException(ex); - } - } - /** - * Create a new transaction. - */ + } catch (SQLException ex) { + // don't need to close connection in this case + throw new PersistenceException(ex); + } + } + + /** + * Create a new transaction. + */ protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) { return new JdbcTransaction(prefix + id, explicit, c, this); } - - /** - * Process a local rolled back transaction. - */ - public void notifyOfRollback(SpiTransaction transaction, Throwable cause) { - - try { - if (TXN_LOGGER.isInfoEnabled()) { - String msg = transaction.getLogPrefix()+"Rollback"; - if (cause != null){ - msg += " error: "+formatThrowable(cause); + + /** + * Process a local rolled back transaction. + */ + public void notifyOfRollback(SpiTransaction transaction, Throwable cause) { + + try { + if (TXN_LOGGER.isInfoEnabled()) { + String msg = transaction.getLogPrefix() + "Rollback"; + if (cause != null) { + msg += " error: " + formatThrowable(cause); } TXN_LOGGER.info(msg); } - - for (TransactionEventListener listener : transactionEventListeners) { + + for (TransactionEventListener listener : transactionEventListeners) { listener.postTransactionRollback(transaction, cause); } - } catch (Exception ex) { - logger.error("Error while notifying TransactionEventListener of rollback event", ex); - } - } + } catch (Exception ex) { + logger.error("Error while notifying TransactionEventListener of rollback event", ex); + } + } /** * Query only transaction in read committed isolation. @@ -327,49 +329,49 @@ public class TransactionManager { // Nothing that interesting here if (TXN_LOGGER.isTraceEnabled()) { - TXN_LOGGER.trace(transaction.getLogPrefix()+"Commit - query only"); + TXN_LOGGER.trace(transaction.getLogPrefix() + "Commit - query only"); } } - - private String formatThrowable(Throwable e){ - if (e == null){ - return ""; - } - StringBuilder sb = new StringBuilder(); - formatThrowable(e, sb); - return sb.toString(); - } - - private void formatThrowable(Throwable e, StringBuilder sb){ - - sb.append(e.toString()); - StackTraceElement[] stackTrace = e.getStackTrace(); - if (stackTrace.length > 0){ - sb.append(" stack0: "); - sb.append(stackTrace[0]); - } - Throwable cause = e.getCause(); - if (cause != null){ - sb.append(" cause: "); - formatThrowable(cause, sb); - } - } - - /** - * Process a local committed transaction. - */ - public void notifyOfCommit(SpiTransaction transaction) { - try { + private String formatThrowable(Throwable e) { + if (e == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + formatThrowable(e, sb); + return sb.toString(); + } + + private void formatThrowable(Throwable e, StringBuilder sb) { + + sb.append(e.toString()); + StackTraceElement[] stackTrace = e.getStackTrace(); + if (stackTrace.length > 0) { + sb.append(" stack0: "); + sb.append(stackTrace[0]); + } + Throwable cause = e.getCause(); + if (cause != null) { + sb.append(" cause: "); + formatThrowable(cause, sb); + } + } + + /** + * Process a local committed transaction. + */ + public void notifyOfCommit(SpiTransaction transaction) { + + try { if (transaction.isExplicit()) { if (TXN_LOGGER.isInfoEnabled()) { - TXN_LOGGER.info(transaction.getLogPrefix()+"Commit"); + TXN_LOGGER.info(transaction.getLogPrefix() + "Commit"); } } else if (TXN_LOGGER.isDebugEnabled()) { - TXN_LOGGER.debug(transaction.getLogPrefix()+"Commit"); + TXN_LOGGER.debug(transaction.getLogPrefix() + "Commit"); } - + PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction.getEvent()); postCommit.notifyLocalCacheIndex(); @@ -383,29 +385,29 @@ public class TransactionManager { } } catch (Exception ex) { - logger.error("NotifyOfCommit failed. L2 Cache potentially not notified.", ex); - } - } + logger.error("NotifyOfCommit failed. L2 Cache potentially not notified.", ex); + } + } - /** - * Process a Transaction that comes from another framework or local code. - *

- * For cases where raw SQL/JDBC or other frameworks are used this can - * invalidate the appropriate parts of the cache. - *

- */ - public void externalModification(TransactionEventTable tableEvents) { - - TransactionEvent event = new TransactionEvent(); - event.add(tableEvents); - - PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event); - - // invalidate parts of local cache and index - postCommit.notifyLocalCacheIndex(); - - backgroundExecutor.execute(postCommit.notifyPersistListeners()); - } + /** + * Process a Transaction that comes from another framework or local code. + *

+ * For cases where raw SQL/JDBC or other frameworks are used this can + * invalidate the appropriate parts of the cache. + *

+ */ + public void externalModification(TransactionEventTable tableEvents) { + + TransactionEvent event = new TransactionEvent(); + event.add(tableEvents); + + PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event); + + // invalidate parts of local cache and index + postCommit.notifyLocalCacheIndex(); + + backgroundExecutor.execute(postCommit.notifyPersistListeners()); + } /** * Notify local BeanPersistListeners etc of events from another server in the cluster.