diff --git a/src/main/java/io/ebean/ExpressionList.java b/src/main/java/io/ebean/ExpressionList.java index be78a5738..4480251e1 100644 --- a/src/main/java/io/ebean/ExpressionList.java +++ b/src/main/java/io/ebean/ExpressionList.java @@ -9,6 +9,7 @@ import io.ebean.search.TextSimple; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.persistence.NonUniqueResultException; +import java.sql.Connection; import java.sql.Timestamp; import java.util.Collection; import java.util.List; @@ -186,6 +187,16 @@ public interface ExpressionList { */ Query setIncludeSoftDeletes(); + /** + * Execute the query using the given transaction. + */ + Query usingTransaction(Transaction transaction); + + /** + * Execute the query using the given connection. + */ + Query usingConnection(Connection connection); + /** * Execute as a delete query deleting the 'root level' beans that match the predicates * in the query. diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java index 9bc8508ca..218f12a43 100644 --- a/src/main/java/io/ebean/Query.java +++ b/src/main/java/io/ebean/Query.java @@ -3,6 +3,7 @@ package io.ebean; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.persistence.NonUniqueResultException; +import java.sql.Connection; import java.sql.Timestamp; import java.util.List; import java.util.Map; @@ -631,6 +632,16 @@ public interface Query { */ Query apply(FetchPath fetchPath); + /** + * Execute the query using the given transaction. + */ + Query usingTransaction(Transaction transaction); + + /** + * Execute the query using the given connection. + */ + Query usingConnection(Connection connection); + /** * Execute the query returning the list of Id's. *

diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index e757214bb..35a388094 100644 --- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -36,6 +36,7 @@ import io.ebeaninternal.api.SpiJunction; import io.ebeaninternal.server.deploy.BeanDescriptor; import java.io.IOException; +import java.sql.Connection; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; @@ -358,6 +359,16 @@ public class DefaultExpressionList implements SpiExpressionList { return query.apply(fetchPath); } + @Override + public Query usingTransaction(Transaction transaction) { + return query.usingTransaction(transaction); + } + + @Override + public Query usingConnection(Connection connection) { + return query.usingConnection(connection); + } + @Override public int delete() { return query.delete(); diff --git a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index c71f82647..32fb51f20 100644 --- a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -34,6 +34,7 @@ import io.ebeaninternal.api.SpiJunction; import io.ebeaninternal.server.deploy.BeanDescriptor; import java.io.IOException; +import java.sql.Connection; import java.sql.Timestamp; import java.util.Collection; import java.util.List; @@ -330,6 +331,16 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression throw new IllegalStateException("filterMany not allowed on Junction expression list"); } + @Override + public Query usingTransaction(Transaction transaction) { + return exprList.usingTransaction(transaction); + } + + @Override + public Query usingConnection(Connection connection) { + return exprList.usingConnection(connection); + } + @Override public int delete() { return exprList.delete(); diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index e5e1639a7..49140b07e 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -44,6 +44,7 @@ import io.ebeaninternal.api.SpiExpressionValidation; import io.ebeaninternal.api.SpiNamedParam; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuerySecondary; +import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.autotune.ProfilingListener; import io.ebeaninternal.server.core.SpiOrmQueryRequest; import io.ebeaninternal.server.deploy.BeanDescriptor; @@ -56,11 +57,12 @@ import io.ebeaninternal.server.expression.SimpleExpression; import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.query.NativeSqlQueryPlanKey; import io.ebeaninternal.server.rawsql.SpiRawSql; +import io.ebeaninternal.server.transaction.ExternalJdbcTransaction; import javax.persistence.PersistenceException; +import java.sql.Connection; import java.sql.Timestamp; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -91,6 +93,8 @@ public class DefaultOrmQuery implements SpiQuery { private final ExpressionFactory expressionFactory; + private SpiTransaction transaction; + /** * For lazy loading of ManyToMany we need to add a join to the intersection table. This is that * join to the intersection table. @@ -812,6 +816,7 @@ public class DefaultOrmQuery implements SpiQuery { @Override public DefaultOrmQuery copy(SpiEbeanServer server) { DefaultOrmQuery copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory); + copy.transaction = transaction; copy.m2mIncludeJoin = m2mIncludeJoin; copy.profilingListener = profilingListener; copy.profileLocation = profileLocation; @@ -1431,9 +1436,21 @@ public class DefaultOrmQuery implements SpiQuery { return this; } + @Override + public Query usingTransaction(Transaction transaction) { + this.transaction = (SpiTransaction)transaction; + return this; + } + + @Override + public Query usingConnection(Connection connection) { + this.transaction = new ExternalJdbcTransaction(connection); + return this; + } + @Override public int delete() { - return server.delete(this, null); + return server.delete(this, transaction); } @Override @@ -1443,7 +1460,7 @@ public class DefaultOrmQuery implements SpiQuery { @Override public int update() { - return server.update(this, null); + return server.update(this, transaction); } @Override @@ -1456,12 +1473,12 @@ public class DefaultOrmQuery implements SpiQuery { // a copy of this query is made in the server // as the query needs to modified (so we modify // the copy rather than this query instance) - return server.findIds(this, null); + return server.findIds(this, transaction); } @Override public boolean exists() { - return server.exists(this, null); + return server.exists(this, transaction); } @Override @@ -1469,28 +1486,28 @@ public class DefaultOrmQuery implements SpiQuery { // a copy of this query is made in the server // as the query needs to modified (so we modify // the copy rather than this query instance) - return server.findCount(this, null); + return server.findCount(this, transaction); } @Override public void findEachWhile(Predicate consumer) { - server.findEachWhile(this, consumer, null); + server.findEachWhile(this, consumer, transaction); } @Override public void findEach(Consumer consumer) { - server.findEach(this, consumer, null); + server.findEach(this, consumer, transaction); } @Override public QueryIterator findIterate() { - return server.findIterate(this, null); + return server.findIterate(this, transaction); } @Override public List> findVersions() { this.temporalMode = TemporalMode.VERSIONS; - return server.findVersions(this, null); + return server.findVersions(this, transaction); } @Override @@ -1501,28 +1518,28 @@ public class DefaultOrmQuery implements SpiQuery { this.temporalMode = TemporalMode.VERSIONS; this.versionsStart = start; this.versionsEnd = end; - return server.findVersions(this, null); + return server.findVersions(this, transaction); } @Override public List findList() { - return server.findList(this, null); + return server.findList(this, transaction); } @Override public Set findSet() { - return server.findSet(this, null); + return server.findSet(this, transaction); } @Override public Map findMap() { - return server.findMap(this, null); + return server.findMap(this, transaction); } @Override @SuppressWarnings("unchecked") public List findSingleAttributeList() { - return (List) server.findSingleAttributeList(this, null); + return (List) server.findSingleAttributeList(this, transaction); } @Override @@ -1533,32 +1550,32 @@ public class DefaultOrmQuery implements SpiQuery { @Override public T findOne() { - return server.findOne(this, null); + return server.findOne(this, transaction); } @Override public Optional findOneOrEmpty() { - return server.findOneOrEmpty(this, null); + return server.findOneOrEmpty(this, transaction); } @Override public FutureIds findFutureIds() { - return server.findFutureIds(this, null); + return server.findFutureIds(this, transaction); } @Override public FutureList findFutureList() { - return server.findFutureList(this, null); + return server.findFutureList(this, transaction); } @Override public FutureRowCount findFutureCount() { - return server.findFutureCount(this, null); + return server.findFutureCount(this, transaction); } @Override public PagedList findPagedList() { - return server.findPagedList(this, null); + return server.findPagedList(this, transaction); } /** diff --git a/src/test/java/org/tests/basic/TestQueryForUpdate.java b/src/test/java/org/tests/basic/TestQueryForUpdate.java index f5013e77e..b6f62cac7 100644 --- a/src/test/java/org/tests/basic/TestQueryForUpdate.java +++ b/src/test/java/org/tests/basic/TestQueryForUpdate.java @@ -2,8 +2,8 @@ package org.tests.basic; import io.ebean.AcquireLockException; import io.ebean.BaseTestCase; -import io.ebean.Ebean; -import io.ebean.EbeanServer; +import io.ebean.DB; +import io.ebean.Database; import io.ebean.Query; import io.ebean.Transaction; import io.ebean.annotation.ForPlatform; @@ -30,7 +30,7 @@ public class TestQueryForUpdate extends BaseTestCase { ResetBasicData.reset(); - Query query = Ebean.find(Customer.class) + Query query = DB.find(Customer.class) .forUpdate() .order().desc("id"); @@ -49,16 +49,16 @@ public class TestQueryForUpdate extends BaseTestCase { public void testForUpdate_when_alreadyInPC() { EBasic basic = new EBasic("test PC cache"); - Ebean.save(basic); + DB.save(basic); - try (Transaction transaction = Ebean.beginTransaction()) { + try (Transaction transaction = DB.beginTransaction()) { LoggedSqlCollector.start(); - EBasic basic0 = Ebean.find(EBasic.class, basic.getId()); + EBasic basic0 = DB.find(EBasic.class, basic.getId()); assertThat(basic0).isNotNull(); - EBasic basic1 = Ebean.find(EBasic.class) + EBasic basic1 = DB.find(EBasic.class) .setId( basic.getId()) .forUpdate() .findOne(); @@ -86,7 +86,7 @@ public class TestQueryForUpdate extends BaseTestCase { ResetBasicData.reset(); - Query query = Ebean.find(Customer.class) + Query query = DB.find(Customer.class) .forUpdateNoWait() .order().desc("id"); @@ -110,10 +110,10 @@ public class TestQueryForUpdate extends BaseTestCase { ResetBasicData.reset(); - EbeanServer server = Ebean.getDefaultServer(); + Database server = DB.getDefault(); - try (Transaction txn = Ebean.beginTransaction()) { - Query query = Ebean.find(Customer.class) + try (Transaction txn = DB.beginTransaction()) { + Query query = DB.find(Customer.class) .forUpdateNoWait() .setMaxRows(1) .order().desc("id"); @@ -131,12 +131,12 @@ public class TestQueryForUpdate extends BaseTestCase { // row is locked and we can't acquire it try (Transaction txn2 = server.createTransaction()) { logger.info("... attempt another acquire using 2nd transaction"); - Query query2 = - server.find(Customer.class) - .where().idEq(first.getId()) - .forUpdateNoWait(); + server.find(Customer.class) + .where().idEq(first.getId()) + .forUpdateNoWait() + .usingTransaction(txn2) + .findOne(); - server.extended().findOne(query2, txn2); assertTrue(false); // never get here } catch (AcquireLockException e) { logger.info("... got AcquireLockException " + e); diff --git a/src/test/java/org/tests/basic/TestQueryUsingConnection.java b/src/test/java/org/tests/basic/TestQueryUsingConnection.java new file mode 100644 index 000000000..656af123f --- /dev/null +++ b/src/test/java/org/tests/basic/TestQueryUsingConnection.java @@ -0,0 +1,63 @@ +package org.tests.basic; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.Transaction; +import org.junit.Test; +import org.tests.model.basic.Country; +import org.tests.model.basic.ResetBasicData; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestQueryUsingConnection extends BaseTestCase { + + @Test + public void usingConnection() throws SQLException { + + ResetBasicData.reset(); + + DataSource dataSource = DB.getDefault().getPluginApi().getDataSource(); + + try (Connection connection = dataSource.getConnection()) { + + int count = DB.find(Country.class) + .usingConnection(connection) + .findCount(); + + assertThat(count).isGreaterThan(0); + } + } + + @Test + public void usingTransaction() { + + ResetBasicData.reset(); + + try (Transaction transaction = DB.getDefault().createTransaction()) { + + Transaction current = Transaction.current(); + assertThat(current).isNull(); + + Country x = new Country(); + x.setCode("xx"); + x.setName("WillRollThisBack"); + + DB.getDefault().insert(x, transaction); + + final int count = DB.find(Country.class) + .usingTransaction(transaction) + .findCount(); + + final int otherCount = DB.find(Country.class).findCount(); + + transaction.rollback(); + + assertThat(count).isEqualTo(otherCount + 1); + } + + } +} diff --git a/src/test/java/org/tests/transaction/TestAutoCommitDataSource.java b/src/test/java/org/tests/transaction/TestAutoCommitDataSource.java index 46006426b..ac737164c 100644 --- a/src/test/java/org/tests/transaction/TestAutoCommitDataSource.java +++ b/src/test/java/org/tests/transaction/TestAutoCommitDataSource.java @@ -1,13 +1,13 @@ package org.tests.transaction; import io.ebean.BaseTestCase; -import io.ebean.EbeanServer; -import io.ebean.EbeanServerFactory; +import io.ebean.Database; +import io.ebean.DatabaseFactory; import io.ebean.Query; import io.ebean.Transaction; import io.ebean.annotation.ForPlatform; import io.ebean.annotation.Platform; -import io.ebean.config.ServerConfig; +import io.ebean.config.DatabaseConfig; import io.ebean.config.properties.PropertiesLoader; import io.ebean.datasource.DataSourceConfig; import io.ebean.datasource.DataSourcePool; @@ -42,7 +42,7 @@ public class TestAutoCommitDataSource extends BaseTestCase { assertTrue(connection.getAutoCommit()); connection.close(); - ServerConfig config = new ServerConfig(); + DatabaseConfig config = new DatabaseConfig(); config.setName("h2autocommit"); config.loadFromProperties(); config.setDataSource(pool); @@ -57,9 +57,9 @@ public class TestAutoCommitDataSource extends BaseTestCase { config.setAutoCommitMode(true); - EbeanServer ebeanServer = EbeanServerFactory.create(config); + Database database = DatabaseFactory.create(config); - Query query = ebeanServer.find(UTMaster.class); + Query query = database.find(UTMaster.class); List details = query.findList(); assertEquals(0, details.size()); @@ -68,28 +68,24 @@ public class TestAutoCommitDataSource extends BaseTestCase { UTMaster bean3 = new UTMaster("three3"); // use a different transaction to do final query check - try (Transaction otherTxn = ebeanServer.createTransaction()) { - - Transaction txn = ebeanServer.beginTransaction(); - try { + try (Transaction otherTxn = database.createTransaction()) { + try (Transaction txn = database.beginTransaction()) { assertTrue(txn.getConnection().getAutoCommit()); - ebeanServer.save(bean1); - ebeanServer.save(bean2); + database.save(bean1); + database.save(bean2); - Query query2 = ebeanServer.find(UTMaster.class); - details = ebeanServer.extended().findList(query2, otherTxn); + details = database.find(UTMaster.class) + .usingTransaction(otherTxn) + .findList(); assertEquals(2, details.size()); - ebeanServer.save(bean3); - + database.save(bean3); txn.rollback(); - - } finally { - txn.end(); } - Query query3 = ebeanServer.find(UTMaster.class); - details = ebeanServer.extended().findList(query3, otherTxn); + details = database.find(UTMaster.class) + .usingTransaction(otherTxn) + .findList(); assertEquals(3, details.size()); } diff --git a/src/test/java/org/tests/transaction/TestExplicitTransactionMode.java b/src/test/java/org/tests/transaction/TestExplicitTransactionMode.java index 66da8f645..23fa375d3 100644 --- a/src/test/java/org/tests/transaction/TestExplicitTransactionMode.java +++ b/src/test/java/org/tests/transaction/TestExplicitTransactionMode.java @@ -1,14 +1,14 @@ package org.tests.transaction; import io.ebean.BaseTestCase; -import io.ebean.EbeanServer; -import io.ebean.EbeanServerFactory; +import io.ebean.Database; +import io.ebean.DatabaseFactory; import io.ebean.Query; import io.ebean.Transaction; import io.ebean.annotation.ForPlatform; import io.ebean.annotation.Platform; +import io.ebean.config.DatabaseConfig; import io.ebean.config.JsonConfig; -import io.ebean.config.ServerConfig; import io.ebean.config.properties.PropertiesLoader; import io.ebean.datasource.DataSourceConfig; import io.ebean.datasource.DataSourcePool; @@ -46,7 +46,7 @@ public class TestExplicitTransactionMode extends BaseTestCase { assertTrue(connection.getAutoCommit()); connection.close(); - ServerConfig config = new ServerConfig(); + DatabaseConfig config = new DatabaseConfig(); config.setName("h2autocommit2"); config.loadFromProperties(); config.setDataSource(pool); @@ -61,44 +61,44 @@ public class TestExplicitTransactionMode extends BaseTestCase { config.setDdlExtra(false); config.addClass(ScalarTypeLocalDateAsString.class); - EbeanServer ebeanServer = EbeanServerFactory.create(config); + Database database = DatabaseFactory.create(config); - testJsonScalarType(ebeanServer); + testJsonScalarType(database); - Query query = ebeanServer.find(UTMaster.class); + Query query = database.find(UTMaster.class); List details = query.findList(); assertEquals(0, details.size()); UTMaster bean0 = new UTMaster("one0"); - Transaction txn0 = ebeanServer.beginTransaction(); + Transaction txn0 = database.beginTransaction(); try { - ebeanServer.save(bean0); + database.save(bean0); txn0.rollback(); } finally { txn0.end(); } // rollback as expected - assertEquals(0, ebeanServer.find(UTMaster.class).findCount()); + assertEquals(0, database.find(UTMaster.class).findCount()); UTMaster bean1 = new UTMaster("one1"); UTMaster bean2 = new UTMaster("two2"); UTMaster bean3 = new UTMaster("three3"); // use a different transaction to do final query check - try (Transaction otherTxn = ebeanServer.createTransaction()) { + try (Transaction otherTxn = database.createTransaction()) { - Transaction txn = ebeanServer.beginTransaction(); + Transaction txn = database.beginTransaction(); try { - ebeanServer.save(bean1); - ebeanServer.save(bean2); + database.save(bean1); + database.save(bean2); // not visible in other transaction - Query query2 = ebeanServer.find(UTMaster.class); - details = ebeanServer.extended().findList(query2, otherTxn); + Query query2 = database.find(UTMaster.class); + details = database.extended().findList(query2, otherTxn); assertEquals(0, details.size()); - ebeanServer.save(bean3); + database.save(bean3); txn.commit(); @@ -107,13 +107,15 @@ public class TestExplicitTransactionMode extends BaseTestCase { } // commit as expected - Query query3 = ebeanServer.find(UTMaster.class); - details = ebeanServer.extended().findList(query3, otherTxn); + details = database.find(UTMaster.class) + .usingTransaction(otherTxn) + .findList(); + assertEquals(3, details.size()); } } - private void testJsonScalarType(EbeanServer ebeanServer) { + private void testJsonScalarType(Database ebeanServer) { UTMaster bean = new UTMaster("one1"); bean.setDate(LocalDate.of(2019, 04, 20));