+ * Note that the DataSource is expected to use AutoCommit true mode avoiding the need + * for explicit commit (or rollback). + *
+ *+ * 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. + *
+ */ + 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. + *+ * This is only used if autoReadOnlyDataSource is true. + *
+ *+ * 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. + *
+ */ + 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); diff --git a/src/main/java/io/ebean/plugin/SpiServer.java b/src/main/java/io/ebean/plugin/SpiServer.java index 956275646..4eb11837e 100644 --- a/src/main/java/io/ebean/plugin/SpiServer.java +++ b/src/main/java/io/ebean/plugin/SpiServer.java @@ -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(); + } diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java index 9d96b7ef1..d257a4056 100644 --- a/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -98,17 +98,30 @@ public interface SpiQuery+ * 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. + *
+ */ + 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. */ diff --git a/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java new file mode 100644 index 000000000..e17444252 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java @@ -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. + *+ * 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). + *
+ */ +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+ * Returning 0 implies to use the system wide default batch size. + *
+ */ + @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. + *+ * This is general will result in a number of batched PreparedStatements + * executing. + *
+ */ + @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. + *+ * 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. + *
+ */ + @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. + *+ * This leaves the transaction active and expects another commit + * to occur later (which closes the underlying connection etc). + *
+ */ + @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(); + } +} diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java index a10f3097c..d93c07753 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java @@ -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()); + } + } diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasicWithRead.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasicWithRead.java new file mode 100644 index 000000000..920fd05de --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasicWithRead.java @@ -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. + *+ * This means for implicit query only transactions we can: + * - Use the read only DataSource + * - Skip explicit commit (as we use AutoCommit instead) + *
+ */ +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); + } + } +} diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBuilder.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBuilder.java new file mode 100644 index 000000000..590fcec63 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBuilder.java @@ -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); + } + } + } +} diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java index b82ddc5c8..68a6ed579 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java @@ -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) { diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenantWithRead.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenantWithRead.java new file mode 100644 index 000000000..15b26ca34 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenantWithRead.java @@ -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. + *+ * This means for implicit query only transactions we can: + * - Use the read only DataSource + * - Skip explicit commit (as we use AutoCommit instead) + *
+ */ +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); + } + } +} diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index 907ad1f63..126820fef 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -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. */ diff --git a/src/test/java/io/ebean/config/ServerConfigTest.java b/src/test/java/io/ebean/config/ServerConfigTest.java index d9e2918bb..dbeb9cdb8 100644 --- a/src/test/java/io/ebean/config/ServerConfigTest.java +++ b/src/test/java/io/ebean/config/ServerConfigTest.java @@ -1,6 +1,7 @@ package io.ebean.config; import io.ebean.annotation.PersistBatch; +import org.avaje.datasource.DataSourceConfig; import org.junit.Test; import java.util.Properties; @@ -27,6 +28,9 @@ public class ServerConfigTest { ServerConfig serverConfig = new ServerConfig(); serverConfig.setPersistBatch(PersistBatch.NONE); serverConfig.setPersistBatchOnCascade(PersistBatch.NONE); + serverConfig.setAutoReadOnlyDataSource(false); + serverConfig.setReadOnlyDataSource(null); + serverConfig.setReadOnlyDataSourceConfig(new DataSourceConfig()); Properties props = new Properties(); props.setProperty("persistBatch", "INSERT"); @@ -38,10 +42,13 @@ public class ServerConfigTest { props.setProperty("backgroundExecutorSchedulePoolSize", "4"); props.setProperty("dbOffline", "true"); props.setProperty("jsonDateTime", "ISO8601"); + props.setProperty("autoReadOnlyDataSource", "true"); serverConfig.loadFromProperties(props); assertTrue(serverConfig.isDbOffline()); + assertTrue(serverConfig.isAutoReadOnlyDataSource()); + assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatch()); assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatchOnCascade()); assertEquals(ServerConfig.DbUuid.BINARY, serverConfig.getDbTypeConfig().getDbUuid()); diff --git a/src/test/java/org/tests/basic/TestQueryWhereBetween.java b/src/test/java/org/tests/basic/TestQueryWhereBetween.java index 042ce5756..aef27680e 100644 --- a/src/test/java/org/tests/basic/TestQueryWhereBetween.java +++ b/src/test/java/org/tests/basic/TestQueryWhereBetween.java @@ -3,15 +3,19 @@ package org.tests.basic; import io.ebean.BaseTestCase; import io.ebean.Ebean; import io.ebean.Query; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; -import org.junit.Assert; -import org.junit.Test; import java.sql.Timestamp; public class TestQueryWhereBetween extends BaseTestCase { + long statCount; + long statTotal; + @Test public void testCountOrderBy() { @@ -27,4 +31,69 @@ public class TestQueryWhereBetween extends BaseTestCase { String sql = query.getGeneratedSql(); Assert.assertTrue(sql.contains("between t0.cretime and t0.updtime")); } + + @Ignore + @Test + public void doStuff() { + + someLoop(3, true); + + int loop = 30000; + someLoop(loop, true); + someLoop(loop, true); + someLoop(loop, true); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + someLoop(loop); + + long avg = statTotal / statCount; + System.out.println("avg "+avg); + } + + private void someLoop(int loop) { + someLoop(loop, false); + } + private void someLoop(int loop, boolean warm) { + + long start = System.currentTimeMillis(); + for (int i = 0; i < loop; i++) { + someQuery(); + } + + long exe = System.currentTimeMillis() - start; + System.out.println("exe: "+exe); + + if (!warm) { + statTotal += exe; + statCount++; + } + } + + private void someQuery() { + + Timestamp t = new Timestamp(System.currentTimeMillis()); + + Query