mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1232 - Performance: Add support for a second read-only DataSource (for implicit query-only transactions)
This commit is contained in:
@@ -273,11 +273,29 @@ public class ServerConfig {
|
||||
*/
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* The read only data source (can be null).
|
||||
*/
|
||||
private DataSource readOnlyDataSource;
|
||||
|
||||
/**
|
||||
* The data source config.
|
||||
*/
|
||||
private DataSourceConfig dataSourceConfig = new DataSourceConfig();
|
||||
|
||||
/**
|
||||
* When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig.
|
||||
* I believe this will default to true in some future release (as it has a nice performance benefit).
|
||||
*
|
||||
* autoReadOnlyDataSource is an unfortunate name for this config option but I haven't come up with a better one.
|
||||
*/
|
||||
private boolean autoReadOnlyDataSource;
|
||||
|
||||
/**
|
||||
* Optional configuration for a read only data source.
|
||||
*/
|
||||
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
|
||||
|
||||
/**
|
||||
* The db migration config (migration resource path etc).
|
||||
*/
|
||||
@@ -1442,6 +1460,28 @@ public class ServerConfig {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the read only DataSource.
|
||||
*/
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read only DataSource.
|
||||
* <p>
|
||||
* Note that the DataSource is expected to use AutoCommit true mode avoiding the need
|
||||
* for explicit commit (or rollback).
|
||||
* </p>
|
||||
* <p>
|
||||
* This read only DataSource will be used for implicit query only transactions. It is not
|
||||
* used if the transaction is created explicitly or if the query is an update or delete query.
|
||||
* </p>
|
||||
*/
|
||||
public void setReadOnlyDataSource(DataSource readOnlyDataSource) {
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration to build a DataSource using Ebean's own DataSource
|
||||
* implementation.
|
||||
@@ -1458,6 +1498,42 @@ public class ServerConfig {
|
||||
this.dataSourceConfig = dataSourceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Ebean should create a DataSource for use with implicit read only transactions.
|
||||
*/
|
||||
public boolean isAutoReadOnlyDataSource() {
|
||||
return autoReadOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if Ebean should create a DataSource for use with implicit read only transactions.
|
||||
*/
|
||||
public void setAutoReadOnlyDataSource(boolean autoReadOnlyDataSource) {
|
||||
this.autoReadOnlyDataSource = autoReadOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration for the read only DataSource.
|
||||
* <p>
|
||||
* This is only used if autoReadOnlyDataSource is true.
|
||||
* </p>
|
||||
* <p>
|
||||
* The driver, url, username and password default to the configuration for the main DataSource if they are not
|
||||
* set on this configuration. This means there is actually no need to set any configuration here and we only
|
||||
* set configuration for url, username and password etc if it is different from the main DataSource.
|
||||
* </p>
|
||||
*/
|
||||
public DataSourceConfig getReadOnlyDataSourceConfig() {
|
||||
return readOnlyDataSourceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for the read only DataSource.
|
||||
*/
|
||||
public void setReadOnlyDataSourceConfig(DataSourceConfig readOnlyDataSourceConfig) {
|
||||
this.readOnlyDataSourceConfig = readOnlyDataSourceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JNDI name of the DataSource to use.
|
||||
*/
|
||||
@@ -2607,6 +2683,7 @@ public class ServerConfig {
|
||||
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
|
||||
|
||||
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
|
||||
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
|
||||
|
||||
@@ -47,4 +47,9 @@ public interface SpiServer extends EbeanServer {
|
||||
*/
|
||||
DataSource getDataSource();
|
||||
|
||||
/**
|
||||
* Return the associated read only DataSource for this EbeanServer instance (can be null).
|
||||
*/
|
||||
DataSource getReadOnlyDataSource();
|
||||
|
||||
}
|
||||
|
||||
@@ -98,17 +98,30 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
/**
|
||||
* Delete query.
|
||||
*/
|
||||
DELETE(FIND_DELETE),
|
||||
DELETE(FIND_DELETE, true),
|
||||
|
||||
/**
|
||||
* Update query.
|
||||
*/
|
||||
UPDATE(FIND_UPDATE);
|
||||
UPDATE(FIND_UPDATE, true);
|
||||
|
||||
boolean update;
|
||||
String profileEventId;
|
||||
|
||||
Type(String profileEventId) {
|
||||
this(profileEventId, false);
|
||||
}
|
||||
|
||||
Type(String profileEventId, boolean update) {
|
||||
this.profileEventId = profileEventId;
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an Update or Delete query (not read only).
|
||||
*/
|
||||
public boolean isUpdate() {
|
||||
return update;
|
||||
}
|
||||
|
||||
public String profileEventId() {
|
||||
|
||||
@@ -278,18 +278,21 @@ public class DefaultContainer implements SpiContainer {
|
||||
*/
|
||||
private void setDataSource(ServerConfig config) {
|
||||
if (config.getDataSource() == null) {
|
||||
config.setDataSource(getDataSourceFromConfig(config));
|
||||
config.setDataSource(getDataSourceFromConfig(config, false));
|
||||
}
|
||||
if (config.getReadOnlyDataSource() == null && config.isAutoReadOnlyDataSource()) {
|
||||
config.setReadOnlyDataSource(getDataSourceFromConfig(config, true));
|
||||
}
|
||||
}
|
||||
|
||||
private DataSource getDataSourceFromConfig(ServerConfig config) {
|
||||
private DataSource getDataSourceFromConfig(ServerConfig config, boolean readOnly) {
|
||||
|
||||
if (isOfflineMode(config)) {
|
||||
logger.debug("... DbOffline using platform [{}]", DbOffline.getPlatform());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.getDataSourceJndiName() != null) {
|
||||
if (!readOnly && config.getDataSourceJndiName() != null) {
|
||||
DataSource ds = jndiDataSourceFactory.lookup(config.getDataSourceJndiName());
|
||||
if (ds == null) {
|
||||
throw new PersistenceException("JNDI lookup for DataSource " + config.getDataSourceJndiName() + " returned null.");
|
||||
@@ -298,7 +301,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
}
|
||||
}
|
||||
|
||||
DataSourceConfig dsConfig = config.getDataSourceConfig();
|
||||
DataSourceConfig dsConfig = (readOnly) ? config.getReadOnlyDataSourceConfig() : config.getDataSourceConfig();
|
||||
if (dsConfig == null) {
|
||||
throw new PersistenceException("No DataSourceConfig defined for " + config.getName());
|
||||
}
|
||||
@@ -323,7 +326,14 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
attachListener(config, dsConfig);
|
||||
|
||||
return factory.createPool(config.getName(), dsConfig);
|
||||
if (readOnly) {
|
||||
// setup to use AutoCommit such that we skip explicit commit
|
||||
dsConfig.setAutoCommit(true);
|
||||
//dsConfig.setReadOnly(true);
|
||||
dsConfig.setDefaults(config.getDataSourceConfig());
|
||||
}
|
||||
String poolName = config.getName() + (readOnly ? "-ro" : "");
|
||||
return factory.createPool(poolName, dsConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -384,10 +384,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return transactionManager.getDataSource();
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public DataSource getReadOnlyDataSource() {
|
||||
// return transactionManager.getReadOnlyDataSource();
|
||||
// }
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return transactionManager.getReadOnlyDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadAuditPrepare getReadAuditPrepare() {
|
||||
|
||||
@@ -410,11 +410,11 @@ public class InternalConfiguration {
|
||||
case DB:
|
||||
return new MultiTenantDbSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getTenantDataSourceProvider());
|
||||
case SCHEMA:
|
||||
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantSchemaProvider());
|
||||
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantSchemaProvider());
|
||||
case CATALOG:
|
||||
return new MultiTenantDbCatalogSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantCatalogProvider());
|
||||
return new MultiTenantDbCatalogSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantCatalogProvider());
|
||||
default:
|
||||
return new SimpleDataSourceProvider(serverConfig.getDataSource());
|
||||
return new SimpleDataSourceProvider(serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,19 +17,21 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
public class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final TenantCatalogProvider catalogProvider;
|
||||
private final DataSource readOnlyDataSource;
|
||||
|
||||
private final CatalogDataSource catalogDataSource;
|
||||
private final CatalogDataSource readOnly;
|
||||
|
||||
MultiTenantDbCatalogSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantCatalogProvider catalogProvider) {
|
||||
this.tenantProvider = tenantProvider;
|
||||
MultiTenantDbCatalogSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, DataSource readOnlyDataSource, TenantCatalogProvider catalogProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.catalogProvider = catalogProvider;
|
||||
this.catalogDataSource = new CatalogDataSource();
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
this.catalogDataSource = new CatalogDataSource(dataSource, tenantProvider, catalogProvider);
|
||||
if (readOnlyDataSource == null) {
|
||||
this.readOnly = null;
|
||||
} else {
|
||||
this.readOnly = new CatalogDataSource(readOnlyDataSource, tenantProvider, catalogProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,28 +39,48 @@ public class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
|
||||
return catalogDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(Object tenantId) throws SQLException {
|
||||
return catalogDataSource.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
return readOnly.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
if (readOnlyDataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) readOnlyDataSource).shutdown(false);
|
||||
}
|
||||
if (dataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB catalog for the current user Tenant Id.
|
||||
*/
|
||||
private String tenantCatalog() {
|
||||
return catalogProvider.catalog(tenantProvider.currentId());
|
||||
}
|
||||
|
||||
private class CatalogDataSource implements DataSource {
|
||||
|
||||
CatalogDataSource() {
|
||||
final DataSource dataSource;
|
||||
final CurrentTenantProvider tenantProvider;
|
||||
final TenantCatalogProvider catalogProvider;
|
||||
|
||||
CatalogDataSource(DataSource dataSource, CurrentTenantProvider tenantProvider, TenantCatalogProvider catalogProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.tenantProvider = tenantProvider;
|
||||
this.catalogProvider = catalogProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB catalog for the current user Tenant Id.
|
||||
*/
|
||||
private String tenantCatalog() {
|
||||
return catalogProvider.catalog(tenantProvider.currentId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,19 +17,21 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final TenantSchemaProvider schemaProvider;
|
||||
private final DataSource readOnlyDataSource;
|
||||
|
||||
private final SchemaDataSource schemaDataSource;
|
||||
private final SchemaDataSource readOnly;
|
||||
|
||||
MultiTenantDbSchemaSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantSchemaProvider schemaProvider) {
|
||||
this.tenantProvider = tenantProvider;
|
||||
MultiTenantDbSchemaSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, DataSource readOnlyDataSource, TenantSchemaProvider schemaProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.schemaProvider = schemaProvider;
|
||||
this.schemaDataSource = new SchemaDataSource();
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
this.schemaDataSource = new SchemaDataSource(dataSource, schemaProvider, tenantProvider);
|
||||
if (readOnlyDataSource == null) {
|
||||
this.readOnly = null;
|
||||
} else {
|
||||
this.readOnly = new SchemaDataSource(readOnlyDataSource, schemaProvider, tenantProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,28 +39,44 @@ class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
|
||||
return schemaDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(Object tenantId) throws SQLException {
|
||||
return schemaDataSource.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
return readOnly.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
if (readOnlyDataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) readOnlyDataSource).shutdown(false);
|
||||
}
|
||||
if (dataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB schema for the current user Tenant Id.
|
||||
* Tenant schema aware DataSource.
|
||||
*/
|
||||
private String tenantSchema() {
|
||||
return schemaProvider.schema(tenantProvider.currentId());
|
||||
}
|
||||
private static class SchemaDataSource implements DataSource {
|
||||
|
||||
private class SchemaDataSource implements DataSource {
|
||||
private final DataSource dataSource;
|
||||
private final TenantSchemaProvider schemaProvider;
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
SchemaDataSource() {
|
||||
SchemaDataSource(DataSource dataSource, TenantSchemaProvider schemaProvider, CurrentTenantProvider tenantProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.schemaProvider = schemaProvider;
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +88,13 @@ class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB schema for the current user Tenant Id.
|
||||
*/
|
||||
private String tenantSchema() {
|
||||
return schemaProvider.schema(tenantProvider.currentId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the connection with the appropriate DB schema set.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,12 @@ class MultiTenantDbSupplier implements DataSourceSupplier {
|
||||
this.dataSourceProvider = dataSourceProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
// read only datasource not supported with DB per tenant at this stage
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return dataSourceProvider.dataSource(tenantProvider.currentId());
|
||||
@@ -32,6 +38,11 @@ class MultiTenantDbSupplier implements DataSourceSupplier {
|
||||
return dataSourceProvider.dataSource(tenantId).getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
throw new SQLException("Not currently supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
dataSourceProvider.shutdown(deregisterDriver);
|
||||
|
||||
@@ -244,8 +244,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
// maybe a current one
|
||||
transaction = ebeanServer.currentServerTransaction();
|
||||
if (transaction == null) {
|
||||
// create an implicit transaction to execute this query
|
||||
transaction = ebeanServer.createQueryTransaction(query.getTenantId());
|
||||
if (query.getType().isUpdate()) {
|
||||
// bulk update or delete query
|
||||
transaction = ebeanServer.beginServerTransaction();
|
||||
} else {
|
||||
// create an implicit transaction to execute this query
|
||||
// potentially using read-only DataSource with autoCommit
|
||||
transaction = ebeanServer.createQueryTransaction(query.getTenantId());
|
||||
}
|
||||
createdTransaction = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,11 @@ class SimpleDataSourceProvider implements DataSourceSupplier {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
SimpleDataSourceProvider(DataSource dataSource) {
|
||||
private final DataSource readOnlyDataSource;
|
||||
|
||||
SimpleDataSourceProvider(DataSource dataSource, DataSource readOnlyDataSource) {
|
||||
this.dataSource = dataSource;
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,13 +26,26 @@ class SimpleDataSourceProvider implements DataSourceSupplier {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnlyDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(Object tenantId) throws SQLException {
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
return readOnlyDataSource.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
if (readOnlyDataSource instanceof DataSourcePool){
|
||||
((DataSourcePool) readOnlyDataSource).shutdown(false);
|
||||
}
|
||||
if (dataSource instanceof DataSourcePool){
|
||||
((DataSourcePool) dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
|
||||
private int paramIndex;
|
||||
|
||||
private final boolean enableBindLog;
|
||||
|
||||
private StringBuilder bindLog;
|
||||
|
||||
public DefaultExpressionRequest(SpiOrmQueryRequest<?> queryRequest, DeployParser deployParser, Binder binder, SpiExpressionList<?> expressionList) {
|
||||
@@ -39,6 +41,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
this.deployParser = deployParser;
|
||||
this.binder = binder;
|
||||
this.expressionList = expressionList;
|
||||
this.enableBindLog = binder.isEnableBindLog();
|
||||
// immediately build the list of bind values (callback style)
|
||||
expressionList.addBindValues(this);
|
||||
}
|
||||
@@ -49,6 +52,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
this.deployParser = null;
|
||||
this.binder = null;
|
||||
this.expressionList = null;
|
||||
this.enableBindLog = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,12 +142,14 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
}
|
||||
|
||||
private void bindLog(Object val) {
|
||||
if (bindLog == null) {
|
||||
bindLog = new StringBuilder();
|
||||
} else {
|
||||
bindLog.append(",");
|
||||
if (enableBindLog) {
|
||||
if (bindLog == null) {
|
||||
bindLog = new StringBuilder();
|
||||
} else {
|
||||
bindLog.append(",");
|
||||
}
|
||||
bindLog.append(val);
|
||||
}
|
||||
bindLog.append(val);
|
||||
}
|
||||
|
||||
public String getBindLog() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.core.DbExpressionHandler;
|
||||
import io.ebeaninternal.server.core.Message;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.persist.platform.MultiValueBind;
|
||||
import io.ebeaninternal.server.transaction.TransactionManager;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
@@ -42,6 +43,8 @@ public class Binder {
|
||||
|
||||
private final MultiValueBind multiValueBind;
|
||||
|
||||
private final boolean enableBindLog;
|
||||
|
||||
/**
|
||||
* Set the PreparedStatement with which to bind variables to.
|
||||
*/
|
||||
@@ -54,6 +57,19 @@ public class Binder {
|
||||
this.dbExpressionHandler = dbExpressionHandler;
|
||||
this.dataTimeZone = dataTimeZone;
|
||||
this.multiValueBind = multiValueBind;
|
||||
this.enableBindLog = enableBindLog();
|
||||
}
|
||||
|
||||
private boolean enableBindLog() {
|
||||
return TransactionManager.SQL_LOGGER.isDebugEnabled()
|
||||
|| TransactionManager.SUM_LOGGER.isDebugEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if bind log is enabled.
|
||||
*/
|
||||
public boolean isEnableBindLog() {
|
||||
return enableBindLog;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* AutoCommit friendly Transaction.
|
||||
@@ -15,17 +14,17 @@ public class AutoCommitJdbcTransaction extends JdbcTransaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkAutoCommit(Connection connection) throws SQLException {
|
||||
protected void checkAutoCommit(Connection connection) {
|
||||
// do nothing as autoCommit
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performRollback() throws SQLException {
|
||||
protected void performRollback() {
|
||||
// do nothing as autoCommit
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCommit() throws SQLException {
|
||||
protected void performCommit() {
|
||||
// do nothing as autoCommit
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,15 @@ public interface DataSourceSupplier {
|
||||
*/
|
||||
DataSource getDataSource();
|
||||
|
||||
/**
|
||||
* Return the read only DataSource to use for the current request.
|
||||
* <p>
|
||||
* This can return null meaning that no read only DataSource (with autoCommit)
|
||||
* is available for use so normal transactions with explicit commit should be used.
|
||||
* </p>
|
||||
*/
|
||||
DataSource getReadOnlyDataSource();
|
||||
|
||||
/**
|
||||
* Return a connection from the DataSource taking into account a tenantId for multi-tenant lazy loading.
|
||||
*
|
||||
@@ -28,6 +37,14 @@ public interface DataSourceSupplier {
|
||||
*/
|
||||
Connection getConnection(Object tenantId) throws SQLException;
|
||||
|
||||
/**
|
||||
* Return a connection from the read only DataSource taking into account a tenantId for multi-tenant lazy loading.
|
||||
*
|
||||
* @param tenantId Most often null but well supplied indicates a multi-tenant lazy loading query
|
||||
* @return the connection to use
|
||||
*/
|
||||
Connection getReadOnlyConnection(Object tenantId) throws SQLException;
|
||||
|
||||
/**
|
||||
* Shutdown the datasource de-registering the JDBC driver if requested.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,591 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebeaninternal.api.SpiProfileTransactionEvent;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.api.TransactionEvent;
|
||||
import io.ebeaninternal.api.TxnProfileEventCodes;
|
||||
import io.ebeaninternal.server.core.PersistDeferredRelationship;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeanservice.docstore.api.DocStoreTransaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Read only transaction expected to use autoCommit connection and for implicit use only.
|
||||
* <p>
|
||||
* This transaction is created implicitly and not expected to be exposed to application code and has
|
||||
* none of the features for supporting inserts, updates and deletes etc (and throws errors if those
|
||||
* persisting features are attempted to be used - which is not expected).
|
||||
* </p>
|
||||
*/
|
||||
class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImplicitReadOnlyTransaction.class);
|
||||
|
||||
private static final String illegalStateMessage = "Transaction is Inactive";
|
||||
|
||||
private static final String notExpectedMessage = "Not expected on read only transaction";
|
||||
|
||||
/**
|
||||
* The status of the transaction.
|
||||
*/
|
||||
protected boolean active;
|
||||
|
||||
/**
|
||||
* The underlying Connection which is expected to use autoCommit such that we avoid the
|
||||
* explicit commit call at the end of the 'transaction' (for performance).
|
||||
*/
|
||||
protected Connection connection;
|
||||
|
||||
/**
|
||||
* Holder of the objects fetched to ensure unique objects are used.
|
||||
*/
|
||||
protected PersistenceContext persistenceContext;
|
||||
|
||||
private Object tenantId;
|
||||
|
||||
private Map<String, Object> userObjects;
|
||||
|
||||
/**
|
||||
* Create without a tenantId.
|
||||
*/
|
||||
ImplicitReadOnlyTransaction(Connection connection) {
|
||||
this.active = true;
|
||||
this.connection = connection;
|
||||
this.persistenceContext = new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a tenantId.
|
||||
*/
|
||||
ImplicitReadOnlyTransaction(Connection connection, Object tenantId) {
|
||||
this(connection);
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long profileOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void profileEvent(SpiProfileTransactionEvent event) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileStream profileStream() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipCache() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkipCache(boolean skipCache) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBeanChange(BeanChange beanChange) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChangeLog(ChangeSet changesRequest) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(TransactionCallback callback) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocStoreBatchSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocStoreBatchSize(int docStoreBatchSize) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocStoreMode getDocStoreMode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocStoreMode(DocStoreMode docStoreMode) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeferred(PersistDeferredRelationship derived) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleteBean(Integer persistingBean) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDeleteBean(Integer persistedBean) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a bean that has already been saved/deleted.
|
||||
*/
|
||||
@Override
|
||||
public boolean isRegisteredDeleteBean(Integer persistingBean) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterBean(Object bean) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a bean that has already been saved. This will
|
||||
* register the bean if it is not already.
|
||||
*/
|
||||
@Override
|
||||
public boolean isRegisteredBean(Object bean) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depth(int diff) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current depth.
|
||||
*/
|
||||
@Override
|
||||
public int depth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markNotQueryOnly() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
if (!isActive()) {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
try {
|
||||
return connection.isReadOnly();
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
if (!isActive()) {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
try {
|
||||
connection.setReadOnly(readOnly);
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isUpdateAllLoadedProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchMode(boolean batchMode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatch(PersistBatch batchMode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatchOnCascade() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getBatchGetGeneratedKeys() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the batchSize specifically set for this transaction or 0.
|
||||
* <p>
|
||||
* Returning 0 implies to use the system wide default batch size.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchSize(int batchSize) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchFlushOnQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request should be batched. Returning false means that
|
||||
* this request should be executed immediately.
|
||||
*/
|
||||
@Override
|
||||
public boolean isBatchThisRequest(PersistRequest.Type type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkBatchEscalationOnCollection() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnCollection() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceException translate(String message, SQLException cause) {
|
||||
return new PersistenceException(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush after completing persist cascade.
|
||||
*/
|
||||
@Override
|
||||
public void flushBatchOnCascade() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnRollback() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchControl getBatchControl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BatchControl to the transaction. This is done once per transaction
|
||||
* on the first persist request.
|
||||
*/
|
||||
@Override
|
||||
public void setBatchControl(BatchControl batchControl) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush any queued persist requests.
|
||||
* <p>
|
||||
* This is general will result in a number of batched PreparedStatements
|
||||
* executing.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatch() {
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the persistence context associated with this transaction.
|
||||
*/
|
||||
@Override
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the persistence context to this transaction.
|
||||
* <p>
|
||||
* This could be considered similar to EJB3 Extended PersistanceContext. In
|
||||
* that you get the PersistanceContext from a transaction, hold onto it, and
|
||||
* then set it back later to a second transaction.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void setPersistenceContext(PersistenceContext context) {
|
||||
if (!isActive()) {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
this.persistenceContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionEvent getEvent() {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this was an explicitly created transaction.
|
||||
*/
|
||||
@Override
|
||||
public boolean isExplicit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogSql() {
|
||||
return TransactionManager.SQL_LOGGER.isDebugEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogSummary() {
|
||||
return TransactionManager.SUM_LOGGER.isDebugEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSql(String msg) {
|
||||
TransactionManager.SQL_LOGGER.debug(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSummary(String msg) {
|
||||
TransactionManager.SUM_LOGGER.debug(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction id.
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTenantId(Object tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying connection for internal use.
|
||||
*/
|
||||
@Override
|
||||
public Connection getInternalConnection() {
|
||||
if (!isActive()) {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying connection for public use.
|
||||
*/
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return getInternalConnection();
|
||||
}
|
||||
|
||||
private void deactivate() {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (Exception ex) {
|
||||
// the connection pool will automatically remove the
|
||||
// connection if it does not pass the test
|
||||
logger.error("Error closing connection", ex);
|
||||
}
|
||||
connection = null;
|
||||
active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a commit, fire callbacks and notify l2 cache etc.
|
||||
* <p>
|
||||
* This leaves the transaction active and expects another commit
|
||||
* to occur later (which closes the underlying connection etc).
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void commitAndContinue() {
|
||||
// do nothing, expect AutoCommit
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the transaction.
|
||||
*/
|
||||
@Override
|
||||
public void commit() {
|
||||
if (!isActive()) {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
// expect AutoCommit so just deactivate / put back into pool
|
||||
deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the transaction is marked as rollback only.
|
||||
*/
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the transaction as rollback only.
|
||||
*/
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
// expect AutoCommit so we can't really support rollbackOnly
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the transaction.
|
||||
*/
|
||||
@Override
|
||||
public void rollback() throws PersistenceException {
|
||||
rollback(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the transaction. If there is a throwable it is logged as the cause
|
||||
* in the transaction log.
|
||||
*/
|
||||
@Override
|
||||
public void rollback(Throwable cause) throws PersistenceException {
|
||||
if (!isActive()) {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
// expect AutoCommit so it really has already committed
|
||||
deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the transaction is active then perform rollback.
|
||||
*/
|
||||
@Override
|
||||
public void end() throws PersistenceException {
|
||||
if (isActive()) {
|
||||
rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the transaction is active.
|
||||
*/
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistCascade() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersistCascade(boolean persistCascade) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocStoreTransaction getDocStoreTransaction() {
|
||||
throw new IllegalStateException(notExpectedMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putUserObject(String name, Object value) {
|
||||
if (userObjects == null) {
|
||||
userObjects = new HashMap<>();
|
||||
}
|
||||
userObjects.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUserObject(String name) {
|
||||
if (userObjects == null) {
|
||||
return null;
|
||||
}
|
||||
return userObjects.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for end(), which enables this class to be used in try-with-resources.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
end();
|
||||
}
|
||||
}
|
||||
@@ -13,37 +13,49 @@ import java.sql.SQLException;
|
||||
*/
|
||||
class TransactionFactoryBasic extends TransactionFactory {
|
||||
|
||||
final DataSourceSupplier dataSourceSupplier;
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
TransactionFactoryBasic(TransactionManager manager, DataSourceSupplier dataSourceSupplier) {
|
||||
super(manager);
|
||||
this.dataSourceSupplier = dataSourceSupplier;
|
||||
this.dataSource = dataSourceSupplier.getDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiTransaction createQueryTransaction(Object tenantId) {
|
||||
return create(0,false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
|
||||
SpiTransaction t = create(profileId, explicit);
|
||||
return setIsolationLevel(t, explicit, isolationLevel);
|
||||
}
|
||||
|
||||
private SpiTransaction create(int profileId, boolean explicit) {
|
||||
Connection c = null;
|
||||
Connection connection = null;
|
||||
try {
|
||||
c = dataSource.getConnection();
|
||||
return manager.createTransaction(profileId, explicit, c, counter.incrementAndGet());
|
||||
connection = dataSource.getConnection();
|
||||
return create(0, false, connection);
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
JdbcClose.close(c);
|
||||
JdbcClose.close(connection);
|
||||
throw ex;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = dataSource.getConnection();
|
||||
SpiTransaction t = create(profileId, explicit, connection);
|
||||
return setIsolationLevel(t, explicit, isolationLevel);
|
||||
} catch (PersistenceException ex) {
|
||||
JdbcClose.close(connection);
|
||||
throw ex;
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private SpiTransaction create(int profileId, boolean explicit, Connection c) {
|
||||
return manager.createTransaction(profileId, explicit, c, counter.incrementAndGet());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.util.JdbcClose;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Aware of read only autoCommit based DataSource.
|
||||
* <p>
|
||||
* This means for implicit query only transactions we can:
|
||||
* - Use the read only DataSource
|
||||
* - Skip explicit commit (as we use AutoCommit instead)
|
||||
* </p>
|
||||
*/
|
||||
class TransactionFactoryBasicWithRead extends TransactionFactoryBasic {
|
||||
|
||||
|
||||
private final DataSource readOnlyDataSource;
|
||||
|
||||
TransactionFactoryBasicWithRead(TransactionManager manager, DataSourceSupplier dataSourceSupplier) {
|
||||
super(manager, dataSourceSupplier);
|
||||
this.readOnlyDataSource = dataSourceSupplier.getReadOnlyDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiTransaction createQueryTransaction(Object tenantId) {
|
||||
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = readOnlyDataSource.getConnection();
|
||||
return new ImplicitReadOnlyTransaction(connection);
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
JdbcClose.close(connection);
|
||||
throw ex;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
/**
|
||||
* Helper to build and return the appropriate TransactionFactory.
|
||||
*/
|
||||
class TransactionFactoryBuilder {
|
||||
|
||||
/**
|
||||
* Build and return based on multi-tenancy and read only DataSource.
|
||||
*/
|
||||
static TransactionFactory build(TransactionManager manager, DataSourceSupplier dataSourceSupplier, CurrentTenantProvider tenantProvider) {
|
||||
|
||||
boolean hasReadOnlyDataSource = dataSourceSupplier.getReadOnlyDataSource() != null;
|
||||
if (tenantProvider == null) {
|
||||
if (hasReadOnlyDataSource) {
|
||||
return new TransactionFactoryBasicWithRead(manager, dataSourceSupplier);
|
||||
} else {
|
||||
return new TransactionFactoryBasic(manager, dataSourceSupplier);
|
||||
}
|
||||
} else {
|
||||
if (hasReadOnlyDataSource) {
|
||||
return new TransactionFactoryTenantWithRead(manager, dataSourceSupplier, tenantProvider);
|
||||
} else {
|
||||
return new TransactionFactoryTenant(manager, dataSourceSupplier, tenantProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ import java.sql.SQLException;
|
||||
*/
|
||||
class TransactionFactoryTenant extends TransactionFactory {
|
||||
|
||||
private final DataSourceSupplier dataSourceSupplier;
|
||||
final DataSourceSupplier dataSourceSupplier;
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
final CurrentTenantProvider tenantProvider;
|
||||
|
||||
TransactionFactoryTenant(TransactionManager manager, DataSourceSupplier dataSourceSupplier, CurrentTenantProvider tenantProvider) {
|
||||
super(manager);
|
||||
@@ -36,19 +36,19 @@ class TransactionFactoryTenant extends TransactionFactory {
|
||||
}
|
||||
|
||||
private SpiTransaction create(int profileId, boolean explicit, Object tenantId) {
|
||||
Connection c = null;
|
||||
Connection connection = null;
|
||||
try {
|
||||
if (tenantId == null) {
|
||||
// tenantId not set (by lazy loading) so get current tenantId
|
||||
tenantId = tenantProvider.currentId();
|
||||
}
|
||||
c = dataSourceSupplier.getConnection(tenantId);
|
||||
SpiTransaction transaction = manager.createTransaction(profileId, explicit, c, counter.incrementAndGet());
|
||||
connection = dataSourceSupplier.getConnection(tenantId);
|
||||
SpiTransaction transaction = manager.createTransaction(profileId, explicit, connection, counter.incrementAndGet());
|
||||
transaction.setTenantId(tenantId);
|
||||
return transaction;
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
JdbcClose.close(c);
|
||||
JdbcClose.close(connection);
|
||||
throw ex;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.util.JdbcClose;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Aware of read only autoCommit based DataSource.
|
||||
* <p>
|
||||
* This means for implicit query only transactions we can:
|
||||
* - Use the read only DataSource
|
||||
* - Skip explicit commit (as we use AutoCommit instead)
|
||||
* </p>
|
||||
*/
|
||||
class TransactionFactoryTenantWithRead extends TransactionFactoryTenant {
|
||||
|
||||
TransactionFactoryTenantWithRead(TransactionManager manager, DataSourceSupplier dataSourceSupplier, CurrentTenantProvider tenantProvider) {
|
||||
super(manager, dataSourceSupplier, tenantProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiTransaction createQueryTransaction(Object tenantId) {
|
||||
|
||||
Connection connection = null;
|
||||
try {
|
||||
if (tenantId == null) {
|
||||
// tenantId not set (by lazy loading) so get current tenantId
|
||||
tenantId = tenantProvider.currentId();
|
||||
}
|
||||
connection = dataSourceSupplier.getReadOnlyConnection(tenantId);
|
||||
return new ImplicitReadOnlyTransaction(connection, tenantId);
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
JdbcClose.close(connection);
|
||||
throw ex;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
|
||||
import io.ebean.event.changelog.ChangeLogListener;
|
||||
@@ -141,11 +141,7 @@ public class TransactionManager {
|
||||
this.onQueryOnly = initOnQueryOnly(options.config.getDatabasePlatform().getOnQueryOnly());
|
||||
|
||||
CurrentTenantProvider tenantProvider = options.config.getCurrentTenantProvider();
|
||||
if (tenantProvider == null) {
|
||||
transactionFactory = new TransactionFactoryBasic(this, dataSourceSupplier);
|
||||
} else {
|
||||
transactionFactory = new TransactionFactoryTenant(this, dataSourceSupplier, tenantProvider);
|
||||
}
|
||||
this.transactionFactory = TransactionFactoryBuilder.build(this, dataSourceSupplier, tenantProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,6 +217,10 @@ public class TransactionManager {
|
||||
return dataSourceSupplier.getDataSource();
|
||||
}
|
||||
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return dataSourceSupplier.getReadOnlyDataSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the type of behavior to use when closing a transaction that was used to query data only.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user