From a0f612ed711f71de45ae8797451e2b34d436d255 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 21 Dec 2023 21:24:02 +1300 Subject: [PATCH] Add usingConnection() to DtoQuery and SqlQuery For the case where we have a java.sql.Connection and wish to execute a DtoQuery or SqlQuery using that connection. --- .../src/main/java/io/ebean/DtoQuery.java | 11 ++++++-- ebean-api/src/main/java/io/ebean/Query.java | 4 +-- .../src/main/java/io/ebean/SqlQuery.java | 11 ++++++-- .../server/querydefn/DefaultDtoQuery.java | 8 ++++++ .../querydefn/DefaultRelationalQuery.java | 8 ++++++ .../tests/basic/TestQueryUsingConnection.java | 19 ++++++++++++- .../tests/query/sqlquery/SqlQueryTests.java | 27 +++++++++++++++++++ 7 files changed, 81 insertions(+), 7 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index 5e5ab0870..899133b47 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -3,6 +3,8 @@ package io.ebean; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; +import javax.sql.DataSource; +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -206,14 +208,19 @@ public interface DtoQuery extends CancelableQuery { */ DtoQuery usingTransaction(Transaction transaction); + /** + * Execute the query using the given connection. + */ + DtoQuery usingConnection(Connection connection); + /** * 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 + * say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(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 + * source. 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 3a7b1e920..28cce0506 100644 --- a/ebean-api/src/main/java/io/ebean/Query.java +++ b/ebean-api/src/main/java/io/ebean/Query.java @@ -702,9 +702,9 @@ public interface Query extends CancelableQuery { * 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 + * say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(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 + * source. We use {@code usingMaster()} to instead ensure that the query is executed * against the master data source. */ Query usingMaster(); diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java index 2dd8c3d3c..f3e9f2f8e 100644 --- a/ebean-api/src/main/java/io/ebean/SqlQuery.java +++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java @@ -3,7 +3,9 @@ package io.ebean; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; +import javax.sql.DataSource; import java.io.Serializable; +import java.sql.Connection; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -45,14 +47,19 @@ public interface SqlQuery extends Serializable, CancelableQuery { */ SqlQuery usingTransaction(Transaction transaction); + /** + * Execute the query using the given connection. + */ + SqlQuery usingConnection(Connection connection); + /** * 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 + * say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(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 + * source. We use {@code usingMaster()} to instead ensure that the query is executed * against the master data source. */ SqlQuery usingMaster(); 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 22444a7c5..7d3b571a1 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 @@ -9,8 +9,10 @@ import io.ebeaninternal.api.*; import io.ebeaninternal.server.dto.DtoBeanDescriptor; import io.ebeaninternal.server.dto.DtoMappingRequest; import io.ebeaninternal.server.dto.DtoQueryPlan; +import io.ebeaninternal.server.transaction.ExternalJdbcTransaction; import javax.annotation.Nullable; +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -86,6 +88,12 @@ public final class DefaultDtoQuery extends AbstractQuery implements SpiDtoQue return this; } + @Override + public DtoQuery usingConnection(Connection connection) { + this.transaction = new ExternalJdbcTransaction(connection); + return this; + } + @Override public DtoQuery usingMaster() { this.useMaster = true; 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 e94fcc71f..d760a4cff 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 @@ -7,7 +7,9 @@ import io.ebeaninternal.api.BindParams; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlQuery; import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.server.transaction.ExternalJdbcTransaction; +import java.sql.Connection; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -50,6 +52,12 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq return this; } + @Override + public SqlQuery usingConnection(Connection connection) { + this.transaction = new ExternalJdbcTransaction(connection); + return this; + } + @Override public SqlQuery usingMaster() { this.useMaster = true; 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 f1b30b586..039c6e617 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java +++ b/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java @@ -73,9 +73,26 @@ public class TestQueryUsingConnection extends BaseTestCase { assertThat(count).isEqualTo(otherCount + 1); assertThat(otherCount).isEqualTo(masterCount); } - } + @IgnorePlatform({Platform.SQLSERVER, Platform.COCKROACH}) + @Test + public void dtoQueryUsingConnection() { + ResetBasicData.reset(); + + try (Transaction transaction = DB.createTransaction()) { + final CountryDto dto = DB.findDto(CountryDto.class, "select code, name from o_country where code=?") + .usingConnection(transaction.connection()) + .setParameter("NZ") + .findOne(); + + assertThat(dto).isNotNull(); + assertEquals("NZ", dto.code); + transaction.rollback(); + } + } + + public static class CountryDto { final String code; final String name; 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 54ea4f31d..d2b527461 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 @@ -265,6 +265,33 @@ class SqlQueryTests extends BaseTestCase { assertThat(custDtos).isNotEmpty(); } + @Test + void queryUsingConnection() throws SQLException { + ResetBasicData.reset(); + boolean h2 = isH2(); + + String sql = h2 ? "select id, name||session_id(), status from o_customer where name is not null" + : "select id, name, status from o_customer where name is not null"; + + try (Transaction txn = DB.createTransaction()) { + String h2SessionId = h2 ? h2SessionId(txn) : null; + + AtomicInteger counter = new AtomicInteger(); + DB.sqlQuery(sql) + .usingConnection(txn.connection()) + .mapTo(CUST_MAPPER) + .findEach(custDto -> { + counter.incrementAndGet(); + assertThat(custDto.name).isNotNull(); + if (h2) { + assertThat(custDto.name).endsWith(h2SessionId); + } + }); + + assertThat(counter.get()).isGreaterThan(0); + } + } + @Test void queryUsingTransaction() throws SQLException { ResetBasicData.reset();