Fix for #168 - ENH: DataSource - add support for configuring autocommit=true

This commit is contained in:
rbygrave
2014-07-16 21:58:32 +12:00
parent 3f29acb25c
commit 3f2676ab49
10 changed files with 299 additions and 89 deletions
@@ -32,6 +32,8 @@ public class DataSourceConfig {
private int isolationLevel = Transaction.READ_COMMITTED;
private boolean autoCommit;
private String heartbeatSql;
private int heartbeatFreqSecs = 30;
@@ -131,6 +133,20 @@ public class DataSourceConfig {
public void setIsolationLevel(int isolationLevel) {
this.isolationLevel = isolationLevel;
}
/**
* Return autoCommit setting.
*/
public boolean isAutoCommit() {
return autoCommit;
}
/**
* Set to true to turn on autoCommit.
*/
public void setAutoCommit(boolean autoCommit) {
this.autoCommit = autoCommit;
}
/**
* Return the minimum number of connections the pool should maintain.
@@ -450,6 +466,7 @@ public class DataSourceConfig {
String dbUrl = properties.get(prefix + "databaseUrl", null);
this.url = properties.get(prefix + "url", dbUrl);
this.autoCommit = properties.getBoolean(prefix + "autoCommit", false);
this.captureStackTrace = properties.getBoolean(prefix + "captureStackTrace", false);
this.maxStackTraceSize = properties.getInt(prefix + "maxStackTraceSize", 5);
this.leakTimeMinutes = properties.getInt(prefix + "leakTimeMinutes", 30);
@@ -175,6 +175,13 @@ public class ServerConfig {
*/
private DataSourceConfig dataSourceConfig = new DataSourceConfig();
/**
* Set to true if the DataSource uses autoCommit.
* <p>
* Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager.
*/
private boolean autoCommitMode;
/**
* The data source JNDI name if using a JNDI DataSource.
*/
@@ -582,6 +589,20 @@ public class ServerConfig {
public void setDataSourceJndiName(String dataSourceJndiName) {
this.dataSourceJndiName = dataSourceJndiName;
}
/**
* Return true if autoCommit mode is on. This indicates to Ebean to use autoCommit friendly Transactions and TransactionManager.
*/
public boolean isAutoCommitMode() {
return autoCommitMode;
}
/**
* Set to true if autoCommit mode is on and Ebean should use autoCommit friendly Transactions and TransactionManager.
*/
public void setAutoCommitMode(boolean autoCommitMode) {
this.autoCommitMode = autoCommitMode;
}
/**
* Return a value used to represent TRUE in the database.
@@ -1281,6 +1302,7 @@ public class ServerConfig {
loadDataSourceSettings(p);
autoCommitMode = p.getBoolean("autoCommitMode", false);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", false);
namingConvention = createNamingConvention(p);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.server.core;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,6 +24,7 @@ import com.avaje.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import com.avaje.ebeaninternal.server.deploy.parse.DeployInherit;
import com.avaje.ebeaninternal.server.deploy.parse.DeployUtil;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory;
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.persist.DefaultPersister;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
@@ -31,6 +34,7 @@ import com.avaje.ebeaninternal.server.resource.ResourceManager;
import com.avaje.ebeaninternal.server.resource.ResourceManagerFactory;
import com.avaje.ebeaninternal.server.text.json.DJsonContext;
import com.avaje.ebeaninternal.server.text.json.DefaultJsonValueAdapter;
import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager;
import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager;
@@ -42,8 +46,6 @@ import com.avaje.ebeaninternal.server.type.TypeManager;
/**
* Used to extend the ServerConfig with additional objects used to configure and
* construct an EbeanServer.
*
* @author rbygrave
*/
public class InternalConfiguration {
@@ -113,8 +115,7 @@ public class InternalConfiguration {
this.beanDescriptorManager = new BeanDescriptorManager(this);
beanDescriptorManager.deploy();
this.transactionManager = new TransactionManager(clusterManager, backgroundExecutor,
serverConfig, beanDescriptorManager, this.getBootupClasses());
this.transactionManager = createTransactionManager();
this.cQueryEngine = new CQueryEngine(serverConfig.getDatabasePlatform(), binder);
@@ -131,6 +132,34 @@ public class InternalConfiguration {
}
}
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
private TransactionManager createTransactionManager() {
if (isAutoCommitMode()) {
return new AutoCommitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
return new TransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
/**
* Return true if autoCommit mode is on.
*/
private boolean isAutoCommitMode() {
if (serverConfig.isAutoCommitMode()) {
// explicitly set
return true;
}
DataSource dataSource = serverConfig.getDataSource();
if (dataSource instanceof DataSourcePool && ((DataSourcePool)dataSource).getAutoCommit()) {
// We know the DataSourcePool is using autoCommit
return true;
}
return false;
}
public JsonContext createJsonContext(SpiEbeanServer server) {
@@ -176,7 +176,7 @@ public class DataSourcePool implements DataSource {
this.name = name;
this.poolListener = createPoolListener(params.getPoolListener());
this.autoCommit = false;
this.autoCommit = params.isAutoCommit();
this.transactionIsolation = params.getIsolationLevel();
this.maxInactiveMillis = 1000 * params.getMaxInactiveTimeSecs();
@@ -0,0 +1,27 @@
package com.avaje.ebeaninternal.server.transaction;
import java.sql.Connection;
import java.sql.SQLException;
/**
* AutoCommit friendly Transaction.
* <p>
* Skips actual commit and rollback as these are performed automatically.
*/
public class AutoCommitJdbcTransaction extends JdbcTransaction {
public AutoCommitJdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) {
super(id, explicit, connection, manager);
}
@Override
protected void performRollback() throws SQLException {
// do nothing as autoCommit
}
@Override
protected void performCommit() throws SQLException {
// do nothing as autoCommit
}
}
@@ -0,0 +1,36 @@
package com.avaje.ebeaninternal.server.transaction;
import java.sql.Connection;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.BootupClasses;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
/**
* AutoCommit based TransactionManager.
* <p>
* Intended to be used if when autoCommit mode is desired.
*/
public class AutoCommitTransactionManager extends TransactionManager {
public AutoCommitTransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
ServerConfig config, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
super(clusterManager, backgroundExecutor, config, descMgr, bootupClasses);
}
/**
* Create an autoCommit based Transaction.
*/
@Override
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
return new AutoCommitJdbcTransaction(prefix + id, explicit, c, this);
}
}
@@ -540,28 +540,40 @@ public class JdbcTransaction implements SpiTransaction {
* rollback or just close the connection for performance.
* </p>
*/
private void connectionEndForQueryOnly() {
protected void connectionEndForQueryOnly() {
try {
switch (onQueryOnly) {
case ROLLBACK:
connection.rollback();
performRollback();
break;
case COMMIT:
connection.commit();
performCommit();
break;
case CLOSE_ON_READCOMMITTED:
// Connection is closed via deactivate() which follows
// This optimisation is only available at READ COMMITTED Isolation
// valid at READ COMMITTED Isolation
break;
default:
connection.rollback();
performRollback();
}
} catch (SQLException e) {
String m = "Error when ending a query only transaction via " + onQueryOnly;
logger.error(m, e);
logger.error("Error when ending a query only transaction via " + onQueryOnly, e);
}
}
/**
* Perform the actual rollback on the connection.
*/
protected void performRollback() throws SQLException {
connection.rollback();
}
/**
* Perform the actual commit on the connection.
*/
protected void performCommit() throws SQLException {
connection.commit();
}
/**
* End the transaction on a query only request.
*/
@@ -594,7 +606,7 @@ public class JdbcTransaction implements SpiTransaction {
if (batchControl != null && !batchControl.isEmpty()) {
batchControl.flush();
}
connection.commit();
performCommit();
}
} catch (Exception e) {
@@ -611,13 +623,12 @@ public class JdbcTransaction implements SpiTransaction {
* Notify the transaction manager.
*/
protected void notifyRollback(Throwable cause) {
if (manager == null) {
return;
}
if (queryOnly) {
manager.notifyOfQueryOnly(false, this, cause);
} else {
manager.notifyOfRollback(this, cause);
if (manager != null) {
if (queryOnly) {
manager.notifyOfQueryOnly(false, this, cause);
} else {
manager.notifyOfRollback(this, cause);
}
}
}
@@ -637,7 +648,7 @@ public class JdbcTransaction implements SpiTransaction {
throw new IllegalStateException(illegalStateMessage);
}
try {
connection.rollback();
performRollback();
} catch (Exception ex) {
throw new PersistenceException(ex);
@@ -42,7 +42,7 @@ public class TransactionManager {
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.TXN");
/**
* The behaviour desired when ending a query only transaction.
* The behavior desired when ending a query only transaction.
*/
public enum OnQueryOnly {
@@ -62,47 +62,47 @@ public class TransactionManager {
COMMIT
}
private final BeanDescriptorManager beanDescriptorManager;
protected final BeanDescriptorManager beanDescriptorManager;
/**
* Prefix for transaction id's (logging).
*/
private final String prefix;
protected final String prefix;
private final String externalTransPrefix;
protected final String externalTransPrefix;
/**
* The dataSource of connections.
*/
private final DataSource dataSource;
protected final DataSource dataSource;
/**
* Flag to indicate the default Isolation is READ COMMITTED. This enables us
* to close queryOnly transactions rather than commit or rollback them.
*/
private final OnQueryOnly onQueryOnly;
protected final OnQueryOnly onQueryOnly;
/**
* The default batchMode for transactions.
*/
private final boolean defaultBatchMode;
protected final boolean defaultBatchMode;
private final BackgroundExecutor backgroundExecutor;
protected final BackgroundExecutor backgroundExecutor;
private final ClusterManager clusterManager;
protected final ClusterManager clusterManager;
private final String serverName;
protected final String serverName;
/**
* Id's for transaction logging.
*/
private AtomicLong transactionCounter = new AtomicLong(1000);
protected AtomicLong transactionCounter = new AtomicLong(1000);
private int clusterDebugLevel;
protected int clusterDebugLevel;
private final BulkEventListenerMap bulkEventListenerMap;
protected final BulkEventListenerMap bulkEventListenerMap;
private TransactionEventListener[] transactionEventListeners;
protected TransactionEventListener[] transactionEventListeners;
/**
* Create the TransactionManager
@@ -146,14 +146,14 @@ public class TransactionManager {
((DataSourcePool)dataSource).shutdown(deregisterDriver);
}
}
public BeanDescriptorManager getBeanDescriptorManager() {
return beanDescriptorManager;
}
public BulkEventListenerMap getBulkEventListenerMap() {
return bulkEventListenerMap;
}
public BeanDescriptorManager getBeanDescriptorManager() {
return beanDescriptorManager;
}
public BulkEventListenerMap getBulkEventListenerMap() {
return bulkEventListenerMap;
}
/**
* Return the behaviour to use when a query only transaction is committed.
@@ -219,26 +219,26 @@ public class TransactionManager {
return dataSource;
}
/**
* Return the cluster debug level.
*/
public int getClusterDebugLevel() {
return clusterDebugLevel;
}
/**
* Return the cluster debug level.
*/
public int getClusterDebugLevel() {
return clusterDebugLevel;
}
/**
* Set the cluster debug level.
*/
public void setClusterDebugLevel(int clusterDebugLevel) {
this.clusterDebugLevel = clusterDebugLevel;
}
/**
* Set the cluster debug level.
*/
public void setClusterDebugLevel(int clusterDebugLevel) {
this.clusterDebugLevel = clusterDebugLevel;
}
/**
* Defines the type of behaviour to use when closing a transaction that was used to query data only.
*/
public OnQueryOnly getOnQueryOnly() {
return onQueryOnly;
}
/**
* Defines the type of behavior to use when closing a transaction that was used to query data only.
*/
public OnQueryOnly getOnQueryOnly() {
return onQueryOnly;
}
/**
* Wrap the externally supplied Connection.
@@ -273,7 +273,7 @@ public class TransactionManager {
c = dataSource.getConnection();
long id = transactionCounter.incrementAndGet();
JdbcTransaction t = new JdbcTransaction(prefix + id, explicit, c, this);
SpiTransaction t = createTransaction(explicit, c, id);
// set the default batch mode. This can be on for
// jdbc drivers that support getGeneratedKeys
@@ -309,7 +309,7 @@ public class TransactionManager {
c = dataSource.getConnection();
long id = transactionCounter.incrementAndGet();
JdbcTransaction t = new JdbcTransaction(prefix + id, false, c, this);
SpiTransaction t = createTransaction(false, c, id);
// set the default batch mode. Can be true for
// jdbc drivers that support getGeneratedKeys
@@ -336,6 +336,13 @@ public class TransactionManager {
}
}
/**
* 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.
*/
@@ -446,34 +453,33 @@ public class TransactionManager {
backgroundExecutor.execute(postCommit.notifyPersistListeners());
}
/**
* Notify local BeanPersistListeners etc of events from another server in the cluster.
*/
public void remoteTransactionEvent(RemoteTransactionEvent remoteEvent) {
if (clusterDebugLevel > 0 || logger.isDebugEnabled()){
logger.info("Cluster Received: "+remoteEvent.toString());
}
List<TableIUD> tableIUDList = remoteEvent.getTableIUDList();
if (tableIUDList != null){
for (int i = 0; i < tableIUDList.size(); i++) {
TableIUD tableIUD = tableIUDList.get(i);
beanDescriptorManager.cacheNotify(tableIUD);
}
}
List<BeanPersistIds> beanPersistList = remoteEvent.getBeanPersistList();
if (beanPersistList != null){
for (int i = 0; i < beanPersistList.size(); i++) {
BeanPersistIds beanPersist = beanPersistList.get(i);
beanPersist.notifyCacheAndListener();
}
}
/**
* Notify local BeanPersistListeners etc of events from another server in the cluster.
*/
public void remoteTransactionEvent(RemoteTransactionEvent remoteEvent) {
if (clusterDebugLevel > 0 || logger.isDebugEnabled()) {
logger.info("Cluster Received: " + remoteEvent.toString());
}
List<TableIUD> tableIUDList = remoteEvent.getTableIUDList();
if (tableIUDList != null) {
for (int i = 0; i < tableIUDList.size(); i++) {
TableIUD tableIUD = tableIUDList.get(i);
beanDescriptorManager.cacheNotify(tableIUD);
}
}
List<BeanPersistIds> beanPersistList = remoteEvent.getBeanPersistList();
if (beanPersistList != null) {
for (int i = 0; i < beanPersistList.size(); i++) {
BeanPersistIds beanPersist = beanPersistList.get(i);
beanPersist.notifyCacheAndListener();
}
}
}
}