diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index 8f2ebe0d0..bd3016a60 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -383,6 +383,15 @@ public interface ExpressionList { */ List findSingleAttributeList(); + /** + * Executes the query returning a set of values for a single property. + * + * This can be used to cache sets. + * + * @return a HashSet of values for the selegted property + */ + Set findSingleAttributeSet(); + /** * Execute a query returning a single value of a single property/column. *
{@code
diff --git a/ebean-api/src/main/java/io/ebean/ExtendedServer.java b/ebean-api/src/main/java/io/ebean/ExtendedServer.java
index 0088e9c2b..d6dc9af97 100644
--- a/ebean-api/src/main/java/io/ebean/ExtendedServer.java
+++ b/ebean-api/src/main/java/io/ebean/ExtendedServer.java
@@ -368,6 +368,11 @@ public interface ExtendedServer {
    */
    List findSingleAttributeList(Query query, Transaction transaction);
 
+  /**
+   * Execute the query returning a hashset of values for a single property.
+   */
+   Set findSingleAttributeSet(Query query, Transaction transaction);
+
   /**
    * Execute the query returning at most one entity bean or null (if no matching
    * bean is found).
diff --git a/ebean-api/src/main/java/io/ebean/Query.java b/ebean-api/src/main/java/io/ebean/Query.java
index 400c9775e..dafabe37f 100644
--- a/ebean-api/src/main/java/io/ebean/Query.java
+++ b/ebean-api/src/main/java/io/ebean/Query.java
@@ -925,6 +925,11 @@ public interface Query extends CancelableQuery {
    */
    List findSingleAttributeList();
 
+  /**
+   * Execute the query returning a hashset of values for a single property.
+   */
+   Set findSingleAttributeSet();
+
   /**
    * Execute a query returning a single value of a single property/column.
    * 

diff --git a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java index b93be5cc8..b2282b468 100644 --- a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java +++ b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java @@ -143,6 +143,7 @@ public class BaseQueryTuner { switch (type) { case COUNT: case ATTRIBUTE: + case ATTRIBUTE_SET: case ID_LIST: case UPDATE: case DELETE: 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 ef939a8f5..4c5c35abd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -92,6 +92,11 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod */ ATTRIBUTE(FIND_ATTRIBUTE, "findAttribute", false, false), + /** + * Find single attribute set. + */ + ATTRIBUTE_SET(FIND_ATTRIBUTE_SET, "findAttributeSet", false, false), + /** * Find rowCount. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/TxnProfileEventCodes.java b/ebean-core/src/main/java/io/ebeaninternal/api/TxnProfileEventCodes.java index c4d1ea0be..8c492d77b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/TxnProfileEventCodes.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/TxnProfileEventCodes.java @@ -28,6 +28,7 @@ public interface TxnProfileEventCodes { String FIND_ID_LIST = "fi"; String FIND_EXISTS = "ex"; String FIND_ATTRIBUTE = "fa"; + String FIND_ATTRIBUTE_SET = "fas"; String FIND_COUNT = "fc"; String FIND_SUBQUERY = "fs"; 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 d188f9539..3b96eb964 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 @@ -364,7 +364,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { startQueryPlanCapture(); } - + @Override public void start() { for (Plugin plugin : serverPlugins) { @@ -1189,7 +1189,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } try { request.initTransIfRequired(); - return request.findSingleAttributeList(); + return request.findSingleAttributeCollection(new ArrayList<>()); + } finally { + request.endTransIfRequired(); + } + } + + @Override + @SuppressWarnings("unchecked") + public Set findSingleAttributeSet(Query query, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.ATTRIBUTE_SET, query, transaction); + Object result = request.getFromQueryCache(); + if (result != null) { + return (Set) result; + } + try { + request.initTransIfRequired(); + return request.findSingleAttributeCollection(new LinkedHashSet<>()); } finally { request.endTransIfRequired(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java index b080e4a29..518b3e7cd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java @@ -6,6 +6,7 @@ import io.ebean.bean.BeanCollection; import javax.persistence.PersistenceException; import java.sql.SQLException; +import java.util.Collection; import java.util.List; /** @@ -29,11 +30,11 @@ public interface OrmQueryEngine { BeanCollection findMany(OrmQueryRequest request); /** - * Execute the findSingleAttributeList query. + * Execute the findSingleAttributeCollection query. */ - List findSingleAttributeList(OrmQueryRequest request); + > A findSingleAttributeCollection(OrmQueryRequest request, A collection); - /** + /** * Execute the findVersions query. */ List> findVersions(OrmQueryRequest request); 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 834ec2a04..ebe0ca971 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 @@ -431,8 +431,8 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery } @Override - public List findSingleAttributeList() { - return queryEngine.findSingleAttributeList(this); + public > A findSingleAttributeCollection(A collection) { + return queryEngine.findSingleAttributeCollection(this, collection); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java index 3c7139476..1591cf4c6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -7,6 +7,7 @@ import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeanservice.docstore.api.DocQueryRequest; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -120,9 +121,9 @@ public interface SpiOrmQueryRequest extends BeanQueryRequest, DocQueryRequ Map findMap(); /** - * Execute the findSingleAttributeList query. + * Execute the findSingleAttributeCollection query. */ - List findSingleAttributeList(); + > A findSingleAttributeCollection(A collection); /** * Execute returning the ResultSet. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index 4bf66743b..c3abfef86 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -467,6 +467,11 @@ public class DefaultExpressionList implements SpiExpressionList { return query.findSingleAttributeList(); } + @Override + public Set findSingleAttributeSet() { + return query.findSingleAttributeSet(); + } + @Override public boolean exists() { return query.exists(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index ee9245356..6984e5c9a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -464,6 +464,11 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.findSingleAttributeList(); } + @Override + public Set findSingleAttributeSet() { + return exprList.findSingleAttributeSet(); + } + @Override public PagedList findPagedList() { return exprList.findPagedList(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java index 6cc408c35..12f56f2de 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java @@ -24,11 +24,7 @@ import org.slf4j.Logger; import javax.persistence.PersistenceException; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Handles the Object Relational fetching. @@ -91,16 +87,16 @@ public final class CQueryEngine { /** * Build and execute the findSingleAttributeList query. */ - public List findSingleAttributeList(OrmQueryRequest request) { + public > A findSingleAttributeList(OrmQueryRequest request, A collection) { CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request); request.setCancelableQuery(rcQuery); - return findAttributeList(request, rcQuery); + return findAttributeCollection(request, rcQuery, collection); } @SuppressWarnings("unchecked") - private List findAttributeList(OrmQueryRequest request, CQueryFetchSingleAttribute rcQuery) { + private > A findAttributeCollection(OrmQueryRequest request, CQueryFetchSingleAttribute rcQuery, A collection) { try { - List list = (List) rcQuery.findList(); + rcQuery.findCollection(collection); if (request.logSql()) { logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog(), rcQuery.micros()); } @@ -109,13 +105,22 @@ public final class CQueryEngine { } if (request.isQueryCachePut()) { request.addDependentTables(rcQuery.getDependentTables()); - list = Collections.unmodifiableList(list); - request.putToQueryCache(list); - if (Boolean.FALSE.equals(request.query().isReadOnly())) { - list = new ArrayList<>(list); + if (collection instanceof List) { + collection = (A) Collections.unmodifiableList((List) collection); + request.putToQueryCache(collection); + if (Boolean.FALSE.equals(request.query().isReadOnly())) { + collection = (A) new ArrayList<>(collection); + } + } else if (collection instanceof Set) { + collection = (A) Collections.unmodifiableSet((Set) collection); + request.putToQueryCache(collection); + if (Boolean.FALSE.equals(request.query().isReadOnly())) { + collection = (A) new LinkedHashSet<>(collection); + } } + } - return list; + return collection; } catch (SQLException e) { throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e); } @@ -144,7 +149,7 @@ public final class CQueryEngine { public List findIds(OrmQueryRequest request) { CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request); request.setCancelableQuery(rcQuery); - return findAttributeList(request, rcQuery); + return findAttributeCollection(request, rcQuery, new ArrayList<>()); } private void logGeneratedSql(OrmQueryRequest request, String sql, String bindLog, long micros) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java index 7e7586d46..747793e85 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java @@ -16,6 +16,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; @@ -76,11 +77,10 @@ final class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Ca /** * Execute the query returning the row count. */ - List findList() throws SQLException { + void findCollection(Collection result) throws SQLException { long startNano = System.nanoTime(); try { prepareExecute(); - List result = new ArrayList<>(); while (dataReader.next()) { Object value = reader.read(dataReader); if (containsCounts) { @@ -95,7 +95,6 @@ final class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Ca queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros); } getTransaction().profileEvent(this); - return result; } finally { close(); } 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 fbc791937..71e13330e 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 @@ -286,6 +286,11 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } + @Override + public Set findSingleAttributeSet() { + throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); + } + @Override public A findSingleAttribute() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java index 86076b379..95c5dbe7e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java @@ -17,6 +17,7 @@ import javax.persistence.PersistenceException; import java.sql.SQLException; import java.util.Collection; import java.util.List; +import java.util.Set; /** * Main Finder implementation. @@ -97,9 +98,9 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine { } @Override - public List findSingleAttributeList(OrmQueryRequest request) { + public > A findSingleAttributeCollection(OrmQueryRequest request, A collection) { flushJdbcBatchOnQuery(request); - return queryEngine.findSingleAttributeList(request); + return queryEngine.findSingleAttributeList(request, collection); } @Override 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 df2e1a2aa..7fbdbb5a5 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 @@ -1498,6 +1498,10 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { return (List) server.findSingleAttributeList(this, transaction); } + @Override + public final Set findSingleAttributeSet() { + return (Set) server.findSingleAttributeSet(this, transaction); + } @Override public final A findSingleAttribute() { List list = findSingleAttributeList(); 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 909ddcbb3..34309393c 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 @@ -695,6 +695,11 @@ public class TDSpiEbeanServer extends TDSpiServer implements SpiEbeanServer { return null; } + @Override + public Set findSingleAttributeSet(Query query, Transaction transaction) { + return null; + } + @Override public T findOne(Query query, Transaction transaction) { return null; diff --git a/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java b/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java index 7b0949b42..7024dddbf 100644 --- a/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java +++ b/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java @@ -1,21 +1,23 @@ package org.tests.cache; -import io.ebean.xtest.BaseTestCase; import io.ebean.CacheMode; import io.ebean.DB; import io.ebean.ExpressionList; import io.ebean.bean.BeanCollection; import io.ebean.cache.ServerCache; import io.ebean.test.LoggedSql; +import io.ebean.xtest.BaseTestCase; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; import org.tests.model.cache.EColAB; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.*; public class TestQueryCache extends BaseTestCase { @@ -50,7 +52,7 @@ public class TestQueryCache extends BaseTestCase { } @Test - public void findSingleAttribute() { + public void findSingleAttributeList() { DB.find(EColAB.class).delete(); new EColAB("03", "SingleAttribute").save(); @@ -76,6 +78,7 @@ public class TestQueryCache extends BaseTestCase { assertThat(colA_Second).isSameAs(colA_first); + List colA_NotDistinct = DB .find(EColAB.class) .setUseQueryCache(true) @@ -89,15 +92,39 @@ public class TestQueryCache extends BaseTestCase { // ensure that findCount & findSingleAttribute use different // slots in cache. If not a "Cannot cast List to int" should happen. int count = DB - .find(EColAB.class) - .setUseQueryCache(true) - .select("columnA") - .where() - .eq("columnB", "SingleAttribute") - .findCount(); + .find(EColAB.class) + .setUseQueryCache(true) + .select("columnA") + .where() + .eq("columnB", "SingleAttribute") + .findCount(); assertThat(count).isEqualTo(2); } + @Test + public void findSingleAttributeSet() { + + DB.find(EColAB.class).delete(); + new EColAB("03", "SingleAttribute").save(); + new EColAB("03", "SingleAttribute").save(); + ExpressionList query = DB + .find(EColAB.class) + .setUseQueryCache(true) + .select("columnA") + .where() + .eq("columnB", "SingleAttribute"); + Set colA_first = query.findSingleAttributeSet(); + + Set colA_Second = query.findSingleAttributeSet(); + + assertThat(colA_Second).isSameAs(colA_first).hasSize(1); + assertThatThrownBy(colA_first::clear).isInstanceOf(UnsupportedOperationException.class); + // ensure, that we do not have cache collisions on same query + query.findSingleAttributeList(); + query.findSingleAttribute(); + query.findCount(); + } + @Test public void findCount() { @@ -279,7 +306,7 @@ public class TestQueryCache extends BaseTestCase { assertSame(list, list2B); List list3 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where() - .ilike("name", "Rob").findList(); + .ilike("name", "Rob").findList(); assertNotSame(list, list3); BeanCollection bc3 = (BeanCollection) list3; @@ -323,10 +350,10 @@ public class TestQueryCache extends BaseTestCase { // and now, ensure that we hit the database LoggedSql.start(); colA_second = DB.find(EColAB.class) - .setUseQueryCache(CacheMode.PUT) - .where() - .eq("columnB", "someId") - .findIds(); + .setUseQueryCache(CacheMode.PUT) + .where() + .eq("columnB", "someId") + .findIds(); sql = LoggedSql.stop(); assertThat(sql).hasSize(1); @@ -335,15 +362,15 @@ public class TestQueryCache extends BaseTestCase { @Test public void findCountDifferentQueriesBit() { DB.getDefault().pluginApi().cacheManager().clearAll(); - differentFindCount(q->q.bitwiseAny("id",1), q->q.bitwiseAny("id",0)); - differentFindCount(q->q.bitwiseAll("id",1), q->q.bitwiseAll("id",0)); + differentFindCount(q -> q.bitwiseAny("id", 1), q -> q.bitwiseAny("id", 0)); + differentFindCount(q -> q.bitwiseAll("id", 1), q -> q.bitwiseAll("id", 0)); // differentFindCount(q->q.bitwiseNot("id",1), q->q.bitwiseNot("id",0)); NOT 1 == AND 1 = 0 - differentFindCount(q->q.bitwiseAnd("id",1, 0), q->q.bitwiseAnd("id",1, 1)); + differentFindCount(q -> q.bitwiseAnd("id", 1, 0), q -> q.bitwiseAnd("id", 1, 1)); - differentFindCount(q->q.bitwiseAnd("id",2, 0), q->q.bitwiseAnd("id",4, 0)); - differentFindCount(q->q.bitwiseAnd("id",2, 1), q->q.bitwiseAnd("id",4, 1)); + differentFindCount(q -> q.bitwiseAnd("id", 2, 0), q -> q.bitwiseAnd("id", 4, 0)); + differentFindCount(q -> q.bitwiseAnd("id", 2, 1), q -> q.bitwiseAnd("id", 4, 1)); // Will produce hash collision - differentFindCount(q->q.bitwiseAnd("id",10, 0), q->q.bitwiseAnd("id",0, 928210)); + differentFindCount(q -> q.bitwiseAnd("id", 10, 0), q -> q.bitwiseAnd("id", 0, 928210)); }