diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..0c09fbd01 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,36 @@ + +name: Build + +on: [push, pull_request] + +jobs: + build: + + runs-on: ${{ matrix.os }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + java_version: [8] + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v2 + - name: Set up Java + uses: actions/setup-java@v2 + with: + java-version: ${{ matrix.java_version }} + distribution: 'adopt' + - name: Maven cache + uses: actions/cache@v2 + env: + cache-name: maven-cache + with: + path: + ~/.m2 + key: build-${{ env.cache-name }} + - name: Build with Maven + run: mvn package + diff --git a/README.md b/README.md index b707f9a92..b92e5f720 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/ebean-orm/ebean.svg?branch=master)](https://travis-ci.org/ebean-orm/ebean) +[![Build](https://github.com/ebean-orm/ebean/actions/workflows/build.yml/badge.svg)](https://github.com/ebean-orm/ebean/actions/workflows/build.yml) [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) # Sponsors diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 1d3c76290..1e711bc62 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean api @@ -36,9 +36,8 @@ --> io.avaje - avaje-jsr305 - 1.1 - provided + avaje-lang + 1.0 diff --git a/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java b/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java index 537fb7f36..f553933db 100644 --- a/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java +++ b/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java @@ -1,5 +1,7 @@ package io.ebean; +import io.avaje.lang.NonNullApi; + import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -18,6 +20,7 @@ import java.util.concurrent.TimeUnit; * This also propagates MDC context from the current thread to the * background task if defined. */ +@NonNullApi public interface BackgroundExecutor { /** diff --git a/ebean-api/src/main/java/io/ebean/BeanFinder.java b/ebean-api/src/main/java/io/ebean/BeanFinder.java index aab61b572..1f5c0a12c 100644 --- a/ebean-api/src/main/java/io/ebean/BeanFinder.java +++ b/ebean-api/src/main/java/io/ebean/BeanFinder.java @@ -1,7 +1,7 @@ package io.ebean; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import java.util.List; import java.util.Optional; @@ -27,10 +27,10 @@ import java.util.Optional; * @param The ID type * @param The Bean type */ +@NonNullApi public abstract class BeanFinder { protected final Database server; - protected final Class type; /** @@ -81,7 +81,6 @@ public abstract class BeanFinder { *

* Equivalent to {@link Database#reference(Class, Object)} */ - @Nonnull public T ref(I id) { return db().reference(type, id); } @@ -97,7 +96,6 @@ public abstract class BeanFinder { /** * Find an entity by ID returning an Optional. */ - @Nullable public Optional findByIdOrEmpty(I id) { return db().find(type).setId(id).findOneOrEmpty(); } @@ -112,7 +110,6 @@ public abstract class BeanFinder { /** * Retrieves all entities of the given type. */ - @Nonnull public List findAll() { return query().findList(); } diff --git a/ebean-api/src/main/java/io/ebean/BeanRepository.java b/ebean-api/src/main/java/io/ebean/BeanRepository.java index b89a49b39..ad973e261 100644 --- a/ebean-api/src/main/java/io/ebean/BeanRepository.java +++ b/ebean-api/src/main/java/io/ebean/BeanRepository.java @@ -1,5 +1,6 @@ package io.ebean; +import io.avaje.lang.NonNullApi; import io.ebean.bean.EntityBean; import java.util.Collection; @@ -31,6 +32,7 @@ import java.util.Collection; * @param The ID type * @param The Bean type */ +@NonNullApi public abstract class BeanRepository extends BeanFinder { /** diff --git a/ebean-api/src/main/java/io/ebean/BeanState.java b/ebean-api/src/main/java/io/ebean/BeanState.java index 20aa0f8e4..912d737fa 100644 --- a/ebean-api/src/main/java/io/ebean/BeanState.java +++ b/ebean-api/src/main/java/io/ebean/BeanState.java @@ -1,6 +1,5 @@ package io.ebean; -import javax.annotation.Nullable; import java.util.Map; import java.util.Set; @@ -138,7 +137,6 @@ public interface BeanState { /** * Returns a map with load errors. */ - @Nullable Map loadErrors(); /** diff --git a/ebean-api/src/main/java/io/ebean/DB.java b/ebean-api/src/main/java/io/ebean/DB.java index 9bcfb047d..b5438eb08 100644 --- a/ebean-api/src/main/java/io/ebean/DB.java +++ b/ebean-api/src/main/java/io/ebean/DB.java @@ -1,13 +1,13 @@ package io.ebean; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.annotation.TxIsolation; import io.ebean.cache.ServerCacheManager; import io.ebean.plugin.Property; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import java.util.Collection; @@ -60,6 +60,7 @@ import java.util.concurrent.Callable; * * } */ +@NonNullApi public final class DB { private static final DbContext context = DbContext.getInstance(); @@ -523,15 +524,13 @@ public final class DB { * @param bean The entity bean to check uniqueness on * @return a set of Properties if constraint validation was detected or empty list. */ - @Nonnull public static Set checkUniqueness(Object bean) { return getDefault().checkUniqueness(bean); } /** - * Same as {@link #checkUniqueness(Object)}. but with given transaction. + * Same as {@link #checkUniqueness(Object)} but with given transaction. */ - @Nonnull public static Set checkUniqueness(Object bean, Transaction transaction) { return getDefault().checkUniqueness(bean, transaction); } @@ -851,7 +850,6 @@ public final class DB { * } */ public static Update createUpdate(Class beanType, String ormUpdate) { - return getDefault().createUpdate(beanType, ormUpdate); } @@ -859,7 +857,6 @@ public final class DB { * Create a CsvReader for a given beanType. */ public static CsvReader createCsvReader(Class beanType) { - return getDefault().createCsvReader(beanType); } @@ -897,7 +894,6 @@ public final class DB { * @return A ORM Query for this beanType */ public static Query createQuery(Class beanType) { - return getDefault().createQuery(beanType); } @@ -935,7 +931,6 @@ public final class DB { * @return The query with expressions defined as per the parsed query statement */ public static Query createQuery(Class beanType, String eql) { - return getDefault().createQuery(beanType, eql); } @@ -951,7 +946,6 @@ public final class DB { * @return A ORM Query object for this beanType */ public static Query find(Class beanType) { - return getDefault().find(beanType); } @@ -1030,77 +1024,6 @@ public final class DB { return getDefault().filter(beanType); } -// /** -// * Execute a Sql Update Delete or Insert statement. This returns the number of -// * rows that where updated, deleted or inserted. If is executed in batch then -// * this returns -1. You can get the actual rowCount after commit() from -// * updateSql.getRowCount(). -// *

-// * If you wish to execute a Sql Select natively then you should use the -// * FindByNativeSql object. -// *

-// *

-// * Note that the table modification information is automatically deduced and -// * you do not need to call the DB.externalModification() method when you -// * use this method. -// *

-// *

-// * Example: -// *

-// *
{@code
-//   *
-//   *   // example that uses 'named' parameters
-//   *   String s = "UPDATE f_topic set post_count = :count where id = :id"
-//   *
-//   *   SqlUpdate update = DB.createSqlUpdate(s);
-//   *
-//   *   update.setParameter("id", 1);
-//   *   update.setParameter("count", 50);
-//   *
-//   *   int modifiedCount = DB.execute(update);
-//   *
-//   *   String msg = "There where " + modifiedCount + "rows updated";
-//   *
-//   * }
-// * -// * @param sqlUpdate the update sql potentially with bind values -// * @return the number of rows updated or deleted. -1 if executed in batch. -// * @see SqlUpdate -// * @see CallableSql -// * @see DB#execute(CallableSql) -// */ -// public static int execute(SqlUpdate sqlUpdate) { -// return defaultDatabase().execute(sqlUpdate); -// } -// -// /** -// * For making calls to stored procedures. -// *

-// * Example: -// *

-// *
{@code
-//   *
-//   *   String sql = "{call sp_order_modify(?,?,?)}";
-//   *
-//   *   CallableSql cs = DB.createCallableSql(sql);
-//   *   cs.setParameter(1, 27);
-//   *   cs.setParameter(2, "SHIPPED");
-//   *   cs.registerOut(3, Types.INTEGER);
-//   *
-//   *   DB.execute(cs);
-//   *
-//   *   // read the out parameter
-//   *   Integer returnValue = (Integer) cs.getObject(3);
-//   *
-//   * }
-// * -// * @see CallableSql -// * @see Ebean#execute(SqlUpdate) -// */ -// public static int execute(CallableSql callableSql) { -// return defaultDatabase().execute(callableSql); -// } - /** * Execute a TxRunnable in a Transaction with an explicit scope. *

diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java index 396f3225a..9c69e33fd 100644 --- a/ebean-api/src/main/java/io/ebean/Database.java +++ b/ebean-api/src/main/java/io/ebean/Database.java @@ -1,5 +1,7 @@ package io.ebean; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.annotation.Platform; import io.ebean.annotation.TxIsolation; import io.ebean.cache.ServerCacheManager; @@ -10,8 +12,6 @@ import io.ebean.plugin.SpiServer; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import javax.sql.DataSource; @@ -92,6 +92,7 @@ import java.util.concurrent.Callable; * @see DatabaseFactory * @see DatabaseConfig */ +@NonNullApi public interface Database { /** @@ -575,8 +576,8 @@ public interface Database { DtoQuery createNamedDtoQuery(Class dtoType, String namedQuery); /** - * Look to execute a native sql query that does not returns beans but instead - * returns SqlRow or direct access to ResultSet (see {@link SqlQuery#findList(RowMapper)}. + * Look to execute a native sql query that does not return beans but instead + * returns SqlRow or direct access to ResultSet. * *

* Refer to {@link DtoQuery} for native sql queries returning DTO beans. @@ -949,7 +950,6 @@ public interface Database { * @param beanType the type of entity bean * @param id the id value */ - @Nonnull T reference(Class beanType, Object id); /** @@ -1236,6 +1236,7 @@ public interface Database { * @param id the bean id value * @param transaction the transaction to use (can be null) */ + @Nullable T find(Class beanType, Object id, Transaction transaction); /** @@ -1294,13 +1295,11 @@ public interface Database { * @param bean The entity bean to check uniqueness on * @return a set of Properties if constraint validation was detected or empty list. */ - @Nonnull Set checkUniqueness(Object bean); /** * Same as {@link #checkUniqueness(Object)}. but with given transaction. */ - @Nonnull Set checkUniqueness(Object bean, Transaction transaction); /** @@ -1614,6 +1613,7 @@ public interface Database { * @param id the id of the entity bean * @param transaction the transaction the publish process should use (can be null) */ + @Nullable T publish(Class beanType, Object id, Transaction transaction); /** @@ -1627,6 +1627,7 @@ public interface Database { * @param beanType the type of the entity bean * @param id the id of the entity bean */ + @Nullable T publish(Class beanType, Object id); /** @@ -1665,6 +1666,7 @@ public interface Database { * @param id the id of the entity bean to restore * @param transaction the transaction the restore process should use (can be null) */ + @Nullable T draftRestore(Class beanType, Object id, Transaction transaction); /** @@ -1678,6 +1680,7 @@ public interface Database { * @param beanType the type of the entity bean * @param id the id of the entity bean to restore */ + @Nullable T draftRestore(Class beanType, Object id); /** diff --git a/ebean-api/src/main/java/io/ebean/DocumentStore.java b/ebean-api/src/main/java/io/ebean/DocumentStore.java index 0a2353ff0..7b5a6c481 100644 --- a/ebean-api/src/main/java/io/ebean/DocumentStore.java +++ b/ebean-api/src/main/java/io/ebean/DocumentStore.java @@ -1,9 +1,10 @@ package io.ebean; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.docstore.DocQueryContext; import io.ebean.docstore.RawDoc; -import javax.annotation.Nullable; import java.io.IOException; import java.util.List; import java.util.Map; @@ -13,6 +14,7 @@ import java.util.function.Predicate; /** * Document storage operations. */ +@NonNullApi public interface DocumentStore { /** diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index 69ed034ef..7de6c2694 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -1,7 +1,9 @@ package io.ebean; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; + +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -38,12 +40,12 @@ import java.util.stream.Stream; * * } */ +@NonNullApi public interface DtoQuery extends CancelableQuery { /** * Execute the query returning a list. */ - @Nonnull List findList(); /** @@ -53,7 +55,6 @@ public interface DtoQuery extends CancelableQuery { * resultSet and potentially connection and MUST be closed. We should use * QueryIterator in a try with resource block. */ - @Nonnull QueryIterator findIterate(); /** @@ -63,7 +64,6 @@ public interface DtoQuery extends CancelableQuery { * resultSet and potentially connection and MUST be closed. We should use * the Stream in a try with resource block. */ - @Nonnull Stream findStream(); /** @@ -105,7 +105,6 @@ public interface DtoQuery extends CancelableQuery { /** * Execute the query returning an optional bean. */ - @Nonnull Optional findOneOrEmpty(); /** @@ -130,6 +129,13 @@ public interface DtoQuery extends CancelableQuery { */ DtoQuery setParameter(String name, Object value); + /** + * Bind the named multi-value array parameter which we would use with Postgres ANY. + *

+ * For Postgres this binds an ARRAY rather than expands into multiple bind values. + */ + DtoQuery setArrayParameter(String name, Collection values); + /** * Bind the parameter by its index position (1 based like JDBC). */ diff --git a/ebean-api/src/main/java/io/ebean/Ebean.java b/ebean-api/src/main/java/io/ebean/Ebean.java index eabc3c5b4..f58c9dac0 100644 --- a/ebean-api/src/main/java/io/ebean/Ebean.java +++ b/ebean-api/src/main/java/io/ebean/Ebean.java @@ -1,13 +1,12 @@ package io.ebean; +import io.avaje.lang.Nullable; import io.ebean.annotation.TxIsolation; import io.ebean.cache.ServerCacheManager; import io.ebean.plugin.Property; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import java.util.Collection; @@ -506,7 +505,6 @@ public final class Ebean { * @param bean The entity bean to check uniqueness on * @return a set of Properties if constraint validation was detected or empty list. */ - @Nonnull public static Set checkUniqueness(Object bean) { return getDefault().checkUniqueness(bean); } @@ -514,7 +512,6 @@ public final class Ebean { /** * Same as {@link #checkUniqueness(Object)}. but with given transaction. */ - @Nonnull public static Set checkUniqueness(Object bean, Transaction transaction) { return getDefault().checkUniqueness(bean, transaction); } diff --git a/ebean-api/src/main/java/io/ebean/EbeanVersion.java b/ebean-api/src/main/java/io/ebean/EbeanVersion.java index c21ccb5e2..6a764d911 100644 --- a/ebean-api/src/main/java/io/ebean/EbeanVersion.java +++ b/ebean-api/src/main/java/io/ebean/EbeanVersion.java @@ -16,15 +16,22 @@ public class EbeanVersion { private static final Logger log = LoggerFactory.getLogger("io.ebean"); + /** + * Maintain the minimum ebean-agent version manually based on required ebean-agent bug fixes. + */ + private static final int MIN_AGENT_MAJOR_VERSION = 12; + private static final int MIN_AGENT_MINOR_VERSION = 12; private static String version = "unknown"; static { + readVersion(); + checkAgentVersion(); + } + + private static void readVersion() { try { - Properties prop = new Properties(); - try (InputStream in = DB.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean-api/pom.properties")) { + try (InputStream in = ClassLoader.getSystemResourceAsStream("META-INF/maven/io.ebean/ebean-api/pom.properties")) { if (in != null) { - prop.load(in); - in.close(); - version = prop.getProperty("version"); + version = readVersion(in); } } log.info("ebean version: {}", version); @@ -33,6 +40,49 @@ public class EbeanVersion { } } + private static void checkAgentVersion() { + try { + try (InputStream in = ClassLoader.getSystemResourceAsStream("META-INF/maven/io.ebean/ebean-agent/pom.properties")) { + // often we only have ebean-agent during development (with build time enhancement), null is expected + if (in != null) { + String agentVersion = readVersion(in); + if (agentVersion != null) { + if (checkMinAgentVersion(agentVersion)) { + log.error("Expected minimum ebean-agent version {}.{}.0 but we have {}, please update the ebean-agent", MIN_AGENT_MAJOR_VERSION, MIN_AGENT_MINOR_VERSION, agentVersion); + } + } + } + } + } catch (IOException e) { + log.warn("Could not check minimum ebean-agent version {}.{}.0 required due to - {}", MIN_AGENT_MAJOR_VERSION, MIN_AGENT_MINOR_VERSION, e.getMessage()); + } + } + + /** + * Return true if ebean-agent is NOT at our minimum version. + */ + static boolean checkMinAgentVersion(String agentVersion) { + String[] versionSegments = agentVersion.split("\\."); + if (versionSegments.length != 3) { + return true; + } else { + int major = Integer.parseInt(versionSegments[0]); + int minor = Integer.parseInt(versionSegments[1]); + if (major < MIN_AGENT_MAJOR_VERSION) { + return true; + } else { + return major == MIN_AGENT_MAJOR_VERSION && minor < MIN_AGENT_MINOR_VERSION; + } + } + } + + private static String readVersion(InputStream in) throws IOException { + Properties prop = new Properties(); + prop.load(in); + in.close(); + return prop.getProperty("version"); + } + private EbeanVersion() { // hide } diff --git a/ebean-api/src/main/java/io/ebean/EmptyPagedList.java b/ebean-api/src/main/java/io/ebean/EmptyPagedList.java index bbf72a731..74886c985 100644 --- a/ebean-api/src/main/java/io/ebean/EmptyPagedList.java +++ b/ebean-api/src/main/java/io/ebean/EmptyPagedList.java @@ -1,6 +1,5 @@ package io.ebean; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.concurrent.Future; @@ -25,13 +24,11 @@ public class EmptyPagedList implements PagedList { // do nothing } - @Nonnull @Override public Future getFutureCount() { return null; } - @Nonnull @Override public List getList() { return Collections.emptyList(); diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index e91846e08..8f2ebe0d0 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -1,21 +1,13 @@ package io.ebean; -import io.ebean.search.Match; -import io.ebean.search.MultiMatch; -import io.ebean.search.TextCommonTerms; -import io.ebean.search.TextQueryString; -import io.ebean.search.TextSimple; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; +import io.ebean.search.*; -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; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -39,6 +31,7 @@ import java.util.function.Predicate; * * @see Query#where() */ +@NonNullApi public interface ExpressionList { /** @@ -327,7 +320,6 @@ public interface ExpressionList { * * @see Query#findList() */ - @Nonnull List findList(); /** @@ -335,7 +327,6 @@ public interface ExpressionList { * * @see Query#findIds() */ - @Nonnull List findIds(); /** @@ -351,7 +342,6 @@ public interface ExpressionList { * * @see Query#findSet() */ - @Nonnull Set findSet(); /** @@ -359,7 +349,6 @@ public interface ExpressionList { * * @see Query#findMap() */ - @Nonnull Map findMap(); /** @@ -392,7 +381,6 @@ public interface ExpressionList { * * @return the list of values for the selected property */ - @Nonnull List findSingleAttributeList(); /** @@ -429,7 +417,6 @@ public interface ExpressionList { /** * Execute the query returning an optional bean. */ - @Nonnull Optional findOneOrEmpty(); /** @@ -442,7 +429,6 @@ public interface ExpressionList { * * @return a Future object for the row count query */ - @Nonnull FutureRowCount findFutureCount(); /** @@ -455,7 +441,6 @@ public interface ExpressionList { * * @return a Future object for the list of Id's */ - @Nonnull FutureIds findFutureIds(); /** @@ -468,7 +453,6 @@ public interface ExpressionList { * * @return a Future object for the list result of the query */ - @Nonnull FutureList findFutureList(); /** @@ -499,7 +483,6 @@ public interface ExpressionList { * @return The PagedList * @see Query#findPagedList() */ - @Nonnull PagedList findPagedList(); /** @@ -509,7 +492,6 @@ public interface ExpressionList { * It will execute the query against the history returning the versions of the bean. *

*/ - @Nonnull List> findVersions(); /** @@ -519,13 +501,11 @@ public interface ExpressionList { * It will execute the query against the history returning the versions of the bean. *

*/ - @Nonnull List> findVersionsBetween(Timestamp start, Timestamp end); /** * Add some filter predicate expressions to the many property. */ - @Nonnull ExpressionList filterMany(String manyProperty); /** diff --git a/ebean-api/src/main/java/io/ebean/ExtendedServer.java b/ebean-api/src/main/java/io/ebean/ExtendedServer.java index 59b1c678e..1bb21a721 100644 --- a/ebean-api/src/main/java/io/ebean/ExtendedServer.java +++ b/ebean-api/src/main/java/io/ebean/ExtendedServer.java @@ -1,7 +1,7 @@ package io.ebean; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import io.avaje.lang.Nullable; + import javax.persistence.NonUniqueResultException; import java.time.Clock; import java.util.List; @@ -82,7 +82,6 @@ public interface ExtendedServer { * * @see Query#findIds() */ - @Nonnull List
findIds(Query query, Transaction transaction); /** @@ -100,7 +99,6 @@ public interface ExtendedServer { * @see Query#findEach(Consumer) * @see Query#findEachWhile(Predicate) */ - @Nonnull QueryIterator findIterate(Query query, Transaction transaction); /** @@ -112,7 +110,6 @@ public interface ExtendedServer { * Note that the stream needs to be closed so use with try with resources. *

*/ - @Nonnull Stream findStream(Query query, Transaction transaction); /** @@ -125,7 +122,6 @@ public interface ExtendedServer { *

* Note that the stream needs to be closed so use with try with resources. */ - @Nonnull @Deprecated Stream findLargeStream(Query query, Transaction transaction); @@ -210,7 +206,6 @@ public interface ExtendedServer { * It will execute the query against the history returning the versions of the bean. *

*/ - @Nonnull List> findVersions(Query query, Transaction transaction); /** @@ -235,7 +230,6 @@ public interface ExtendedServer { * @return the list of fetched beans. * @see Query#findList() */ - @Nonnull List findList(Query query, Transaction transaction); /** @@ -251,7 +245,6 @@ public interface ExtendedServer { * @return a Future object for the row count query * @see Query#findFutureCount() */ - @Nonnull FutureRowCount findFutureCount(Query query, Transaction transaction); /** @@ -267,7 +260,6 @@ public interface ExtendedServer { * @return a Future object for the list of Id's * @see Query#findFutureIds() */ - @Nonnull FutureIds findFutureIds(Query query, Transaction transaction); /** @@ -284,7 +276,6 @@ public interface ExtendedServer { * @return a Future object for the list result of the query * @see Query#findFutureList() */ - @Nonnull FutureList findFutureList(Query query, Transaction transaction); /** @@ -316,7 +307,6 @@ public interface ExtendedServer { * @return The PagedList * @see Query#findPagedList() */ - @Nonnull PagedList findPagedList(Query query, Transaction transaction); /** @@ -341,7 +331,6 @@ public interface ExtendedServer { * @return the set of fetched beans. * @see Query#findSet() */ - @Nonnull Set findSet(Query query, Transaction transaction); /** @@ -358,7 +347,6 @@ public interface ExtendedServer { * @return the map of fetched beans. * @see Query#findMap() */ - @Nonnull Map findMap(Query query, Transaction transaction); /** @@ -391,7 +379,6 @@ public interface ExtendedServer { * @return the list of values for the selected property * @see Query#findSingleAttributeList() */ - @Nonnull List
findSingleAttributeList(Query query, Transaction transaction); /** @@ -419,7 +406,6 @@ public interface ExtendedServer { /** * Similar to findOne() but returns an Optional (rather than nullable). */ - @Nonnull Optional findOneOrEmpty(Query query, Transaction transaction); /** @@ -463,7 +449,6 @@ public interface ExtendedServer { * @return the list of fetched MapBean. * @see SqlQuery#findList() */ - @Nonnull List findList(SqlQuery query, Transaction transaction); /** diff --git a/ebean-api/src/main/java/io/ebean/FetchGroup.java b/ebean-api/src/main/java/io/ebean/FetchGroup.java index 95ea70412..26e3d84c2 100644 --- a/ebean-api/src/main/java/io/ebean/FetchGroup.java +++ b/ebean-api/src/main/java/io/ebean/FetchGroup.java @@ -1,9 +1,8 @@ package io.ebean; +import io.avaje.lang.NonNullApi; import io.ebean.service.SpiFetchGroupQuery; -import javax.annotation.Nonnull; - /** * Defines what part of the object graph to load (select and fetch clauses). *

@@ -62,6 +61,7 @@ import javax.annotation.Nonnull; * * @param The bean type the Fetch group can be applied to */ +@NonNullApi public interface FetchGroup { /** @@ -83,7 +83,6 @@ public interface FetchGroup { * * @return The FetchGroup with the given select clause */ - @Nonnull static FetchGroup of(Class cls, String select) { return XServiceProvider.fetchGroupOf(cls, select); } @@ -108,7 +107,6 @@ public interface FetchGroup { * * @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to */ - @Nonnull static FetchGroupBuilder of(Class cls) { return XServiceProvider.fetchGroupOf(cls); } diff --git a/ebean-api/src/main/java/io/ebean/FetchGroupBuilder.java b/ebean-api/src/main/java/io/ebean/FetchGroupBuilder.java index 121cceb53..2b282fb05 100644 --- a/ebean-api/src/main/java/io/ebean/FetchGroupBuilder.java +++ b/ebean-api/src/main/java/io/ebean/FetchGroupBuilder.java @@ -1,6 +1,6 @@ package io.ebean; -import javax.annotation.Nonnull; +import io.avaje.lang.NonNullApi; /** * Builds a FetchGroup by adding fetch clauses. @@ -23,85 +23,73 @@ import javax.annotation.Nonnull; * * } */ +@NonNullApi public interface FetchGroupBuilder { /** * Specify specific properties to select (top level properties). */ - @Nonnull FetchGroupBuilder select(String select); /** * Fetch all the properties at the given path. */ - @Nonnull FetchGroupBuilder fetch(String path); /** * Fetch the path with the nested fetch group. */ - @Nonnull FetchGroupBuilder fetch(String path, FetchGroup nestedGroup); /** * Fetch the path using a query join with the nested fetch group. */ - @Nonnull FetchGroupBuilder fetchQuery(String path, FetchGroup nestedGroup); /** * Fetch the path lazily with the nested fetch group. */ - @Nonnull FetchGroupBuilder fetchLazy(String path, FetchGroup nestedGroup); /** * Fetch the path including specified properties. */ - @Nonnull FetchGroupBuilder fetch(String path, String properties); /** * Fetch the path including all its properties using a query join. */ - @Nonnull FetchGroupBuilder fetchQuery(String path); /** * Fetch the path including all its properties using L2 cache. * Cache misses fallback to fetchQuery(). */ - @Nonnull FetchGroupBuilder fetchCache(String path); /** * Fetch the path including specified properties using a query join. */ - @Nonnull FetchGroupBuilder fetchQuery(String path, String properties); /** * Fetch the path including specified properties using L2 cache. * Cache misses fallback to fetchQuery(). */ - @Nonnull FetchGroupBuilder fetchCache(String path, String properties); /** * Fetch the path including all its properties lazily. */ - @Nonnull FetchGroupBuilder fetchLazy(String path); /** * Fetch the path including specified properties lazily. */ - @Nonnull FetchGroupBuilder fetchLazy(String path, String properties); /** * Build and return the FetchGroup. */ - @Nonnull FetchGroup build(); } diff --git a/ebean-api/src/main/java/io/ebean/Filter.java b/ebean-api/src/main/java/io/ebean/Filter.java index 758f88b57..5181b0e99 100644 --- a/ebean-api/src/main/java/io/ebean/Filter.java +++ b/ebean-api/src/main/java/io/ebean/Filter.java @@ -1,5 +1,7 @@ package io.ebean; +import io.avaje.lang.NonNullApi; + import java.util.List; import java.util.Set; @@ -77,6 +79,7 @@ import java.util.Set; * * @param the entity bean type */ +@NonNullApi public interface Filter { /** diff --git a/ebean-api/src/main/java/io/ebean/Finder.java b/ebean-api/src/main/java/io/ebean/Finder.java index cc5ef87f6..2bb927640 100644 --- a/ebean-api/src/main/java/io/ebean/Finder.java +++ b/ebean-api/src/main/java/io/ebean/Finder.java @@ -1,7 +1,7 @@ package io.ebean; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import java.util.List; /** @@ -55,6 +55,7 @@ import java.util.List; * } * */ +@NonNullApi public class Finder { /** @@ -139,7 +140,6 @@ public class Finder { *

* Equivalent to {@link Database#reference(Class, Object)} */ - @Nonnull public T ref(I id) { return db().reference(type, id); } @@ -166,7 +166,6 @@ public class Finder { /** * Retrieves all entities of the given type. */ - @Nonnull public List all() { return query().findList(); } diff --git a/ebean-api/src/main/java/io/ebean/Junction.java b/ebean-api/src/main/java/io/ebean/Junction.java index 0ab001b37..b1bec8d76 100644 --- a/ebean-api/src/main/java/io/ebean/Junction.java +++ b/ebean-api/src/main/java/io/ebean/Junction.java @@ -113,9 +113,9 @@ public interface Junction extends Expression, ExpressionList { */ SHOULD("should", "", true); - private String prefix; - private String literal; - private boolean text; + private final String prefix; + private final String literal; + private final boolean text; Type(String literal, String prefix, boolean text) { this.literal = literal; diff --git a/ebean-api/src/main/java/io/ebean/PagedList.java b/ebean-api/src/main/java/io/ebean/PagedList.java index 662f426f8..f4e1d3610 100644 --- a/ebean-api/src/main/java/io/ebean/PagedList.java +++ b/ebean-api/src/main/java/io/ebean/PagedList.java @@ -1,6 +1,5 @@ package io.ebean; -import javax.annotation.Nonnull; import java.util.List; import java.util.concurrent.Future; @@ -116,13 +115,11 @@ public interface PagedList { * * } */ - @Nonnull Future getFutureCount(); /** * Return the list of entities for this page. */ - @Nonnull List getList(); /** diff --git a/ebean-api/src/main/java/io/ebean/Query.java b/ebean-api/src/main/java/io/ebean/Query.java index 0dc86905e..4ddfc9af5 100644 --- a/ebean-api/src/main/java/io/ebean/Query.java +++ b/ebean-api/src/main/java/io/ebean/Query.java @@ -1,7 +1,7 @@ package io.ebean; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import javax.persistence.NonUniqueResultException; import java.sql.Connection; import java.sql.Timestamp; @@ -177,6 +177,7 @@ import java.util.stream.Stream; * * @param the type of Entity bean this query will fetch. */ +@NonNullApi public interface Query extends CancelableQuery { /** @@ -689,7 +690,6 @@ public interface Query extends CancelableQuery { * This query will execute against the Database that was used to create it. *

*/ - @Nonnull
List findIds(); /** @@ -727,7 +727,6 @@ public interface Query extends CancelableQuery { * * } */ - @Nonnull QueryIterator findIterate(); /** @@ -749,7 +748,6 @@ public interface Query extends CancelableQuery { * * } */ - @Nonnull Stream findStream(); /** @@ -772,7 +770,6 @@ public interface Query extends CancelableQuery { * * } */ - @Nonnull @Deprecated Stream findLargeStream(); @@ -882,7 +879,6 @@ public interface Query extends CancelableQuery { * * } */ - @Nonnull List findList(); /** @@ -898,7 +894,6 @@ public interface Query extends CancelableQuery { * * } */ - @Nonnull Set findSet(); /** @@ -918,7 +913,6 @@ public interface Query extends CancelableQuery { * * } */ - @Nonnull Map findMap(); /** @@ -951,7 +945,6 @@ public interface Query extends CancelableQuery { * * @return the list of values for the selected property */ - @Nonnull List findSingleAttributeList(); /** @@ -1049,7 +1042,6 @@ public interface Query extends CancelableQuery { /** * Execute the query returning an optional bean. */ - @Nonnull Optional findOneOrEmpty(); /** @@ -1064,7 +1056,6 @@ public interface Query extends CancelableQuery { * It will execute the query against the history returning the versions of the bean. *

*/ - @Nonnull List> findVersions(); /** @@ -1074,7 +1065,6 @@ public interface Query extends CancelableQuery { * It will execute the query against the history returning the versions of the bean. *

*/ - @Nonnull List> findVersionsBetween(Timestamp start, Timestamp end); /** @@ -1132,7 +1122,6 @@ public interface Query extends CancelableQuery { * * @return a Future object for the row count query */ - @Nonnull FutureRowCount findFutureCount(); /** @@ -1145,7 +1134,6 @@ public interface Query extends CancelableQuery { * * @return a Future object for the list of Id's */ - @Nonnull FutureIds findFutureIds(); /** @@ -1157,7 +1145,6 @@ public interface Query extends CancelableQuery { * * @return a Future object for the list result of the query */ - @Nonnull FutureList findFutureList(); /** @@ -1187,7 +1174,6 @@ public interface Query extends CancelableQuery { * * @return The PagedList */ - @Nonnull PagedList findPagedList(); /** diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java index 9a878f233..240d42aad 100644 --- a/ebean-api/src/main/java/io/ebean/SqlQuery.java +++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java @@ -1,7 +1,7 @@ package io.ebean; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import java.io.Serializable; import java.math.BigDecimal; import java.util.List; @@ -37,12 +37,12 @@ import java.util.function.Predicate; * * } */ +@NonNullApi public interface SqlQuery extends Serializable, CancelableQuery { /** * Execute the query returning a list. */ - @Nonnull List findList(); /** @@ -119,7 +119,6 @@ public interface SqlQuery extends Serializable, CancelableQuery { /** * Execute the query returning an optional row. */ - @Nonnull Optional findOneOrEmpty(); /** @@ -354,6 +353,7 @@ public interface SqlQuery extends Serializable, CancelableQuery { /** * Return the single value. */ + @Nullable T findOne(); /** diff --git a/ebean-api/src/main/java/io/ebean/SqlUpdate.java b/ebean-api/src/main/java/io/ebean/SqlUpdate.java index 927050d3f..9ce704922 100644 --- a/ebean-api/src/main/java/io/ebean/SqlUpdate.java +++ b/ebean-api/src/main/java/io/ebean/SqlUpdate.java @@ -1,5 +1,7 @@ package io.ebean; +import java.util.Collection; + /** * A SqlUpdate for executing insert update or delete statements. *

@@ -324,6 +326,13 @@ public interface SqlUpdate { */ SqlUpdate setParameter(String name, Object param); + /** + * Bind the named multi-value array parameter which we would use with Postgres ANY. + *

+ * For Postgres this binds an ARRAY rather than expands into multiple bind values. + */ + SqlUpdate setArrayParameter(String name, Collection values); + /** * Set a named parameter that has a null value. Exactly the same as * {@link #setNullParameter(String, int)}. diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java index 3725cc73c..28ac8a8e7 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -9,11 +9,7 @@ import javax.persistence.PersistenceException; import java.io.Serializable; import java.math.BigDecimal; import java.net.URL; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -1164,7 +1160,7 @@ public final class EntityBeanIntercept implements Serializable { */ public Map getLoadErrors() { if (loadErrors == null) { - return null; + return Collections.emptyMap(); } Map ret = null; int len = getPropertyLength(); diff --git a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java index 9d67499d1..ed03080cd 100644 --- a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java +++ b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java @@ -60,21 +60,15 @@ public interface PersistenceContext { int size(Class rootType); /** - * Return a copy of the Persistence context to use for large query iteration. + * Signalizes the PersistenceContext, the begin for large query iteration. */ - PersistenceContext forIterate(); + void beginIterate(); /** - * Return a new Persistence context during iteration of large query result. + * Signalizes the PersistenceContext, the end for large query iteration. */ - PersistenceContext forIterateReset(); - - /** - * Return true if the persistence context has grown and hit the 'reset limit' - * during large query iteration. - */ - boolean resetLimit(); - + void endIterate(); + /** * Wrapper on a bean to also indicate if a bean has been deleted. *

diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java index ba0ca78d1..281d0ea79 100644 --- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java @@ -1547,6 +1547,9 @@ public class DatabaseConfig { /** * Set to true if all DB column and table names should use quoted identifiers. + *

+ * For Postgres pgjdbc version 42.3.0 should be used with datasource property + * quoteReturningIdentifiers set to false (refer #2303). */ public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) { platformConfig.setAllQuotedIdentifiers(allQuotedIdentifiers); diff --git a/ebean-api/src/main/java/io/ebean/config/PlatformConfig.java b/ebean-api/src/main/java/io/ebean/config/PlatformConfig.java index 065af2393..bedbd7de5 100644 --- a/ebean-api/src/main/java/io/ebean/config/PlatformConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/PlatformConfig.java @@ -120,6 +120,9 @@ public class PlatformConfig { /** * Set to true if all DB column and table names should use quoted identifiers. + *

+ * For Postgres pgjdbc version 42.3.0 should be used with datasource property + * quoteReturningIdentifiers set to false (refer #2303). */ public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) { this.allQuotedIdentifiers = allQuotedIdentifiers; diff --git a/ebean-api/src/main/java/io/ebean/plugin/BeanType.java b/ebean-api/src/main/java/io/ebean/plugin/BeanType.java index 661b370d2..e7f324a56 100644 --- a/ebean-api/src/main/java/io/ebean/plugin/BeanType.java +++ b/ebean-api/src/main/java/io/ebean/plugin/BeanType.java @@ -8,7 +8,6 @@ import io.ebean.event.BeanPersistController; import io.ebean.event.BeanPersistListener; import io.ebean.event.BeanQueryAdapter; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.List; import java.util.function.Consumer; @@ -21,7 +20,6 @@ public interface BeanType { /** * Return the short name of the bean type. */ - @Nonnull String name(); /** @@ -35,7 +33,6 @@ public interface BeanType { /** * Return the full name of the bean type. */ - @Nonnull String fullName(); /** @@ -49,7 +46,6 @@ public interface BeanType { /** * Return the class type this BeanDescriptor describes. */ - @Nonnull Class type(); /** @@ -76,7 +72,6 @@ public interface BeanType { /** * Return all the properties for this bean type. */ - @Nonnull Collection allProperties(); /** diff --git a/ebean-api/src/main/java/io/ebean/plugin/Property.java b/ebean-api/src/main/java/io/ebean/plugin/Property.java index 50a1ebc3d..d37fa2959 100644 --- a/ebean-api/src/main/java/io/ebean/plugin/Property.java +++ b/ebean-api/src/main/java/io/ebean/plugin/Property.java @@ -1,7 +1,5 @@ package io.ebean.plugin; -import javax.annotation.Nonnull; - /** * Property of a entity bean that can be read. */ @@ -10,7 +8,6 @@ public interface Property { /** * Return the name of the property. */ - @Nonnull String name(); /** @@ -24,7 +21,6 @@ public interface Property { /** * Return the type of the property. */ - @Nonnull Class type(); /** diff --git a/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java b/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java index fc948016f..7acdb976f 100644 --- a/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java +++ b/ebean-api/src/main/java/io/ebean/text/json/JsonBeanReader.java @@ -20,7 +20,7 @@ public interface JsonBeanReader { /** * Create a new reader taking the context from the existing one but using a new JsonParser. */ - JsonBeanReader forJson(JsonParser moreJson, boolean resetContext); + JsonBeanReader forJson(JsonParser moreJson); /** * Add a bean explicitly to the persistence context. diff --git a/ebean-api/src/main/java/io/ebean/util/AnnotationUtil.java b/ebean-api/src/main/java/io/ebean/util/AnnotationUtil.java index 238a3217d..583c99009 100644 --- a/ebean-api/src/main/java/io/ebean/util/AnnotationUtil.java +++ b/ebean-api/src/main/java/io/ebean/util/AnnotationUtil.java @@ -2,6 +2,7 @@ package io.ebean.util; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; @@ -57,10 +58,8 @@ public class AnnotationUtil { private static void typeGetAllCollect(Class clazz, Class annotationType, Set result) { while (clazz != null && clazz != Object.class) { - final A val = clazz.getAnnotation(annotationType); - if (val != null) { - result.add(val); - } + final A[] annotations = clazz.getAnnotationsByType(annotationType); + Collections.addAll(result, annotations); clazz = clazz.getSuperclass(); } } diff --git a/ebean-api/src/test/java/io/ebean/EbeanVersionTest.java b/ebean-api/src/test/java/io/ebean/EbeanVersionTest.java new file mode 100644 index 000000000..5eb17bbe8 --- /dev/null +++ b/ebean-api/src/test/java/io/ebean/EbeanVersionTest.java @@ -0,0 +1,31 @@ +package io.ebean; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EbeanVersionTest { + + @Test + void checkMinAgentVersion_ok() { + assertFalse(EbeanVersion.checkMinAgentVersion("12.12.0")); + assertFalse(EbeanVersion.checkMinAgentVersion("12.12.99")); + assertFalse(EbeanVersion.checkMinAgentVersion("13.1.0")); + } + + @Test + void checkMinAgentVersion_agentTooOld() { + assertTrue(EbeanVersion.checkMinAgentVersion("11.13.0")); + assertTrue(EbeanVersion.checkMinAgentVersion("12.11.0")); + assertTrue(EbeanVersion.checkMinAgentVersion("12.11.99")); + } + + @Test + void checkMinAgentVersion_unexpectedAgentVersion() { + assertTrue(EbeanVersion.checkMinAgentVersion("13.13")); + assertTrue(EbeanVersion.checkMinAgentVersion("13")); + assertTrue(EbeanVersion.checkMinAgentVersion("")); + } + +} diff --git a/ebean-api/src/test/java/io/ebean/util/TestAnnotationUtil.java b/ebean-api/src/test/java/io/ebean/util/TestAnnotationUtil.java new file mode 100644 index 000000000..7d7519c03 --- /dev/null +++ b/ebean-api/src/test/java/io/ebean/util/TestAnnotationUtil.java @@ -0,0 +1,28 @@ +package io.ebean.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import io.ebean.annotation.Formula; + +public class TestAnnotationUtil { + + @Formula(select = "x") + @Formula(select = "y") + private static class TestObject { + + } + + @Test + public void testRepeatableAnnotation() { + + Set list = AnnotationUtil.typeGetAll(TestObject.class, Formula.class); + assertThat(list).hasSize(2); + + } + +} diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index a9650d5d0..dc7b6c0ac 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 9c151ac50..417420c68 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean bom @@ -71,88 +71,88 @@ io.ebean ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-core-type - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-ddl-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-externalmapping-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-autotune - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-querybean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean querybean-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided io.ebean ebean-test - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test io.ebean ebean-postgis - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-redis - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 322ac4fe3..3d3e11a75 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index a3fd43f5a..b250f7a0a 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean-core @@ -41,19 +41,19 @@ io.ebean ebean-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-core-type - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-externalmapping-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT @@ -136,7 +136,7 @@ org.postgresql postgresql - 42.2.20 + 42.2.24 true diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java index 7c5264892..ca813ae14 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java @@ -223,6 +223,15 @@ public final class BindParams implements Serializable { return p; } + /** + * Set a named In parameter that is multi-valued. + */ + public Param setArrayParameter(String name, Collection value) { + Param p = getParam(name); + p.setInValue(new MultiValueWrapper(value)); + return p; + } + /** * Set an encryption key as a bind value. *

@@ -285,12 +294,17 @@ public final class BindParams implements Serializable { */ public boolean isSameBindHash() { if (bindHash == null) { - bindHash = calcQueryPlanHash(); return false; } - String oldPlan = bindHash; + String newHash = calcQueryPlanHash(); + return bindHash.equals(newHash); + } + + /** + * Updates the hash. + */ + public void updateHash() { bindHash = calcQueryPlanHash(); - return bindHash.equals(oldPlan); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java index 51f02826c..b23a183bb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java @@ -36,14 +36,6 @@ public interface LoadContext { */ PersistenceContext getPersistenceContext(); - /** - * Set the persistence context used by this query and future lazy loading. - *

- * Used by query iterator when processing large result sets. - *

- */ - void resetPersistenceContext(PersistenceContext persistenceContext); - /** * Register a Bean for lazy loading. */ 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 449a528f4..53d0c4551 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -16,6 +16,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.query.CQuery; import io.ebeaninternal.server.transaction.RemoteTransactionEvent; +import javax.annotation.Nullable; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; @@ -141,7 +142,7 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, EbeanServer, /** * Compile a query. */ - CQuery compileQuery(Type type, Query query, Transaction t); + CQuery compileQuery(Type type, Query query, Transaction transaction); /** * Execute the findId's query but without copying the query. @@ -150,12 +151,12 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, EbeanServer, * the query has finished (if executing in a background thread). *

*/ - List
findIdsWithCopy(Query query, Transaction t); + List findIdsWithCopy(Query query, Transaction transaction); /** * Execute the findCount query but without copying the query. */ - int findCountWithCopy(Query query, Transaction t); + int findCountWithCopy(Query query, Transaction transaction); /** * Load a batch of Associated One Beans. @@ -257,6 +258,7 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, EbeanServer, /** * DTO findOne query. */ + @Nullable T findDtoOne(SpiDtoQuery query); /** 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 3f2e97893..222f00b9a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -24,6 +24,7 @@ import io.ebeaninternal.server.querydefn.OrmUpdateProperties; import io.ebeaninternal.server.rawsql.SpiRawSql; import java.sql.Timestamp; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -399,6 +400,11 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod List getSoftDeletePredicates(); + /** + * Bind the named multi-value array parameter which we would use with Postgres ANY. + */ + void setArrayParameter(String name, Collection values); + /** * Return a copy of the query. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java b/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java index aaf4e2ce8..4594cbe25 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/json/SpiJsonReader.java @@ -14,7 +14,7 @@ public interface SpiJsonReader { PersistenceContext getPersistenceContext(); - SpiJsonReader forJson(JsonParser moreJson, boolean resetContext); + SpiJsonReader forJson(JsonParser moreJson); void persistenceContextPut(Object beanId, T currentBean); 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 33cfb36cb..1cdabc9f8 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 @@ -1,5 +1,7 @@ package io.ebeaninternal.server.core; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.AutoTune; import io.ebean.BackgroundExecutor; import io.ebean.BeanState; @@ -101,7 +103,6 @@ import io.ebeaninternal.util.ParamTypeHelper.TypeInfo; import io.ebeanservice.docstore.api.DocStoreIntegration; import org.slf4j.Logger; -import javax.annotation.Nonnull; import javax.persistence.NonUniqueResultException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; @@ -125,6 +126,7 @@ import static java.util.stream.StreamSupport.stream; /** * The default server side implementation of EbeanServer. */ +@NonNullApi public final class DefaultServer implements SpiServer, SpiEbeanServer { private static final Logger log = CoreLog.internal; @@ -145,7 +147,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final DtoQueryEngine dtoQueryEngine; private final ServerCacheManager serverCacheManager; private final DtoBeanManager dtoBeanManager; - private final BeanDescriptorManager beanDescriptorManager; + private final BeanDescriptorManager descriptorManager; private final AutoTuneService autoTuneService; private final ReadAuditPrepare readAuditPrepare; private final ReadAuditLogger readAuditLogger; @@ -191,8 +193,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { this.currentTenantProvider = this.config.getCurrentTenantProvider(); this.slowQueryMicros = config.getSlowQueryMicros(); this.slowQueryListener = config.getSlowQueryListener(); - this.beanDescriptorManager = config.getBeanDescriptorManager(); - beanDescriptorManager.setEbeanServer(this); + this.descriptorManager = config.getBeanDescriptorManager(); + descriptorManager.setEbeanServer(this); this.updateAllPropertiesInBatch = this.config.isUpdateAllPropertiesInBatch(); this.callStackFactory = initCallStackFactory(this.config); this.persister = config.createPersister(this); @@ -271,6 +273,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return lazyLoadBatchSize; } + @Nullable @Override public Object currentTenantId() { return currentTenantProvider == null ? null : currentTenantProvider.currentId(); @@ -496,16 +499,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (bean instanceof EntityBean) { return new DefaultBeanState((EntityBean) bean); } - // Not an entity bean - return null; + throw new IllegalArgumentException("Bean is not an entity bean"); } /** * Compile a query. Only valid for ORM queries. */ @Override - public CQuery compileQuery(Type type, Query query, Transaction t) { - SpiOrmQueryRequest qr = createQueryRequest(type, query, t); + public CQuery compileQuery(Type type, Query query, Transaction transaction) { + SpiOrmQueryRequest qr = createQueryRequest(type, query, transaction); OrmQueryRequest orm = (OrmQueryRequest) qr; return cqueryEngine.buildQuery(orm); } @@ -564,9 +566,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public Map diff(Object a, Object b) { + public Map diff(@Nullable Object a, Object b) { if (a == null) { - return null; + return Collections.emptyMap(); } BeanDescriptor desc = descriptor(a.getClass()); return DiffHelp.diff(a, b, desc); @@ -618,7 +620,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - private void executeSql(Connection connection, String sql) throws SQLException { + private void executeSql(Connection connection, @Nullable String sql) throws SQLException { if (sql != null) { try (PreparedStatement stmt = connection.prepareStatement(sql)) { transactionManager.log().sql().debug(sql); @@ -661,9 +663,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override @SuppressWarnings({"unchecked", "rawtypes"}) public T reference(Class type, Object id) { - if (id == null) { - throw new NullPointerException("The id is null"); - } + Objects.requireNonNull(id); BeanDescriptor desc = descriptor(type); id = desc.convertId(id); PersistenceContext pc = null; @@ -675,11 +675,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return (T) existing; } } - InheritInfo inheritInfo = desc.inheritInfo(); if (inheritInfo == null || inheritInfo.isConcrete()) { return (T) desc.contextRef(pc, null, false, id); } + return referenceFindOne(type, id, desc); + } + + private T referenceFindOne(Class type, Object id, BeanDescriptor desc) { BeanProperty idProp = desc.idProperty(); if (idProp == null) { throw new PersistenceException("No ID properties for this type? " + desc); @@ -691,7 +694,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public void register(TransactionCallback transactionCallback) { - Transaction transaction = currentTransaction(); + Transaction transaction = transactionManager.active(); if (transaction == null) { throw new PersistenceException("Not currently active transaction when trying to register transactionCallback"); } @@ -719,15 +722,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public T executeCall(Callable c) { - return executeCall(null, c); + public T executeCall(Callable callable) { + return executeCall(null, callable); } @Override - public T executeCall(TxScope scope, Callable c) { + public T executeCall(@Nullable TxScope scope, Callable callable) { ScopedTransaction scopeTrans = transactionManager.beginScopedTransaction(scope); try { - return c.call(); + return callable.call(); } catch (Error e) { throw scopeTrans.caughtError(e); } catch (Exception e) { @@ -738,15 +741,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void execute(Runnable r) { - execute(null, r); + public void execute(Runnable runnable) { + execute(null, runnable); } @Override - public void execute(TxScope scope, Runnable r) { + public void execute(@Nullable TxScope scope, Runnable runnable) { ScopedTransaction t = transactionManager.beginScopedTransaction(scope); try { - r.run(); + runnable.run(); } catch (Error e) { throw t.caughtError(e); } catch (Exception e) { @@ -766,6 +769,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { transactionManager.exitScopedTransaction(returnOrThrowable, opCode); } + @Nullable @Override public SpiTransaction currentServerTransaction() { return transactionManager.active(); @@ -825,58 +829,36 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public Object nextId(Class beanType) { - BeanDescriptor desc = descriptor(beanType); - return desc.nextId(null); + return descriptor(beanType).nextId(null); } @Override @SuppressWarnings("unchecked") public void sort(List list, String sortByClause) { - if (list == null) { - throw new NullPointerException("list is null"); - } - if (sortByClause == null) { - throw new NullPointerException("sortByClause is null"); - } + Objects.requireNonNull(list); + Objects.requireNonNull(sortByClause); if (list.isEmpty()) { // don't need to sort an empty list return; } // use first bean in the list as the correct type Class beanType = (Class) list.get(0).getClass(); - BeanDescriptor beanDescriptor = descriptor(beanType); - if (beanDescriptor == null) { - throw new PersistenceException("BeanDescriptor not found, is [" + beanType + "] an entity bean?"); - } - beanDescriptor.sort(list, sortByClause); + desc(beanType).sort(list, sortByClause); } @Override public Set validateQuery(Query query) { - BeanDescriptor beanDescriptor = descriptor(query.getBeanType()); - if (beanDescriptor == null) { - throw new PersistenceException("BeanDescriptor not found, is [" + query.getBeanType() + "] an entity bean?"); - } - return ((SpiQuery) query).validate(beanDescriptor); + return ((SpiQuery) query).validate(desc(query.getBeanType())); } @Override public Filter filter(Class beanType) { - BeanDescriptor desc = descriptor(beanType); - if (desc == null) { - String m = beanType.getName() + " is NOT an Entity Bean registered with this server?"; - throw new PersistenceException(m); - } - return new ElFilter<>(desc); + return new ElFilter<>(desc(beanType)); } @Override public CsvReader createCsvReader(Class beanType) { - BeanDescriptor descriptor = descriptor(beanType); - if (descriptor == null) { - throw new NullPointerException("BeanDescriptor for " + beanType.getName() + " not found"); - } - return new TCsvReader<>(this, descriptor); + return new TCsvReader<>(this, desc(beanType)); } @Override @@ -895,20 +877,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void merge(Object bean, MergeOptions options, Transaction transaction) { - BeanDescriptor desc = descriptor(bean.getClass()); - if (desc == null) { - throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?"); - } + public void merge(Object bean, MergeOptions options, @Nullable Transaction transaction) { + BeanDescriptor desc = desc(bean.getClass()); executeInTrans((txn) -> persister.merge(desc, checkEntityBean(bean), options, txn), transaction); } @Override public void lock(Object bean) { - BeanDescriptor desc = descriptor(bean.getClass()); - if (desc == null) { - throw new PersistenceException(bean.getClass() + " is NOT an Entity Bean registered with this server?"); - } + BeanDescriptor desc = desc(bean.getClass()); Object id = desc.id(bean); Objects.requireNonNull(id, "Bean missing an @Id value which is required to lock"); new DefaultOrmQuery<>(desc, this, expressionFactory) @@ -924,21 +900,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public Query findNative(Class beanType, String nativeSql) { - BeanDescriptor desc = descriptor(beanType); - if (desc == null) { - throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?"); - } - DefaultOrmQuery query = new DefaultOrmQuery<>(desc, this, expressionFactory); + DefaultOrmQuery query = new DefaultOrmQuery<>(desc(beanType), this, expressionFactory); query.setNativeSql(nativeSql); return query; } @Override public Query createNamedQuery(Class beanType, String namedQuery) { - BeanDescriptor desc = descriptor(beanType); - if (desc == null) { - throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?"); - } + BeanDescriptor desc = desc(beanType); String named = desc.namedQuery(namedQuery); if (named != null) { return createQuery(beanType, named); @@ -961,21 +930,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public DefaultOrmQuery createQuery(Class beanType) { - BeanDescriptor desc = descriptor(beanType); - if (desc == null) { - throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?"); - } - return new DefaultOrmQuery<>(desc, this, expressionFactory); + return new DefaultOrmQuery<>(desc(beanType), this, expressionFactory); } @Override public Update createUpdate(Class beanType, String ormUpdate) { - BeanDescriptor desc = descriptor(beanType); - if (desc == null) { - String m = beanType.getName() + " is NOT an Entity Bean registered with this server?"; - throw new PersistenceException(m); - } - return new DefaultOrmUpdate<>(beanType, this, desc.baseTable(), ormUpdate); + return new DefaultOrmUpdate<>(beanType, this, desc(beanType).baseTable(), ormUpdate); } @Override @@ -1041,30 +1001,28 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Find a bean using its unique id. */ @Override - public T find(Class beanType, Object id, Transaction t) { - if (id == null) { - throw new NullPointerException("The id is null"); - } + public T find(Class beanType, Object id, @Nullable Transaction transaction) { + Objects.requireNonNull(id); Query query = createQuery(beanType).setId(id); - return findId(query, t); + return findId(query, transaction); } - SpiOrmQueryRequest createQueryRequest(Type type, Query query, Transaction t) { - SpiOrmQueryRequest request = buildQueryRequest(type, query, t); + SpiOrmQueryRequest createQueryRequest(Type type, Query query, @Nullable Transaction transaction) { + SpiOrmQueryRequest request = buildQueryRequest(type, query, transaction); request.prepareQuery(); return request; } - SpiOrmQueryRequest buildQueryRequest(Type type, Query query, Transaction t) { + SpiOrmQueryRequest buildQueryRequest(Type type, Query query, @Nullable Transaction transaction) { SpiQuery spiQuery = (SpiQuery) query; spiQuery.setType(type); spiQuery.checkNamedParameters(); - return buildQueryRequest(spiQuery, t); + return buildQueryRequest(spiQuery, transaction); } - private SpiOrmQueryRequest buildQueryRequest(SpiQuery query, Transaction t) { - if (t == null) { - t = currentServerTransaction(); + private SpiOrmQueryRequest buildQueryRequest(SpiQuery query, @Nullable Transaction transaction) { + if (transaction == null) { + transaction = currentServerTransaction(); } query.setDefaultRawSqlIfRequired(); if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) { @@ -1081,14 +1039,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (query.getParentNode() == null) { query.setOrigin(createCallOrigin()); } - return new OrmQueryRequest<>(this, queryEngine, query, (SpiTransaction) t); + return new OrmQueryRequest<>(this, queryEngine, query, (SpiTransaction) transaction); } /** * Try to get the object out of the persistence context. */ + @Nullable @SuppressWarnings("unchecked") - private T findIdCheckPersistenceContextAndCache(Transaction transaction, SpiQuery query, Object id) { + private T findIdCheckPersistenceContextAndCache(@Nullable Transaction transaction, SpiQuery query, Object id) { SpiTransaction t = (SpiTransaction) transaction; if (t == null) { t = currentServerTransaction(); @@ -1133,19 +1092,20 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return (scope != null) ? scope : defaultPersistenceContextScope; } + @Nullable @SuppressWarnings("unchecked") - private T findId(Query query, Transaction t) { + private T findId(Query query, @Nullable Transaction transaction) { SpiQuery spiQuery = (SpiQuery) query; spiQuery.setType(Type.BEAN); if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isForceHitDatabase()) { // See if we can skip doing the fetch completely by getting the bean from the // persistence context or the bean cache - T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId()); + T bean = findIdCheckPersistenceContextAndCache(transaction, spiQuery, spiQuery.getId()); if (bean != null) { return bean; } } - SpiOrmQueryRequest request = buildQueryRequest(spiQuery, t); + SpiOrmQueryRequest request = buildQueryRequest(spiQuery, transaction); request.prepareQuery(); if (request.isUseDocStore()) { return docStore().find(request); @@ -1158,14 +1118,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override public Optional findOneOrEmpty(Query query, Transaction transaction) { return Optional.ofNullable(findOne(query, transaction)); } + @Nullable @Override - public T findOne(Query query, Transaction transaction) { + public T findOne(Query query, @Nullable Transaction transaction) { SpiQuery spiQuery = (SpiQuery) query; if (spiQuery.isFindById()) { // actually a find by Id query @@ -1179,6 +1139,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return extractUnique(list); } + @Nullable private T extractUnique(List list) { if (list.isEmpty()) { return null; @@ -1189,11 +1150,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override @SuppressWarnings({"unchecked", "rawtypes"}) - public Set findSet(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.SET, query, t); + public Set findSet(Query query, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.SET, query, transaction); Object result = request.getFromQueryCache(); if (result != null) { return (Set) result; @@ -1206,13 +1166,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override @SuppressWarnings({"unchecked", "rawtypes"}) - public Map findMap(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.MAP, query, t); + public Map findMap(Query query, @Nullable Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.MAP, query, transaction); request.resetBeanCacheAutoMode(false); - if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) { + if ((transaction == null || !transaction.isSkipCache()) && request.getFromBeanCache()) { // hit bean cache and got all results from cache return request.beanCacheHitsAsMap(); } @@ -1228,11 +1187,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override @SuppressWarnings("unchecked") - public List findSingleAttributeList(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.ATTRIBUTE, query, t); + public List findSingleAttributeList(Query query, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.ATTRIBUTE, query, transaction); Object result = request.getFromQueryCache(); if (result != null) { return (List) result; @@ -1246,17 +1204,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int findCount(Query query, Transaction t) { + public int findCount(Query query, @Nullable Transaction transaction) { SpiQuery spiQuery = ((SpiQuery) query); if (!spiQuery.isDistinct()) { spiQuery = spiQuery.copy(); } - return findCountWithCopy(spiQuery, t); + return findCountWithCopy(spiQuery, transaction); } @Override - public int findCountWithCopy(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.COUNT, query, t); + public int findCountWithCopy(Query query, @Nullable Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.COUNT, query, transaction); Integer result = request.getFromQueryCache(); if (result != null) { return result; @@ -1271,8 +1229,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public boolean exists(Class beanType, Object beanId, Transaction transaction) { - List ids = findIds(find(beanType).setId(beanId), transaction); - return !ids.isEmpty(); + return !findIdsWithCopy(find(beanType).setId(beanId), transaction).isEmpty(); } @Override @@ -1283,7 +1240,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (ids != null) { return !ids.isEmpty(); } - try { request.initTransIfRequired(); return !request.findIds().isEmpty(); @@ -1292,16 +1248,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override - public List findIds(Query query, Transaction t) { - return findIdsWithCopy(((SpiQuery) query).copy(), t); + public List findIds(Query query, Transaction transaction) { + return findIdsWithCopy(((SpiQuery) query).copy(), transaction); } @SuppressWarnings("unchecked") @Override - public List findIdsWithCopy(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.ID_LIST, query, t); + public List findIdsWithCopy(Query query, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.ID_LIST, query, transaction); Object result = request.getFromQueryCache(); if (result != null) { if (Boolean.FALSE.equals(request.query().isReadOnly())) { @@ -1319,8 +1274,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int delete(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.DELETE, query, t); + public int delete(Query query, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.DELETE, query, transaction); try { request.initTransIfRequired(); request.markNotQueryOnly(); @@ -1342,8 +1297,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int update(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.UPDATE, query, t); + public int update(Query query, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.UPDATE, query, transaction); try { request.initTransIfRequired(); request.markNotQueryOnly(); @@ -1353,10 +1308,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override - public FutureRowCount findFutureCount(Query q, Transaction t) { - SpiQuery copy = ((SpiQuery) q).copy(); + public FutureRowCount findFutureCount(Query query, Transaction transaction) { + SpiQuery copy = ((SpiQuery) query).copy(); copy.setFutureFetch(true); Transaction newTxn = createTransaction(); QueryFutureRowCount queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<>(this, copy, newTxn)); @@ -1364,9 +1318,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return queryFuture; } - @Nonnull @Override - public FutureIds findFutureIds(Query query, Transaction t) { + public FutureIds findFutureIds(Query query, Transaction transaction) { SpiQuery copy = ((SpiQuery) query).copy(); copy.setFutureFetch(true); Transaction newTxn = createTransaction(); @@ -1375,15 +1328,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return queryFuture; } - @Nonnull @Override - public FutureList findFutureList(Query query, Transaction t) { + public FutureList findFutureList(Query query, Transaction transaction) { SpiQuery spiQuery = (SpiQuery) query.copy(); spiQuery.setFutureFetch(true); // FutureList query always run in it's own persistence content spiQuery.setPersistenceContext(new DefaultPersistenceContext()); if (!spiQuery.isDisableReadAudit()) { - BeanDescriptor desc = beanDescriptorManager.descriptor(spiQuery.getBeanType()); + BeanDescriptor desc = descriptorManager.descriptor(spiQuery.getBeanType()); desc.readAuditFutureList(spiQuery); } // Create a new transaction solely to execute the findList() at some future time @@ -1393,7 +1345,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return queryFuture; } - @Nonnull @Override public PagedList findPagedList(Query query, Transaction transaction) { SpiQuery spiQuery = (SpiQuery) query; @@ -1407,10 +1358,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return new LimitOffsetPagedList<>(this, spiQuery); } - @Nonnull @Override - public QueryIterator findIterate(Query query, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t); + public QueryIterator findIterate(Query query, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, transaction); try { request.initTransIfRequired(); return request.findIterate(); @@ -1420,13 +1370,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override public Stream findLargeStream(Query query, Transaction transaction) { return findStream(query, transaction); } - @Nonnull @Override public Stream findStream(Query query, Transaction transaction) { return toStream(findIterate(query, transaction)); @@ -1437,8 +1385,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void findEach(Query query, Consumer consumer, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t); + public void findEach(Query query, Consumer consumer, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, transaction); if (request.isUseDocStore()) { docStore().findEach(request, consumer); return; @@ -1449,8 +1397,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void findEach(Query query, int batch, Consumer> consumer, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t); + public void findEach(Query query, int batch, Consumer> consumer, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, transaction); // if (request.isUseDocStore()) { // docStore().findEach(request, consumer); // return; @@ -1461,8 +1409,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void findEachWhile(Query query, Predicate consumer, Transaction t) { - SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t); + public void findEachWhile(Query query, Predicate consumer, Transaction transaction) { + SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, transaction); if (request.isUseDocStore()) { docStore().findEachWhile(request, consumer); return; @@ -1472,9 +1420,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { // no try finally - findEachWhile guarantee's cleanup of the transaction if required } - @Nonnull @Override - public List> findVersions(Query query, Transaction transaction) { + public List> findVersions(Query query, @Nullable Transaction transaction) { SpiOrmQueryRequest request = createQueryRequest(Type.LIST, query, transaction); try { request.initTransIfRequired(); @@ -1484,17 +1431,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override - public List findList(Query query, Transaction t) { - return findList(query, t, false); + public List findList(Query query, Transaction transaction) { + return findList(query, transaction, false); } @SuppressWarnings("unchecked") - private List findList(Query query, Transaction t, boolean findOne) { - SpiOrmQueryRequest request = buildQueryRequest(Type.LIST, query, t); + private List findList(Query query, @Nullable Transaction transaction, boolean findOne) { + SpiOrmQueryRequest request = buildQueryRequest(Type.LIST, query, transaction); request.resetBeanCacheAutoMode(findOne); - if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) { + if ((transaction == null || !transaction.isSkipCache()) && request.getFromBeanCache()) { // hit bean cache and got all results from cache return request.beanCacheHits(); } @@ -1514,11 +1460,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } + @Nullable @Override - public SqlRow findOne(SqlQuery query, Transaction t) { + public SqlRow findOne(SqlQuery query, Transaction transaction) { // no findId() method for SqlQuery... // a query that is expected to return either 0 or 1 rows - List list = findList(query, t); + List list = findList(query, transaction); return extractUnique(list); } @@ -1544,10 +1491,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - @Nonnull @Override - public List findList(SqlQuery query, Transaction t) { - RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t); + public List findList(SqlQuery query, Transaction transaction) { + RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, transaction); try { request.initTransIfRequired(); return request.findList(); @@ -1657,6 +1603,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } + @Nullable @Override public T findDtoOne(SpiDtoQuery query) { DtoQueryRequest request = new DtoQueryRequest<>(this, dtoQueryEngine, query); @@ -1680,8 +1627,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Save the bean with an explicit transaction. */ @Override - public void save(Object bean, Transaction t) { - persister.save(checkEntityBean(bean), t); + public void save(Object bean, @Nullable Transaction transaction) { + persister.save(checkEntityBean(bean), transaction); } @Override @@ -1699,8 +1646,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void update(Object bean, Transaction t) { - persister.update(checkEntityBean(bean), t); + public void update(Object bean, @Nullable Transaction transaction) { + persister.update(checkEntityBean(bean), transaction); } @Override @@ -1712,7 +1659,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Update all beans in the collection with an explicit transaction. */ @Override - public void updateAll(Collection beans, Transaction transaction) { + public void updateAll(@Nullable Collection beans, @Nullable Transaction transaction) { if (beans == null || beans.isEmpty()) { return; } @@ -1736,8 +1683,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Insert the bean with a transaction. */ @Override - public void insert(Object bean, Transaction t) { - persister.insert(checkEntityBean(bean), t); + public void insert(Object bean, @Nullable Transaction transaction) { + persister.insert(checkEntityBean(bean), transaction); } /** @@ -1752,7 +1699,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Insert all beans in the collection with a transaction. */ @Override - public void insertAll(Collection beans, Transaction transaction) { + public void insertAll(@Nullable Collection beans, @Nullable Transaction transaction) { if (beans == null || beans.isEmpty()) { return; } @@ -1765,10 +1712,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public List publish(Query query, Transaction transaction) { + public List publish(Query query, @Nullable Transaction transaction) { return executeInTrans((txn) -> persister.publish(query, txn), transaction); } + @Nullable @Override public T publish(Class beanType, Object id) { return publish(beanType, id, null); @@ -1779,25 +1727,28 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return publish(query, null); } + @Nullable @Override - public T publish(Class beanType, Object id, Transaction transaction) { + public T publish(Class beanType, Object id, @Nullable Transaction transaction) { Query query = find(beanType).setId(id); List liveBeans = publish(query, transaction); return (liveBeans.size() == 1) ? liveBeans.get(0) : null; } @Override - public List draftRestore(Query query, Transaction transaction) { + public List draftRestore(Query query, @Nullable Transaction transaction) { return executeInTrans((txn) -> persister.draftRestore(query, txn), transaction); } + @Nullable @Override - public T draftRestore(Class beanType, Object id, Transaction transaction) { + public T draftRestore(Class beanType, Object id, @Nullable Transaction transaction) { Query query = find(beanType).setId(id); List beans = draftRestore(query, transaction); return (beans.size() == 1) ? beans.get(0) : null; } + @Nullable @Override public T draftRestore(Class beanType, Object id) { return draftRestore(beanType, id, null); @@ -1809,13 +1760,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } private EntityBean checkEntityBean(Object bean) { - if (bean == null) { - throw new IllegalArgumentException("The bean is null?"); - } - if (!(bean instanceof EntityBean)) { - throw new IllegalArgumentException("Was expecting an EntityBean but got a " + bean.getClass()); - } - return (EntityBean) bean; + return (EntityBean) Objects.requireNonNull(bean); } @Override @@ -1836,7 +1781,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { /** * Save all beans in the iterator with an explicit transaction. */ - private int saveAllInternal(Collection beans, Transaction transaction) { + private int saveAllInternal(@Nullable Collection beans, @Nullable Transaction transaction) { if (beans == null || beans.isEmpty()) { return 0; } @@ -1858,7 +1803,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int delete(Class beanType, Object id, Transaction transaction) { + public int delete(Class beanType, Object id, @Nullable Transaction transaction) { return delete(beanType, id, transaction, false); } @@ -1868,11 +1813,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int deletePermanent(Class beanType, Object id, Transaction transaction) { + public int deletePermanent(Class beanType, Object id, @Nullable Transaction transaction) { return delete(beanType, id, transaction, true); } - private int delete(Class beanType, Object id, Transaction transaction, boolean permanent) { + private int delete(Class beanType, Object id, @Nullable Transaction transaction, boolean permanent) { return executeInTrans((txn) -> persister.delete(beanType, id, txn, permanent), transaction); } @@ -1882,8 +1827,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int deleteAll(Class beanType, Collection ids, Transaction t) { - return deleteAll(beanType, ids, t, false); + public int deleteAll(Class beanType, Collection ids, @Nullable Transaction transaction) { + return deleteAll(beanType, ids, transaction, false); } @Override @@ -1892,11 +1837,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int deleteAllPermanent(Class beanType, Collection ids, Transaction t) { - return deleteAll(beanType, ids, t, true); + public int deleteAllPermanent(Class beanType, Collection ids, @Nullable Transaction transaction) { + return deleteAll(beanType, ids, transaction, true); } - private int deleteAll(Class beanType, Collection ids, Transaction transaction, boolean permanent) { + private int deleteAll(Class beanType, Collection ids, @Nullable Transaction transaction, boolean permanent) { return executeInTrans((txn) -> persister.deleteMany(beanType, ids, txn, permanent), transaction); } @@ -1912,9 +1857,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Delete the bean with the explicit transaction. */ @Override - public boolean delete(Object bean, Transaction t) throws OptimisticLockException { + public boolean delete(Object bean, @Nullable Transaction transaction) throws OptimisticLockException { // this should really return an int where -1 means jdbc batch/unknown - return persister.delete(checkEntityBean(bean), t, false) != 0; + return persister.delete(checkEntityBean(bean), transaction, false) != 0; } @Override @@ -1923,9 +1868,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public boolean deletePermanent(Object bean, Transaction t) throws OptimisticLockException { + public boolean deletePermanent(Object bean, @Nullable Transaction transaction) throws OptimisticLockException { // this should really return an int where -1 means jdbc batch/unknown - return persister.delete(checkEntityBean(bean), t, true) != 0; + return persister.delete(checkEntityBean(bean), transaction, true) != 0; } @Override @@ -1934,8 +1879,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public int deleteAllPermanent(Collection beans, Transaction t) { - return deleteAllInternal(beans, t, true); + public int deleteAllPermanent(Collection beans, @Nullable Transaction transaction) { + return deleteAllInternal(beans, transaction, true); } /** @@ -1950,14 +1895,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Delete all the beans in the collection. */ @Override - public int deleteAll(Collection beans, Transaction t) { - return deleteAllInternal(beans, t, false); + public int deleteAll(Collection beans, @Nullable Transaction transaction) { + return deleteAllInternal(beans, transaction, false); } /** * Delete all the beans in the iterator with an explicit transaction. */ - private int deleteAllInternal(Collection beans, Transaction transaction, boolean permanent) { + private int deleteAllInternal(@Nullable Collection beans, @Nullable Transaction transaction, boolean permanent) { if (beans == null || beans.isEmpty()) { return 0; } @@ -1977,8 +1922,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Execute the CallableSql with an explicit transaction. */ @Override - public int execute(CallableSql callSql, Transaction t) { - return persister.executeCallable(callSql, t); + public int execute(CallableSql callSql, @Nullable Transaction transaction) { + return persister.executeCallable(callSql, transaction); } /** @@ -1993,8 +1938,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Execute the updateSql with an explicit transaction. */ @Override - public int execute(SqlUpdate updSql, Transaction t) { - return persister.executeSqlUpdate(updSql, t); + public int execute(SqlUpdate updSql, @Nullable Transaction transaction) { + return persister.executeSqlUpdate(updSql, transaction); } @Override @@ -2003,12 +1948,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) { + public void addBatch(SpiSqlUpdate sqlUpdate, @Nullable SpiTransaction transaction) { persister.addBatch(sqlUpdate, transaction); } @Override - public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) { + public int[] executeBatch(SpiSqlUpdate sqlUpdate, @Nullable SpiTransaction transaction) { return persister.executeBatch(sqlUpdate, transaction); } @@ -2024,8 +1969,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { * Execute the updateSql with an explicit transaction. */ @Override - public int execute(Update update, Transaction t) { - return persister.executeOrmUpdate(update, t); + public int execute(Update update, @Nullable Transaction transaction) { + return persister.executeOrmUpdate(update, transaction); } /** @@ -2041,7 +1986,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ @Override public List> descriptors() { - return beanDescriptorManager.descriptorList(); + return descriptorManager.descriptorList(); } /** @@ -2053,41 +1998,41 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } public void register(BeanPersistController controller) { - for (BeanDescriptor desc : beanDescriptorManager.descriptorList()) { + for (BeanDescriptor desc : descriptorManager.descriptorList()) { desc.register(controller); } } - public void deregister(BeanPersistController c) { - for (BeanDescriptor desc : beanDescriptorManager.descriptorList()) { - desc.deregister(c); + public void deregister(BeanPersistController controller) { + for (BeanDescriptor desc : descriptorManager.descriptorList()) { + desc.deregister(controller); } } @Override public boolean isSupportedType(java.lang.reflect.Type genericType) { TypeInfo typeInfo = ParamTypeHelper.getTypeInfo(genericType); - return typeInfo != null && descriptor(typeInfo.getBeanType()) != null; + return typeInfo != null && descriptorManager.descriptor(typeInfo.getBeanType()) != null; } @Override public Object beanId(Object bean, Object id) { EntityBean eb = checkEntityBean(bean); - BeanDescriptor desc = descriptor(bean.getClass()); - if (desc == null) { - throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?"); - } - return desc.convertSetId(id, eb); + return desc(bean.getClass()).convertSetId(id, eb); } @Override public Object beanId(Object bean) { EntityBean eb = checkEntityBean(bean); - BeanDescriptor desc = descriptor(bean.getClass()); + return desc(bean.getClass()).getId(eb); + } + + private BeanDescriptor desc(Class beanClass) { + BeanDescriptor desc = descriptorManager.descriptor(beanClass); if (desc == null) { - throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?"); + throw new PersistenceException(beanClass.getName() + " is NOT an Entity Bean registered with this server?"); } - return desc.getId(eb); + return desc; } /** @@ -2095,7 +2040,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ @Override public BeanDescriptor descriptor(Class beanClass) { - return beanDescriptorManager.descriptor(beanClass); + return descriptorManager.descriptor(beanClass); } /** @@ -2103,7 +2048,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ @Override public List> descriptors(String tableName) { - return beanDescriptorManager.descriptors(tableName); + return descriptorManager.descriptors(tableName); } /** @@ -2119,7 +2064,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ @Override public List> beanTypes(String tableName) { - return beanDescriptorManager.beanTypes(tableName); + return descriptorManager.beanTypes(tableName); } @Override @@ -2129,7 +2074,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public BeanDescriptor descriptorByQueueId(String queueId) { - return beanDescriptorManager.descriptorByQueueId(queueId); + return descriptorManager.descriptorByQueueId(queueId); } /** @@ -2145,7 +2090,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ @Override public BeanDescriptor descriptorById(String beanClassName) { - return beanDescriptorManager.descriptorByClassName(beanClassName); + return descriptorManager.descriptorByClassName(beanClassName); } /** @@ -2182,8 +2127,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } - private

P executeInTrans(Function fun, Transaction t) { - ObtainedTransaction wrap = initTransIfRequired(t); + private

P executeInTrans(Function fun, @Nullable Transaction transaction) { + ObtainedTransaction wrap = initTransIfRequired(transaction); try { P result = fun.apply(wrap.transaction()); wrap.commitIfCreated(); @@ -2199,9 +2144,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { /** * Create a transaction if one is not currently active. */ - ObtainedTransaction initTransIfRequired(Transaction t) { - if (t != null) { - return new ObtainedTransaction((SpiTransaction) t); + ObtainedTransaction initTransIfRequired(@Nullable Transaction transaction) { + if (transaction != null) { + return new ObtainedTransaction((SpiTransaction) transaction); } SpiTransaction trans = transactionManager.active(); if (trans != null) { @@ -2266,9 +2211,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return checkUniqueness(bean, null); } - @Nonnull @Override - public Set checkUniqueness(Object bean, Transaction transaction) { + public Set checkUniqueness(Object bean, @Nullable Transaction transaction) { EntityBean entityBean = checkEntityBean(bean); BeanDescriptor beanDesc = descriptor(entityBean.getClass()); BeanProperty idProperty = beanDesc.idProperty(); @@ -2297,7 +2241,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { /** * Returns a set of properties if saving the bean will violate the unique constraints (defined by given properties). */ - private Set checkUniqueness(EntityBean entityBean, BeanDescriptor beanDesc, BeanProperty[] props, Transaction transaction) { + @Nullable + private Set checkUniqueness(EntityBean entityBean, BeanDescriptor beanDesc, BeanProperty[] props, @Nullable Transaction transaction) { BeanProperty idProperty = beanDesc.idProperty(); Query query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory); ExpressionList exprList = query.where(); @@ -2330,7 +2275,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { serverCacheManager.visitMetrics(visitor); } if (visitor.collectQueryMetrics()) { - beanDescriptorManager.visitMetrics(visitor); + descriptorManager.visitMetrics(visitor); dtoBeanManager.visitMetrics(visitor); relationalQueryEngine.visitMetrics(visitor); persister.visitMetrics(visitor); @@ -2348,7 +2293,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (initRequest.isAll()) { queryPlanManager.setDefaultThreshold(initRequest.thresholdMicros()); } - return beanDescriptorManager.queryPlanInit(initRequest); + return descriptorManager.queryPlanInit(initRequest); } List queryPlanCollectNow(QueryPlanRequest request) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java index 8f66ae6eb..756c5665f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java @@ -331,6 +331,12 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { return this; } + @Override + public SqlUpdate setArrayParameter(String name, Collection values) { + bindParams.setArrayParameter(name, values); + return this; + } + @Override public SqlUpdate setNull(String name, int jdbcType) { bindParams.setNullParameter(name, jdbcType); 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 4335b56d5..6ff7e5acb 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 @@ -2,7 +2,6 @@ package io.ebeaninternal.server.core; import io.ebean.*; import io.ebean.bean.BeanCollection; -import io.ebean.bean.EntityBean; import io.ebean.bean.PersistenceContext; import io.ebean.cache.QueryCacheEntry; import io.ebean.common.BeanList; @@ -37,7 +36,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery private final Boolean readOnly; private LoadContext loadContext; private PersistenceContext persistenceContext; - private JsonReadOptions jsonRead; private HashQuery cacheKey; private CQueryPlanKey queryPlanKey; private SpiQuerySecondary secondaryQueries; @@ -201,14 +199,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery return persistenceContext; } - /** - * Add the bean to the persistence context. - */ - public void persistenceContextAdd(EntityBean bean) { - Object id = beanDescriptor.getId(bean); - beanDescriptor.contextPut(persistenceContext, id, bean); - } - /** * This will create a local (readOnly) transaction if no current transaction * exists. @@ -233,6 +223,9 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery createdTransaction = true; } persistenceContext = persistenceContext(query, transaction); + if (Type.ITERATE == query.getType()) { + persistenceContext.beginIterate(); + } loadContext = new DLoadContext(this, secondaryQueries); } @@ -241,6 +234,9 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery */ @Override public void rollbackTransIfRequired() { + if (Type.ITERATE == query.getType()) { + persistenceContext.endIterate(); + } if (createdTransaction) { try { transaction.end(); @@ -262,7 +258,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery if (query.getPersistenceContext() == null) { query.setPersistenceContext(persistenceContext); } - jsonRead = new JsonReadOptions(); + JsonReadOptions jsonRead = new JsonReadOptions(); jsonRead.setPersistenceContext(persistenceContext); if (!query.isDisableLazyLoading()) { loadContext = new DLoadContext(this, secondaryQueries); @@ -271,20 +267,6 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery return jsonRead; } - /** - * For iterate queries reset the persistenceContext and loadContext. - */ - public void flushPersistenceContextOnIterate() { - if (persistenceContext.resetLimit()) { - persistenceContext = persistenceContext.forIterateReset(); - loadContext.resetPersistenceContext(persistenceContext); - if (jsonRead != null) { - jsonRead.setPersistenceContext(persistenceContext); - jsonRead.setLoadContext(loadContext); - } - } - } - /** * Get the TransactionContext either explicitly set on the query or * transaction scoped. @@ -300,11 +282,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery if (scope == PersistenceContextScope.QUERY || t == null) { return new DefaultPersistenceContext(); } - if (Type.ITERATE == query.getType()) { - return t.getPersistenceContext().forIterate(); - } else { - return t.getPersistenceContext(); - } + return t.getPersistenceContext(); } /** @@ -314,6 +292,9 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery */ @Override public void endTransIfRequired() { + if (Type.ITERATE == query.getType()) { + persistenceContext.endIterate(); + } if (createdTransaction && transaction.isActive()) { transaction.commit(); if (query.getType().isUpdate()) { @@ -552,7 +533,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery @SuppressWarnings({"rawtypes"}) private void mergeCacheHitsToMap(BeanCollection result) { - BeanMap map = (BeanMap)result; + BeanMap map = (BeanMap) result; ElPropertyValue property = mapProperty(); for (T bean : cacheBeans) { map.internalPut(property.pathGet(bean), bean); @@ -580,17 +561,18 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery @SuppressWarnings("unchecked") private Map cacheBeansToMap() { ElPropertyValue property = mapProperty(); - Map map = new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); for (T bean : cacheBeans) { - map.put((K)property.pathGet(bean), bean); + map.put((K) property.pathGet(bean), bean); } return map; } private ElPropertyValue mapProperty() { - ElPropertyValue property = beanDescriptor.elGetValue(query.getMapKey()); + final String key = query.getMapKey(); + final ElPropertyValue property = key == null ? beanDescriptor.idProperty() : beanDescriptor.elGetValue(key); if (property == null) { - throw new IllegalStateException("Unknown map key property "+query.getMapKey()); + throw new IllegalStateException("Unknown map key property " + key); } return property; } @@ -652,7 +634,7 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery if (cached != null && isAuditReads() && readAuditQueryType()) { if (cached instanceof BeanCollection) { // raw sql can't use L2 cache so normal queries only in here - Collection actualDetails = ((BeanCollection)cached).getActualDetails(); + Collection actualDetails = ((BeanCollection) cached).getActualDetails(); List ids = new ArrayList<>(actualDetails.size()); for (T bean : actualDetails) { ids.add(beanDescriptor.idForJson(bean)); @@ -663,13 +645,13 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery if (Boolean.FALSE.equals(query.isReadOnly())) { // return shallow copies if readonly is explicitly set to false if (cached instanceof BeanCollection) { - cached = ((BeanCollection)cached).getShallowCopy(); + cached = ((BeanCollection) cached).getShallowCopy(); } else if (cached instanceof List) { - cached = new CopyOnFirstWriteList<>((List)cached); + cached = new CopyOnFirstWriteList<>((List) cached); } else if (cached instanceof Set) { - cached = new LinkedHashSet<>((Set)cached); + cached = new LinkedHashSet<>((Set) cached); } else if (cached instanceof Map) { - cached = new LinkedHashMap<>((Map)cached); + cached = new LinkedHashMap<>((Map) cached); } } return cached; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefInherit.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefInherit.java index 3a471f666..b797c5a0c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefInherit.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefInherit.java @@ -31,7 +31,12 @@ final class AssocOneHelpRefInherit extends AssocOneHelp { Object read(DbReadContext ctx) throws SQLException { // read discriminator to determine the type InheritInfo rowInheritInfo = inherit.readType(ctx); - if (rowInheritInfo == null) { + BeanDescriptor desc; + if (rowInheritInfo != null) { + desc = rowInheritInfo.desc(); + } else if (!inherit.hasChildren()) { + desc = inherit.desc(); + } else { // ignore the id property property.targetIdBinder.loadIgnore(ctx); return null; @@ -42,7 +47,6 @@ final class AssocOneHelpRefInherit extends AssocOneHelp { } // check transaction context to see if it already exists PersistenceContext pc = ctx.getPersistenceContext(); - BeanDescriptor desc = rowInheritInfo.desc(); Object existing = desc.contextGet(pc, id); if (existing != null) { return existing; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index d165815e4..eba1eea95 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -56,7 +56,6 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyMapping; import io.ebeanservice.docstore.api.mapping.DocumentMapping; import org.slf4j.Logger; -import javax.annotation.Nonnull; import javax.persistence.PersistenceException; import java.io.IOException; import java.io.StringWriter; @@ -209,6 +208,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { private final EntityBean prototypeEntityBean; private final IdBinder idBinder; + private final String idSelect; private String idBinderInLHSSql; private String idBinderIdSql; private String deleteByIdSql; @@ -350,6 +350,23 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { propertiesIndex[i] = propMap.get(ebi.getProperty(i)); } } + idSelect = initIdSelect(); + } + + String initIdSelect() { + if (idProperty != null && !idProperty.name().equals("_idClass")) { + return idProperty.name(); + } else if (entityType == EntityType.EMBEDDED) { + return null; + } else { + StringJoiner sj = new StringJoiner(","); + for (BeanProperty prop : propertiesNonMany) { + if (prop.isImportedPrimaryKey()) { + sj.add(prop.name()); + } + } + return sj.toString().intern(); + } } public boolean isJacksonCorePresent() { @@ -1961,7 +1978,6 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { * Return the class type this BeanDescriptor describes. */ @Override - @Nonnull public Class type() { return beanType; } @@ -1973,7 +1989,6 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { * instead. */ @Override - @Nonnull public String fullName() { return fullName; } @@ -1982,7 +1997,6 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { * Return the short name of the entity bean. */ @Override - @Nonnull public String name() { return name; } @@ -2936,7 +2950,6 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { } @Override - @Nonnull public Collection allProperties() { return propertiesAll(); } @@ -3016,6 +3029,10 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { return idProperty; } + public String idSelect() { + return idSelect; + } + /** * Return true if this bean should be inserted rather than updated. * @@ -3060,6 +3077,12 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { return versionPropertyIndex > -1 && ebi.isLoadedProperty(versionPropertyIndex); } + void setReferenceIfIdOnly(EntityBeanIntercept ebi) { + if (referenceIdPropertyOnly(ebi)) { + ebi.setReference(idPropertyIndex); + } + } + /** * Set the version value returning it in primitive long form. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java index c69fbb265..938a4c3d4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java @@ -23,6 +23,11 @@ abstract class BeanDescriptorElement extends BeanDescriptor { this.elementHelp = elementHelp; } + @Override + String initIdSelect() { + return null; + } + private String shortName(String name) { int pos = name.lastIndexOf('.'); if (pos > 1) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java index 5abb988c7..6eef2b06d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java @@ -90,7 +90,7 @@ final class BeanDescriptorJsonHelp { return null; } JsonParser newParser = node.traverse(); - SpiJsonReader newReader = jsonRead.forJson(newParser, false); + SpiJsonReader newReader = jsonRead.forJson(newParser); // check for the discriminator value to determine the correct sub type String discColumn = inheritInfo.getRoot().getDiscriminatorColumn(); @@ -152,6 +152,9 @@ final class BeanDescriptorJsonHelp { } if (contextBean == null) { readJson.beanVisitor(bean, unmappedProperties); + if (!isNullOrZero(id)) { + desc.setReferenceIfIdOnly(bean._ebean_getIntercept()); + } } if (path != null) { readJson.popPath(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index 4ce4bd6c5..eee73e4c0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -349,10 +349,10 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy builder = RawSqlBuilder.unparsed(sql.getQuery()); } - for (Map.Entry columnMapping : sql.getColumnMapping().entrySet()) { + for (Map.Entry columnMapping : sql.getColumnMapping().entrySet()) { builder.columnMapping(columnMapping.getKey(), columnMapping.getValue()); } - for (Map.Entry aliasMapping : sql.getAliasMapping().entrySet()) { + for (Map.Entry aliasMapping : sql.getAliasMapping().entrySet()) { builder.tableAliasMapping(aliasMapping.getKey(), aliasMapping.getValue()); } info.addRawSql(sql.getName(), builder.create()); @@ -410,7 +410,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy @Override public boolean isTableManaged(String tableName) { return tableToDescMap.get(tableName.toLowerCase()) != null - || tableToViewDescMap.get(tableName.toLowerCase()) != null; + || tableToViewDescMap.get(tableName.toLowerCase()) != null; } /** @@ -618,7 +618,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy DeployBeanInfo info = createDeployBeanInfo(entityClass); deployInfoMap.put(entityClass, info); Class embeddedIdType = info.getEmbeddedIdType(); - if (embeddedIdType != null){ + if (embeddedIdType != null) { embeddedIdTypes.add(embeddedIdType); } } @@ -788,8 +788,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy Class targetType = prop.getTargetType(); DeployBeanInfo info = deployInfoMap.get(targetType); if (info == null) { - String msg = "Can not find descriptor [" + targetType + "] for " + prop.getFullBeanName(); - throw new PersistenceException(msg); + throw new PersistenceException("Can not find descriptor [" + targetType + "] for " + prop.getFullBeanName()); } return info.getDescriptor(); } @@ -901,21 +900,17 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy Class owningType = oneToMany.getOwningType(); if (!oneToMany.getCascadeInfo().isSave()) { // The property MUST have persist cascading so that inserts work. - Class targetType = oneToMany.getTargetType(); String msg = "Error on " + oneToMany.getFullBeanName() + ". @OneToMany MUST have "; msg += "Cascade.PERSIST or Cascade.ALL because this is a unidirectional "; msg += "relationship. That is, there is no property of type " + owningType + " on " + targetType; - throw new PersistenceException(msg); } // mark this property as unidirectional oneToMany.setUnidirectional(); - // specify table and table alias... BeanTable beanTable = beanTable(owningType); - // define the TableJoin DeployTableJoin oneToManyJoin = oneToMany.getTableJoin(); if (!oneToManyJoin.hasJoinColumns()) { @@ -951,27 +946,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy String mappedBy = prop.getMappedBy(); // get the mappedBy property DeployBeanDescriptor targetDesc = targetDescriptor(prop); - DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); - if (mappedProp == null) { - String m = "Error on " + prop.getFullBeanName(); - m += " Can not find mappedBy property [" + targetDesc + "." + mappedBy + "] "; - throw new PersistenceException(m); - } - - if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { - String m = "Error on " + prop.getFullBeanName(); - m += ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"; - throw new PersistenceException(m); - } - - DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; - - if (!mappedAssocOne.isOneToOne()) { - String m = "Error on " + prop.getFullBeanName(); - m += ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"; - throw new PersistenceException(m); - } - + DeployBeanPropertyAssocOne mappedAssocOne = mappedOneToOne(prop, mappedBy, targetDesc); DeployTableJoin tableJoin = prop.getTableJoin(); if (!tableJoin.hasJoinColumns()) { // define Join as the inverse of the mappedBy property @@ -987,6 +962,21 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } } + private DeployBeanPropertyAssocOne mappedOneToOne(DeployBeanPropertyAssocOne prop, String mappedBy, DeployBeanDescriptor targetDesc) { + DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); + if (mappedProp == null) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + " Can not find mappedBy property [" + targetDesc + "." + mappedBy + "]"); + } + if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"); + } + DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; + if (!mappedAssocOne.isOneToOne()) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + ". mappedBy property [" + targetDesc + "." + mappedBy + "]is not a OneToOne?"); + } + return mappedAssocOne; + } + private void checkUniDirectionalPrimaryKeyJoin(DeployBeanPropertyAssocOne prop) { if (prop.isPrimaryKeyJoin()) { // uni-directional PrimaryKeyJoin ... @@ -1008,7 +998,6 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy return; } DeployBeanDescriptor targetDesc = targetDescriptor(prop); - if (targetDesc.isDraftableElement()) { // automatically turning on orphan removal and CascadeType.ALL prop.setModifyListenMode(BeanCollection.ModifyListenMode.REMOVALS); @@ -1040,23 +1029,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy String mappedBy = prop.getMappedBy(); // get the mappedBy property - DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); - if (mappedProp == null) { - String m = "Error on " + prop.getFullBeanName(); - m += " Can not find mappedBy property [" + mappedBy + "] "; - m += "in [" + targetDesc + "]"; - throw new PersistenceException(m); - } - - if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { - String m = "Error on " + prop.getFullBeanName(); - m += ". mappedBy property [" + mappedBy + "]is not a ManyToOne?"; - m += "in [" + targetDesc + "]"; - throw new PersistenceException(m); - } - - DeployBeanPropertyAssocOne mappedAssocOne = (DeployBeanPropertyAssocOne) mappedProp; - + DeployBeanPropertyAssocOne mappedAssocOne = mappedManyToOne(prop, targetDesc, mappedBy); DeployTableJoin tableJoin = prop.getTableJoin(); if (!tableJoin.hasJoinColumns()) { // define Join as the inverse of the mappedBy property @@ -1079,6 +1052,17 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } } + private DeployBeanPropertyAssocOne mappedManyToOne(DeployBeanPropertyAssocMany prop, DeployBeanDescriptor targetDesc, String mappedBy) { + DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); + if (mappedProp == null) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + " Can not find mappedBy property [" + mappedBy + "] " + "in [" + targetDesc + "]"); + } + if (!(mappedProp instanceof DeployBeanPropertyAssocOne)) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + ". mappedBy property [" + mappedBy + "]is not a ManyToOne?" + "in [" + targetDesc + "]"); + } + return (DeployBeanPropertyAssocOne) mappedProp; + } + /** * For mappedBy copy the joins from the other side. */ @@ -1094,33 +1078,10 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy // get the mappedBy property DeployBeanDescriptor targetDesc = targetDescriptor(prop); - DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); - - if (mappedProp == null) { - String m = "Error on " + prop.getFullBeanName(); - m += " Can not find mappedBy property [" + mappedBy + "] "; - m += "in [" + targetDesc + "]"; - throw new PersistenceException(m); - } - - if (!(mappedProp instanceof DeployBeanPropertyAssocMany)) { - String m = "Error on " + prop.getFullBeanName(); - m += ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"; - throw new PersistenceException(m); - } - - DeployBeanPropertyAssocMany mappedAssocMany = (DeployBeanPropertyAssocMany) mappedProp; - - if (!mappedAssocMany.isManyToMany()) { - String m = "Error on " + prop.getFullBeanName(); - m += ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"; - throw new PersistenceException(m); - } + DeployBeanPropertyAssocMany mappedAssocMany = mappedManyToMany(prop, mappedBy, targetDesc); // define the relationships/joins on this side as the // reverse of the other mappedBy side ... - - // DeployTableJoin mappedJoin = mappedAssocMany.getTableJoin(); DeployTableJoin mappedIntJoin = mappedAssocMany.getIntersectionJoin(); DeployTableJoin mappendInverseJoin = mappedAssocMany.getInverseJoin(); @@ -1142,6 +1103,22 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy } } + private DeployBeanPropertyAssocMany mappedManyToMany(DeployBeanPropertyAssocMany prop, String mappedBy, DeployBeanDescriptor targetDesc) { + DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy); + if (mappedProp == null) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + " Can not find mappedBy property [" + mappedBy + "] " + "in [" + targetDesc + "]"); + } + if (!(mappedProp instanceof DeployBeanPropertyAssocMany)) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"); + } + + DeployBeanPropertyAssocMany mappedAssocMany = (DeployBeanPropertyAssocMany) mappedProp; + if (!mappedAssocMany.isManyToMany()) { + throw new PersistenceException("Error on " + prop.getFullBeanName() + ". mappedBy property [" + targetDesc + "." + mappedBy + "] is not a ManyToMany?"); + } + return mappedAssocMany; + } + private void setBeanControllerFinderListener(DeployBeanDescriptor descriptor) { persistControllerManager.addPersistControllers(descriptor); postLoadManager.addPostLoad(descriptor); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index 12709ee70..bbee58e60 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -40,7 +40,6 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyMapping; import io.ebeanservice.docstore.api.mapping.DocPropertyOptions; import io.ebeanservice.docstore.api.support.DocStructure; -import javax.annotation.Nonnull; import javax.persistence.PersistenceException; import java.io.DataInput; import java.io.DataOutput; @@ -813,7 +812,6 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { * Return the name of the property. */ @Override - @Nonnull public String name() { return name; } @@ -1299,7 +1297,6 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { * Return the property type. */ @Override - @Nonnull public Class type() { return propertyType; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java index 5776b7ccc..7f6c3d96e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java @@ -35,8 +35,7 @@ public final class ImportedIdEmbedded implements ImportedId { BeanProperty[] embeddedProps = foreignAssocOne.properties(); for (int i = 0; i < imported.length; i++) { String n = name + "." + foreignAssocOne.name() + "." + embeddedProps[i].name(); - BeanFkeyProperty fkey = new BeanFkeyProperty(n, imported[i].localDbColumn, foreignAssocOne.deployOrder()); - owner.descriptor().add(fkey); + owner.descriptor().add(new BeanFkeyProperty(n, imported[i].localDbColumn, foreignAssocOne.deployOrder())); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java index df4c3272c..fe3e8ce2e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java @@ -88,8 +88,7 @@ public final class ImportedIdSimple implements ImportedId, Comparable type = prop.getPropertyType(); - ScalarType scalarType = typeManager.getArrayScalarType(type, dbArray, prop.getGenericType(), prop.isNullable()); + ScalarType scalarType = typeManager.getArrayScalarType(type, prop.getGenericType(), prop.isNullable()); if (scalarType == null) { throw new RuntimeException("No ScalarType for @DbArray type for [" + prop.getFullBeanName() + "]"); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DetermineManyType.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DetermineManyType.java similarity index 67% rename from ebean-core/src/main/java/io/ebeaninternal/server/deploy/DetermineManyType.java rename to ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DetermineManyType.java index fc1859074..40fc6016b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DetermineManyType.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DetermineManyType.java @@ -1,4 +1,6 @@ -package io.ebeaninternal.server.deploy; +package io.ebeaninternal.server.deploy.parse; + +import io.ebeaninternal.server.deploy.ManyType; import java.util.List; import java.util.Map; @@ -7,9 +9,9 @@ import java.util.Set; /** * Determine the Many Type for a property. */ -public final class DetermineManyType { +final class DetermineManyType { - public ManyType getManyType(Class type) { + ManyType getManyType(Class type) { if (type.equals(List.class)) { return ManyType.LIST; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaBuilder.java index f3cced766..0632e89f2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaBuilder.java @@ -37,8 +37,7 @@ final class DtoMetaBuilder { if (includeMethod(method)) { try { final String name = propertyName(method.getName()); - final Class propertyType = propertyType(method); - properties.add(new DtoMetaProperty(typeManager, dtoType, method, name, propertyType)); + properties.add(new DtoMetaProperty(typeManager, dtoType, method, name)); } catch (Exception e) { CoreLog.log.debug("exclude on " + dtoType + " method " + method, e); } @@ -46,10 +45,6 @@ final class DtoMetaBuilder { } } - static Class propertyType(Method method) { - return method.getParameterTypes()[0]; - } - static String propertyName(String methodName) { final String name = methodName.substring(3); return Character.toLowerCase(name.charAt(0)) + name.substring(1); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java index fe7d794a1..b73500434 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaProperty.java @@ -8,6 +8,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.sql.SQLException; final class DtoMetaProperty implements DtoReadSet { @@ -19,18 +20,30 @@ final class DtoMetaProperty implements DtoReadSet { private final MethodHandle setter; private final ScalarType scalarType; - DtoMetaProperty(TypeManager typeManager, Class dtoType, Method writeMethod, String name, Class propertyType) throws IllegalAccessException, NoSuchMethodException { + DtoMetaProperty(TypeManager typeManager, Class dtoType, Method writeMethod, String name) throws IllegalAccessException, NoSuchMethodException { this.dtoType = dtoType; this.name = name; if (writeMethod != null) { - this.setter = LOOKUP.findVirtual(dtoType, writeMethod.getName(), MethodType.methodType(void.class, propertyType)); - this.scalarType = typeManager.getScalarType(propertyType); + this.setter = lookupMethodHandle(dtoType, writeMethod); + this.scalarType = typeManager.getScalarType(propertyType(writeMethod), propertyClass(writeMethod)); } else { this.scalarType = null; this.setter = null; } } + private static MethodHandle lookupMethodHandle(Class dtoType, Method method) throws NoSuchMethodException, IllegalAccessException { + return LOOKUP.findVirtual(dtoType, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes())); + } + + static Type propertyType(Method method) { + return method.getParameters()[0].getParameterizedType(); + } + + static Class propertyClass(Method method) { + return method.getParameterTypes()[0]; + } + String getName() { return name; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/el/ElFilter.java b/ebean-core/src/main/java/io/ebeaninternal/server/el/ElFilter.java index 6f8aec3aa..bd28af35c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/el/ElFilter.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/el/ElFilter.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.el; +import io.avaje.lang.NonNullApi; import io.ebean.Filter; import io.ebeaninternal.server.deploy.BeanDescriptor; @@ -11,6 +12,7 @@ import java.util.regex.Pattern; /** * Default implementation of the Filter interface. */ +@NonNullApi public final class ElFilter implements Filter { private final BeanDescriptor beanDescriptor; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/executor/DefaultBackgroundExecutor.java b/ebean-core/src/main/java/io/ebeaninternal/server/executor/DefaultBackgroundExecutor.java index 305da21ca..7090a63e4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/executor/DefaultBackgroundExecutor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/executor/DefaultBackgroundExecutor.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.executor; +import io.avaje.lang.NonNullApi; import io.ebeaninternal.api.SpiBackgroundExecutor; import org.slf4j.MDC; @@ -13,6 +14,7 @@ import java.util.concurrent.TimeUnit; /** * The default implementation of the BackgroundExecutor. */ +@NonNullApi public final class DefaultBackgroundExecutor implements SpiBackgroundExecutor { private final ScheduledExecutorService executor; 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 b9693df6d..7141b708b 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 @@ -1,5 +1,7 @@ package io.ebeaninternal.server.expression; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.CacheMode; import io.ebean.CountDistinctOrder; import io.ebean.DtoQuery; @@ -51,6 +53,7 @@ import java.util.function.Predicate; /** * Default implementation of ExpressionList. */ +@NonNullApi public class DefaultExpressionList implements SpiExpressionList { private static final String AND = " and "; @@ -469,6 +472,7 @@ public class DefaultExpressionList implements SpiExpressionList { return query.exists(); } + @Nullable @Override public T findOne() { return query.findOne(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/FilterExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/FilterExpressionList.java index 0e8d4ba0b..45d308eec 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/FilterExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/FilterExpressionList.java @@ -1,23 +1,15 @@ package io.ebeaninternal.server.expression; -import io.ebean.ExpressionFactory; -import io.ebean.ExpressionList; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.Junction; -import io.ebean.OrderBy; -import io.ebean.Query; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; +import io.ebean.*; import io.ebeaninternal.api.SpiExpressionList; import io.ebeaninternal.api.SpiQuery; import javax.persistence.PersistenceException; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; +@NonNullApi public final class FilterExpressionList extends DefaultExpressionList { private static final String notAllowedMessage = "This method is not allowed on a filter"; @@ -92,6 +84,7 @@ public final class FilterExpressionList extends DefaultExpressionList { return rootQuery.findSet(); } + @Nullable @Override public T findOne() { return rootQuery.findOne(); 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 a408b5e74..d83db0cde 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 @@ -1,5 +1,7 @@ package io.ebeaninternal.server.expression; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.CacheMode; import io.ebean.CountDistinctOrder; import io.ebean.DtoQuery; @@ -48,6 +50,7 @@ import java.util.function.Predicate; /** * Junction implementation. */ +@NonNullApi final class JunctionExpression implements SpiJunction, SpiExpression, ExpressionList { DefaultExpressionList exprList; @@ -476,6 +479,7 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.findSet(); } + @Nullable @Override public T findOne() { return exprList.findOne(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/grammer/EqlWhereListener.java b/ebean-core/src/main/java/io/ebeaninternal/server/grammer/EqlWhereListener.java index f66677a64..683bf1421 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/grammer/EqlWhereListener.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/grammer/EqlWhereListener.java @@ -136,7 +136,7 @@ abstract class EqlWhereListener extends EQLBaseListener { } } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) private List toList(Object value) { if (value == null) return null; if (value instanceof List) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java index 2dd6c1378..427d59de2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -46,7 +46,7 @@ final class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext /** * Reset the buffers after a query iterator reset. */ - public void clear() { + private void clear() { if (bufferList != null) { bufferList.clear(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java index 018a9f647..bfef45804 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java @@ -57,7 +57,7 @@ public final class DLoadContext implements LoadContext { private final ProfileLocation profileLocation; private final ProfilingListener profilingListener; private final Map nodePathMap = new HashMap<>(); - private PersistenceContext persistenceContext; + private final PersistenceContext persistenceContext; private List secQuery; private Object tenantId; @@ -251,19 +251,6 @@ public final class DLoadContext implements LoadContext { return persistenceContext; } - @Override - public void resetPersistenceContext(PersistenceContext persistenceContext) { - this.persistenceContext = persistenceContext; - // clear the load contexts for beans and beanCollections - for (DLoadBeanContext beanContext : beanMap.values()) { - beanContext.clear(); - } - for (DLoadManyContext manyContext : manyMap.values()) { - manyContext.clear(); - } - this.rootBeanContext.clear(); - } - @Override public void register(String path, EntityBeanIntercept ebi) { getBeanContext(path).register(ebi); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java index 8473e148b..68bd61d81 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -48,7 +48,7 @@ final class DLoadManyContext extends DLoadBaseContext implements LoadManyContext /** * Reset the buffers for a query iterator reset. */ - public void clear() { + private void clear() { if (bufferList != null) { bufferList.clear(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java index 6783beb7d..ca81e4ac8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java @@ -118,7 +118,11 @@ public final class Binder { bindLog.append(value); } } - if (value == null) { + if (value instanceof Collection) { + for (Object entry: (Collection) value) { + bindObject(dataBind, entry); + } + } else if (value == null) { // this doesn't work for query predicates bindObject(dataBind, null, param.getType()); } else { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java index d722bfa32..f2c56db74 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java @@ -1,11 +1,6 @@ package io.ebeaninternal.server.persist; -import io.ebean.CallableSql; -import io.ebean.MergeOptions; -import io.ebean.Query; -import io.ebean.SqlUpdate; -import io.ebean.Transaction; -import io.ebean.Update; +import io.ebean.*; import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollection.ModifyListenMode; import io.ebean.bean.EntityBean; @@ -13,30 +8,15 @@ import io.ebean.bean.PersistenceContext; import io.ebean.event.BeanPersistController; import io.ebean.meta.MetricVisitor; import io.ebeaninternal.api.*; -import io.ebeaninternal.server.core.PersistRequest; +import io.ebeaninternal.server.core.*; import io.ebeaninternal.server.core.PersistRequest.Type; -import io.ebeaninternal.server.core.PersistRequestBean; -import io.ebeaninternal.server.core.PersistRequestCallableSql; -import io.ebeaninternal.server.core.PersistRequestOrmUpdate; -import io.ebeaninternal.server.core.PersistRequestUpdateSql; -import io.ebeaninternal.server.core.Persister; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanDescriptorManager; -import io.ebeaninternal.server.deploy.BeanManager; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import io.ebeaninternal.server.deploy.IntersectionRow; +import io.ebeaninternal.server.deploy.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.persistence.PersistenceException; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Persister implementation using DML. @@ -82,7 +62,6 @@ public final class DefaultPersister implements Persister { */ @Override public int executeCallable(CallableSql callSql, Transaction t) { - return executeOrQueue(new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute)); } @@ -91,16 +70,8 @@ public final class DefaultPersister implements Persister { */ @Override public int executeOrmUpdate(Update update, Transaction t) { - SpiUpdate ormUpdate = (SpiUpdate) update; - - BeanManager mgr = beanDescriptorManager.beanManager(ormUpdate.getBeanType()); - - if (mgr == null) { - String msg = "No BeanManager found for type [" + ormUpdate.getBeanType() + "]. Is it an entity?"; - throw new PersistenceException(msg); - } - + BeanManager mgr = beanManager(ormUpdate.getBeanType()); return executeOrQueue(new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute)); } @@ -169,10 +140,8 @@ public final class DefaultPersister implements Persister { */ @Override public List draftRestore(Query query, Transaction transaction) { - Class beanType = query.getBeanType(); BeanDescriptor desc = server.descriptor(beanType); - DraftHandler draftHandler = new DraftHandler<>(desc, transaction); List liveBeans = draftHandler.fetchSourceBeans(query, false); @@ -182,7 +151,6 @@ public final class DefaultPersister implements Persister { } draftHandler.fetchDestinationBeans(liveBeans, true); - BeanManager mgr = beanDescriptorManager.beanManager(beanType); for (T liveBean : liveBeans) { @@ -214,10 +182,8 @@ public final class DefaultPersister implements Persister { */ @Override public List publish(Query query, Transaction transaction) { - Class beanType = query.getBeanType(); BeanDescriptor desc = server.descriptor(beanType); - DraftHandler draftHandler = new DraftHandler<>(desc, transaction); List draftBeans = draftHandler.fetchSourceBeans(query, true); @@ -227,7 +193,6 @@ public final class DefaultPersister implements Persister { } draftHandler.fetchDestinationBeans(draftBeans, false); - BeanManager mgr = beanDescriptorManager.beanManager(beanType); List livePublish = new ArrayList<>(draftBeans.size()); @@ -251,7 +216,6 @@ public final class DefaultPersister implements Persister { } draftHandler.updateDrafts(transaction, mgr); - PUB.debug("publish - complete for [{}]", desc.name()); return livePublish; } @@ -330,9 +294,7 @@ public final class DefaultPersister implements Persister { * Fetch the destination beans that will be published to. */ void fetchDestinationBeans(List sourceBeans, boolean asDraft) { - List ids = getBeanIds(desc, sourceBeans); - Query destQuery = server.find(desc.type()).where().idIn(ids).query(); if (asDraft) { destQuery.asDraft(); @@ -376,7 +338,6 @@ public final class DefaultPersister implements Persister { @Override public int merge(BeanDescriptor desc, EntityBean bean, MergeOptions options, SpiTransaction transaction) { - MergeHandler merge = new MergeHandler(server, desc, bean, options, transaction); List deleteBeans = merge.merge(); if (!deleteBeans.isEmpty()) { @@ -385,13 +346,11 @@ public final class DefaultPersister implements Persister { delete(deleteBean, transaction, options.isDeletePermanent()); } } - // cascade save as normal with forceUpdate flags set PersistRequestBean request = createRequestRecurse(bean, transaction, null, Flags.MERGE); request.checkBatchEscalationOnCascade(); saveRecurse(request); request.flushBatchOnCascade(); - // lambda expects a return return 0; } @@ -443,7 +402,6 @@ public final class DefaultPersister implements Persister { */ @Override public void insert(EntityBean bean, Transaction t) { - PersistRequestBean req = createRequest(bean, t, PersistRequest.Type.INSERT); if (req.isSkipReference()) { // skip insert on reference bean @@ -465,7 +423,6 @@ public final class DefaultPersister implements Persister { } void saveRecurse(EntityBean bean, Transaction t, Object parentBean, int flags) { - // determine insert or update taking into account stateless updates saveRecurse(createRequestRecurse(bean, t, parentBean, flags)); } @@ -493,7 +450,6 @@ public final class DefaultPersister implements Persister { * Insert the bean. */ private void insert(PersistRequestBean request) { - if (request.isRegisteredBean()) { // skip as already inserted/updated in this request (recursive cascading) return; @@ -520,7 +476,6 @@ public final class DefaultPersister implements Persister { * Update the bean. */ private void update(PersistRequestBean request) { - if (request.isRegisteredBean()) { // skip as already inserted/updated in this request (recursive cascading) return; @@ -531,21 +486,16 @@ public final class DefaultPersister implements Persister { // save associated One beans recursively first saveAssocOne(request); } - if (request.isDirty()) { request.executeOrQueue(); - } else if (log.isDebugEnabled()) { log.debug("Update skipped as bean is unchanged: {}", request.bean()); } - if (request.isPersistCascade()) { // save all the beans in assocMany's after saveAssocMany(request); } - request.completeUpdate(); - } finally { request.unRegisterBean(); } @@ -557,7 +507,6 @@ public final class DefaultPersister implements Persister { */ @Override public int delete(EntityBean bean, Transaction t, boolean permanent) { - Type deleteType = permanent ? Type.DELETE_PERMANENT : Type.DELETE; PersistRequestBean originalRequest = createDeleteRequest(bean, t, deleteType); if (originalRequest.isHardDeleteDraft()) { @@ -601,7 +550,6 @@ public final class DefaultPersister implements Persister { } req.commitTransIfRequired(); req.flushBatchOnCascade(); - return rows; } catch (RuntimeException ex) { @@ -631,23 +579,19 @@ public final class DefaultPersister implements Persister { */ @Override public int deleteMany(Class beanType, Collection ids, Transaction transaction, boolean permanent) { - if (ids == null || ids.isEmpty()) { return 0; } - BeanDescriptor descriptor = beanDescriptorManager.descriptor(beanType); DeleteMode deleteMode = (permanent || !descriptor.isSoftDelete()) ? DeleteMode.HARD : DeleteMode.SOFT; if (descriptor.isMultiTenant()) { return deleteAsBeans(ids, transaction, deleteMode, descriptor); } - ArrayList idList = new ArrayList<>(ids.size()); for (Object id : ids) { // convert to appropriate type if required idList.add(descriptor.convertId(id)); } - return delete(descriptor, null, idList, transaction, deleteMode); } @@ -698,7 +642,6 @@ public final class DefaultPersister implements Persister { * Delete by Id or a List of Id's. */ private int delete(BeanDescriptor descriptor, Object id, List idList, Transaction transaction, DeleteMode deleteMode) { - SpiTransaction t = (SpiTransaction) transaction; if (t.isPersistCascade()) { BeanPropertyAssocOne[] propImportDelete = descriptor.propertiesOneImportedDelete(); @@ -818,7 +761,6 @@ public final class DefaultPersister implements Persister { } private void notifyDeleteById(BeanDescriptor descriptor, Object id, List idList, Transaction transaction) { - BeanPersistController controller = descriptor.persistController(); if (controller != null) { DeleteIdRequest request = new DeleteIdRequest(server, transaction, descriptor.type(), id); @@ -838,7 +780,6 @@ public final class DefaultPersister implements Persister { * the delete cascades to them (foreign keys). */ private Query deleteRequiresQuery(BeanDescriptor desc, BeanPropertyAssocOne[] propImportDelete, DeleteMode deleteMode) { - Query q = server.createQuery(desc.type()); StringBuilder sb = new StringBuilder(30); for (BeanPropertyAssocOne aPropImportDelete : propImportDelete) { @@ -860,9 +801,7 @@ public final class DefaultPersister implements Persister { *

*/ private int delete(PersistRequestBean request) { - DeleteUnloadedForeignKeys unloadedForeignKeys = null; - if (request.isPersistCascade()) { // delete children first ... register the // bean to handle bi-directional cascading @@ -879,16 +818,13 @@ public final class DefaultPersister implements Persister { int count = request.executeOrQueue(); request.removeFromPersistenceContext(); - if (request.isPersistCascade()) { deleteAssocOne(request); - if (unloadedForeignKeys != null) { unloadedForeignKeys.deleteCascade(); } } request.complete(); - // return true if using JDBC batch (as we can't tell until the batch is flushed) return count; } @@ -901,11 +837,9 @@ public final class DefaultPersister implements Persister { *

*/ private void saveAssocMany(PersistRequestBean request) { - EntityBean parentBean = request.entityBean(); BeanDescriptor desc = request.descriptor(); SpiTransaction t = request.transaction(); - EntityBean orphanForRemoval = request.importedOrphanForRemoval(); if (orphanForRemoval != null) { delete(orphanForRemoval, request.transaction(), true); @@ -972,7 +906,6 @@ public final class DefaultPersister implements Persister { *

*/ private void deleteAssocMany(PersistRequestBean request) { - SpiTransaction t = request.transaction(); t.depth(-1); @@ -1014,7 +947,6 @@ public final class DefaultPersister implements Persister { deleteManyIntersection(parentBean, many, t, request.isPublish(), false); } } else { - if (ModifyListenMode.REMOVALS == many.modifyListenMode()) { // PrivateOwned ... // if soft delete then check target also supports soft delete @@ -1023,7 +955,6 @@ public final class DefaultPersister implements Persister { if (details instanceof BeanCollection) { Set modifyRemovals = ((BeanCollection) details).getModifyRemovals(); if (modifyRemovals != null && !modifyRemovals.isEmpty()) { - // delete the orphans that have been removed from the collection for (Object detail : modifyRemovals) { EntityBean detailBean = (EntityBean) detail; @@ -1035,11 +966,9 @@ public final class DefaultPersister implements Persister { } } } - deleteManyDetails(t, desc, parentBean, many, null, deleteMode); } } - // restore the depth t.depth(+1); } @@ -1054,7 +983,6 @@ public final class DefaultPersister implements Persister { */ void deleteManyDetails(SpiTransaction t, BeanDescriptor desc, EntityBean parentBean, BeanPropertyAssocMany many, List excludeDetailIds, DeleteMode deleteMode) { - if (many.cascadeInfo().isDelete()) { // cascade delete the beans in the collection BeanDescriptor targetDesc = many.targetDescriptor(); @@ -1083,7 +1011,6 @@ public final class DefaultPersister implements Persister { * Will use delete by object if the child entity has manyToMany relationships. */ private void deleteChildrenById(SpiTransaction t, BeanDescriptor targetDesc, List childIds, DeleteMode deleteMode) { - if (!targetDesc.isDeleteByBulk()) { // convert into a list of reference objects and perform delete by object List refList = new ArrayList<>(childIds.size()); @@ -1091,7 +1018,6 @@ public final class DefaultPersister implements Persister { refList.add(targetDesc.createReference(id, null)); } deleteList(refList, t, deleteMode, true); - } else { // perform delete by statement if possible delete(targetDesc, null, childIds, t, deleteMode); @@ -1102,16 +1028,13 @@ public final class DefaultPersister implements Persister { * Save any associated one beans. */ private void saveAssocOne(PersistRequestBean request) { - BeanDescriptor desc = request.descriptor(); - // imported ones with save cascade for (BeanPropertyAssocOne prop : desc.propertiesOneImportedSave()) { // check for partial objects if (prop.isOrphanRemoval() && request.isDirtyProperty(prop)) { request.setImportedOrphanForRemoval(prop); } - if (request.isLoadedProperty(prop)) { EntityBean detailBean = prop.getValueAsEntityBean(request.entityBean()); if (detailBean != null @@ -1125,7 +1048,6 @@ public final class DefaultPersister implements Persister { } } } - for (BeanPropertyAssocOne prop : desc.propertiesOneExportedSave()) { if (prop.isOrphanRemoval() && request.isDirtyProperty(prop)) { deleteOrphan(request, prop); @@ -1145,9 +1067,7 @@ public final class DefaultPersister implements Persister { * loaded but required for Delete cascade. */ private DeleteUnloadedForeignKeys getDeleteUnloadedForeignKeys(PersistRequestBean request) { - DeleteUnloadedForeignKeys fkeys = null; - for (BeanPropertyAssocOne one : request.descriptor().propertiesOneImportedDelete()) { if (!request.isLoadedProperty(one)) { // we have cascade Delete on a partially populated bean and @@ -1158,7 +1078,6 @@ public final class DefaultPersister implements Persister { fkeys.add(one); } } - return fkeys; } @@ -1166,9 +1085,7 @@ public final class DefaultPersister implements Persister { * Delete any associated one beans. */ private void deleteAssocOne(PersistRequestBean request) { - DeleteMode deleteMode = request.deleteMode(); - for (BeanPropertyAssocOne prop : request.descriptor().propertiesOneImportedDelete()) { if (deleteMode.isHard() || prop.isTargetSoftDelete()) { if (request.isLoadedProperty(prop)) { @@ -1196,7 +1113,7 @@ public final class DefaultPersister implements Persister { * Create the Persist Request Object additionally specifying the publish status. */ private PersistRequestBean createRequestInternal(T bean, Transaction t, PersistRequest.Type type) { - BeanManager mgr = getBeanManager(bean); + BeanManager mgr = beanManager(bean.getClass()); return createRequest(bean, t, null, mgr, type, Flags.ZERO); } @@ -1206,7 +1123,7 @@ public final class DefaultPersister implements Persister { * This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type). */ private PersistRequestBean createRequestRecurse(T bean, Transaction t, Object parentBean, int flags) { - BeanManager mgr = getBeanManager(bean); + BeanManager mgr = beanManager(bean.getClass()); BeanDescriptor desc = mgr.getBeanDescriptor(); EntityBean entityBean = (EntityBean) bean; PersistRequest.Type type; @@ -1225,7 +1142,7 @@ public final class DefaultPersister implements Persister { * Create the Persist Request Object that wraps all the objects used to * perform an insert, update or delete. */ - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked", "rawtypes"}) private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, BeanManager mgr, PersistRequest.Type type, int flags) { // no delete requests come here @@ -1242,7 +1159,7 @@ public final class DefaultPersister implements Persister { @SuppressWarnings({"unchecked"}) private PersistRequestBean createDeleteRequest(Object bean, Transaction t, PersistRequest.Type type, int flags) { - BeanManager mgr = getBeanManager(bean); + BeanManager mgr = beanManager(bean.getClass()); if (type == Type.DELETE_PERMANENT) { type = Type.DELETE; } else if (type == Type.DELETE && mgr.getBeanDescriptor().isSoftDelete()) { @@ -1256,9 +1173,8 @@ public final class DefaultPersister implements Persister { } private String errNotRegistered(Class beanClass) { - String msg = "The type [" + beanClass + "] is not a registered entity?"; - msg += " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath."; - return msg; + return "The type [" + beanClass + "] is not a registered entity?" + + " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath."; } /** @@ -1269,10 +1185,10 @@ public final class DefaultPersister implements Persister { *

*/ @SuppressWarnings("unchecked") - private BeanManager getBeanManager(Object bean) { - BeanManager mgr = (BeanManager) beanDescriptorManager.beanManager(bean.getClass()); + private BeanManager beanManager(Class cls) { + BeanManager mgr = (BeanManager)beanDescriptorManager.beanManager(cls); if (mgr == null) { - throw new PersistenceException(errNotRegistered(bean.getClass())); + throw new PersistenceException(errNotRegistered(cls)); } return mgr; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index a865275c6..45a5b5a11 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -416,7 +416,6 @@ public final class CQuery implements DbReadContext, CancelableQuery, SpiProfi } else { // nextBean set to previously read currentBean nextBean = currentBean; - request.persistenceContextAdd(nextBean); // check the current row we have just moved to if (checkForDifferentBean()) { return true; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java index d438ec747..6d183b936 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorSimple.java @@ -26,7 +26,6 @@ final class CQueryIteratorSimple implements QueryIterator { public boolean hasNext() { boolean ret = false; try { - request.flushPersistenceContextOnIterate(); ret = cquery.hasNext(); return ret; } catch (SQLException e) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java index ad1a924b5..3df436716 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryIteratorWithBuffer.java @@ -35,8 +35,6 @@ final class CQueryIteratorWithBuffer implements QueryIterator { try { if (buffer.isEmpty() && moreToLoad) { // load buffer - request.flushPersistenceContextOnIterate(); - int i = -1; while (moreToLoad && ++i < bufferSize) { if (cquery.hasNext()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java index 50966a45f..712e27153 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DFetchGroupBuilder.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.query; +import io.avaje.lang.NonNullApi; import io.ebean.FetchConfig; import io.ebean.FetchGroup; import io.ebean.FetchGroupBuilder; @@ -9,14 +10,12 @@ import io.ebeaninternal.server.querydefn.SpiFetchGroup; /** * Default implementation of the FetchGroupBuilder. */ +@NonNullApi final class DFetchGroupBuilder implements FetchGroupBuilder { private static final FetchConfig DEFAULT_FETCH = FetchConfig.ofDefault(); - private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache(); - private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery(); - private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy(); private final OrmQueryDetail detail; @@ -38,22 +37,22 @@ final class DFetchGroupBuilder implements FetchGroupBuilder { } @Override - public FetchGroupBuilder fetch(String path, FetchGroup nestedGroup) { + public FetchGroupBuilder fetch(String path, FetchGroup nestedGroup) { return fetchNested(path, nestedGroup, DEFAULT_FETCH); } @Override - public FetchGroupBuilder fetchQuery(String path, FetchGroup nestedGroup) { + public FetchGroupBuilder fetchQuery(String path, FetchGroup nestedGroup) { return fetchNested(path, nestedGroup, FETCH_QUERY); } @Override - public FetchGroupBuilder fetchLazy(String path, FetchGroup nestedGroup) { + public FetchGroupBuilder fetchLazy(String path, FetchGroup nestedGroup) { return fetchNested(path, nestedGroup, FETCH_LAZY); } - private FetchGroupBuilder fetchNested(String path, FetchGroup nestedGroup, FetchConfig fetchConfig) { - OrmQueryDetail nestedDetail = ((SpiFetchGroup) nestedGroup).underlying(); + private FetchGroupBuilder fetchNested(String path, FetchGroup nestedGroup, FetchConfig fetchConfig) { + OrmQueryDetail nestedDetail = ((SpiFetchGroup) nestedGroup).underlying(); detail.addNested(path, nestedDetail, fetchConfig); return this; } 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 459bba998..8011cc1b5 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 @@ -1,5 +1,7 @@ package io.ebeaninternal.server.query; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.CacheMode; import io.ebean.CountDistinctOrder; import io.ebean.Database; @@ -29,8 +31,6 @@ import io.ebeaninternal.api.SpiQueryFetch; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.querydefn.SpiFetchGroup; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.sql.Connection; import java.sql.Timestamp; import java.util.List; @@ -44,6 +44,7 @@ import java.util.stream.Stream; /** * Implementation of FetchGroup query for use to create FetchGroup via query beans. */ +@NonNullApi final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQueryFetch { private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache(); @@ -65,6 +66,7 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery return this; } + @SuppressWarnings("rawtypes") @Override public Query select(FetchGroup fetchGroup) { this.detail = ((SpiFetchGroup) fetchGroup).detail(); @@ -117,7 +119,7 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery } @Override - public Query fetch(String property, String columns, FetchConfig config) { + public Query fetch(String property, @Nullable String columns, @Nullable FetchConfig config) { detail.fetch(property, columns, config); return this; } @@ -234,25 +236,21 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public List findIds() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public QueryIterator findIterate() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public Stream findStream() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public Stream findLargeStream() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); @@ -273,25 +271,21 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public List findList() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public Set findSet() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public Map findMap() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public List findSingleAttributeList() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); @@ -318,19 +312,16 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public Optional findOneOrEmpty() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public List> findVersions() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public List> findVersionsBetween(Timestamp start, Timestamp end) { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); @@ -361,25 +352,21 @@ final class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQuery throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public FutureRowCount findFutureCount() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public FutureIds findFutureIds() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public FutureList findFutureList() { throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup"); } - @Nonnull @Override public PagedList findPagedList() { 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/SqlTreeNodeBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java index 19488ccec..547eededc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -268,9 +268,16 @@ class SqlTreeNodeBean implements SqlTreeNode { contextBean = localBean; } else { // bean already exists in persistenceContext - if (isLoadContextBeanNeeded(queryMode, contextBean)) { - // refresh it anyway (lazy loading for example) + + if (queryMode.isLoadContextBean()) { + // if explicitly set loadContextBean to true, then reload localBean = contextBean; + } else if (!contextBean._ebean_getIntercept().isFullyLoadedBean()) { + // reload if contextBean is partial object + localBean = contextBean; + // and switch to lazyLoad query mode in order not to overwrite + // existing properties in SqlBeanLoad::load + queryMode = Mode.LAZYLOAD_BEAN; } else { // ignore the DB data... localBean = null; @@ -683,14 +690,6 @@ class SqlTreeNodeBean implements SqlTreeNode { return "SqlTreeNodeBean: " + desc; } - private boolean isLoadContextBeanNeeded(Mode queryMode, EntityBean contextBean) { - // if explicitly set loadContextBean to true, then reload - if (queryMode.isLoadContextBean()) { - return true; - } - // reload if contextBean is partial object - return !contextBean._ebean_getIntercept().isFullyLoadedBean(); - } @Override public boolean hasMany() { 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 4a72c0057..8120aa093 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 @@ -1,5 +1,6 @@ package io.ebeaninternal.server.querydefn; +import io.avaje.lang.NonNullApi; import io.ebean.DtoQuery; import io.ebean.ProfileLocation; import io.ebean.QueryIterator; @@ -12,6 +13,8 @@ import io.ebeaninternal.server.dto.DtoBeanDescriptor; import io.ebeaninternal.server.dto.DtoMappingRequest; import io.ebeaninternal.server.dto.DtoQueryPlan; +import javax.annotation.Nullable; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -21,6 +24,7 @@ import java.util.stream.Stream; /** * Default implementation of DtoQuery. */ +@NonNullApi public final class DefaultDtoQuery extends AbstractQuery implements SpiDtoQuery { private final SpiEbeanServer server; @@ -114,6 +118,7 @@ public final class DefaultDtoQuery extends AbstractQuery implements SpiDtoQue return server.findDtoList(this); } + @Nullable @Override public T findOne() { return server.findDtoOne(this); @@ -144,6 +149,16 @@ public final class DefaultDtoQuery extends AbstractQuery implements SpiDtoQue return this; } + @Override + public DtoQuery setArrayParameter(String paramName, Collection values) { + if (ormQuery != null) { + ormQuery.setArrayParameter(paramName, values); + } else { + bindParams.setArrayParameter(paramName, values); + } + return this; + } + @Override public DtoQuery setParameters(Object... values) { if (ormQuery != null) { @@ -206,6 +221,7 @@ public final class DefaultDtoQuery extends AbstractQuery implements SpiDtoQue return label; } + @Nullable @Override public String getPlanLabel() { if (label != null) { 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 792a9192d..307b2713c 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 @@ -1,5 +1,6 @@ package io.ebeaninternal.server.querydefn; +import io.avaje.lang.NonNullApi; import io.ebean.*; import io.ebean.OrderBy.Property; import io.ebean.bean.CallOrigin; @@ -30,6 +31,7 @@ import java.util.stream.Stream; /** * Default implementation of an Object Relational query. */ +@NonNullApi public final class DefaultOrmQuery extends AbstractQuery implements SpiQuery { private static final String DEFAULT_QUERY_NAME = "default"; @@ -584,9 +586,9 @@ public final class DefaultOrmQuery extends AbstractQuery implements SpiQuery< */ @Override public void setSelectId() { - // clear select and fetch joins.. + // clear select and fetch joins detail.clear(); - select(beanDescriptor.idBinder().getIdProperty()); + select(beanDescriptor.idSelect()); } @Override @@ -1584,6 +1586,17 @@ public final class DefaultOrmQuery extends AbstractQuery implements SpiQuery< return this; } + @Override + public void setArrayParameter(String name, Collection values) { + if (namedParams != null) { + throw new IllegalStateException("setArrayParameter() not supported when EQL parsed query"); + } + if (bindParams == null) { + bindParams = new BindParams(); + } + bindParams.setArrayParameter(name, values); + } + @Override public boolean checkPagingOrderBy() { return orderById && !useDocStore; 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 879d1bdaf..69c8dbb7a 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 @@ -1,5 +1,7 @@ package io.ebeaninternal.server.querydefn; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; import io.ebean.RowConsumer; import io.ebean.RowMapper; import io.ebean.SqlQuery; @@ -17,6 +19,7 @@ import java.util.function.Predicate; /** * Default implementation of SQuery - SQL Query. */ +@NonNullApi public final class DefaultRelationalQuery extends AbstractQuery implements SpiSqlQuery { private static final long serialVersionUID = -1098305779779591068L; @@ -263,6 +266,7 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq this.mapper = mapper; } + @Nullable @Override public T findOne() { return mapperFindOne(mapper); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java index 568a6165c..6a75b82a9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPropertiesParser.java @@ -37,7 +37,11 @@ final class OrmQueryPropertiesParser { if (rawProperties.equals("*")) { return ALL; } - return new Response(false, splitRawSelect(rawProperties)); + final Set included = splitRawSelect(rawProperties); + if (included.contains("*")) { + return ALL; + } + return new Response(false, included); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java index 8193d397c..79465e037 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/DJsonBeanReader.java @@ -45,7 +45,7 @@ public final class DJsonBeanReader implements JsonBeanReader { } @Override - public JsonBeanReader forJson(JsonParser moreJson, boolean resetContext) { - return new DJsonBeanReader<>(desc, readJson.forJson(moreJson, resetContext)); + public JsonBeanReader forJson(JsonParser moreJson) { + return new DJsonBeanReader<>(desc, readJson.forJson(moreJson)); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java index f685188b6..7acf4e531 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/ReadJson.java @@ -50,22 +50,14 @@ public final class ReadJson implements SpiJsonReader { /** * Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance. */ - private ReadJson(JsonParser moreJson, ReadJson source, boolean resetContext) { + private ReadJson(JsonParser moreJson, ReadJson source) { this.parser = moreJson; this.rootDesc = source.rootDesc; this.pathStack = source.pathStack; this.visitorMap = source.visitorMap; this.objectMapper = source.objectMapper; - if (resetContext) { - this.persistenceContext = new DefaultPersistenceContext(); - this.loadContext = source.loadContext; - if (loadContext != null) { - loadContext.resetPersistenceContext(persistenceContext); - } - } else { - this.persistenceContext = source.persistenceContext; - this.loadContext = source.loadContext; - } + this.persistenceContext = source.persistenceContext; + this.loadContext = source.loadContext; } private LoadContext initLoadContext(BeanDescriptor desc, JsonReadOptions readOptions) { @@ -96,8 +88,8 @@ public final class ReadJson implements SpiJsonReader { * Return a new instance of ReadJson using the existing context but with a new JsonParser. */ @Override - public SpiJsonReader forJson(JsonParser moreJson, boolean resetContext) { - return new ReadJson(moreJson, this, resetContext); + public SpiJsonReader forJson(JsonParser moreJson) { + return new ReadJson(moreJson, this); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java index fb3e53b9b..3ede05de1 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -1,11 +1,13 @@ package io.ebeaninternal.server.transaction; import io.ebean.bean.EntityBean; -import io.ebean.bean.PersistenceContext; import io.ebeaninternal.api.SpiBeanType; import io.ebeaninternal.api.SpiBeanTypeManager; import io.ebeaninternal.api.SpiPersistenceContext; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.locks.ReentrantLock; @@ -25,14 +27,16 @@ import java.util.concurrent.locks.ReentrantLock; */ public final class DefaultPersistenceContext implements SpiPersistenceContext { - /** - * Map used hold caches. One cache per bean type. - */ private final HashMap, ClassContext> typeCache = new HashMap<>(); - private final ReentrantLock lock = new ReentrantLock(); + private final ReferenceQueue queue = new ReferenceQueue<>(); - private int putCount; + /** + * When we are inside an iterate loop, we will add only WeakReferences. This + * allows the JVM GC to collect beans, which are not referenced elsewhere. In + * normal operation, we will use hard references, to avoid performance impact + */ + private int iterateDepth; /** * Create a new PersistenceContext. @@ -40,60 +44,33 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public DefaultPersistenceContext() { } - /** - * Create as a shallow copy with initial or types that have not been added to. - */ - private DefaultPersistenceContext(DefaultPersistenceContext parent, boolean initial) { - for (Map.Entry, ClassContext> entry : parent.typeCache.entrySet()) { - typeCache.put(entry.getKey(), entry.getValue().copy(initial)); - } - } - - /** - * Return the initial shallow copy with each ClassContext noting it's initialSize (to detect additions). - */ @Override - public PersistenceContext forIterate() { - return new DefaultPersistenceContext(this, true); - } - - /** - * Return a shallow copy including each ClassContext that has had no additions (still at initialSize). - */ - @Override - public PersistenceContext forIterateReset() { - return new DefaultPersistenceContext(this, false); - } - - @Override - public boolean resetLimit() { + public void beginIterate() { lock.lock(); try { - if (putCount < 100) { - return false; - } - putCount = 0; - for (ClassContext value : typeCache.values()) { - if (value.resetLimit()) { - return true; - } - } - // checking after another 100 puts - return false; + iterateDepth++; + } finally { + lock.unlock(); + } + } + + @Override + public void endIterate() { + lock.lock(); + try { + iterateDepth--; + expungeStaleEntries(); // when leaving the iterator, cleanup. } finally { lock.unlock(); } } - /** - * Set an object into the PersistenceContext. - */ @Override public void put(Class rootType, Object id, Object bean) { lock.lock(); try { - putCount++; - getClassContext(rootType).put(id, bean); + expungeStaleEntries(); + classContext(rootType).useReferences(iterateDepth > 0).put(id, bean); } finally { lock.unlock(); } @@ -103,8 +80,8 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public Object putIfAbsent(Class rootType, Object id, Object bean) { lock.lock(); try { - putCount++; - return getClassContext(rootType).putIfAbsent(id, bean); + expungeStaleEntries(); + return classContext(rootType).useReferences(iterateDepth > 0).putIfAbsent(id, bean); } finally { lock.unlock(); } @@ -117,7 +94,8 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public Object get(Class rootType, Object id) { lock.lock(); try { - return getClassContext(rootType).get(id); + expungeStaleEntries(); + return classContext(rootType).get(id); } finally { lock.unlock(); } @@ -127,19 +105,18 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public WithOption getWithOption(Class rootType, Object id) { lock.lock(); try { - return getClassContext(rootType).getWithOption(id); + expungeStaleEntries(); + return classContext(rootType).getWithOption(id); } finally { lock.unlock(); } } - /** - * Return the number of beans of the given type in the persistence context. - */ @Override public int size(Class rootType) { lock.lock(); try { + expungeStaleEntries(); ClassContext classMap = typeCache.get(rootType); return classMap == null ? 0 : classMap.size(); } finally { @@ -147,14 +124,12 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } } - /** - * Clear the PersistenceContext. - */ @Override public void clear() { lock.lock(); try { typeCache.clear(); + expungeStaleEntries(); } finally { lock.unlock(); } @@ -168,6 +143,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { if (classMap != null) { classMap.clear(); } + expungeStaleEntries(); } finally { lock.unlock(); } @@ -181,6 +157,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { if (classMap != null && id != null) { classMap.deleted(id); } + expungeStaleEntries(); } finally { lock.unlock(); } @@ -194,6 +171,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { if (classMap != null && id != null) { classMap.remove(id); } + expungeStaleEntries(); } finally { lock.unlock(); } @@ -203,6 +181,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { public List dirtyBeans(SpiBeanTypeManager manager) { lock.lock(); try { + expungeStaleEntries(); List list = new ArrayList<>(); for (ClassContext classContext : typeCache.values()) { classContext.dirtyBeans(manager, list); @@ -213,80 +192,68 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } } + /** + * When there is a queue, poll it to remove stale entries from the map. Note: + * This is always done AFTER useReferences was called with + * true. Polling an empty queue has no performance impact. + */ + private void expungeStaleEntries() { + Reference ref; + while ((ref = queue.poll()) != null) { + ((BeanRef) ref).expunge(); + } + } + @Override public String toString() { lock.lock(); try { + expungeStaleEntries(); return typeCache.toString(); } finally { lock.unlock(); } } - private ClassContext getClassContext(Class rootType) { - return typeCache.computeIfAbsent(rootType, k -> new ClassContext(rootType)); + private ClassContext classContext(Class rootType) { + return typeCache.computeIfAbsent(rootType, k -> new ClassContext(k, queue)); } private static class ClassContext { private final Map map = new HashMap<>(); private final Class rootType; + private final ReferenceQueue queue; private Set deleteSet; - private int initialSize; - private ClassContext parent; + private boolean useReferences; + private int weakCount; - private ClassContext(Class rootType) { + private ClassContext(Class rootType, ReferenceQueue queue) { this.rootType = rootType; + this.queue = queue; } /** - * Create as a shallow copy. + * When called with "true", initialize referenceQueue and store BeanRefs instead + * of real object references. */ - private ClassContext(ClassContext source, boolean initial) { - this.rootType = source.rootType; - if (initial || source.isTransfer()) { - parent = source.transferParent(); - initialSize = parent.size(); - if (source.deleteSet != null) { - deleteSet = new HashSet<>(source.deleteSet); - } - } - } - - /** - * True if this should be transferred to a new iterator persistence context. - */ - private boolean isTransfer() { - // map not added to and has some original/parent beans - return map.isEmpty() && initialSize > 0; - } - - private ClassContext transferParent() { - return (parent != null) ? parent : this; - } - - /** - * Return a shallow copy if initial copy or it has not grown (still at initialSize). - */ - private ClassContext copy(boolean initial) { - return new ClassContext(this, initial); - } - - /** - * Return true if grown above the reset limit size of 1000. - */ - private boolean resetLimit() { - return map.size() > 1000; + private ClassContext useReferences(boolean useReferences) { + this.useReferences = useReferences; + return this; } @Override public String toString() { - return "size:" + map.size(); + return "size:" + map.size() + " (" + weakCount + " weak)"; } private Object get(Object id) { - Object bean = (parent == null) ? null : parent.get(id); - return bean != null ? bean : map.get(id); + Object ret = map.get(id); + if (ret instanceof BeanRef) { + return ((BeanRef) ret).get(); + } else { + return ret; + } } private WithOption getWithOption(Object id) { @@ -304,24 +271,39 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { return existingValue; } // put the new value and return null indicating the put was successful - map.put(id, bean); + put(id, bean); return null; } - private void put(Object id, Object b) { - map.put(id, b); + private void put(Object id, Object bean) { + Object existing; + if (useReferences) { + weakCount++; + existing = map.put(id, new BeanRef(this, id, bean, queue)); + } else { + existing = map.put(id, bean); + } + if (existing instanceof BeanRef) { + // when a BeanRef is replaced, its expunge() must NOT remove an entry + ((BeanRef) existing).setReplaced(); + weakCount--; + } } private int size() { - return map.size() + initialSize; + return map.size(); } private void clear() { map.clear(); + weakCount = 0; } private void remove(Object id) { - map.remove(id); + Object ret = map.remove(id); + if (ret instanceof BeanRef) { + weakCount--; + } } private void deleted(Object id) { @@ -329,7 +311,7 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { deleteSet = new HashSet<>(); } deleteSet.add(id); - map.remove(id); + remove(id); } /** @@ -338,6 +320,10 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { void dirtyBeans(SpiBeanTypeManager manager, List list) { final SpiBeanType beanType = manager.beanType(rootType); for (Object value : map.values()) { + if (value instanceof BeanRef) { + value = ((BeanRef) value).get(); + if (value == null) continue; + } EntityBean bean = (EntityBean) value; if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) { list.add(value); @@ -346,4 +332,27 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext { } } + private static class BeanRef extends WeakReference { + + private final ClassContext classContext; + private final Object key; + private boolean replaced; + + private BeanRef(ClassContext classContext, Object key, Object referent, ReferenceQueue q) { + super(referent, q); + this.classContext = classContext; + this.key = key; + } + + private void setReplaced() { + replaced = true; + } + + private void expunge() { + if (!replaced) { + classContext.remove(key); + } + } + + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 5bc51bbb3..544d8433e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -251,6 +251,18 @@ public final class DefaultTypeManager implements TypeManager { return nativeMap.get(jdbcType); } + @Override + public ScalarType getScalarType(Type propertyType, Class propertyClass) { + if (propertyType instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType)propertyType; + Type rawType = pt.getRawType(); + if (List.class == rawType || Set.class == rawType) { + return getArrayScalarType((Class)rawType, propertyType, true); + } + } + return getScalarType(propertyClass); + } + /** * This can return null if no matching ScalarType is found. */ @@ -286,7 +298,7 @@ public final class DefaultTypeManager implements TypeManager { } @Override - public ScalarType getArrayScalarType(Class type, DbArray dbArray, Type genericType, boolean nullable) { + public ScalarType getArrayScalarType(Class type, Type genericType, boolean nullable) { Type valueType = getValueType(genericType); if (type.equals(List.class)) { return getArrayScalarTypeList(valueType, nullable); @@ -591,8 +603,7 @@ public final class DefaultTypeManager implements TypeManager { */ private ScalarTypeEnum createEnumScalarTypeDbValue(Class> enumType, Method method, boolean integerType, int length, boolean withConstraint) { Map nameValueMap = new LinkedHashMap<>(); - Enum[] enumConstants = enumType.getEnumConstants(); - for (Enum enumConstant : enumConstants) { + for (Enum enumConstant : enumType.getEnumConstants()) { try { Object value = method.invoke(enumConstant); nameValueMap.put(enumConstant.name(), value.toString()); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/JsonTrim.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/JsonTrim.java index 97592fb34..ec0808f14 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/JsonTrim.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/JsonTrim.java @@ -19,8 +19,13 @@ final class JsonTrim { for (int i = 0; i < len; i++) { char c = json.charAt(i); if (c == '\"') { - if (!escaped) quoted = !quoted; - else escaped = false; + if (!escaped) { + quoted = !quoted; + } else { + escaped = false; + } + } else if (escaped) { + escaped = false; } else if (quoted && c == '\\') { escaped = true; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java index 3a7071317..692e65681 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java @@ -47,6 +47,13 @@ public interface TypeManager { */ ScalarType getScalarType(Class type, int jdbcType); + /** + * Find and return the ScalarType taking into account the property type with generics. + *

+ * For example Array based ScalarType for types like {@code List}. + */ + ScalarType getScalarType(Type propertyType, Class type); + /** * Create a ScalarType for an Enum using a mapping (rather than JPA Ordinal * or String which has limitations). @@ -64,7 +71,7 @@ public interface TypeManager { /** * Return the ScalarType used to handle DB ARRAY. */ - ScalarType getArrayScalarType(Class type, DbArray dbArray, Type genericType, boolean nullable); + ScalarType getArrayScalarType(Class type, Type genericType, boolean nullable); /** * Return the ScalarType used to handle HSTORE (Map). diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/util/BindParamsParser.java b/ebean-core/src/main/java/io/ebeaninternal/server/util/BindParamsParser.java index cd14831e5..3a05759d2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/util/BindParamsParser.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/util/BindParamsParser.java @@ -60,7 +60,6 @@ public final class BindParamsParser { *

*/ private String parseSql() { - if (params.isSameBindHash()) { String preparedSql = params.getPreparedSql(); if (preparedSql != null && !preparedSql.isEmpty()) { @@ -68,114 +67,121 @@ public final class BindParamsParser { return preparedSql; } } + String preparedSql = prepareSql(); + params.setPreparedSql(preparedSql); + params.updateHash(); + return preparedSql; + } - String preparedSql; - if (params.requiresNamedParamsPrepare()) { + private String prepareSql() { + if (!params.requiresNamedParamsPrepare()) { + return sql; + } else { // convert named parameters into ordered list OrderedList orderedList = params.createOrderedList(); parseNamedParams(orderedList); - preparedSql = orderedList.getPreparedSql(); - } else { - preparedSql = sql; + return orderedList.getPreparedSql(); } - params.setPreparedSql(preparedSql); - return preparedSql; } /** * Named parameters need to be parsed and replaced with ?. */ private void parseNamedParams(OrderedList orderedList) { - parseNamedParams(0, orderedList); } private void parseNamedParams(int startPos, OrderedList orderedList) { - if (sql == null) { throw new PersistenceException("query does not contain any named bind parameters?"); } if (startPos > sql.length()) { return; } - - // search for quotes and named params... in order... + // search for quotes and named params in order int beginQuotePos = sql.indexOf(quote, startPos); int nameParamStart = findNameStart(sql, startPos); if (beginQuotePos > 0 && beginQuotePos < nameParamStart) { - // the quote precedes the named parameter... - // find and add up to the end quote - int endQuotePos = sql.indexOf(quote, beginQuotePos + 1); - String sub = sql.substring(startPos, endQuotePos + 1); - orderedList.appendSql(sub); - - // start again after the end quote - parseNamedParams(endQuotePos + 1, orderedList); + addNamedParam(startPos, orderedList, beginQuotePos); } else { if (nameParamStart < 0) { // no more params, add the rest - String sub = sql.substring(startPos, sql.length()); - orderedList.appendSql(sub); + orderedList.appendSql(sql.substring(startPos)); } else { // find the end of the parameter name - int endOfParam = nameParamStart + 1; - do { - char c = sql.charAt(endOfParam); - if (c != '_' && !Character.isLetterOrDigit(c)) { - break; - } - endOfParam++; - } while (endOfParam < sql.length()); - + int endOfParam = findEndOfParam(nameParamStart); // add the named parameter value to bindList String paramName = sql.substring(nameParamStart + 1, endOfParam); + Param param = extractNamedParam(paramName); - Param param; - if (paramName.startsWith(ENCRYPTKEY_PREFIX)) { - param = addEncryptKeyParam(paramName); - } else { - param = params.getParameter(paramName); - } - - if (param == null) { - String msg = "Bind value is not set or null for [" + paramName + "] in [" + sql + "]"; - throw new PersistenceException(msg); - } - - String sub = sql.substring(startPos, nameParamStart); - orderedList.appendSql(sub); - - // check if inValue is a Collection type... + orderedList.appendSql(sql.substring(startPos, nameParamStart)); Object inValue = param.getInValue(); if (inValue instanceof Collection) { - // Chop up Collection parameter into a number - // of individual parameters and add each one individually - Collection collection = (Collection) inValue; - int c = 0; - for (Object elVal : collection) { - if (++c > 1) { - orderedList.appendSql(","); - } - orderedList.appendSql("?"); - BindParams.Param elParam = new BindParams.Param(); - elParam.setInValue(elVal); - orderedList.add(elParam); - } - + addCollectionParams(orderedList, param, (Collection) inValue); } else { - // its a normal scalar value parameter... - orderedList.add(param); - orderedList.appendSql("?"); + addScalarParam(orderedList, param); } - // continue on after the end of the parameter parseNamedParams(endOfParam, orderedList); } } } + private void addScalarParam(OrderedList orderedList, Param param) { + orderedList.add(param); + orderedList.appendSql("?"); + } + + private Param extractNamedParam(String paramName) { + Param param; + if (paramName.startsWith(ENCRYPTKEY_PREFIX)) { + param = addEncryptKeyParam(paramName); + } else { + param = params.getParameter(paramName); + } + if (param == null) { + throw new PersistenceException("Bind value is not set or null for [" + paramName + "] in [" + sql + "]"); + } + return param; + } + + private int findEndOfParam(int nameParamStart) { + int endOfParam = nameParamStart + 1; + do { + char c = sql.charAt(endOfParam); + if (c != '_' && !Character.isLetterOrDigit(c)) { + break; + } + endOfParam++; + } while (endOfParam < sql.length()); + return endOfParam; + } + + private void addNamedParam(int startPos, OrderedList orderedList, int beginQuotePos) { + // the quote precedes the named parameter... + // find and add up to the end quote + int endQuotePos = sql.indexOf(quote, beginQuotePos + 1); + String sub = sql.substring(startPos, endQuotePos + 1); + orderedList.appendSql(sub); + + // start again after the end quote + parseNamedParams(endQuotePos + 1, orderedList); + } + + private void addCollectionParams(OrderedList orderedList, Param param, Collection inValue) { + // Chop up Collection parameter into a number of individual parameters + Collection collection = inValue; + for (int c = 0; c < collection.size(); c++) { + if (c > 0) { + orderedList.appendSql(","); + } + orderedList.appendSql("?"); + } + orderedList.add(param); + } + /** * Find the next named parameter start position (based on colon). */ @@ -200,15 +206,11 @@ public final class BindParamsParser { * Add an encryption key bind parameter. */ private Param addEncryptKeyParam(String keyNamedParam) { - int pos = keyNamedParam.indexOf(ENCRYPTKEY_GAP, ENCRYPTKEY_PREFIX_LEN); - String tableName = keyNamedParam.substring(ENCRYPTKEY_PREFIX_LEN, pos); String columnName = keyNamedParam.substring(pos + ENCRYPTKEY_GAP_LEN); - EncryptKey key = beanDescriptor.encryptKey(tableName, columnName); String strKey = key.getStringValue(); - return params.setEncryptionKey(keyNamedParam, strKey); } diff --git a/ebean-core/src/main/java/io/ebeanservice/docstore/none/NoneDocStore.java b/ebean-core/src/main/java/io/ebeanservice/docstore/none/NoneDocStore.java index d4932791e..a93f202bf 100644 --- a/ebean-core/src/main/java/io/ebeanservice/docstore/none/NoneDocStore.java +++ b/ebean-core/src/main/java/io/ebeanservice/docstore/none/NoneDocStore.java @@ -1,5 +1,6 @@ package io.ebeanservice.docstore.none; +import io.avaje.lang.NonNullApi; import io.ebean.DocStoreQueueEntry; import io.ebean.DocumentStore; import io.ebean.PagedList; @@ -16,6 +17,7 @@ import java.util.function.Predicate; /** * DocumentStore that barfs it is used. */ +@NonNullApi public final class NoneDocStore implements DocumentStore { public static IllegalStateException implementationNotInClassPath() { diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index cb2f0ca56..fc0e83a5f 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java index 939592942..a20874497 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java @@ -243,11 +243,11 @@ public class ModelBuildContext { */ public FkeyBuilder addForeignKey(BeanDescriptor desc, TableJoin tableJoin, boolean direction) { - String baseTable = ctx.normaliseTable(desc.getBaseTable()); + String baseTable = ctx.normaliseTable(desc.baseTable()); String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, count.incrementAndGet()); String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, count.get()); - MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex); + MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.baseTable(), fkIndex); for (TableJoinColumn column : tableJoin.columns()) { String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn(); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java index 0a9f29eca..398bee86f 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -203,10 +203,10 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { if (columns.length == 1) { if (p.hasForeignKeyConstraint() && !importedProperty.descriptor().suppressForeignKey()) { // single references column (put it on the column) - String refTable = importedProperty.descriptor().getBaseTable(); + String refTable = importedProperty.descriptor().baseTable(); if (refTable == null) { // odd case where an EmbeddedId only has 1 property - refTable = p.targetDescriptor().getBaseTable(); + refTable = p.targetDescriptor().baseTable(); } col.setReferences(refTable + "." + refColumn); col.setForeignKeyName(foreignKeyConstraintName(col.getName())); diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 8fb3b8c2c..72278f50b 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index 3033ccebe..8155aa728 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test io.ebean ebean-ddl-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 56ee87ebd..3d1fa16d0 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided @@ -44,7 +44,7 @@ org.postgresql postgresql - 42.2.8 + 42.2.24 provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index f5e6b67de..5ed7e1669 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided @@ -27,9 +27,8 @@ --> io.avaje - avaje-jsr305 - 1.1 - provided + avaje-lang + 1.0 @@ -57,21 +56,21 @@ io.ebean ebean-ddl-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test io.ebean querybean-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test io.ebean ebean-test - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test @@ -93,14 +92,6 @@ - - maven-surefire-plugin - - - --add-opens io.ebean.querybean/org.querytest=io.ebean.core - - - io.repaint.maven diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java index 2b25938c2..ed3c35350 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java @@ -1,24 +1,8 @@ package io.ebean.typequery; -import io.ebean.CacheMode; -import io.ebean.DB; -import io.ebean.Database; -import io.ebean.DtoQuery; -import io.ebean.ExpressionList; -import io.ebean.FetchConfig; -import io.ebean.FetchGroup; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.PagedList; -import io.ebean.PersistenceContextScope; -import io.ebean.ProfileLocation; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.RawSql; -import io.ebean.Transaction; -import io.ebean.UpdateQuery; -import io.ebean.Version; +import io.avaje.lang.NonNullApi; +import io.avaje.lang.Nullable; +import io.ebean.*; import io.ebean.search.MultiMatch; import io.ebean.search.TextCommonTerms; import io.ebean.search.TextQueryString; @@ -28,16 +12,9 @@ import io.ebean.text.PathProperties; import io.ebeaninternal.api.SpiQueryFetch; import io.ebeaninternal.server.util.ArrayStack; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.sql.Connection; import java.sql.Timestamp; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; @@ -81,6 +58,7 @@ import java.util.stream.Stream; * @param the entity bean type (normal entity bean type e.g. Customer) * @param the specific root query bean type (e.g. QCustomer) */ +@NonNullApi public abstract class TQRootBean { /** @@ -177,7 +155,6 @@ public abstract class TQRootBean { * the find methods available on this 'root query bean' instance like findList(). *

*/ - @Nonnull public Query query() { return query; } @@ -1621,7 +1598,6 @@ public abstract class TQRootBean { /** * Execute the query returning an optional bean. */ - @Nonnull public Optional findOneOrEmpty() { return query.findOneOrEmpty(); } @@ -1643,7 +1619,6 @@ public abstract class TQRootBean { * * @see Query#findList() */ - @Nonnull public List findList() { return query.findList(); } @@ -1667,7 +1642,6 @@ public abstract class TQRootBean { * * } */ - @Nonnull public Stream findStream() { return query.findStream(); } @@ -1697,7 +1671,6 @@ public abstract class TQRootBean { * * @see Query#findSet() */ - @Nonnull public Set findSet() { return query.findSet(); } @@ -1710,7 +1683,6 @@ public abstract class TQRootBean { * * @see Query#findIds() */ - @Nonnull public List findIds() { return query.findIds(); } @@ -1736,7 +1708,6 @@ public abstract class TQRootBean { * * @see Query#findMap() */ - @Nonnull public Map findMap() { return query.findMap(); } @@ -1777,7 +1748,6 @@ public abstract class TQRootBean { * * } */ - @Nonnull public QueryIterator findIterate() { return query.findIterate(); } @@ -1798,7 +1768,6 @@ public abstract class TQRootBean { * * @return the list of values for the selected property */ - @Nonnull public List findSingleAttributeList() { return query.findSingleAttributeList(); } @@ -1913,7 +1882,6 @@ public abstract class TQRootBean { * It will execute the query against the history returning the versions of the bean. *

*/ - @Nonnull public List> findVersions() { return query.findVersions(); } @@ -1925,7 +1893,6 @@ public abstract class TQRootBean { * It will execute the query against the history returning the versions of the bean. *

*/ - @Nonnull public List> findVersionsBetween(Timestamp start, Timestamp end) { return query.findVersionsBetween(start, end); } @@ -1936,7 +1903,6 @@ public abstract class TQRootBean { * This is the number of 'top level' or 'root level' entities. *

*/ - @Nonnull public int findCount() { return query.findCount(); } @@ -1951,7 +1917,6 @@ public abstract class TQRootBean { * * @return a Future object for the row count query */ - @Nonnull public FutureRowCount findFutureCount() { return query.findFutureCount(); } @@ -1966,7 +1931,6 @@ public abstract class TQRootBean { * * @return a Future object for the list of Id's */ - @Nonnull public FutureIds findFutureIds() { return query.findFutureIds(); } @@ -1980,7 +1944,6 @@ public abstract class TQRootBean { * * @return a Future object for the list result of the query */ - @Nonnull public FutureList findFutureList() { return query.findFutureList(); } @@ -2014,7 +1977,6 @@ public abstract class TQRootBean { * * @return The PagedList */ - @Nonnull public PagedList findPagedList() { return query.findPagedList(); } @@ -2047,7 +2009,6 @@ public abstract class TQRootBean { /** * Return the type of beans being queried. */ - @Nonnull public Class getBeanType() { return query.getBeanType(); } @@ -2055,7 +2016,6 @@ public abstract class TQRootBean { /** * Return the expression list that has been built for this query. */ - @Nonnull public ExpressionList getExpressionList() { return query.where(); } diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java index 865499458..00922b438 100644 --- a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java @@ -1,19 +1,9 @@ package org.querytest; -import io.ebean.DB; -import io.ebean.Database; -import io.ebean.PagedList; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.Transaction; +import io.ebean.*; import io.ebean.annotation.Transactional; import io.ebean.types.Inet; -import org.example.domain.ACat; -import org.example.domain.ADog; -import org.example.domain.Address; -import org.example.domain.Animal; -import org.example.domain.Country; -import org.example.domain.Customer; +import org.example.domain.*; import org.example.domain.otherpackage.PhoneNumber; import org.example.domain.otherpackage.ValidEmail; import org.example.domain.query.QAnimal; @@ -26,15 +16,7 @@ import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; @@ -155,23 +137,21 @@ public class QCustomerTest { assertThat(ids).isNotEmpty(); - Map map = new QCustomer() + Map map = new QCustomer() .status.equalTo(Customer.Status.GOOD) .findMap(); assertThat(map.size()).isEqualTo(ids.size()); - QueryIterator iterate = new QCustomer() - .status.equalTo(Customer.Status.GOOD) - .findIterate(); + try (QueryIterator iterate = + new QCustomer() + .status.equalTo(Customer.Status.GOOD) + .findIterate()) { - try { while (iterate.hasNext()) { Customer customer = iterate.next(); assertThat(customer.getName()).isNotNull(); } - } finally { - iterate.close(); } } diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index d016ad72f..7ada5696b 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided io.ebean ebean-querybean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test io.ebean querybean-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test io.ebean ebean-test - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index c45b654c9..8b5723151 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean test @@ -29,14 +29,14 @@ io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT provided io.ebean ebean-ddl-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT @@ -147,7 +147,7 @@ org.postgresql postgresql - 42.2.23 + 42.2.24 org.checkerframework diff --git a/ebean-test/src/test/java/io/ebean/DtoQueryTest.java b/ebean-test/src/test/java/io/ebean/DtoQueryTest.java index 53b2b4ca4..f41928909 100644 --- a/ebean-test/src/test/java/io/ebean/DtoQueryTest.java +++ b/ebean-test/src/test/java/io/ebean/DtoQueryTest.java @@ -208,19 +208,33 @@ public class DtoQueryTest extends BaseTestCase { ResetBasicData.reset(); + LoggedSql.start(); List ids = Arrays.asList(1, 2); - List list = server().findDto(DCust.class, "select id, name from o_customer where id = any(?)") + List list = DB.findDto(DCust.class, "select id, name from o_customer where id = any(?)") .setParameter(ids) .findList(); assertThat(list).isNotEmpty(); - list = server().findDto(DCust.class, "select id, name from o_customer where id in (:idList)") + List list1 = DB.findDto(DCust.class, "select id, name from o_customer where id in (:idList)") .setParameter("idList", ids) .findList(); - assertThat(list).isNotEmpty(); + assertThat(list1).isNotEmpty(); + + + List list2 = DB.findDto(DCust.class, "select id, name from o_customer where id = any(:idList)") + .setArrayParameter("idList", ids) + .findList(); + + assertThat(list2).isNotEmpty(); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(3); + assertThat(sql.get(0)).contains(" id = any(?)"); + assertThat(sql.get(1)).contains(" id in (?,?)"); + assertThat(sql.get(2)).contains(" id = any(?)"); } @ForPlatform(Platform.POSTGRES) diff --git a/ebean-test/src/test/java/io/ebean/text/PathPropertiesTests.java b/ebean-test/src/test/java/io/ebean/text/PathPropertiesTests.java index 2fb3b5fce..f366a6d1d 100644 --- a/ebean-test/src/test/java/io/ebean/text/PathPropertiesTests.java +++ b/ebean-test/src/test/java/io/ebean/text/PathPropertiesTests.java @@ -1,8 +1,11 @@ package io.ebean.text; +import io.ebean.BaseTestCase; import io.ebean.DB; import io.ebean.FetchPath; import io.ebean.Query; +import io.ebean.test.LoggedSql; + import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +17,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -public class PathPropertiesTests { +public class PathPropertiesTests extends BaseTestCase { private static final Logger log = LoggerFactory.getLogger(PathPropertiesTests.class); @@ -212,4 +215,16 @@ public class PathPropertiesTests { String asJson = DB.json().toJson(list, pathProps); log.info("Json: {}", asJson); } + + @Test + public void test_withAllPropsQuery() { + PathProperties root = PathProperties.parse("*,billingAddress(line1)"); + LoggedSql.start(); + Query query = DB.find(Customer.class).apply(root); + query.findList(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.shipping_address_id, t1.id, t1.line_1 from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id;"); + } + } diff --git a/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java b/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java index 6cf8f52db..a60b18d3f 100644 --- a/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java +++ b/ebean-test/src/test/java/io/ebean/text/json/JsonBeanReaderTest.java @@ -47,7 +47,7 @@ public class JsonBeanReaderTest extends BaseTestCase { beanReader.read(); JsonParser more = getParser(); - JsonBeanReader moreReader = beanReader.forJson(more, true); + JsonBeanReader moreReader = beanReader.forJson(more); Customer customer = moreReader.read(); assertThat(customer.getId()).isEqualTo(42); diff --git a/ebean-test/src/test/java/io/ebeaninternal/api/BindParamsTest.java b/ebean-test/src/test/java/io/ebeaninternal/api/BindParamsTest.java index eef160dd7..1a9a52669 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/api/BindParamsTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/api/BindParamsTest.java @@ -19,17 +19,19 @@ public class BindParamsTest { BindParams.Param param = bindParams.getParameter("ids"); assertEquals(3, param.queryBindCount()); assertFalse(bindParams.isSameBindHash()); + bindParams.updateHash(); List ids2 = Arrays.asList("1", "2", "3", "4"); bindParams.setParameter("ids", ids2); assertEquals(4, param.queryBindCount()); assertFalse(bindParams.isSameBindHash()); + bindParams.updateHash(); List ids3 = Arrays.asList("2", "99", "44"); bindParams.setParameter("ids", ids3); assertEquals(3, param.queryBindCount()); assertFalse(bindParams.isSameBindHash()); - + bindParams.updateHash(); List ids4 = Arrays.asList("4545", "3499", "3444"); bindParams.setParameter("ids", ids4); diff --git a/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index 17bd8f19b..d4b0e4b20 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -1,5 +1,6 @@ package io.ebeaninternal.api; +import io.avaje.lang.NonNullApi; import io.ebean.*; import io.ebean.annotation.Platform; import io.ebean.annotation.TxIsolation; @@ -37,6 +38,7 @@ import java.util.stream.Stream; /** * Test double for SpiEbeanServer. */ +@NonNullApi public class TDSpiEbeanServer extends TDSpiServer implements SpiEbeanServer { String name; diff --git a/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java b/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java index 620e9664a..2cf23625b 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java +++ b/ebean-test/src/test/java/io/ebeaninternal/api/TDSpiServer.java @@ -1,5 +1,6 @@ package io.ebeaninternal.api; +import io.avaje.lang.NonNullApi; import io.ebean.*; import io.ebean.annotation.Platform; import io.ebean.annotation.TxIsolation; @@ -15,7 +16,6 @@ import io.ebean.plugin.SpiServer; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; +@NonNullApi public class TDSpiServer implements SpiServer { @Override public void shutdown() { @@ -267,7 +268,6 @@ public class TDSpiServer implements SpiServer { return null; } - @Nonnull @Override public T reference(Class beanType, Object id) { return null; @@ -413,13 +413,11 @@ public class TDSpiServer implements SpiServer { return 0; } - @Nonnull @Override public Set checkUniqueness(Object bean) { return null; } - @Nonnull @Override public Set checkUniqueness(Object bean, Transaction transaction) { return null; diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/cluster/binarymessage/BinaryTransactionEventReadWriteTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/cluster/binarymessage/BinaryTransactionEventReadWriteTest.java index c77b855e0..61956ace5 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/cluster/binarymessage/BinaryTransactionEventReadWriteTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/cluster/binarymessage/BinaryTransactionEventReadWriteTest.java @@ -15,6 +15,7 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; +import javax.annotation.Nonnull; import java.io.IOException; import java.util.Collections; import java.util.HashSet; @@ -99,8 +100,9 @@ public class BinaryTransactionEventReadWriteTest extends BaseTestCase { } class TDEbeanServer extends TDSpiEbeanServer { + @Nonnull @Override - public BeanDescriptor descriptorById(String descriptorId) { + public BeanDescriptor descriptorById(@Nonnull String descriptorId) { return customerBeanDescriptor; } } diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/core/TestDiffHelpSimple.java b/ebean-test/src/test/java/io/ebeaninternal/server/core/TestDiffHelpSimple.java index 56be7e09c..5a91d0416 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/core/TestDiffHelpSimple.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/core/TestDiffHelpSimple.java @@ -16,9 +16,10 @@ import java.sql.Timestamp; import java.util.Map; import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -public class TestDiffHelpSimple extends BaseTestCase { +class TestDiffHelpSimple extends BaseTestCase { long firstTime = System.currentTimeMillis() - 10000; long secondTime = System.currentTimeMillis(); @@ -26,7 +27,7 @@ public class TestDiffHelpSimple extends BaseTestCase { Database server; BeanDescriptor orderDesc; - public TestDiffHelpSimple() { + TestDiffHelpSimple() { server = DB.getDefault(); SpiEbeanServer spiServer = (SpiEbeanServer) server; orderDesc = spiServer.descriptor(Order.class); @@ -44,9 +45,12 @@ public class TestDiffHelpSimple extends BaseTestCase { } @Test - public void testBasicChanges() { - + void diffWhenNull_expect_empty() { + assertThat(DB.diff(null, null)).isEmpty(); + } + @Test + void testBasicChanges() { Order order1 = createBaseOrder(server); Order order2 = new Order(); @@ -70,9 +74,7 @@ public class TestDiffHelpSimple extends BaseTestCase { } @Test - public void testBasicChanges_given_flatMode() { - - + void testBasicChanges_given_flatMode() { Order order1 = createBaseOrder(server); Order order2 = new Order(); @@ -98,8 +100,7 @@ public class TestDiffHelpSimple extends BaseTestCase { } @Test - public void testIdIgnored() { - + void testIdIgnored() { Order order1 = createBaseOrder(server); Order order2 = createBaseOrder(server); order2.setId(14); @@ -110,8 +111,7 @@ public class TestDiffHelpSimple extends BaseTestCase { } @Test - public void testSecondValueNull() { - + void testSecondValueNull() { Order order1 = createBaseOrder(server); Order order2 = createBaseOrder(server); @@ -134,10 +134,8 @@ public class TestDiffHelpSimple extends BaseTestCase { assertNull(shipDatePair.getOldValue()); } - @Test - public void testFirstValueNull() { - + void testFirstValueNull() { Order order1 = createBaseOrder(server); order1.setShipDate(null); @@ -157,8 +155,7 @@ public class TestDiffHelpSimple extends BaseTestCase { } @Test - public void testBothNull() { - + void testBothNull() { Order order1 = createBaseOrder(server); order1.setShipDate(null); diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/dto/DtoMetaBuilderTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/dto/DtoMetaBuilderTest.java index 3d476132c..bd75d835c 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/dto/DtoMetaBuilderTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/dto/DtoMetaBuilderTest.java @@ -34,8 +34,10 @@ public class DtoMetaBuilderTest { Map methods = getIncludedMethodsFor(D0.class); assertThat(methods).hasSize(2); - assertThat(DtoMetaBuilder.propertyType(methods.get("setName"))).isEqualTo(String.class); - assertThat(DtoMetaBuilder.propertyType(methods.get("setId"))).isEqualTo(long.class); + assertThat(DtoMetaProperty.propertyClass(methods.get("setName"))).isEqualTo(String.class); + assertThat(DtoMetaProperty.propertyClass(methods.get("setId"))).isEqualTo(long.class); + assertThat(DtoMetaProperty.propertyType(methods.get("setName"))).isEqualTo(String.class); + assertThat(DtoMetaProperty.propertyType(methods.get("setId"))).isEqualTo(long.class); } @Test diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java index 4983c31e3..236d3a2fc 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java @@ -6,16 +6,13 @@ import org.junit.jupiter.api.Test; import org.tests.model.basic.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -public class DefaultPersistenceContextTest { +class DefaultPersistenceContextTest { private final Customer customer42; - private final Car car1; - public DefaultPersistenceContextTest() { + DefaultPersistenceContextTest() { customer42 = new Customer(); customer42.setId(42); car1 = new Car(); @@ -37,8 +34,7 @@ public class DefaultPersistenceContextTest { } @Test - public void put_get_withInheritance() { - + void put_get_withInheritance() { PersistenceContext pc = pc(); pc.put(root(Vehicle.class), 1, car1); @@ -47,8 +43,7 @@ public class DefaultPersistenceContextTest { } @Test - public void put_get() { - + void put_get() { PersistenceContext pc = pc(); pc.put(Customer.class, customer42.getId(), customer42); @@ -57,8 +52,7 @@ public class DefaultPersistenceContextTest { } @Test - public void putIfAbsent_when_absent() { - + void putIfAbsent_when_absent() { PersistenceContext pc = pc(); Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), customer42); @@ -66,8 +60,7 @@ public class DefaultPersistenceContextTest { } @Test - public void putIfAbsent_when_notAbsent() { - + void putIfAbsent_when_notAbsent() { PersistenceContext pc = pcWith42(); Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), new Customer()); @@ -75,30 +68,28 @@ public class DefaultPersistenceContextTest { } @Test - public void get_when_empty() { + void get_when_empty() { PersistenceContext pc = pc(); Object found = pc.get(Customer.class, 42); assertThat(found).isNull(); } @Test - public void get_when_there() { + void get_when_there() { PersistenceContext pc = pcWith42(); Object found = pc.get(Customer.class, 42); assertThat(found).isSameAs(customer42); } @Test - public void getWithOption_when_empty() { - + void getWithOption_when_empty() { PersistenceContext pc = pc(); PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42); assertThat(withOption).isNull(); } @Test - public void getWithOption_when_there() { - + void getWithOption_when_there() { PersistenceContext pc = pcWith42(); PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42); @@ -106,8 +97,7 @@ public class DefaultPersistenceContextTest { } @Test - public void getWithOption_when_deleted() { - + void getWithOption_when_deleted() { PersistenceContext pc = pcWith42(); pc.deleted(Customer.class, 42); @@ -117,38 +107,33 @@ public class DefaultPersistenceContextTest { } @Test - public void size_when_empty() { - + void size_when_empty() { PersistenceContext pc = pc(); assertThat(pc.size(Customer.class)).isEqualTo(0); } @Test - public void size_when_some() { - + void size_when_some() { PersistenceContext pc = pcWith42(); assertThat(pc.size(Customer.class)).isEqualTo(1); } @Test - public void clear() { - + void clear() { PersistenceContext pc = pcWith42(); pc.clear(); assertThat(pc.size(Customer.class)).isEqualTo(0); } @Test - public void clearClass() { - + void clearClass() { PersistenceContext pc = pcWith42(); pc.clear(Customer.class); assertThat(pc.size(Customer.class)).isEqualTo(0); } @Test - public void clearClassAndId() { - + void clearClassAndId() { PersistenceContext pc = pcWith42(); pc.put(Customer.class, 43, new Customer()); @@ -160,24 +145,24 @@ public class DefaultPersistenceContextTest { } @Test - public void forIterate() { + void beginIterate() { final DefaultPersistenceContext pc = pcWith42(); final Object origCustomer42 = pc.get(Customer.class, 42); // act - final PersistenceContext pcIterate = pc.forIterate(); - assertThat(pc).isNotSameAs(pcIterate); - assertThat(pcIterate.size(Customer.class)).isEqualTo(1); + pc.beginIterate(); + assertThat(pc.size(Customer.class)).isEqualTo(1); // assert same instance (bean effectively transferred to iterator persistence context - final Object customer42 = pcIterate.get(Customer.class, 42); + final Object customer42 = pc.get(Customer.class, 42); assertThat(customer42).isSameAs(origCustomer42); - final PersistenceContext.WithOption option = pcIterate.getWithOption(Customer.class, 42); + final PersistenceContext.WithOption option = pc.getWithOption(Customer.class, 42); assertThat(option.getBean()).isSameAs(origCustomer42); + pc.endIterate(); } @Test - public void forIterate_many() { + void beginIterate_many() throws InterruptedException { DefaultPersistenceContext pc = new DefaultPersistenceContext(); addCustomers(pc, 1, 100); addContacts(pc, 1, 1010); @@ -185,50 +170,26 @@ public class DefaultPersistenceContextTest { assertThat(pc.size(Contact.class)).isEqualTo(1010); // act - final PersistenceContext pcIterate = pc.forIterate(); - assertThat(pcIterate.size(Customer.class)).isEqualTo(100); - assertThat(pcIterate.size(Contact.class)).isEqualTo(1010); + pc.beginIterate(); + assertThat(pc.size(Customer.class)).isEqualTo(100); + assertThat(pc.size(Contact.class)).isEqualTo(1010); + + addCustomers(pc, 200, 100); + addContacts(pc, 2000, 1010); + + assertThat(pc.size(Customer.class)).isEqualTo(200); + assertThat(pc.size(Contact.class)).isEqualTo(2020); + pc.endIterate(); + + System.gc(); + Thread.sleep(50); // give the GC some time + + assertThat(pc.size(Customer.class)).isEqualTo(100); + assertThat(pc.size(Contact.class)).isEqualTo(1010); } @Test - public void forIterate_resetLimit_forIterateReset() { - DefaultPersistenceContext initialPc = new DefaultPersistenceContext(); - addCustomers(initialPc, 1, 100); - addContacts(initialPc, 1, 1010); - - final PersistenceContext pcIterate = initialPc.forIterate(); - assertFalse(pcIterate.resetLimit()); - - // added 900 NEW contact beans - addContacts(pcIterate, 2000, 900); - assertThat(pcIterate.size(Contact.class)).isEqualTo(1910); - assertFalse(pcIterate.resetLimit()); - - // boundary, added 1000 NEW contact beans (still false) - addContacts(pcIterate, 3000, 100); - assertFalse(pcIterate.resetLimit()); - - addContacts(pcIterate, 4000, 1); - addProducts(pcIterate, 1, 100); - // ACT - over 1000 added beans boundary for contacts so returns true - assertTrue(pcIterate.resetLimit()); - - assertThat(pcIterate.size(Contact.class)).isEqualTo(2011); - assertThat(pcIterate.size(Customer.class)).isEqualTo(100); - assertThat(pcIterate.size(Product.class)).isEqualTo(100); - - // ACT - obtain new PC forIterateReset - PersistenceContext pcReset = pcIterate.forIterateReset(); - - // keeps original customer beans as no new added beans there - assertThat(pcReset.size(Customer.class)).isEqualTo(100); // customers didn't change - // added beans to contacts and products so those where reset - assertThat(pcReset.size(Contact.class)).isEqualTo(0); - assertThat(pcReset.size(Product.class)).isEqualTo(0); - } - - @Test - public void toString_sillyTest() { + void toString_sillyTest() { DefaultPersistenceContext pc = pcWith42(); assertThat(pc.toString()).contains("org.tests.model.basic.Customer"); } @@ -248,12 +209,4 @@ public class DefaultPersistenceContextTest { pc.put(Contact.class, i, bean); } } - - private void addProducts(PersistenceContext pc, int start, int loop) { - for (int i = start; i < start + loop; i++) { - Product bean = new Product(); - bean.setId(i); - pc.put(Product.class, i, bean); - } - } } diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/JsonTrimTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/JsonTrimTest.java index c08ceeab7..27d1ad8f9 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/type/JsonTrimTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/JsonTrimTest.java @@ -5,17 +5,59 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -public class JsonTrimTest { +class JsonTrimTest { @Test - public void trim_array() { + void trim_array() { final String trimmed = JsonTrim.trim("[{\"name\": \"one\", \"along\": 1, \"timestamp\": 1629609021559}, {\"name\": \"two\", \"along\": 2, \"timestamp\": 1629609021559}]"); assertThat(trimmed).isEqualTo("[{\"name\":\"one\",\"along\":1,\"timestamp\":1629609021559},{\"name\":\"two\",\"along\":2,\"timestamp\":1629609021559}]"); } @Test - public void trim_object() { + void trim_object() { final String trimmed = JsonTrim.trim("{\"name\": \"one\",\t \t \"along\": 1, \"timestamp\": 1629609021559}"); assertThat(trimmed).isEqualTo("{\"name\":\"one\",\"along\":1,\"timestamp\":1629609021559}"); } + + @Test + void trim_object_embeddedEscapeSlash() { + final String trimmed = JsonTrim.trim("{\"a\": \"o\\nf\",\t \t \n \"b\": 1}"); + assertThat(trimmed).isEqualTo("{\"a\":\"o\\nf\",\"b\":1}"); + } + + @Test + void trim_escapedTabNewLine() { + final String trimmed = JsonTrim.trim("{\"a\": \"o\\t\\nf\",\t \t \n \"b\": 1}"); + assertThat(trimmed).isEqualTo("{\"a\":\"o\\t\\nf\",\"b\":1}"); + } + + @Test + void trim_leadingEscapedNewLine() { + final String trimmed = JsonTrim.trim("{\"a\": \"\\no\\t\\nf\\n\",\t \t \n \"b\": 1}"); + assertThat(trimmed).isEqualTo("{\"a\":\"\\no\\t\\nf\\n\",\"b\":1}"); + } + + @Test + void trim_trailingEscapedNewLine() { + final String trimmed = JsonTrim.trim("{\"a\": \"o\\t\\nf\\n\",\t \t \n \"b\": 1}"); + assertThat(trimmed).isEqualTo("{\"a\":\"o\\t\\nf\\n\",\"b\":1}"); + } + + @Test + void trim_trailingEscapedSlash() { + final String trimmed = JsonTrim.trim("{\"a\": \"o\\t\\nf\\\\\",\t \t \n \"b\": 1}"); + assertThat(trimmed).isEqualTo("{\"a\":\"o\\t\\nf\\\\\",\"b\":1}"); + } + + @Test + void trim_object_embeddedEscaped() { + final String trimmed = JsonTrim.trim("{\"name\": \"one\nfoo\nbar\\bazz\tboo\",\t \t \n \"along\": 1}"); + assertThat(trimmed).isEqualTo("{\"name\":\"one\nfoo\nbar\\bazz\tboo\",\"along\":1}"); + } + + @Test + void trim_escaped() { + final String trimmed = JsonTrim.trim(" \t \n {\"a\": \n \"\\t1\\t2\\n3\\\\\",\t \n \"b\": \"\\t1\\t2\\n3\\\\\" , \t \n \"c\": \"\\t1\\t2\\n3\\\\\" \t \n }"); + assertThat(trimmed).isEqualTo("{\"a\":\"\\t1\\t2\\n3\\\\\",\"b\":\"\\t1\\t2\\n3\\\\\",\"c\":\"\\t1\\t2\\n3\\\\\"}"); + } } diff --git a/ebean-test/src/test/java/org/tests/basic/TestLoadBeanCache.java b/ebean-test/src/test/java/org/tests/basic/TestLoadBeanCache.java index c96373de3..47eb98793 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestLoadBeanCache.java +++ b/ebean-test/src/test/java/org/tests/basic/TestLoadBeanCache.java @@ -2,12 +2,18 @@ package org.tests.basic; import io.ebean.BaseTestCase; import io.ebean.DB; +import io.ebean.test.LoggedSql; + import org.junit.jupiter.api.Test; import org.tests.model.basic.Country; +import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; +import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class TestLoadBeanCache extends BaseTestCase { @@ -31,4 +37,30 @@ public class TestLoadBeanCache extends BaseTestCase { assertTrue(loadedNz == nz); } + + @Test + public void testLoadWithFindMap() { + + ResetBasicData.reset(); + + List ids = DB.find(Customer.class).findIds(); + assertEquals(ids.size(), 4); + + DB.getDefault().pluginApi().cacheManager().clearAll(); + + // hit database + LoggedSql.start(); + DB.find(Customer.class).where().idIn(ids).findMap(); + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + + // hit beanCache + LoggedSql.start(); + DB.find(Customer.class).where().idIn(ids).findMap(); + sql = LoggedSql.stop(); + assertThat(sql).hasSize(0); + + } + + } diff --git a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java index e18c55c57..82788d8a0 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java +++ b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java @@ -2,37 +2,52 @@ package org.tests.basic; import io.ebean.BaseTestCase; import io.ebean.DB; +import io.ebean.Transaction; +import io.ebeaninternal.api.SpiPersistenceContext; +import io.ebeaninternal.api.SpiTransaction; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.tests.model.basic.ContactNote; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.List; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; -public class TestPersistenceContext extends BaseTestCase { +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class TestPersistenceContext extends BaseTestCase { @Test - public void test() { + void testReload() { + ResetBasicData.reset(); + try (Transaction txn = DB.beginTransaction()) { + List notes = new ArrayList<>(); + DB.find(ContactNote.class).findEach(notes::add); + notes.get(0).setTitle("FooBar"); + DB.find(ContactNote.class).findList(); + assertThat(notes.get(0).getTitle()).isEqualTo("FooBar"); + } + } + @Test + void test() { ResetBasicData.reset(); - // implicit transaction with its own - // persistence context + // implicit transaction with its own persistence context Order oBefore = DB.find(Order.class, 1); - - Order order = null; - // start a persistence context DB.beginTransaction(); + + Order order; try { - order = DB.find(Order.class, 1); - - // not the same instance ...as a different - // persistence context - assertTrue(order != oBefore); - + // not the same instance ...as a different persistence context + assertNotSame(order, oBefore); // finds an existing bean in the persistence context // ... so doesn't even execute a query @@ -40,19 +55,17 @@ public class TestPersistenceContext extends BaseTestCase { Order o3 = DB.reference(Order.class, 1); // all the same instance - assertTrue(order == o2); - assertTrue(order == o3); + assertSame(order, o2); + assertSame(order, o3); } finally { DB.endTransaction(); } - // implicit transaction with its own - // persistence context + // implicit transaction with its own persistence context Order oAfter = DB.find(Order.class, 1); - - assertTrue(oAfter != oBefore); - assertTrue(oAfter != order); + assertNotSame(oAfter, oBefore); + assertNotSame(oAfter, order); // start a persistence context DB.beginTransaction(); @@ -65,17 +78,142 @@ public class TestPersistenceContext extends BaseTestCase { .setUseCache(false) .setId(id) .findOne(); + assert customer != null; System.gc(); Order order2 = DB.find(Order.class, orderId); + assert order2 != null; Customer customer2 = order2.getCustomer(); + assert customer2 != null; assertEquals(customer.getId(), customer2.getId()); - assertTrue(customer == customer2); + assertSame(customer, customer2); } finally { DB.endTransaction(); } } + @Disabled + @Test + void findWithGcTest() { + for (int j = 0; j < 20; j++) { + for (int i = 0; i < 500; i++) { + Customer c = new Customer(); + c.setName("Customer #" + i); + DB.save(c); + } + int customerCount = DB.find(Customer.class).findCount(); + AtomicInteger count = new AtomicInteger(customerCount); + + WeakHashMap customers = new WeakHashMap<>(); + + DB.find(Customer.class).fetch("orders").findEach(customer -> { + customers.put(customer, customer.getId()); + if (count.decrementAndGet() == 0) { + // Trigger garbage collection on last iteration and check if beans disappear from memory + System.gc(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + customers.size(); // expunge stale entries + System.out.println("Total instances: " + customerCount + ", instances left in memory: " + customers.size()); + } + }); + } + } + + @Disabled // run manually + @Test + void testPcScopes_with_weakReferences() throws InterruptedException { + for (int i = 0; i < 5000; i++) { + Customer c = new Customer(); + c.setName("Customer #" + i); + DB.save(c); + Order o = new Order(); + o.setCustomer(c); + DB.save(o); + } + + try (Transaction txn = DB.beginTransaction()) { + List first100 = DB.find(Customer.class).where().le("id", 100).findList(); + assertEquals(100, first100.size()); + for (Customer c : first100) { + c.setSmallnote("one of the first 100"); + } + + // use lastBean to hold onto a weak reference bean + Customer[] lastBean = new Customer[1]; + // findEach switches on use of weak reference in persistence context + DB.find(Customer.class).setLazyLoadBatchSize(1).findEach(customer -> { + if (customer.getId() <= 100) { + assertEquals("one of the first 100", customer.getSmallnote()); + // nested finds + DB.find(Order.class).where().eq("customer", customer).findEach(20, consumer -> { + }); + } else { + assertNotEquals("one of the first 100", customer.getSmallnote()); + } + lastBean[0] = customer; + }); + + SpiPersistenceContext pc = ((SpiTransaction) txn).getPersistenceContext(); + // the first 100 customers using strong references + assertThat(pc.toString()).contains("Customer=size:5000 (4900 weak)"); + assertThat(pc.toString()).contains("Order=size:100 (100 weak)"); + + System.gc(); + Thread.sleep(100); + pc.get(Customer.class, 1); // trigger expungeStaleEntries + // pc.get(Order.class, 1); + assertThat(pc.toString()).contains("Customer=size:101 (1 weak)"); + assertThat(pc.toString()).contains("Order=size:0 (0 weak)"); + + first100 = DB.find(Customer.class).where().le("id", 100).findList(); + for (Customer c : first100) { + assertEquals("one of the first 100", c.getSmallnote()); + } + Customer lastBeanFromDb = DB.find(Customer.class).setId(lastBean[0].getId()).findOne(); + assertSame(lastBeanFromDb, lastBean[0]); + + // read 200 + DB.find(Customer.class).where().le("id", 200).findList(); + assertThat(pc.toString()).contains("Customer=size:201 (1 weak)"); + + lastBean[0] = null; // allow GC on this one + lastBeanFromDb = null; + System.gc(); + Thread.sleep(100); + assertThat(pc.toString()).contains("Customer=size:200 (0 weak)"); + } + } + + @Disabled // run manually + @Test + void testPcScopes_with_findEachFindList() { + for (int i = 0; i < 5000; i++) { + Customer c = new Customer(); + c.setName("Customer #" + i); + DB.save(c); + Order o = new Order(); + o.setCustomer(c); + DB.save(o); + } + + for (int i = 0; i < 1000; i++) { + try (Transaction txn = DB.beginTransaction()) { + List customers = new ArrayList<>(); + DB.find(Customer.class).select("id").findEach(customers::add); + SpiPersistenceContext pc = ((SpiTransaction) txn).getPersistenceContext(); + assertThat(pc.toString()).contains("Customer=size:5000 (5000 weak)"); + customers.clear(); + customers = DB.find(Customer.class).select("id").findList(); + + assertThat(pc.toString()).contains("Customer=size:5000"); // We expect ALWAYS 5000 entries in the PC + } + } + } } diff --git a/ebean-test/src/test/java/org/tests/basic/TestUpdate.java b/ebean-test/src/test/java/org/tests/basic/TestUpdate.java new file mode 100644 index 000000000..c4274bfc5 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/basic/TestUpdate.java @@ -0,0 +1,70 @@ +package org.tests.basic; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.Update; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.tests.model.basic.Customer; +import org.tests.model.basic.ResetBasicData; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Testcase identified a bug when collections are used as bind parameters + * + * @author Roland Praml, FOCONIS AG + */ +public class TestUpdate extends BaseTestCase { + + @BeforeEach + public void createCustomers() { + ResetBasicData.reset(); + for (int i = 1; i <= 3; i++) { + Customer cust = new Customer(); + cust.setName("testUpdate" + i); + DB.save(cust); + } + } + + @AfterEach + public void deleteCustomers() { + DB.createUpdate(Customer.class, "delete from customer where name like 'testUpdate%'").execute(); + } + + @Test + public void testNormal() { + for (int i = 1; i <= 3; i++) { + Update update = DB.createUpdate(Customer.class, + "update customer set smallnote = :smallnote where name in (:name)"); + update.setParameter("name", Arrays.asList("testUpdate" + i)).setParameter("smallnote", "Note #" + i).execute(); + } + Customer cust = DB.find(Customer.class).where().eq("name", "testUpdate3").findOne(); + assertThat(cust.getSmallnote()).isEqualTo("Note #3"); + } + + @Test + public void testReuse() { + Update update = DB.createUpdate(Customer.class, + "update customer set smallnote = :smallnote where name in (:name)"); + for (int i = 1; i <= 3; i++) { + update.setParameter("name", Arrays.asList("testUpdate" + i)).setParameter("smallnote", "Note #" + i).execute(); + } + Customer cust = DB.find(Customer.class).where().eq("name", "testUpdate3").findOne(); + assertThat(cust.getSmallnote()).isEqualTo("Note #3"); + } + + @Test + public void testReuseNoArray() { + Update update = DB.createUpdate(Customer.class, + "update customer set smallnote = :smallnote where name = :name"); + for (int i = 1; i <= 3; i++) { + update.setParameter("name", "testUpdate" + i).setParameter("smallnote", "Note #" + i).execute(); + } + Customer cust = DB.find(Customer.class).where().eq("name", "testUpdate3").findOne(); + assertThat(cust.getSmallnote()).isEqualTo("Note #3"); + } +} diff --git a/ebean-test/src/test/java/org/tests/basic/type/TestExtraScalarTypes.java b/ebean-test/src/test/java/org/tests/basic/type/TestExtraScalarTypes.java index 133adada4..d5ee6435a 100644 --- a/ebean-test/src/test/java/org/tests/basic/type/TestExtraScalarTypes.java +++ b/ebean-test/src/test/java/org/tests/basic/type/TestExtraScalarTypes.java @@ -10,16 +10,16 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -public class TestExtraScalarTypes extends BaseTestCase { +class TestExtraScalarTypes extends BaseTestCase { @Test - public void test() { + void test() { - Locale locale = Locale.getDefault(); - Currency currency = Currency.getInstance(locale); + Locale locale = Locale.ENGLISH; + Currency currency = Currency.getInstance(Locale.US); TimeZone tz = TimeZone.getDefault(); ESomeType e = new ESomeType(); @@ -29,20 +29,20 @@ public class TestExtraScalarTypes extends BaseTestCase { DB.save(e); - ESomeType e2 = DB.find(ESomeType.class).setAutoTune(false).setId(e.getId()).findOne(); + ESomeType e2 = DB.find(ESomeType.class).setId(e.getId()).findOne(); assertNotNull(e2.getCurrency()); assertNotNull(e2.getLocale()); assertNotNull(e2.getTimeZone()); List list = DB.find(ESomeType.class) - .setAutoTune(false).where() + .where() .eq("locale", locale) .eq("timeZone", tz.getID()) .eq("currency", currency) .findList(); - assertTrue(!list.isEmpty()); + assertThat(list).isNotEmpty(); } } diff --git a/ebean-test/src/test/java/org/tests/batchload/TestBeanState.java b/ebean-test/src/test/java/org/tests/batchload/TestBeanState.java index 2f244435e..69033d33c 100644 --- a/ebean-test/src/test/java/org/tests/batchload/TestBeanState.java +++ b/ebean-test/src/test/java/org/tests/batchload/TestBeanState.java @@ -16,11 +16,24 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -public class TestBeanState extends BaseTestCase { +class TestBeanState extends BaseTestCase { @Test - public void test() { + void invalid() { + assertThrows(IllegalArgumentException.class, () -> DB.beanState(new Object())); + } + @Test + void loadErrors_when_empty() { + ResetBasicData.reset(); + Customer one = DB.find(Customer.class).setMaxRows(1).findOne(); + BeanState beanState = DB.beanState(one); + + assertThat(beanState.loadErrors()).isEmpty(); + } + + @Test + void test() { ResetBasicData.reset(); List custs = DB.find(Customer.class).findList(); @@ -55,8 +68,7 @@ public class TestBeanState extends BaseTestCase { } @Test - public void setDisableLazyLoad_expect_lazyLoadingDisabled() { - + void setDisableLazyLoad_expect_lazyLoadingDisabled() { ResetBasicData.reset(); List custs = DB.find(Customer.class).order("id").findList(); @@ -73,8 +85,7 @@ public class TestBeanState extends BaseTestCase { } @Test - public void getChangedProps_when_setManyProperty() { - + void changedProps_when_setManyProperty() { ResetBasicData.reset(); Customer customer = DB.find(Customer.class).order("id").setMaxRows(1).findOne(); @@ -87,8 +98,7 @@ public class TestBeanState extends BaseTestCase { } @Test - public void getChangedProps_when_setManyProperty_onNewBean() { - + void changedProps_when_setManyProperty_onNewBean() { Customer customer = new Customer(); BeanState beanState = DB.beanState(customer); @@ -107,7 +117,7 @@ public class TestBeanState extends BaseTestCase { } @Test - public void readOnly_when_setManyProperty() { + void readOnly_when_setManyProperty() { Customer customer = new Customer(); customer.setContacts(new ArrayList<>()); @@ -120,7 +130,7 @@ public class TestBeanState extends BaseTestCase { } @Test - public void readOnly_when_setProperty() { + void readOnly_when_setProperty() { Customer customer = new Customer(); customer.setName("a"); diff --git a/ebean-test/src/test/java/org/tests/inheritance/bothsides/Target1.java b/ebean-test/src/test/java/org/tests/inheritance/bothsides/Target1.java index 5803d8046..f39c3ee61 100644 --- a/ebean-test/src/test/java/org/tests/inheritance/bothsides/Target1.java +++ b/ebean-test/src/test/java/org/tests/inheritance/bothsides/Target1.java @@ -4,7 +4,6 @@ import javax.persistence.Entity; import javax.persistence.Inheritance; @Entity -@Inheritance public class Target1 extends TargetBase { public Target1(String name) { diff --git a/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java b/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java index 5da5ffdbf..19d2bfd48 100644 --- a/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java +++ b/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java @@ -3,6 +3,9 @@ package org.tests.lazyforeignkeys; import io.ebean.annotation.DbForeignKey; import javax.persistence.*; + +import org.tests.model.basic.Cat; + import java.util.UUID; @@ -22,6 +25,11 @@ public class MainEntityRelation { @JoinColumn(name = "id2") @DbForeignKey(noConstraint = true) private MainEntity entity2; + + @ManyToOne + @JoinColumn(name = "cat_id") + @DbForeignKey(noConstraint = true) + private Cat cat; private String attr1; @@ -41,6 +49,14 @@ public class MainEntityRelation { this.entity2 = entity2; } + public Cat getCat() { + return cat; + } + + public void setCat(Cat cat) { + this.cat = cat; + } + public String getAttr1() { return attr1; } diff --git a/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java b/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java index 9d88e84ed..7fb3fcc05 100644 --- a/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java +++ b/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java @@ -8,6 +8,7 @@ import io.ebean.text.PathProperties; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.tests.model.basic.Cat; import java.util.List; @@ -56,7 +57,7 @@ public class TestLazyForeignKeys extends BaseTestCase { List sql = LoggedSql.stop(); assertThat(sql).hasSize(3); - assertSql(sql.get(0)).contains("select t0.id, t0.attr1, t0.id1, t0.id2 from main_entity_relation"); + assertSql(sql.get(0)).contains("select t0.id, t0.attr1, t0.id1, t0.id2, t1.species, t0.cat_id from main_entity_relation t0 left join animal t1 on t1.id = t0.cat_id"); if (isSqlServer() || isOracle()) { assertSql(sql.get(1)).contains("select t0.id, t0.attr1, t0.attr2, CASE WHEN t0.id is null THEN 1 ELSE 0 END from main_entity t0"); } else { @@ -86,4 +87,17 @@ public class TestLazyForeignKeys extends BaseTestCase { assertFalse(rel1.getEntity1().isDeleted()); assertTrue(rel1.getEntity2().isDeleted()); } + + @Test + public void testGetWithDbForeignKey() { + MainEntityRelation relation = DB.find(MainEntityRelation.class).findOne(); + Cat cat = new Cat(); + cat.setId(123L); + relation.setCat(cat); + DB.save(relation); + + relation = DB.find(MainEntityRelation.class).findOne(); + + assertNotNull(relation.getCat()); + } } diff --git a/ebean-test/src/test/java/org/tests/model/array/TestDbArray_basic.java b/ebean-test/src/test/java/org/tests/model/array/TestDbArray_basic.java index 69328ce15..0a4b1a0fb 100644 --- a/ebean-test/src/test/java/org/tests/model/array/TestDbArray_basic.java +++ b/ebean-test/src/test/java/org/tests/model/array/TestDbArray_basic.java @@ -1,9 +1,6 @@ package org.tests.model.array; -import io.ebean.BaseTestCase; -import io.ebean.DB; -import io.ebean.Query; -import io.ebean.SqlRow; +import io.ebean.*; import io.ebean.annotation.ForPlatform; import io.ebean.annotation.IgnorePlatform; import io.ebean.annotation.Platform; @@ -272,4 +269,123 @@ public class TestDbArray_basic extends BaseTestCase { assertThat(found.getUids()).hasSameElementsAs(uids); assertThat(found.getStatuses()).hasSameElementsAs(statuses); } + + @Test + @ForPlatform(Platform.POSTGRES) + public void asDto_withArray() { + DB.find(EArrayBean.class).delete(); + bean.setName("array in dto test"); + + List phNumbers = bean.getPhoneNumbers(); + phNumbers.add("4321"); + phNumbers.add("9823"); + List doubs = bean.getDoubs(); + doubs.add(1.23); + doubs.add(4.56); + DB.save(bean); + // Data is saved correctly + + LoggedSql.start(); + DtoQuery query = DB.find(EArrayBean.class) + // Interestingly writing `select("id,name,phone_numbers,doubs")` + // generates `select t0.id, t0.name, t0.id, t0.doubs` + // surprisingly changing unknown property to id + .select("id,name,phoneNumbers,doubs") + .asDto(EArrayBeanDto.class) + // Shouldn't be necessary + // But without it I see error + // Unable to map DB column phone_numbers to a property with a setter method on class org.tests.model.array.TestDbArray_basic$EArrayBeanDto + .setRelaxedMode(); + + List dtos = query.findList(); + + List sql = LoggedSql.stop(); + assertSql(sql.get(0)).contains("select t0.id, t0.name, t0.phone_numbers, t0.doubs"); + + for (EArrayBeanDto dto : dtos) { + assertThat(dto.id).isNotNull(); + assertThat(dto.name).isNotNull(); + // Failure: null + assertThat(dto.phoneNumbers).isNotNull(); + // Failure: null + assertThat(dto.doubs).isNotNull(); + } + } + + @Test + @ForPlatform(Platform.POSTGRES) + public void sqlUpdate_withArray() { + DB.find(EArrayBean.class).delete(); + bean.setName("array in sql update test"); + DB.save(bean); + // Data is saved correctly + List phNumbers = new ArrayList<>(); + phNumbers.add("4321"); + phNumbers.add("9823"); + + LoggedSql.start(); + + // Positional param works + SqlUpdate update1 = DB.sqlUpdate("UPDATE earray_bean SET phone_numbers = ?") + .setParameter(1, phNumbers); + update1.execute(); + System.out.println("done"); + + // Named param fails with + // javax.persistence.PersistenceException: + // ERROR: syntax error at or near "$2" + SqlUpdate update2 = DB.sqlUpdate("UPDATE earray_bean SET phone_numbers = :pns") + .setArrayParameter("pns", phNumbers); + update2.execute(); + + found = DB.find(EArrayBean.class, bean.getId()); + System.out.println(found); + assertEquals("array in sql update test", found.getName()); + assertThat(found.getPhoneNumbers()).containsExactly("4321", "9823"); + } + + public static class EArrayBeanDto { + + Integer id; + List phoneNumbers; + List doubs; + String name; + + @Override + public String toString() { + return "id:" + id + " name:" + name + " phoneNumbers:" + phoneNumbers + " doubs:" + doubs; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + public List getDoubs() { + return doubs; + } + + public void setDoubs(List doubs) { + this.doubs = doubs; + } + } } diff --git a/ebean-test/src/test/java/org/tests/model/bridge/TestIdClassScalar.java b/ebean-test/src/test/java/org/tests/model/bridge/TestIdClassScalar.java index 78952eb7a..2cedcc84d 100644 --- a/ebean-test/src/test/java/org/tests/model/bridge/TestIdClassScalar.java +++ b/ebean-test/src/test/java/org/tests/model/bridge/TestIdClassScalar.java @@ -6,18 +6,19 @@ import io.ebean.Transaction; import io.ebean.test.LoggedSql; import org.junit.jupiter.api.Test; +import java.util.Arrays; import java.util.List; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; -public class TestIdClassScalar extends BaseTestCase { +class TestIdClassScalar extends BaseTestCase { - private BUser user = new BUser("Fiona"); - private BSite site = new BSite("avaje.io"); + private final BUser user = new BUser("Fiona"); + private final BSite site = new BSite("avaje.io"); @Test - public void testBEmbId_equalsHashcode() { + void testBEmbId_equalsHashcode() { BEmbId a = new BEmbId(UUID.randomUUID(), UUID.randomUUID()); BEmbId b = new BEmbId(a.getSiteId(), a.getUserId()); @@ -35,13 +36,11 @@ public class TestIdClassScalar extends BaseTestCase { assertThat(a).isNotEqualTo(d); assertThat(a.hashCode()).isNotEqualTo(d.hashCode()); - assertThat(a.hashCode()).isEqualTo(a.otherHash()); - } @Test - public void fetchMany() { + void fetchMany() { UUID siteId = UUID.randomUUID(); UUID userId = UUID.randomUUID(); @@ -59,7 +58,7 @@ public class TestIdClassScalar extends BaseTestCase { } @Test - public void insertBatch() { + void insertBatch() { UUID siteId = UUID.randomUUID(); UUID userId = UUID.randomUUID(); @@ -81,11 +80,9 @@ public class TestIdClassScalar extends BaseTestCase { } @Test - public void test() { - + void test() { DB.save(user); DB.save(site); - insertUpdateBridgeD(user, site); insertUpdateBridgeE(user, site); } @@ -94,7 +91,6 @@ public class TestIdClassScalar extends BaseTestCase { * Test where matching by db column naming convention. */ private void insertUpdateBridgeD(BUser user, BSite site) { - LoggedSql.start(); BSiteUserD access = new BSiteUserD(BAccessLevel.ONE, site.id, user.id); @@ -139,7 +135,6 @@ public class TestIdClassScalar extends BaseTestCase { * Test where matching by db column naming convention. */ private void insertUpdateBridgeE(BUser user, BSite site) { - LoggedSql.start(); BSiteUserE access = new BSiteUserE(BAccessLevel.ONE, site, user); @@ -181,4 +176,47 @@ public class TestIdClassScalar extends BaseTestCase { assertSql(sql.get(1)).contains("update bsite_user_e set access_level=? where site_id=? and user_id=?"); } + + @Test + void idClass_existsQuery() { + BUser user = new BUser("JunkUser2"); + BSite site = new BSite("JunkSite2"); + BSiteUserE access = new BSiteUserE(BAccessLevel.ONE, site, user); + + DB.save(user); + DB.save(site); + DB.save(access); + + boolean exists = DB.find(BSiteUserE.class) + .where().eq("user.id", user.id) + .exists(); + + assertThat(exists).isTrue(); + assertThat(DB.find(BSiteUserE.class).where().eq("user.id", user.id).findCount()).isEqualTo(1); + + DB.delete(access); + DB.deleteAll(Arrays.asList(user, site)); + } + + @Test + void idClass_existsQuery_scalarImportedProperties() { + BUser user = new BUser("JunkUser3"); + BSite site = new BSite("JunkSite3"); + DB.save(user); + DB.save(site); + + BSiteUserD access = new BSiteUserD(BAccessLevel.ONE, site.id, user.id); + DB.save(access); + + boolean exists = DB.find(BSiteUserD.class) + .where().eq("userId", user.id) + .exists(); + + assertThat(exists).isTrue(); + assertThat(DB.find(BSiteUserD.class).where().eq("userId", user.id).findCount()).isEqualTo(1); + + DB.delete(access); + DB.deleteAll(Arrays.asList(user, site)); + } + } diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java index 3f3f8342d..fa99f45da 100644 --- a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasicSet.java @@ -10,11 +10,10 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; -public class TestElementCollectionBasicSet extends BaseTestCase { +class TestElementCollectionBasicSet extends BaseTestCase { @Test - public void test() { - + void test() { LoggedSql.start(); EcsPerson person = new EcsPerson("Fiona021"); @@ -172,8 +171,7 @@ public class TestElementCollectionBasicSet extends BaseTestCase { } @Test - public void json() { - + void json() { EcsPerson person = new EcsPerson("Fiona021"); person.getPhoneNumbers().add("021 1234"); person.getPhoneNumbers().add("021 4321"); diff --git a/ebean-test/src/test/java/org/tests/o2m/TestOneToManyEnhancement.java b/ebean-test/src/test/java/org/tests/o2m/TestOneToManyEnhancement.java new file mode 100644 index 000000000..be1936e6a --- /dev/null +++ b/ebean-test/src/test/java/org/tests/o2m/TestOneToManyEnhancement.java @@ -0,0 +1,29 @@ +package org.tests.o2m; + +import io.ebean.BaseTestCase; +import io.ebean.common.BeanList; +import org.junit.jupiter.api.Test; +import org.tests.model.json.EBasicJsonMap; +import org.tests.o2m.lazy.OmlBaz; +import org.tests.o2m.lazy.OmlFoo; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestOneToManyEnhancement extends BaseTestCase { + + @Test + void test_when_constructorAddsEntry() { + OmlFoo foo = new OmlFoo(new OmlBaz()); + + assertThat(foo.getBazList()).isInstanceOf(BeanList.class); + assertThat(foo.getBazList()).isNotEmpty(); + } + + @Test + void test_commonCase() { + EBasicJsonMap bean = new EBasicJsonMap(); + + assertThat(bean.getDetails()).isInstanceOf(BeanList.class); + assertThat(bean.getDetails()).isEmpty(); + } +} diff --git a/ebean-test/src/test/java/org/tests/o2m/lazy/OmlFoo.java b/ebean-test/src/test/java/org/tests/o2m/lazy/OmlFoo.java index 8195d1757..c460d5d50 100644 --- a/ebean-test/src/test/java/org/tests/o2m/lazy/OmlFoo.java +++ b/ebean-test/src/test/java/org/tests/o2m/lazy/OmlFoo.java @@ -15,7 +15,12 @@ public class OmlFoo { private OmlBar bar; @OneToMany(mappedBy = "foo", cascade = CascadeType.ALL) - private List bazList = new ArrayList(); + private List bazList = new ArrayList<>(); + + public OmlFoo(OmlBaz baz) { + bazList.add(baz); + baz.setFoo(this); + } public Long getId() { return id; diff --git a/ebean-test/src/test/java/org/tests/o2m/lazy/TestLazyManyViaPath.java b/ebean-test/src/test/java/org/tests/o2m/lazy/TestLazyManyViaPath.java index f6c41bebe..e94d21fa9 100644 --- a/ebean-test/src/test/java/org/tests/o2m/lazy/TestLazyManyViaPath.java +++ b/ebean-test/src/test/java/org/tests/o2m/lazy/TestLazyManyViaPath.java @@ -13,11 +13,9 @@ public class TestLazyManyViaPath extends BaseTestCase { @Test public void test() { - OmlBar bar = new OmlBar(); List fooList = new ArrayList<>(); fooList.add(createNewFooWithBar(bar)); - bar.setFooList(fooList); DB.save(bar); @@ -30,26 +28,24 @@ public class TestLazyManyViaPath extends BaseTestCase { // This works List foosList = fooFromDb.getBar().getFooList(); - assertThat(fooList.size()).isEqualTo(1); + assertThat(foosList).hasSize(1); + assertThat(fooList).hasSize(1); OmlBaz bazFromDb = DB .find(OmlBaz.class) .where() .eq("id", bar.getFooList().get(0).getBazList().get(0).getId()) .findOne(); + assert bazFromDb != null; // This does not work and gives the exception List foosList1 = bazFromDb.getFoo().getBar().getFooList(); assertThat(foosList1.size()).isEqualTo(1); - } private static OmlFoo createNewFooWithBar(OmlBar bar) { - OmlFoo foo = new OmlFoo(); + OmlFoo foo = new OmlFoo(new OmlBaz()); foo.setBar(bar); - OmlBaz baz = new OmlBaz(); - baz.setFoo(foo); - foo.getBazList().add(baz); return foo; } diff --git a/ebean-test/src/test/java/org/tests/query/TestOneToManyCorrectGrouping.java b/ebean-test/src/test/java/org/tests/query/TestOneToManyCorrectGrouping.java index 47db41e2c..50723dc9b 100644 --- a/ebean-test/src/test/java/org/tests/query/TestOneToManyCorrectGrouping.java +++ b/ebean-test/src/test/java/org/tests/query/TestOneToManyCorrectGrouping.java @@ -12,17 +12,16 @@ import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.assertEquals; -public class TestOneToManyCorrectGrouping extends BaseTestCase { +class TestOneToManyCorrectGrouping extends BaseTestCase { - public static final int EXPECTED_ITERATIONS = 2; + static final int EXPECTED_ITERATIONS = 4; @Test - public void test() { - + void test() { ResetBasicData.reset(); Query customerQuery = DB.find(Customer.class) .fetch("orders") - .where().le("id", 2) + .where().le("id", 4) .query(); final AtomicInteger count = new AtomicInteger(); diff --git a/ebean-test/src/test/java/org/tests/query/cache/Contract.java b/ebean-test/src/test/java/org/tests/query/cache/Contract.java index 344a09be6..71cfcb727 100644 --- a/ebean-test/src/test/java/org/tests/query/cache/Contract.java +++ b/ebean-test/src/test/java/org/tests/query/cache/Contract.java @@ -1,6 +1,5 @@ package org.tests.query.cache; -import javax.annotation.Nonnull; import javax.persistence.*; import java.util.ArrayList; import java.util.List; @@ -17,7 +16,6 @@ public class Contract { private List positions; @OneToMany(cascade = CascadeType.ALL, mappedBy = "container", fetch = FetchType.LAZY) - @Nonnull private List aclEntries = new ArrayList<>(); public Long getId() { diff --git a/ebean-test/src/test/java/org/tests/text/json/TestTextJsonReferenceBean.java b/ebean-test/src/test/java/org/tests/text/json/TestTextJsonReferenceBean.java index 0f0f66f02..95ce27683 100644 --- a/ebean-test/src/test/java/org/tests/text/json/TestTextJsonReferenceBean.java +++ b/ebean-test/src/test/java/org/tests/text/json/TestTextJsonReferenceBean.java @@ -47,15 +47,10 @@ public class TestTextJsonReferenceBean extends BaseTestCase { prodDesc.isReference(eb._ebean_getIntercept()); BeanState beanState = DB.beanState(refProd); - assertTrue(beanState.isNew()); + assertTrue(beanState.isReference()); String name = refProd.getName(); - assertNull(name); - - // Set to be 'loaded' to invoke lazy loading - beanState.setLoaded(); - String name2 = refProd.getName(); - assertNotNull(name2); + assertNotNull(name); } List orders = DB.find(Order.class) diff --git a/ebean-test/src/test/resources/ebean.properties b/ebean-test/src/test/resources/ebean.properties index 0fc9586f9..6701fdf38 100644 --- a/ebean-test/src/test/resources/ebean.properties +++ b/ebean-test/src/test/resources/ebean.properties @@ -25,7 +25,7 @@ ebean.packages=org.tests,org.etest datasource.default=h2 #datasource.h2.capturestacktrace=true -ebean.dumpMetricsOnShutdown=true +#ebean.dumpMetricsOnShutdown=true ebean.dumpMetricsOptions=sql,hash,loc #ebean.queryPlan.thresholdMicros=1000 ebean.queryPlan.enable=true diff --git a/ebean/pom.xml b/ebean/pom.xml index 7b1aa36d1..bf039e209 100644 --- a/ebean/pom.xml +++ b/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT ebean composite @@ -22,20 +22,20 @@ io.ebean ebean-api - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT io.ebean ebean-querybean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index 280f0c246..759567f09 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test @@ -64,7 +64,7 @@ io.ebean ebean-ddl-generator - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT test diff --git a/pom.xml b/pom.xml index 68653b957..6cf80ad6e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.ebean ebean-parent - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT pom ebean parent @@ -38,15 +38,15 @@ - true + false 2.12.1 1.2 1.1 12.12.1 4.2 7.3 - 12.12.0 - 12.12.0 + 12.13.0 + 12.13.0 diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index eed542e93..d61f1d143 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.12.1-SNAPSHOT + 12.13.2-SNAPSHOT querybean generator diff --git a/tests/pom.xml b/tests/pom.xml index a2b7c3faa..9edd759f2 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -15,6 +15,10 @@ tests test modules + + true + + diff --git a/tests/test-java16/pom.xml b/tests/test-java16/pom.xml index d8500179f..4a416d12e 100644 --- a/tests/test-java16/pom.xml +++ b/tests/test-java16/pom.xml @@ -13,13 +13,14 @@ 3.8.1 + true io.ebean ebean - 12.12.0-SNAPSHOT + 12.13.0-SNAPSHOT @@ -31,7 +32,7 @@ io.ebean ebean-test - 12.12.0-SNAPSHOT + 12.13.0-SNAPSHOT test @@ -57,7 +58,7 @@ io.ebean querybean-generator - 12.12.0-SNAPSHOT + 12.13.0-SNAPSHOT diff --git a/tests/test-kotlin/pom.xml b/tests/test-kotlin/pom.xml index 4d892af66..1321332b8 100644 --- a/tests/test-kotlin/pom.xml +++ b/tests/test-kotlin/pom.xml @@ -18,9 +18,10 @@ 11 11 - 12.12.0 + 12.13.0 1.5.31 2.12.1 + true @@ -34,14 +35,14 @@ io.ebean ebean-test - 12.12.0-SNAPSHOT + 12.13.0-SNAPSHOT test io.ebean ebean-core - 12.12.0-SNAPSHOT + 12.13.0-SNAPSHOT test diff --git a/tests/test-kotlin/src/test/kotlin/io/ebean/test/kotlin/BasicInsertQueryTest.kt b/tests/test-kotlin/src/test/kotlin/io/ebean/test/kotlin/BasicInsertQueryTest.kt new file mode 100644 index 000000000..550e4973c --- /dev/null +++ b/tests/test-kotlin/src/test/kotlin/io/ebean/test/kotlin/BasicInsertQueryTest.kt @@ -0,0 +1,32 @@ +package io.ebean.test.kotlin + +import io.ebean.DB +import org.assertj.core.api.Assertions.assertThat +import org.example.order.Customer +import org.junit.jupiter.api.Test + +class BasicInsertQueryTest { + + @Test + fun `insert find`() { + + val customer = Customer("HelloThere") + DB.save(customer) + + val found = DB.find(Customer::class.java) + .setId(customer.id) + .findOne() + + val name = found?.name + assertThat(name).isEqualTo("HelloThere") + assertThat(found).isNotNull + + val list = DB.find(Customer::class.java) + .findList() + + list.size + assertThat(list).isNotEmpty + + DB.delete(customer) + } +} diff --git a/tests/test-kotlin/src/test/kotlin/io/ebean/test/kotlin/OneToManyEnhancementTest.kt b/tests/test-kotlin/src/test/kotlin/io/ebean/test/kotlin/OneToManyEnhancementTest.kt new file mode 100644 index 000000000..e506115e2 --- /dev/null +++ b/tests/test-kotlin/src/test/kotlin/io/ebean/test/kotlin/OneToManyEnhancementTest.kt @@ -0,0 +1,18 @@ +package io.ebean.test.kotlin + +import io.ebean.common.BeanList +import org.assertj.core.api.Assertions.assertThat +import org.example.order.Customer +import org.example.order.Order +import org.junit.jupiter.api.Test + +class OneToManyEnhancementTest { + + @Test + fun `o2m is BeanCollection`() { + val customer = Customer("foo") + val order = Order(customer) + + assertThat(order.items).isInstanceOf(BeanList::class.java) + } +}