From c4af1d9b23642aeb831026031bfdad84f75bdbe1 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 3 Jul 2023 16:41:22 +1200 Subject: [PATCH] Add query.usingMaster() to explicitly choose master data source When we have a read replica with potential for replication lag, and config a read only DataSource to use that read replica, by default a query executed without a current transaction will use the read replica. We specify usingMaster() when we explicitly want to execute a query and use the master data source. --- .../src/main/java/io/ebean/DtoQuery.java | 12 ++++++ ebean-api/src/main/java/io/ebean/Query.java | 13 +++++++ .../src/main/java/io/ebean/SqlQuery.java | 12 ++++++ .../io/ebeaninternal/api/SpiEbeanServer.java | 3 +- .../java/io/ebeaninternal/api/SpiQuery.java | 5 +++ .../io/ebeaninternal/api/SpiSqlBinding.java | 5 +++ .../server/core/AbstractSqlQueryRequest.java | 2 +- .../server/core/DefaultBeanLoader.java | 2 +- .../server/core/DefaultServer.java | 4 +- .../core/MultiTenantDbCatalogSupplier.java | 6 ++- .../core/MultiTenantDbSchemaSupplier.java | 9 +++-- .../server/core/MultiTenantDbSupplier.java | 4 +- .../server/core/OrmQueryRequest.java | 2 +- .../server/core/SimpleDataSourceProvider.java | 8 +++- .../server/query/DefaultFetchGroupQuery.java | 5 +++ .../server/querydefn/AbstractQuery.java | 1 + .../server/querydefn/DefaultDtoQuery.java | 12 ++++++ .../server/querydefn/DefaultOrmQuery.java | 12 ++++++ .../querydefn/DefaultRelationalQuery.java | 11 ++++++ .../transaction/DataSourceSupplier.java | 5 ++- .../DocStoreTransactionManager.java | 2 +- .../transaction/TransactionFactory.java | 5 ++- .../transaction/TransactionFactoryBasic.java | 7 ++-- .../TransactionFactoryBasicWithRead.java | 4 +- .../transaction/TransactionFactoryTenant.java | 2 +- .../TransactionFactoryTenantWithRead.java | 4 +- .../transaction/TransactionManager.java | 6 +-- .../ebean/xtest/base/DtoQueryFromOrmTest.java | 38 +++++++++++++++++++ .../xtest/internal/api/TDSpiEbeanServer.java | 2 +- .../tests/basic/TestQueryUsingConnection.java | 2 + .../tests/query/sqlquery/SqlQueryTests.java | 13 +++++++ 31 files changed, 185 insertions(+), 33 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index c5e2ee421..5e5ab0870 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -205,4 +205,16 @@ public interface DtoQuery extends CancelableQuery { * Use the explicit transaction to execute the query. */ DtoQuery usingTransaction(Transaction transaction); + + /** + * Ensure that the master DataSource is used if there is a read only data source + * being used (that is using a read replica database potentially with replication lag). + *

+ * When the database is configured with a read-only DataSource via + * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then + * by default when a query is run without an active transaction, it uses the read-only data + * source. We we use {@code usingMaster()} to instead ensure that the query is executed + * against the master data source. + */ + DtoQuery usingMaster(); } diff --git a/ebean-api/src/main/java/io/ebean/Query.java b/ebean-api/src/main/java/io/ebean/Query.java index f7cd56824..f3e39056c 100644 --- a/ebean-api/src/main/java/io/ebean/Query.java +++ b/ebean-api/src/main/java/io/ebean/Query.java @@ -4,6 +4,7 @@ import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; import javax.persistence.NonUniqueResultException; +import javax.sql.DataSource; import java.sql.Connection; import java.sql.Timestamp; import java.util.List; @@ -685,6 +686,18 @@ public interface Query extends CancelableQuery { */ Query usingDatabase(Database database); + /** + * Ensure that the master DataSource is used if there is a read only data source + * being used (that is using a read replica database potentially with replication lag). + *

+ * When the database is configured with a read-only DataSource via + * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then + * by default when a query is run without an active transaction, it uses the read-only data + * source. We we use {@code usingMaster()} to instead ensure that the query is executed + * against the master data source. + */ + Query usingMaster(); + /** * Execute the query returning the list of Id's. *

diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java index 7811382ed..2dd8c3d3c 100644 --- a/ebean-api/src/main/java/io/ebean/SqlQuery.java +++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java @@ -45,6 +45,18 @@ public interface SqlQuery extends Serializable, CancelableQuery { */ SqlQuery usingTransaction(Transaction transaction); + /** + * Ensure that the master DataSource is used if there is a read only data source + * being used (that is using a read replica database potentially with replication lag). + *

+ * When the database is configured with a read-only DataSource via + * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then + * by default when a query is run without an active transaction, it uses the read-only data + * source. We we use {@code usingMaster()} to instead ensure that the query is executed + * against the master data source. + */ + SqlQuery usingMaster(); + /** * Execute the query returning a list. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index cac3c874f..df33048c3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -135,8 +135,9 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio * Create a ServerTransaction for query purposes. * * @param tenantId For multi-tenant lazy loading provide the tenantId to use. + * @param useMaster Set to true when the query should use the master data source. */ - SpiTransaction createReadOnlyTransaction(Object tenantId); + SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster); /** * An event from another server in the cluster used to notify local diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java index f7293d611..315658492 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -235,6 +235,11 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod */ String planLabel(); + /** + * Return true if this query should not use the read only data source. + */ + boolean isUseMaster(); + /** * Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java index 62b93d34c..3f6cfd8fc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlBinding.java @@ -5,6 +5,11 @@ package io.ebeaninternal.api; */ public interface SpiSqlBinding extends SpiCancelableQuery { + /** + * Return true if this query should not use the read only data source. + */ + boolean isUseMaster(); + /** * Return the named or positioned parameters. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java index 685232dec..591fc39f2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java @@ -48,7 +48,7 @@ public abstract class AbstractSqlQueryRequest implements CancelableQuery { transaction = server.currentServerTransaction(); if (transaction == null || !transaction.isActive()) { // create a local readOnly transaction - transaction = server.createReadOnlyTransaction(null); + transaction = server.createReadOnlyTransaction(null, query.isUseMaster()); createdTransaction = true; } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java index a421044b0..c19790909 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultBeanLoader.java @@ -137,7 +137,7 @@ final class DefaultBeanLoader { private List executeQuery(LoadRequest loadRequest, SpiQuery query) { if (onIterateUseExtraTxn && loadRequest.isParentFindIterate()) { // MySql - we need a different transaction to execute the secondary query - SpiTransaction extraTxn = server.createReadOnlyTransaction(query.tenantId()); + SpiTransaction extraTxn = server.createReadOnlyTransaction(query.tenantId(), query.isUseMaster()); try { return server.findList(query, extraTxn); } finally { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index d7b54ff69..42699be59 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -2123,8 +2123,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public SpiTransaction createReadOnlyTransaction(Object tenantId) { - return transactionManager.createReadOnlyTransaction(tenantId); + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { + return transactionManager.createReadOnlyTransaction(tenantId, useMaster); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java index 9ab4b86a5..9f619817a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbCatalogSupplier.java @@ -56,7 +56,10 @@ final class MultiTenantDbCatalogSupplier implements DataSourceSupplier { } @Override - public Connection readOnlyConnection(Object tenantId) throws SQLException { + public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException { + if (readOnly == null || useMaster) { + return catalogDataSource.getConnectionForTenant(tenantId); + } return readOnly.getConnectionForTenant(tenantId); } @@ -103,7 +106,6 @@ final class MultiTenantDbCatalogSupplier implements DataSourceSupplier { */ @Override public Connection getConnection() throws SQLException { - Connection connection = dataSource.getConnection(); connection.setCatalog(tenantCatalog()); return connection; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java index 806a90d05..d6f53f067 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java @@ -56,8 +56,12 @@ final class MultiTenantDbSchemaSupplier implements DataSourceSupplier { } @Override - public Connection readOnlyConnection(Object tenantId) throws SQLException { - return readOnly.getConnectionForTenant(tenantId); + public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException { + if (readOnly == null || useMaster) { + return schemaDataSource.getConnectionForTenant(tenantId); + } else { + return readOnly.getConnectionForTenant(tenantId); + } } @Override @@ -106,7 +110,6 @@ final class MultiTenantDbSchemaSupplier implements DataSourceSupplier { */ @Override public Connection getConnection() throws SQLException { - Connection connection = dataSource.getConnection(); connection.setSchema(tenantSchema()); return connection; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSupplier.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSupplier.java index 5eeefded3..b636be64d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSupplier.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/MultiTenantDbSupplier.java @@ -43,8 +43,8 @@ final class MultiTenantDbSupplier implements DataSourceSupplier { } @Override - public Connection readOnlyConnection(Object tenantId) throws SQLException { - throw new SQLException("Not currently supported"); + public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException { + return dataSourceProvider.dataSource(tenantId).getConnection(); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index 3f0d2ea37..e312771fd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -220,7 +220,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery } else { // create an implicit transaction to execute this query // potentially using read-only DataSource with autoCommit - transaction = server.createReadOnlyTransaction(query.tenantId()); + transaction = server.createReadOnlyTransaction(query.tenantId(), query.isUseMaster()); } createdTransaction = true; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/SimpleDataSourceProvider.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/SimpleDataSourceProvider.java index 0d544045e..6d1452c88 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/SimpleDataSourceProvider.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/SimpleDataSourceProvider.java @@ -41,8 +41,12 @@ final class SimpleDataSourceProvider implements DataSourceSupplier { } @Override - public Connection readOnlyConnection(Object tenantId) throws SQLException { - return readOnlyDataSource.getConnection(); + public Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException { + if (readOnlyDataSource == null || useMaster) { + return dataSource.getConnection(); + } else { + return readOnlyDataSource.getConnection(); + } } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java index 5775fb693..a68a30d92 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java @@ -234,6 +234,11 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } + @Override + public Query usingMaster() { + throw new RuntimeException("EB102: Not allowed on FetchGroup"); + } + @Override public List findIds() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java index 4ad835188..26baf4f17 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/AbstractQuery.java @@ -18,6 +18,7 @@ public class AbstractQuery implements SpiCancelableQuery { private final ReentrantLock lock = new ReentrantLock(); private boolean cancelled; private CancelableQuery cancelableQuery; + protected boolean useMaster; @Override public final void cancel() { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java index e9aa83bab..33178afa1 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultDtoQuery.java @@ -48,6 +48,7 @@ public final class DefaultDtoQuery extends AbstractQuery implements SpiDtoQue this.server = server; this.descriptor = descriptor; this.ormQuery = ormQuery; + this.useMaster = ormQuery.isUseMaster(); this.label = ormQuery.label(); this.profileLocation = ormQuery.profileLocation(); } @@ -88,6 +89,17 @@ public final class DefaultDtoQuery extends AbstractQuery implements SpiDtoQue return this; } + @Override + public DtoQuery usingMaster() { + this.useMaster = true; + return this; + } + + @Override + public boolean isUseMaster() { + return useMaster; + } + @Override public void findEach(Consumer consumer) { server.findDtoEach(this, consumer); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index dce828bcf..9e8a41225 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -715,6 +715,7 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { // forUpdate is NOT copied - see #2762 DefaultOrmQuery copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory); copy.transaction = transaction; + copy.useMaster = useMaster; copy.m2mIncludeJoin = m2mIncludeJoin; copy.profilingListener = profilingListener; copy.profileLocation = profileLocation; @@ -1396,6 +1397,17 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { return this; } + @Override + public Query usingMaster() { + this.useMaster = true; + return this; + } + + @Override + public boolean isUseMaster() { + return useMaster; + } + @Override public final int delete() { return server.delete(this, transaction); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java index 4b7c59625..89f08b726 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java @@ -49,6 +49,17 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq return this; } + @Override + public SqlQuery usingMaster() { + this.useMaster = true; + return this; + } + + @Override + public boolean isUseMaster() { + return useMaster; + } + private void transaction(Transaction transaction) { this.transaction = transaction; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DataSourceSupplier.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DataSourceSupplier.java index cc9c88f53..075774716 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DataSourceSupplier.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DataSourceSupplier.java @@ -43,10 +43,11 @@ public interface DataSourceSupplier { /** * 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 + * @param tenantId Most often null but well supplied indicates a multi-tenant lazy loading query + * @param useMaster When true use the master data source * @return the connection to use */ - Connection readOnlyConnection(Object tenantId) throws SQLException; + Connection readOnlyConnection(Object tenantId, boolean useMaster) throws SQLException; /** * Shutdown the datasource de-registering the JDBC driver if requested. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DocStoreTransactionManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DocStoreTransactionManager.java index a6f58c228..1e32c46ea 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DocStoreTransactionManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DocStoreTransactionManager.java @@ -24,7 +24,7 @@ public final class DocStoreTransactionManager extends TransactionManager { } @Override - public SpiTransaction createReadOnlyTransaction(Object tenantId) { + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { return new DocStoreOnlyTransaction(false, this); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactory.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactory.java index f8eeba38a..7225a2c00 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactory.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactory.java @@ -21,9 +21,10 @@ abstract class TransactionFactory { /** * Return a new query only transaction. * - * @param tenantId The tenantId for lazy loading queries. + * @param tenantId The tenantId for lazy loading queries. + * @param useMaster Explicitly use the master data source rather than read only data source */ - abstract SpiTransaction createReadOnlyTransaction(Object tenantId); + abstract SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster); /** * Return a new transaction. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java index 181646cf8..a0683e37c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasic.java @@ -13,7 +13,7 @@ import java.sql.SQLException; */ class TransactionFactoryBasic extends TransactionFactory { - private final DataSource dataSource; + protected final DataSource dataSource; TransactionFactoryBasic(TransactionManager manager, DataSourceSupplier dataSourceSupplier) { super(manager); @@ -21,12 +21,11 @@ class TransactionFactoryBasic extends TransactionFactory { } @Override - public SpiTransaction createReadOnlyTransaction(Object tenantId) { + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { Connection connection = null; try { connection = dataSource.getConnection(); - return create(false, connection); - + return new ImplicitReadOnlyTransaction(manager, connection); } catch (PersistenceException ex) { JdbcClose.close(connection); throw ex; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasicWithRead.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasicWithRead.java index c0ca58dd0..16a268382 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasicWithRead.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryBasicWithRead.java @@ -26,10 +26,10 @@ final class TransactionFactoryBasicWithRead extends TransactionFactoryBasic { } @Override - public SpiTransaction createReadOnlyTransaction(Object tenantId) { + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { Connection connection = null; try { - connection = readOnlyDataSource.getConnection(); + connection = useMaster ? dataSource.getConnection() : readOnlyDataSource.getConnection(); return new ImplicitReadOnlyTransaction(manager, connection); } catch (PersistenceException ex) { JdbcClose.close(connection); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java index c29a12b42..cfc51370a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenant.java @@ -24,7 +24,7 @@ class TransactionFactoryTenant extends TransactionFactory { } @Override - public SpiTransaction createReadOnlyTransaction(Object tenantId) { + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { return create(false, tenantId); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenantWithRead.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenantWithRead.java index 7c63f685a..cf3992c9e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenantWithRead.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionFactoryTenantWithRead.java @@ -23,14 +23,14 @@ final class TransactionFactoryTenantWithRead extends TransactionFactoryTenant { } @Override - public SpiTransaction createReadOnlyTransaction(Object tenantId) { + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { Connection connection = null; try { if (tenantId == null) { // obtain the tenantId if the DataSource requires it tenantId = dataSourceSupplier.currentTenantId(); } - connection = dataSourceSupplier.readOnlyConnection(tenantId); + connection = dataSourceSupplier.readOnlyConnection(tenantId, useMaster); return new ImplicitReadOnlyTransaction(manager, connection, tenantId); } catch (PersistenceException ex) { JdbcClose.close(connection); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index 7d4293af5..c0b0610fa 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -274,7 +274,7 @@ public class TransactionManager implements SpiTransactionManager { private SpiTransaction createTransaction(TxScope txScope) { if (txScope.isReadonly()) { - return createReadOnlyTransaction(null); + return createReadOnlyTransaction(null, false); } else { return createTransaction(true, txScope.getIsolationLevel()); } @@ -290,8 +290,8 @@ public class TransactionManager implements SpiTransactionManager { /** * Create a new Transaction for query only purposes (can use read only datasource). */ - public SpiTransaction createReadOnlyTransaction(Object tenantId) { - return transactionFactory.createReadOnlyTransaction(tenantId); + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { + return transactionFactory.createReadOnlyTransaction(tenantId, useMaster); } /** diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/DtoQueryFromOrmTest.java b/ebean-test/src/test/java/io/ebean/xtest/base/DtoQueryFromOrmTest.java index 101e8a370..6505e120d 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/base/DtoQueryFromOrmTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/base/DtoQueryFromOrmTest.java @@ -117,6 +117,44 @@ public class DtoQueryFromOrmTest extends BaseTestCase { // assertSql(sql.get(0)).contains("select t0.id, t0.email, concat(t0.last_name, ISO_WEEK(?)) lastName from contact"); // } + @Test + public void asDto_usingMaster() { + ResetBasicData.reset(); + LoggedSql.start(); + + DtoQuery query = DB.find(Contact.class) + .select("id, email") + .where().isNotNull("email") + .asDto(ContactDto.class) + .usingMaster(); + + List dtos = query.findList(); + + assertThat(dtos).isNotEmpty(); + for (ContactDto dto : dtos) { + assertThat(dto.getEmail()).isNotNull(); + } + } + + @Test + public void asDto_usingMaster2() { + ResetBasicData.reset(); + LoggedSql.start(); + + DtoQuery query = DB.find(Contact.class) + .select("id, email") + .usingMaster() + .where().isNotNull("email") + .asDto(ContactDto.class); + + List dtos = query.findList(); + + assertThat(dtos).isNotEmpty(); + for (ContactDto dto : dtos) { + assertThat(dto.getEmail()).isNotNull(); + } + } + @Test public void asDto_withExplicitId() { diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java index 124b04a9d..6d9a75214 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java +++ b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java @@ -215,7 +215,7 @@ public class TDSpiEbeanServer extends TDSpiServer implements SpiEbeanServer { } @Override - public SpiTransaction createReadOnlyTransaction(Object tenantId) { + public SpiTransaction createReadOnlyTransaction(Object tenantId, boolean useMaster) { return null; } diff --git a/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java b/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java index 67191e6bb..f1b30b586 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java +++ b/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java @@ -66,10 +66,12 @@ public class TestQueryUsingConnection extends BaseTestCase { .findCount(); final int otherCount = DB.find(Country.class).findCount(); + final int masterCount = DB.find(Country.class).usingMaster().findCount(); transaction.rollback(); assertThat(count).isEqualTo(otherCount + 1); + assertThat(otherCount).isEqualTo(masterCount); } } diff --git a/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java b/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java index 550c2c44d..54ea4f31d 100644 --- a/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java +++ b/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java @@ -252,6 +252,19 @@ class SqlQueryTests extends BaseTestCase { return null; } + @Test + void queryUsingMaster() { + ResetBasicData.reset(); + + String sql = "select id, name, status from o_customer where name is not null"; + List custDtos = DB.sqlQuery(sql) + .usingMaster() + .mapTo(CUST_MAPPER) + .findList(); + + assertThat(custDtos).isNotEmpty(); + } + @Test void queryUsingTransaction() throws SQLException { ResetBasicData.reset();