diff --git a/composites/ebean-clickhouse/pom.xml b/composites/ebean-clickhouse/pom.xml index 94595b91e..1327456b0 100644 --- a/composites/ebean-clickhouse/pom.xml +++ b/composites/ebean-clickhouse/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-clickhouse - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-cockroach/pom.xml b/composites/ebean-cockroach/pom.xml index e92e5e748..2bda7c244 100644 --- a/composites/ebean-cockroach/pom.xml +++ b/composites/ebean-cockroach/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-postgres - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-db2/pom.xml b/composites/ebean-db2/pom.xml index 61745fa3c..364bc8759 100644 --- a/composites/ebean-db2/pom.xml +++ b/composites/ebean-db2/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-db2 - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-h2/pom.xml b/composites/ebean-h2/pom.xml index 9dd44bce6..867d9aa58 100644 --- a/composites/ebean-h2/pom.xml +++ b/composites/ebean-h2/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-h2 - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-hana/pom.xml b/composites/ebean-hana/pom.xml index 6dbe8eb92..7b4880994 100644 --- a/composites/ebean-hana/pom.xml +++ b/composites/ebean-hana/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-hana - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-mariadb/pom.xml b/composites/ebean-mariadb/pom.xml index d1990a045..fc6c5925c 100644 --- a/composites/ebean-mariadb/pom.xml +++ b/composites/ebean-mariadb/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-mariadb - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-mysql/pom.xml b/composites/ebean-mysql/pom.xml index ce5c67eb6..602da3ceb 100644 --- a/composites/ebean-mysql/pom.xml +++ b/composites/ebean-mysql/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-mysql - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-nuodb/pom.xml b/composites/ebean-nuodb/pom.xml index eab241f55..a58897819 100644 --- a/composites/ebean-nuodb/pom.xml +++ b/composites/ebean-nuodb/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-nuodb - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-oracle/pom.xml b/composites/ebean-oracle/pom.xml index 8a46df534..dcf5b76a5 100644 --- a/composites/ebean-oracle/pom.xml +++ b/composites/ebean-oracle/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-oracle - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-postgis/pom.xml b/composites/ebean-postgis/pom.xml index bd48a49c4..98b667668 100644 --- a/composites/ebean-postgis/pom.xml +++ b/composites/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -22,13 +22,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -47,19 +47,19 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-postgres - 13.25.0-jakarta + 14.0.0 io.ebean ebean-postgis-types - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-postgres/pom.xml b/composites/ebean-postgres/pom.xml index f24c9c9b0..6e23f7650 100644 --- a/composites/ebean-postgres/pom.xml +++ b/composites/ebean-postgres/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-postgres - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-sqlite/pom.xml b/composites/ebean-sqlite/pom.xml index 33625c085..ac7a96dea 100644 --- a/composites/ebean-sqlite/pom.xml +++ b/composites/ebean-sqlite/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-sqlite - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-sqlserver/pom.xml b/composites/ebean-sqlserver/pom.xml index 35bf6d25c..4cebb6dd0 100644 --- a/composites/ebean-sqlserver/pom.xml +++ b/composites/ebean-sqlserver/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-sqlserver - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean-yugabyte/pom.xml b/composites/ebean-yugabyte/pom.xml index c284138dd..8febd2908 100644 --- a/composites/ebean-yugabyte/pom.xml +++ b/composites/ebean-yugabyte/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -42,13 +42,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-postgres - 13.25.0-jakarta + 14.0.0 diff --git a/composites/ebean/pom.xml b/composites/ebean/pom.xml index 41e99c03d..e54b6ea9d 100644 --- a/composites/ebean/pom.xml +++ b/composites/ebean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ../.. @@ -17,13 +17,13 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 @@ -41,7 +41,7 @@ io.ebean ebean-jackson-mapper - 13.25.0-jakarta + 14.0.0 @@ -60,13 +60,13 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-platform-all - 13.25.0-jakarta + 14.0.0 diff --git a/composites/pom.xml b/composites/pom.xml index 7df080bbc..7fb663ed6 100644 --- a/composites/pom.xml +++ b/composites/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 composites diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 75e29cfaa..93960d07d 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean api @@ -39,7 +39,7 @@ io.avaje avaje-config - 3.9 + 3.10 diff --git a/ebean-api/src/main/java/io/ebean/DB.java b/ebean-api/src/main/java/io/ebean/DB.java index 5dfe1ed37..0ea78cab6 100644 --- a/ebean-api/src/main/java/io/ebean/DB.java +++ b/ebean-api/src/main/java/io/ebean/DB.java @@ -266,55 +266,6 @@ public final class DB { getDefault().register(transactionCallback); } - /** - * Deprecated for removal migrate using try-with-resources and commit on the transaction itself. - *

- * Commit the current transaction. - */ - @Deprecated(forRemoval = true) - public static void commitTransaction() { - getDefault().commitTransaction(); - } - - /** - * Deprecated for removal migrate to using try-with-resources and rollback on the transaction itself. - *

- * Rollback the current transaction. - */ - @Deprecated(forRemoval = true) - public static void rollbackTransaction() { - getDefault().rollbackTransaction(); - } - - /** - * If the current transaction has already been committed do nothing otherwise - * rollback the transaction. - *

- * It is preferable to use try with resources rather than this. - *

- * Useful to put in a finally block to ensure the transaction is ended, rather - * than a rollbackTransaction() in each catch block. - *

- * Code example: - * - *

{@code
-   *   DB.beginTransaction();
-   *   try {
-   *     // do some fetching and or persisting
-   *
-   *     // commit at the end
-   *     DB.commitTransaction();
-   *
-   *   } finally {
-   *     // if commit didn't occur then rollback the transaction
-   *     DB.endTransaction();
-   *   }
-   * }
- */ - public static void endTransaction() { - getDefault().endTransaction(); - } - /** * Mark the current transaction as rollback only. */ diff --git a/ebean-api/src/main/java/io/ebean/DInsertOptionsBuilder.java b/ebean-api/src/main/java/io/ebean/DInsertOptionsBuilder.java new file mode 100644 index 000000000..ffec19f91 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/DInsertOptionsBuilder.java @@ -0,0 +1,104 @@ +package io.ebean; + +final class DInsertOptionsBuilder implements InsertOptions.Builder { + + private Boolean getGeneratedKeys; + private boolean onConflictUpdate; + private boolean onConflictNothing; + private String constraint; + private String uniqueColumns; + private String updateSet; + + @Override + public InsertOptions.Builder onConflictNothing() { + this.onConflictNothing = true; + return this; + } + + @Override + public InsertOptions.Builder onConflictUpdate() { + this.onConflictUpdate = true; + return this; + } + + @Override + public InsertOptions.Builder constraint(String constraint) { + this.constraint = constraint; + return this; + } + + @Override + public InsertOptions.Builder uniqueColumns(String uniqueColumns) { + this.uniqueColumns = uniqueColumns; + return this; + } + + @Override + public InsertOptions.Builder updateSet(String updateSet) { + this.updateSet = updateSet; + return this; + } + + @Override + public InsertOptions.Builder getGeneratedKeys(boolean getGeneratedKeys) { + this.getGeneratedKeys = getGeneratedKeys; + return this; + } + + @Override + public InsertOptions build() { + return new Options(constraint, uniqueColumns, updateSet, onConflictUpdate, onConflictNothing, getGeneratedKeys); + } + + static final class Options implements InsertOptions { + + private static final String UPDATE = "U"; + private static final String NOTHING = "N"; + private static final String NORMAL = "_"; + private final String key; + private final Boolean getGeneratedKeys; + private final String constraint; + private final String uniqueColumns; + private final String updateSet; + + Options(String constraint, String uniqueColumns, String updateSet, boolean onConflictUpdate, boolean onConflictNothing, Boolean getGeneratedKeys) { + this.constraint = constraint; + this.uniqueColumns = uniqueColumns; + this.updateSet = updateSet; + this.getGeneratedKeys = getGeneratedKeys; + this.key = (onConflictUpdate ? UPDATE : onConflictNothing ? NOTHING : NORMAL) + + '+' + plus(constraint) + + '+' + plus(uniqueColumns) + + '+' + plus(updateSet); + } + + private String plus(String val) { + return val == null ? "" : val; + } + + @Override + public String key() { + return key; + } + + @Override + public String constraint() { + return constraint; + } + + @Override + public String uniqueColumns() { + return uniqueColumns; + } + + @Override + public String updateSet() { + return updateSet; + } + + @Override + public Boolean getGetGeneratedKeys() { + return getGeneratedKeys; + } + } +} diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java index 88ce8c202..bb9ab9b72 100644 --- a/ebean-api/src/main/java/io/ebean/Database.java +++ b/ebean-api/src/main/java/io/ebean/Database.java @@ -607,49 +607,6 @@ public interface Database { */ void flush(); - /** - * Deprecated for removal migrate using try-with-resources and commit on the transaction itself. - *

- * Commit the current transaction. - */ - @Deprecated(forRemoval = true) - void commitTransaction(); - - /** - * Deprecated for removal migrate to using try-with-resources and rollback on the transaction itself. - *

- * Rollback the current transaction. - */ - @Deprecated(forRemoval = true) - void rollbackTransaction(); - - /** - * If the current transaction has already been committed do nothing otherwise - * rollback the transaction. - *

- * Useful to put in a finally block to ensure the transaction is ended, rather - * than a rollbackTransaction() in each catch block. - *

- * Code example: - *

- *

{@code
-   *
-   *   database.beginTransaction();
-   *   try {
-   *     // do some fetching and or persisting ...
-   *
-   *     // commit at the end
-   *     database.commitTransaction();
-   *
-   *   } finally {
-   *     // if commit didn't occur then rollback the transaction
-   *     database.endTransaction();
-   *   }
-   *
-   * }
- */ - void endTransaction(); - /** * Refresh the values of a bean. *

@@ -1168,22 +1125,53 @@ public interface Database { */ void insert(Object bean); + /** + * Insert the bean with options (ON CONFLICT DO UPDATE | DO NOTHING). + *

+ * Currently, this is limited to use with Postgres only, + *

+ * When using this ebean will look to determine the unique columns by looking at + * the mapping like {@code @Column(unique=true} and {@code @Index(unique=true}. + */ + void insert(Object bean, InsertOptions insertOptions); + /** * Insert the bean with a transaction. */ void insert(Object bean, Transaction transaction); + /** + * Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction. + *

+ * Currently, this is limited to use with Postgres only, + */ + void insert(Object bean, InsertOptions insertOptions, Transaction transaction); + /** * Insert a collection of beans. If there is no current transaction one is created and used to * insert all the beans in the collection. */ void insertAll(Collection beans); + /** + * Insert the beans with options - typically ON CONFLICT DO UPDATE | DO NOTHING. + *

+ * Currently, this is limited to use with Postgres only, + */ + void insertAll(Collection beans, InsertOptions options); + /** * Insert a collection of beans with an explicit transaction. */ void insertAll(Collection beans, Transaction transaction); + /** + * Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction. + *

+ * Currently, this is limited to use with Postgres only, + */ + void insertAll(Collection beans, InsertOptions options, Transaction transaction); + /** * Execute explicitly passing a transaction. */ diff --git a/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java b/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java index 19144b00f..80ba130b1 100644 --- a/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java +++ b/ebean-api/src/main/java/io/ebean/DatabaseBuilder.java @@ -1009,6 +1009,14 @@ public interface DatabaseBuilder { @Deprecated DatabaseBuilder setSkipDataSourceCheck(boolean skipDataSourceCheck); + /** + * Set to true if this database is used in a read only way. + *

+ * The DataSource and read-only DataSource are expected to be the same + * and use readOnly=true and autoCommit=true. + */ + DatabaseBuilder readOnlyDatabase(boolean readOnlyDatabase); + /** * Set a DataSource. */ @@ -2543,6 +2551,14 @@ public interface DatabaseBuilder { */ boolean skipDataSourceCheck(); + /** + * Return true if this database is used in a read only way. + *

+ * The DataSource and read-only DataSource are expected to be the same + * and use readOnly=true and autoCommit=true. + */ + boolean readOnlyDatabase(); + /** * Return the DataSource. */ diff --git a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java index f887c1416..85dd2cbb6 100644 --- a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java +++ b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java @@ -3,10 +3,9 @@ package io.ebean; import io.ebean.config.ContainerConfig; import io.ebean.service.SpiContainer; import io.ebean.service.SpiContainerFactory; - import jakarta.persistence.PersistenceException; + import java.util.Iterator; -import java.util.Properties; import java.util.ServiceLoader; import java.util.concurrent.locks.ReentrantLock; @@ -146,12 +145,9 @@ public final class DatabaseFactory { if (container != null) { return container; } - if (containerConfig == null) { // effectively load configuration from ebean.properties - Properties properties = DbPrimary.getProperties(); containerConfig = new ContainerConfig(); - containerConfig.loadFromProperties(properties); } container = createContainer(containerConfig); return container; @@ -160,7 +156,7 @@ public final class DatabaseFactory { /** * Create the container instance using the configuration. */ - protected static SpiContainer createContainer(ContainerConfig containerConfig) { + private static SpiContainer createContainer(ContainerConfig containerConfig) { Iterator factories = ServiceLoader.load(SpiContainerFactory.class).iterator(); if (factories.hasNext()) { return factories.next().create(containerConfig); diff --git a/ebean-api/src/main/java/io/ebean/DbPrimary.java b/ebean-api/src/main/java/io/ebean/DbPrimary.java index ea1980de6..48b1d5f9c 100644 --- a/ebean-api/src/main/java/io/ebean/DbPrimary.java +++ b/ebean-api/src/main/java/io/ebean/DbPrimary.java @@ -44,25 +44,12 @@ final class DbPrimary { * Return the default database name. */ static String getDefaultServerName() { - lock.lock(); - try { - getProperties(); - return defaultServerName; - } finally { - lock.unlock(); - } - } - - /** - * Return the default configuration Properties. - */ - static Properties getProperties() { lock.lock(); try { if (defaultServerName == null) { defaultServerName = determineDefaultServerName(); } - return Config.asProperties(); + return defaultServerName; } finally { lock.unlock(); } diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index 5e5ab0870..899133b47 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -3,6 +3,8 @@ package io.ebean; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; +import javax.sql.DataSource; +import java.sql.Connection; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -206,14 +208,19 @@ public interface DtoQuery extends CancelableQuery { */ DtoQuery usingTransaction(Transaction transaction); + /** + * Execute the query using the given connection. + */ + DtoQuery usingConnection(Connection connection); + /** * Ensure that the master DataSource is used if there is a read only data source * being used (that is using a read replica database potentially with replication lag). *

* When the database is configured with a read-only DataSource via - * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then + * say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)} then * by default when a query is run without an active transaction, it uses the read-only data - * source. We we use {@code usingMaster()} to instead ensure that the query is executed + * source. We use {@code usingMaster()} to instead ensure that the query is executed * against the master data source. */ DtoQuery usingMaster(); diff --git a/ebean-api/src/main/java/io/ebean/InsertOptions.java b/ebean-api/src/main/java/io/ebean/InsertOptions.java new file mode 100644 index 000000000..ec0c5ab3a --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/InsertOptions.java @@ -0,0 +1,119 @@ +package io.ebean; + +import io.avaje.lang.Nullable; + +/** + * Options to be used with insert such as ON CONFLICT DO UPDATE | NOTHING. + */ +public interface InsertOptions { + + /** + * Use ON CONFLICT UPDATE with automatic determination of the unique columns to conflict on. + *

+ * Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} . + */ + InsertOptions ON_CONFLICT_UPDATE = InsertOptions.builder() + .onConflictUpdate() + .build(); + + /** + * Use ON CONFLICT DO NOTHING with automatic determination of the unique columns to conflict on. + *

+ * Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} . + */ + InsertOptions ON_CONFLICT_NOTHING = InsertOptions.builder() + .onConflictNothing() + .build(); + + /** + * Return a builder for InsertOptions. + */ + static Builder builder() { + return new DInsertOptionsBuilder(); + } + + /** + * Return the constraint name that is used for ON CONFLICT. + */ + @Nullable + String constraint(); + + /** + * Return the unique columns that is used for ON CONFLICT. + *

+ * When not explicitly set will use mapping like {@code @Column(unique=true)} to determine the + * non-unique columns. + */ + @Nullable + String uniqueColumns(); + + /** + * Return the ON CONFLICT UPDATE SET clause. + *

+ * When not set will use the non-unique columns. + */ + @Nullable + String updateSet(); + + /** + * Return if GetGeneratedKeys should be used to fetch the generated keys after insert. + */ + @Nullable + Boolean getGetGeneratedKeys(); + + /** + * Return the key for these build options. + */ + String key(); + + /** + * The builder for InsertOptions. + */ + interface Builder { + + /** + * Use a ON CONFLICT UPDATE automatically determining the unique columns. + */ + Builder onConflictUpdate(); + + /** + * Use a ON CONFLICT DO NOTHING automatically determining the unique columns. + */ + Builder onConflictNothing(); + + /** + * Specify an explicit conflict constraint name. + *

+ * When this is used then unique columns will not be used. + */ + Builder constraint(String constraint); + + /** + * Specify the unique columns for the conflict target. + *

+ * When not specified and constraint is also not specified then + * it will automatically determine the unique columns + * based on mapping like {@code @Column(unique=true)} and + * {@code @Index(unique=true)} . + */ + Builder uniqueColumns(String uniqueColumns); + + /** + * Specify the ON CONFLICT DO UPDATE SET clause. + *

+ * When not specified ebean will include all the non-unique columns. + */ + Builder updateSet(String updateSet); + + /** + * Specify if GetGeneratedKeys should be used to return generated keys. + */ + Builder getGeneratedKeys(boolean getGeneratedKeys); + + /** + * Build and return the insert options. + */ + InsertOptions build(); + + } +} diff --git a/ebean-api/src/main/java/io/ebean/Query.java b/ebean-api/src/main/java/io/ebean/Query.java index 7ac727331..b12dcd5ae 100644 --- a/ebean-api/src/main/java/io/ebean/Query.java +++ b/ebean-api/src/main/java/io/ebean/Query.java @@ -650,9 +650,9 @@ public interface Query extends CancelableQuery { * being used (that is using a read replica database potentially with replication lag). *

* When the database is configured with a read-only DataSource via - * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then + * say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)} then * by default when a query is run without an active transaction, it uses the read-only data - * source. We we use {@code usingMaster()} to instead ensure that the query is executed + * source. We use {@code usingMaster()} to instead ensure that the query is executed * against the master data source. */ Query usingMaster(); @@ -918,6 +918,25 @@ public interface Query extends CancelableQuery { @Nullable A findSingleAttribute(); + /** + * Execute the query returning a single optional attribute value. + *

+ *

Example

+ *
{@code
+   *
+   *  Optional maybeName =
+   *    new QCustomer()
+   *      .select(name)
+   *      .id.eq(42)
+   *      .status.eq(NEW)
+   *      .findSingleAttributeOrEmpty();
+   *
+   * }
+ * + * @return an optional value for the selected property + */ +
Optional findSingleAttributeOrEmpty(); + /** * Return true if this is countDistinct query. */ diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java index 2dd8c3d3c..f3e9f2f8e 100644 --- a/ebean-api/src/main/java/io/ebean/SqlQuery.java +++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java @@ -3,7 +3,9 @@ package io.ebean; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; +import javax.sql.DataSource; import java.io.Serializable; +import java.sql.Connection; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -45,14 +47,19 @@ public interface SqlQuery extends Serializable, CancelableQuery { */ SqlQuery usingTransaction(Transaction transaction); + /** + * Execute the query using the given connection. + */ + SqlQuery usingConnection(Connection connection); + /** * Ensure that the master DataSource is used if there is a read only data source * being used (that is using a read replica database potentially with replication lag). *

* When the database is configured with a read-only DataSource via - * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then + * say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)}then * by default when a query is run without an active transaction, it uses the read-only data - * source. We we use {@code usingMaster()} to instead ensure that the query is executed + * source. We use {@code usingMaster()} to instead ensure that the query is executed * against the master data source. */ SqlQuery usingMaster(); diff --git a/ebean-api/src/main/java/io/ebean/config/ContainerConfig.java b/ebean-api/src/main/java/io/ebean/config/ContainerConfig.java index 00cf8f8bf..20c05c27d 100644 --- a/ebean-api/src/main/java/io/ebean/config/ContainerConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/ContainerConfig.java @@ -1,5 +1,8 @@ package io.ebean.config; +import io.avaje.config.Config; +import io.avaje.config.Configuration; + import java.util.Properties; /** @@ -15,8 +18,17 @@ public class ContainerConfig { private String namespace; private String podName; private int port; - private Properties properties; + private Configuration configuration; + + public ContainerConfig() { + this.configuration = Config.asConfiguration(); + this.active = configuration.getBool("ebean.cluster.active", active); + this.serviceName = configuration.getNullable("ebean.cluster.serviceName", serviceName); + this.namespace = configuration.getNullable("ebean.cluster.namespace", namespace); + this.podName = configuration.getNullable("ebean.cluster.podName", podName); + this.port = configuration.getInt("ebean.cluster.port", 0); + } /** * Return the service name. @@ -92,7 +104,7 @@ public class ContainerConfig { * Return the deployment properties. */ public Properties getProperties() { - return properties; + return properties != null ? properties : configuration.asProperties(); } /** @@ -102,26 +114,4 @@ public class ContainerConfig { this.properties = properties; } - /** - * Load the settings from properties. - */ - public void loadFromProperties(Properties properties) { - this.properties = properties; - this.active = getProperty(properties, "ebean.cluster.active", active); - this.serviceName = properties.getProperty("ebean.cluster.serviceName", serviceName); - this.namespace = properties.getProperty("ebean.cluster.namespace", namespace); - this.podName = properties.getProperty("ebean.cluster.podName", podName); - String portParam = properties.getProperty("ebean.cluster.port"); - if (portParam != null) { - this.port = Integer.parseInt(portParam); - } - } - - /** - * Return the boolean property setting. - */ - protected boolean getProperty(Properties properties, String key, boolean defaultValue) { - return "true".equalsIgnoreCase(properties.getProperty(key, Boolean.toString(defaultValue))); - } - } 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 651f05b87..c689e7bda 100644 --- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java @@ -224,7 +224,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings { */ private PersistBatch persistBatchOnCascade = PersistBatch.INHERIT; - private int persistBatchSize = 20; + private int persistBatchSize = 100; private EnumType defaultEnumType = EnumType.ORDINAL; @@ -292,6 +292,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings { private boolean skipDataSourceCheck; + private boolean readOnlyDatabase; + /** * The data source (if programmatically provided). */ @@ -1276,7 +1278,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings { @Override public boolean skipDataSourceCheck() { - return skipDataSourceCheck; + return skipDataSourceCheck || readOnlyDatabase; } @Override @@ -1285,6 +1287,17 @@ public class DatabaseConfig implements DatabaseBuilder.Settings { return this; } + @Override + public DatabaseBuilder readOnlyDatabase(boolean readOnlyDatabase) { + this.readOnlyDatabase = readOnlyDatabase; + return this; + } + + @Override + public boolean readOnlyDatabase() { + return readOnlyDatabase; + } + @Override public DataSource getDataSource() { return dataSource; @@ -2045,6 +2058,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings { loadDataSourceSettings(p); defaultServer = p.getBoolean("defaultServer", defaultServer); + readOnlyDatabase = p.getBoolean("readOnlyDatabase", readOnlyDatabase); autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates); loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo); maxCallStack = p.getInt("maxCallStack", maxCallStack); diff --git a/ebean-api/src/test/java/io/ebean/DbPrimaryTest.java b/ebean-api/src/test/java/io/ebean/DbPrimaryTest.java index 38da9f413..418f57ecd 100644 --- a/ebean-api/src/test/java/io/ebean/DbPrimaryTest.java +++ b/ebean-api/src/test/java/io/ebean/DbPrimaryTest.java @@ -21,9 +21,4 @@ class DbPrimaryTest { assertEquals("h2", DbPrimary.getDefaultServerName()); } - @Test - void testLoadProperties() { - Properties properties = DbPrimary.getProperties(); - assertFalse(properties.isEmpty()); - } } diff --git a/ebean-api/src/test/java/io/ebean/config/DatabaseConfigTest.java b/ebean-api/src/test/java/io/ebean/config/DatabaseConfigTest.java index d816584b2..63bc0f2eb 100644 --- a/ebean-api/src/test/java/io/ebean/config/DatabaseConfigTest.java +++ b/ebean-api/src/test/java/io/ebean/config/DatabaseConfigTest.java @@ -74,6 +74,7 @@ class DatabaseConfigTest { props.setProperty("forUpdateNoKey", "true"); props.setProperty("defaultServer", "false"); props.setProperty("skipDataSourceCheck", "true"); + props.setProperty("readOnlyDatabase", "true"); props.setProperty("queryPlan.enable", "true"); props.setProperty("queryPlan.thresholdMicros", "10000"); @@ -93,6 +94,7 @@ class DatabaseConfigTest { assertTrue(settings.isAutoLoadModuleInfo()); assertTrue(settings.isLoadModuleInfo()); assertTrue(settings.skipDataSourceCheck()); + assertTrue(settings.readOnlyDatabase()); assertTrue(settings.isIdGeneratorAutomatic()); assertFalse(settings.getPlatformConfig().isCaseSensitiveCollation()); diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 76be7fa03..001501f40 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean bom @@ -89,25 +89,25 @@ io.ebean ebean - 13.25.0-jakarta + 14.0.0 io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core-type - 13.25.0-jakarta + 14.0.0 @@ -125,13 +125,13 @@ io.ebean ebean-jackson-mapper - 13.25.0-jakarta + 14.0.0 io.ebean ebean-ddl-generator - 13.25.0-jakarta + 14.0.0 @@ -155,37 +155,37 @@ io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 io.ebean querybean-generator - 13.25.0-jakarta + 14.0.0 io.ebean kotlin-querybean-generator - 13.25.0-jakarta + 14.0.0 io.ebean ebean-test - 13.25.0-jakarta + 14.0.0 io.ebean ebean-redis - 13.25.0-jakarta + 14.0.0 io.ebean ebean-spring-txn - 13.25.0-jakarta + 14.0.0 @@ -193,79 +193,79 @@ io.ebean ebean-clickhouse - 13.25.0-jakarta + 14.0.0 io.ebean ebean-db2 - 13.25.0-jakarta + 14.0.0 io.ebean ebean-h2 - 13.25.0-jakarta + 14.0.0 io.ebean ebean-hana - 13.25.0-jakarta + 14.0.0 io.ebean ebean-mariadb - 13.25.0-jakarta + 14.0.0 io.ebean ebean-mysql - 13.25.0-jakarta + 14.0.0 io.ebean ebean-nuodb - 13.25.0-jakarta + 14.0.0 io.ebean ebean-oracle - 13.25.0-jakarta + 14.0.0 io.ebean ebean-postgres - 13.25.0-jakarta + 14.0.0 io.ebean ebean-postgis - 13.25.0-jakarta + 14.0.0 io.ebean ebean-postgis-types - 13.25.0-jakarta + 14.0.0 io.ebean ebean-sqlite - 13.25.0-jakarta + 14.0.0 io.ebean ebean-sqlserver - 13.25.0-jakarta + 14.0.0 diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 15c4ace58..d239a9c26 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 0a5cde6e4..4c5d16349 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean-core @@ -22,7 +22,7 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 @@ -46,7 +46,7 @@ io.ebean ebean-core-type - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 provided @@ -62,7 +62,7 @@ io.ebean ebean-test - 13.25.0-jakarta + 14.0.0 test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index b6872d846..9e3c19402 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 provided @@ -63,14 +63,14 @@ io.ebean ebean-ddl-generator - 13.25.0-jakarta + 14.0.0 test io.ebean ebean-test - 13.25.0-jakarta + 14.0.0 test @@ -84,7 +84,7 @@ io.ebean querybean-generator - 13.25.0-jakarta + 14.0.0 provided diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java index 0514f7ada..14c850a10 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java @@ -304,6 +304,14 @@ public abstract class PBaseComparable extends PBaseValueEqual { return _root; } + /** + * Between - value between this property and another property + */ + public final R betweenProperties(Query.Property highProperty, T value) { + expr().betweenProperties(_name, highProperty.toString(), value); + return _root; + } + /** * Greater than. * diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java index a373afa75..c546dcbff 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java @@ -32,7 +32,7 @@ public abstract class PBaseString extends PBaseComparable { * @param value the equal to bind value * @return the root query bean instance */ - @Deprecated + @Deprecated(forRemoval = true) public final R equalToType(T value) { expr().eq(_name, value); return _root; @@ -46,7 +46,7 @@ public abstract class PBaseString extends PBaseComparable { * @param value the equal to bind value * @return the root query bean instance */ - @Deprecated + @Deprecated(forRemoval = true) public final R notEqualToType(T value) { expr().ne(_name, value); return _root; 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 2db53974f..7114b0b93 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java @@ -665,7 +665,7 @@ public abstract class TQRootBean { * Add EXISTS sub-query predicate. */ public R exists(Query subQuery) { - query.where().exists(subQuery); + peekExprList().exists(subQuery); return root; } @@ -673,7 +673,7 @@ public abstract class TQRootBean { * Add NOT EXISTS sub-query predicate. */ public R notExists(Query subQuery) { - query.where().notExists(subQuery); + peekExprList().notExists(subQuery); return root; } @@ -684,7 +684,7 @@ public abstract class TQRootBean { * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. */ public final R exists(String sqlSubQuery, Object... bindValues) { - query().where().exists(sqlSubQuery, bindValues); + peekExprList().exists(sqlSubQuery, bindValues); return root; } @@ -695,7 +695,7 @@ public abstract class TQRootBean { * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. */ public final R notExists(String sqlSubQuery, Object... bindValues) { - query().where().notExists(sqlSubQuery, bindValues); + peekExprList().notExists(sqlSubQuery, bindValues); return root; } @@ -1171,7 +1171,7 @@ public abstract class TQRootBean { /** * @deprecated migrate to {@link #orderBy()}. */ - @Deprecated(since = "13.19") + @Deprecated(since = "13.19", forRemoval = true) public R order() { return root; } @@ -1191,7 +1191,7 @@ public abstract class TQRootBean { /** * @deprecated migrate to {@link #orderBy(String)} */ - @Deprecated(since = "13.19") + @Deprecated(since = "13.19", forRemoval = true) public R order(String orderByClause) { return orderBy(orderByClause); } @@ -1644,6 +1644,28 @@ public abstract class TQRootBean { return query.findSingleAttribute(); } + /** + * Execute the query returning a single optional attribute value. + *

+ *

Example

+ *
{@code
+   *
+   *  Optional maybeName =
+   *    new QCustomer()
+   *      .select(name)
+   *      .id.eq(42)
+   *      .status.eq(NEW)
+   *      .findSingleAttributeOrEmpty();
+   *
+   * }
+ * + * @return an optional value for the selected property + */ + @Nullable + public
Optional findSingleAttributeOrEmpty() { + return query.findSingleAttributeOrEmpty(); + } + /** * Execute the query processing the beans one at a time. *

diff --git a/ebean-querybean/src/test/java/org/example/domain/Contact.java b/ebean-querybean/src/test/java/org/example/domain/Contact.java index 7d2554f57..505cb556a 100644 --- a/ebean-querybean/src/test/java/org/example/domain/Contact.java +++ b/ebean-querybean/src/test/java/org/example/domain/Contact.java @@ -1,8 +1,9 @@ package org.example.domain; import io.ebean.annotation.DbArray; - import jakarta.persistence.*; +import org.example.domain.finder.ContactFinder; + import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.time.ZonedDateTime; @@ -17,6 +18,11 @@ import java.util.Map; @Table(name = "be_contact") public class Contact extends BaseModel { + /** + * Convenience Finder for 'active record' style. + */ + public static final ContactFinder find = new ContactFinder(); + @DbArray List<@Size(max=20) String> phoneNumbers = new ArrayList<>(); diff --git a/ebean-querybean/src/test/java/org/example/domain/PFile.java b/ebean-querybean/src/test/java/org/example/domain/PFile.java new file mode 100644 index 000000000..63b5ef1bf --- /dev/null +++ b/ebean-querybean/src/test/java/org/example/domain/PFile.java @@ -0,0 +1,12 @@ +package org.example.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class PFile { + + @Id + long id; + String name; +} diff --git a/ebean-querybean/src/test/java/org/example/domain/PLong.java b/ebean-querybean/src/test/java/org/example/domain/PLong.java new file mode 100644 index 000000000..7df62ba00 --- /dev/null +++ b/ebean-querybean/src/test/java/org/example/domain/PLong.java @@ -0,0 +1,12 @@ +package org.example.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class PLong { + + @Id + long id; + String name; +} diff --git a/ebean-querybean/src/test/java/org/example/domain/Pinstant.java b/ebean-querybean/src/test/java/org/example/domain/Pinstant.java new file mode 100644 index 000000000..0f4b210fa --- /dev/null +++ b/ebean-querybean/src/test/java/org/example/domain/Pinstant.java @@ -0,0 +1,12 @@ +package org.example.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class Pinstant { + + @Id + long id; + String name; +} diff --git a/ebean-querybean/src/test/java/org/example/domain/finder/ContactFinder.java b/ebean-querybean/src/test/java/org/example/domain/finder/ContactFinder.java new file mode 100644 index 000000000..42f0daccb --- /dev/null +++ b/ebean-querybean/src/test/java/org/example/domain/finder/ContactFinder.java @@ -0,0 +1,18 @@ +package org.example.domain.finder; + +import io.ebean.Finder; +import org.example.domain.Contact; +import org.example.domain.query.QContact; + +/** + */ +public class ContactFinder extends Finder { + + public ContactFinder() { + super(Contact.class); + } + + public QContact typed() { + return new QContact(); + } +} diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java index b930c1509..7862e0dcd 100644 --- a/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java @@ -1,6 +1,8 @@ package org.querytest; +import org.example.domain.Contact; import org.example.domain.Customer; +import org.example.domain.query.QCustomer; import org.junit.jupiter.api.Test; import java.time.LocalDate; @@ -9,6 +11,8 @@ import java.time.ZoneId; import java.util.Date; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; + public class QCustomerAndOrTest { @Test @@ -55,6 +59,30 @@ public class QCustomerAndOrTest { } + @Test + public void testOrWithExists() { + QCustomer query = Customer.find.typed() + .alias("_cust") + .or() + .name.eq("Superman") + .exists(Contact.find.typed() + .alias("contact") + .firstName.eq("Superman") + .raw("contact.customer_id = _cust.id") + .query() + ) + .endOr() + .select(QCustomer.alias().id); + + query.findList(); + + assertThat(query.getGeneratedSql()).isEqualTo( + "select _cust.id from be_customer _cust where (" + + "_cust.name = ? or exists (select 1 from be_contact contact where " + + "contact.first_name = ? and contact.customer_id = _cust.id))" + ); + } + private Date fiveDaysAgo() { LocalDateTime fiveDaysAgo = LocalDate.now().atStartOfDay().minusDays(5); return new Date(fiveDaysAgo.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java index 72a82654b..de80d71b5 100644 --- a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java @@ -63,6 +63,25 @@ public class QCustomerTest { } + @Test + public void copy() { + var origin = new QCustomer() + .setDistinct(true) + .status.equalTo(Customer.Status.BAD); + + var copy1 = origin.copy().name.isNotNull(); + var q1 = copy1.query(); + copy1.findList(); + + assertThat(q1.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.name is not null"); + + var copy2 = origin.copy().version.ge(1L); + var q2 = copy2.query(); + copy2.findList(); + + assertThat(q2.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.version >= ?"); + } + @Test public void findSingleAttribute() { @@ -813,6 +832,30 @@ public class QCustomerTest { new QContact() .firstName.inRangeWith(lastName, "B") .findList(); + + new QContact() + .firstName.between("A", "B") + .findList(); + } + + @Test + void betweenProperties() { + var query = new QContact() + .firstName.betweenProperties(lastName, "B"); + + query.findList(); + assertThat(query.getGeneratedSql()).contains(" where ? between t0.first_name and t0.last_name"); + } + + @Test + void betweenProperties_notFirstPredicate() { + var query = new QContact() + .lastName.isNotNull() + .firstName.betweenProperties(lastName, "B") + .email.isNotNull(); + + query.findList(); + assertThat(query.getGeneratedSql()).contains(" where t0.last_name is not null and ? between t0.first_name and t0.last_name and t0.email is not null"); } @Test @@ -1026,6 +1069,32 @@ public class QCustomerTest { assertThat(maxDate).isNotNull(); } + @Test + public void findSingleAttributeOrEmpty() { + + Customer cust = new Customer(); + cust.setName("MaybeIExist yeah"); + cust.setStatus(Customer.Status.GOOD); + cust.setRegistered(new Date()); + cust.save(); + + Optional customerName = new QCustomer() + .select(name) + .status.eq(Customer.Status.GOOD) + .name.startsWith("MaybeIExist") + .findSingleAttributeOrEmpty(); + + assertThat(customerName).isPresent(); + + Optional customerName2 = new QCustomer() + .select(name) + .status.eq(Customer.Status.GOOD) + .name.eq("NahIDoNotExist") + .findSingleAttributeOrEmpty(); + + assertThat(customerName2).isEmpty(); + } + @Test public void testFetchByScalarValue() { diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index 7b54f4117..350068c70 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean-redis @@ -22,28 +22,28 @@ io.ebean ebean-api - 13.25.0-jakarta + 14.0.0 provided io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 provided io.ebean ebean-querybean - 13.25.0-jakarta + 14.0.0 test io.ebean ebean-test - 13.25.0-jakarta + 14.0.0 test @@ -57,7 +57,7 @@ io.ebean querybean-generator - 13.25.0-jakarta + 14.0.0 provided diff --git a/ebean-spring-txn/pom.xml b/ebean-spring-txn/pom.xml index 03694ce61..6d2d9a2c8 100644 --- a/ebean-spring-txn/pom.xml +++ b/ebean-spring-txn/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean-spring-txn @@ -28,7 +28,7 @@ io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 provided @@ -77,7 +77,7 @@ io.ebean ebean-test - 13.25.0-jakarta + 14.0.0 test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index e7d584120..6cf4553db 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.25.0-jakarta + 14.0.0 ebean test @@ -12,8 +12,8 @@ ebean-test - 1.14.9 - 3.24.2 + 1.14.12 + 3.25.3 @@ -33,20 +33,20 @@ io.ebean ebean-platform-h2 - 13.25.0-jakarta + 14.0.0 io.ebean ebean-core - 13.25.0-jakarta + 14.0.0 provided io.ebean ebean-ddl-generator - 13.25.0-jakarta + 14.0.0 @@ -149,14 +149,14 @@ io.ebean ebean-jackson-mapper - 13.25.0-jakarta + 14.0.0 test io.ebean ebean-platform-all - 13.25.0-jakarta + 14.0.0 test @@ -293,7 +293,7 @@ io.avaje junit - 1.3 + 1.4 provided diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/TransactionalTestCase.java b/ebean-test/src/test/java/io/ebean/xtest/base/TransactionalTestCase.java index 225888156..9e18e4b9d 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/base/TransactionalTestCase.java +++ b/ebean-test/src/test/java/io/ebean/xtest/base/TransactionalTestCase.java @@ -1,5 +1,6 @@ package io.ebean.xtest.base; +import io.ebean.Transaction; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import org.junit.jupiter.api.AfterEach; @@ -18,15 +19,17 @@ import org.tests.model.basic.ResetBasicData; */ public abstract class TransactionalTestCase extends BaseTestCase { + private Transaction transaction; + @BeforeEach public void startTransaction() { ResetBasicData.reset(); - DB.beginTransaction(); + transaction = DB.beginTransaction(); } @AfterEach public void endTransaction() { - DB.rollbackTransaction(); - DB.endTransaction(); + transaction.rollback(); + transaction.end(); } } diff --git a/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java b/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java index 69aea90ff..3285b60a8 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java @@ -3,6 +3,7 @@ package io.ebean.xtest.common; import io.ebean.bean.BeanCollection; import io.ebean.common.BeanList; import org.junit.jupiter.api.Test; +import org.tests.model.basic.Product; import java.util.ArrayList; import java.util.Comparator; @@ -13,22 +14,22 @@ import static org.assertj.core.api.Assertions.assertThat; public class BeanListTest { - private final Object object1 = new Object(); - private final Object object2 = new Object(); - private final Object object3 = new Object(); + private final Product object1 = new Product(1); + private final Product object2 = new Product(2); + private final Product object3 = new Product(3); private List all() { List all = new ArrayList<>(); - all.add(object1); - all.add(object2); - all.add(object3); + all.add(new Product(1)); + all.add(new Product(2)); + all.add(new Product(3)); return all; } private List some() { List some = new ArrayList<>(); - some.add(object2); - some.add(object3); + some.add(new Product(2)); + some.add(new Product(3)); return some; } @@ -163,14 +164,15 @@ public class BeanListTest { @Test public void testRemove_given_beansInAdditions() { + List all = all(); BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); - list.addAll(all()); + list.addAll(all); assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3); // act - list.remove(object2); - list.remove(object3); + list.remove(all.get(1)); + list.remove(all.get(2)); assertThat(list.modifyAdditions()).containsOnly(object1); assertThat(list.modifyRemovals()).isEmpty(); @@ -179,13 +181,14 @@ public class BeanListTest { @Test public void testRemoveAll_given_beansInAdditions() { + List all = all(); BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); - list.addAll(all()); + list.addAll(all); assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3); // act - list.removeAll(some()); + list.removeAll(List.of(all.get(1), all.get(2))); assertThat(list.modifyAdditions()).containsOnly(object1); assertThat(list.modifyRemovals()).isEmpty(); diff --git a/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java b/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java index 4b6a48c76..a72a17b2a 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java @@ -3,7 +3,7 @@ package io.ebean.xtest.common; import io.ebean.bean.BeanCollection; import io.ebean.common.BeanMap; import org.junit.jupiter.api.Test; -import org.tests.model.basic.EBasic; +import org.tests.model.basic.Product; import java.util.*; @@ -14,24 +14,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class BeanMapTest { - private final EBasic object1 = new EBasic("o1"); - private final EBasic object2 = new EBasic("o2"); - private final EBasic object3 = new EBasic("o3"); - private final EBasic object4 = new EBasic("o4"); - private final EBasic object5 = new EBasic("o5"); + private final Product object1 = new Product(1); + private final Product object2 = new Product(2); + private final Product object3 = new Product(3); + private final Product object4 = new Product(4); + private final Product object5 = new Product(5); private Map all() { Map all = new LinkedHashMap<>(); - all.put("1", object1); - all.put("2", object2); - all.put("3", object3); + all.put("1", new Product(1)); + all.put("2", new Product(2)); + all.put("3", new Product(3)); return all; } private Map some() { Map all = new LinkedHashMap<>(); - all.put("2", object2); - all.put("3", object3); + all.put("2", new Product(2)); + all.put("3", new Product(3)); return all; } @@ -73,8 +73,8 @@ public class BeanMapTest { @Test public void testAdd_given_someAlreadyIn() { - - BeanMap map = new BeanMap<>(some()); + Map some = new LinkedHashMap<>(Map.of("2", object2, "3", object3)); + BeanMap map = new BeanMap<>(some); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); // act @@ -90,11 +90,13 @@ public class BeanMapTest { @Test public void testAddSome_given_someAlreadyIn() { - BeanMap map = new BeanMap<>(some()); + Map some = new LinkedHashMap<>(Map.of("2", object2, "3", object3)); + BeanMap map = new BeanMap<>(some); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); // act - map.putAll(all()); + Map all = Map.of("1", object1, "2", object2, "3", object3); + map.putAll(all); assertThat(map.modifyAdditions()).containsOnly(object1); assertThat(map.modifyRemovals()).isEmpty(); @@ -179,7 +181,7 @@ public class BeanMapTest { @Test public void testClear_given_someBeansInAdditions() { - BeanMap map = newModifyListeningMap(); + BeanMap map = newModifyListeningMap(); map.put("2", object2); map.put("3", object3); @@ -193,7 +195,7 @@ public class BeanMapTest { @Test public void keySet_add_whenModifyListening() { - BeanMap map = newModifyListeningMap(); + BeanMap map = newModifyListeningMap(); assertThrows(UnsupportedOperationException.class, () -> map.keySet().add("3")); } @@ -205,7 +207,7 @@ public class BeanMapTest { @Test public void keySet_addAll_whenModifyListening() { - BeanMap map = newModifyListeningMap(); + BeanMap map = newModifyListeningMap(); assertThrows(UnsupportedOperationException.class, () -> map.keySet().addAll(asList("3", "4"))); } @@ -317,13 +319,13 @@ public class BeanMapTest { @Test public void values_add() { - BeanMap map = new BeanMap<>(); + BeanMap map = new BeanMap<>(); assertThrows(UnsupportedOperationException.class, () -> map.values().add(object3)); } @Test public void values_addAll() { - BeanMap map = new BeanMap<>(); + BeanMap map = new BeanMap<>(); assertThrows(UnsupportedOperationException.class, () -> map.values().addAll(asList(object3, object5))); } @@ -337,8 +339,8 @@ public class BeanMapTest { @Test public void entrySet_clear() { - final BeanMap map = newModifyListeningMap(); - final Set> entries = map.entrySet(); + final BeanMap map = newModifyListeningMap(); + final Set> entries = map.entrySet(); entries.clear(); assertThat(entries).isEmpty(); @@ -348,8 +350,8 @@ public class BeanMapTest { @Test public void entrySet_remove() { - final BeanMap map = newModifyListeningMap5(); - final Set> entries = map.entrySet(); + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); assertThat(map).hasSize(5); @@ -365,8 +367,8 @@ public class BeanMapTest { @Test public void entrySet_remove_whenNotEqualValue() { - final BeanMap map = newModifyListeningMap5(); - final Set> entries = map.entrySet(); + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); assertThat(map).hasSize(5); @@ -379,11 +381,11 @@ public class BeanMapTest { @Test public void entrySet_iterator_remove() { - final BeanMap map = newModifyListeningMap5(); - final Set> entries = map.entrySet(); - final Iterator> iterator = entries.iterator(); + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); + final Iterator> iterator = entries.iterator(); while (iterator.hasNext()) { - final Map.Entry entry = iterator.next(); + final Map.Entry entry = iterator.next(); if (entry.getKey().equals("2") || entry.getKey().equals("5")) { iterator.remove(); } @@ -395,8 +397,8 @@ public class BeanMapTest { @Test public void entrySet_removeAll() { - final BeanMap map = newModifyListeningMap5(); - final Set> entries = map.entrySet(); + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4))); assertThat(map).hasSize(3); @@ -406,8 +408,8 @@ public class BeanMapTest { @Test public void entrySet_retainAll() { - final BeanMap map = newModifyListeningMap5(); - final Set> entries = map.entrySet(); + final BeanMap map = newModifyListeningMap5(); + final Set> entries = map.entrySet(); entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4))); assertThat(map).hasSize(2); @@ -415,15 +417,15 @@ public class BeanMapTest { assertThat(map.modifyRemovals()).containsOnly(object2, object3, object5); } - private BeanMap newModifyListeningMap() { - BeanMap map = new BeanMap<>(); + private BeanMap newModifyListeningMap() { + BeanMap map = new BeanMap<>(); map.put("1", object1); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); return map; } - private BeanMap newModifyListeningMap5() { - BeanMap map = new BeanMap<>(); + private BeanMap newModifyListeningMap5() { + BeanMap map = new BeanMap<>(); map.put("1", object1); map.put("2", object2); map.put("3", object3); diff --git a/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java b/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java index 5053f71dc..2152af71b 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java @@ -3,6 +3,7 @@ package io.ebean.xtest.common; import io.ebean.bean.BeanCollection; import io.ebean.common.BeanSet; import org.junit.jupiter.api.Test; +import org.tests.model.basic.Product; import java.util.LinkedHashSet; import java.util.Set; @@ -12,22 +13,22 @@ import static org.assertj.core.api.Assertions.assertThat; public class BeanSetTest { - Object object1 = new Object(); - Object object2 = new Object(); - Object object3 = new Object(); + Product object1 = new Product(1); + Product object2 = new Product(2); + Product object3 = new Product(3); private Set all() { Set all = new LinkedHashSet<>(); - all.add(object1); - all.add(object2); - all.add(object3); + all.add(new Product(1)); + all.add(new Product(2)); + all.add(new Product(3)); return all; } private Set some() { Set some = new LinkedHashSet<>(); - some.add(object2); - some.add(object3); + some.add(new Product(2)); + some.add(new Product(3)); return some; } @@ -99,7 +100,9 @@ public class BeanSetTest { BeanSet set = new BeanSet<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); - set.addAll(all()); + + Set all = Set.of(object1, object2, object3); + set.addAll(all); assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3); // act @@ -115,11 +118,13 @@ public class BeanSetTest { BeanSet set = new BeanSet<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); - set.addAll(all()); + + Set all = Set.of(object1, object2, object3); + set.addAll(all); assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3); // act - set.removeAll(some()); + set.removeAll(Set.of(object2, object3)); assertThat(set.modifyAdditions()).containsOnly(object1); assertThat(set.modifyRemovals()).isEmpty(); diff --git a/ebean-test/src/test/java/io/ebean/xtest/config/ContainerConfigTest.java b/ebean-test/src/test/java/io/ebean/xtest/config/ContainerConfigTest.java index 2ae4f24e2..6fba07abe 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/config/ContainerConfigTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/config/ContainerConfigTest.java @@ -5,28 +5,19 @@ import org.junit.jupiter.api.Test; import java.util.Properties; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -public class ContainerConfigTest { +class ContainerConfigTest { @Test - public void loadFromProperties() { - - Properties p = new Properties(); - p.setProperty("ebean.cluster.active", "true"); - p.setProperty("ebean.cluster.serviceName", "a"); - p.setProperty("ebean.cluster.namespace", "b"); - p.setProperty("ebean.cluster.podName", "c"); - p.setProperty("ebean.cluster.port", "42"); - - + void loadFromProperties() { ContainerConfig containerConfig = new ContainerConfig(); - containerConfig.loadFromProperties(p); - assertEquals(true, containerConfig.isActive()); - assertEquals("a", containerConfig.getServiceName()); - assertEquals("b", containerConfig.getNamespace()); - assertEquals("c", containerConfig.getPodName()); - assertEquals(42, containerConfig.getPort()); + assertThat(containerConfig.isActive()).isFalse(); + assertThat(containerConfig.getServiceName()).isNull(); + assertThat(containerConfig.getNamespace()).isNull(); + assertThat(containerConfig.getPodName()).isNull(); + assertThat(containerConfig.getPort()).isEqualTo(0); } } diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java index 18af5d4c4..289360021 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java +++ b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiEbeanServer.java @@ -500,14 +500,6 @@ public class TDSpiEbeanServer extends TDSpiServer implements SpiEbeanServer { public void flush() { } - @Override - public void commitTransaction() { - } - - @Override - public void rollbackTransaction() { - } - @Override public void endTransaction() { } diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java index 5efc5458e..1a8bd2c9c 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java +++ b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java @@ -207,21 +207,6 @@ public class TDSpiServer implements SpiServer { } - @Override - public void commitTransaction() { - - } - - @Override - public void rollbackTransaction() { - - } - - @Override - public void endTransaction() { - - } - @Override public void refresh(Object bean) { @@ -438,21 +423,41 @@ public class TDSpiServer implements SpiServer { } + @Override + public void insert(Object bean, InsertOptions insertOptions) { + + } + @Override public void insert(Object bean, Transaction transaction) { } + @Override + public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) { + + } + @Override public void insertAll(Collection beans) { } + @Override + public void insertAll(Collection beans, InsertOptions options) { + + } + @Override public void insertAll(Collection beans, Transaction transaction) { } + @Override + public void insertAll(Collection beans, InsertOptions options, Transaction transaction) { + + } + @Override public int execute(SqlUpdate updSql, Transaction transaction) { return 0; diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/server/grammer/EqlParserTest.java b/ebean-test/src/test/java/io/ebean/xtest/internal/server/grammer/EqlParserTest.java new file mode 100644 index 000000000..e69de29bb diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/DefaultTransactionThreadLocalTest.java b/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/DefaultTransactionThreadLocalTest.java index 8c9455bcb..96a2353d8 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/DefaultTransactionThreadLocalTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/DefaultTransactionThreadLocalTest.java @@ -79,9 +79,7 @@ public class DefaultTransactionThreadLocalTest extends BaseTestCase { @ForPlatform({Platform.H2}) @Test public void end_withoutActiveTransaction_isFine() { - assertNull(DB.currentTransaction()); - DB.endTransaction(); } @ForPlatform({Platform.H2}) diff --git a/ebean-test/src/test/java/io/ebean/xtest/plugin/SpiServerTest.java b/ebean-test/src/test/java/io/ebean/xtest/plugin/SpiServerTest.java index e5669c2dc..7fdc4b859 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/plugin/SpiServerTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/plugin/SpiServerTest.java @@ -26,7 +26,7 @@ public class SpiServerTest extends BaseTestCase { BeanType beanType = pluginApi.beanType(Customer.class); assertEquals("o_customer", beanType.baseTable()); assertNotNull(pluginApi.databasePlatform()); - assertNull(beanType.findController()); + assertNotNull(beanType.findController()); assertNotNull(beanType.persistController()); assertNull(beanType.persistListener()); assertNull(beanType.queryAdapter()); diff --git a/ebean-test/src/test/java/org/tests/basic/TestDynamicUpdate.java b/ebean-test/src/test/java/org/tests/basic/TestDynamicUpdate.java index 1174f7a34..8675c2387 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestDynamicUpdate.java +++ b/ebean-test/src/test/java/org/tests/basic/TestDynamicUpdate.java @@ -1,5 +1,6 @@ package org.tests.basic; +import io.ebean.Transaction; import io.ebean.xtest.BaseTestCase; import io.ebean.BeanState; import io.ebean.DB; @@ -36,13 +37,11 @@ public class TestDynamicUpdate extends BaseTestCase { server.save(b2); - server.beginTransaction(); - try { + try (Transaction txn = server.beginTransaction()) { EMain b3 = server.find(EMain.class, b.getId()); assertEquals("ABC", b3.getEmbeddable().getDescription()); - } finally { - server.endTransaction(); } + EMain b4 = server.find(EMain.class, b.getId()); b4.setName("bbb"); b4.getEmbeddable().setDescription("123"); diff --git a/ebean-test/src/test/java/org/tests/basic/TestInheritRef.java b/ebean-test/src/test/java/org/tests/basic/TestInheritRef.java new file mode 100644 index 000000000..e69de29bb 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 a41255450..3bfa60b61 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java +++ b/ebean-test/src/test/java/org/tests/basic/TestPersistenceContext.java @@ -47,10 +47,9 @@ public class TestPersistenceContext extends BaseTestCase { // implicit transaction with its own persistence context Order oBefore = DB.find(Order.class, 1); // start a persistence context - DB.beginTransaction(); Order order; - try { + try (Transaction txn = DB.beginTransaction()) { order = DB.find(Order.class, 1); // not the same instance ...as a different persistence context assertNotSame(order, oBefore); @@ -63,9 +62,6 @@ public class TestPersistenceContext extends BaseTestCase { // all the same instance assertSame(order, o2); assertSame(order, o3); - - } finally { - DB.endTransaction(); } // implicit transaction with its own persistence context @@ -74,8 +70,7 @@ public class TestPersistenceContext extends BaseTestCase { assertNotSame(oAfter, order); // start a persistence context - DB.beginTransaction(); - try { + try (Transaction txn = DB.beginTransaction()) { Order testOrder = ResetBasicData.createOrderCustAndOrder("testPC"); Integer id = testOrder.getCustomer().getId(); Integer orderId = testOrder.getId(); @@ -94,9 +89,6 @@ public class TestPersistenceContext extends BaseTestCase { assertEquals(customer.getId(), customer2.getId()); assertSame(customer, customer2); - - } finally { - DB.endTransaction(); } } diff --git a/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java b/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java index 5b331a59b..75b0c3e6c 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java +++ b/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java @@ -27,14 +27,14 @@ public class TestQueryForUpdate extends BaseTestCase { try (final Transaction transaction = DB.beginTransaction()) { query = DB.find(Customer.class) .forUpdate() - .order().desc("id"); + .orderBy().desc("id"); query.findList(); } if (isSqlServer()) { assertThat(sqlOf(query)).contains("with (updlock)"); - } else if (!isDb2()){ + } else { assertThat(sqlOf(query)).contains("for update"); } } @@ -48,21 +48,21 @@ public class TestQueryForUpdate extends BaseTestCase { query = DB.find(Customer.class) .forUpdate() .setMaxRows(3) - .order().desc("id"); + .orderBy().desc("id"); query.findList(); } if (isSqlServer()) { assertThat(sqlOf(query)).contains("with (updlock)"); - } else if (!isOracle() && !isDb2()) { + } else if (!isOracle()) { // Oracle does not support FOR UPDATE with FETCH assertThat(sqlOf(query)).contains("for update"); } } @Test - @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB}) + @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2}) public void testForUpdate_when_alreadyInPCAsReference() { ResetBasicData.reset(); Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne(); @@ -96,7 +96,7 @@ public class TestQueryForUpdate extends BaseTestCase { } @Test - @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB}) + @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2}) public void testForUpdate_when_alreadyInPCAsReference_usingLock() { ResetBasicData.reset(); Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne(); @@ -131,7 +131,7 @@ public class TestQueryForUpdate extends BaseTestCase { } @Test - @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB}) + @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2}) public void testForUpdate_when_alreadyInPC() { EBasic basic = new EBasic("initialValue"); @@ -193,7 +193,7 @@ public class TestQueryForUpdate extends BaseTestCase { Query query = DB.find(Customer.class) .forUpdateNoWait() - .order().desc("id"); + .orderBy().desc("id"); query.findList(); if (isOracle()) { @@ -219,7 +219,7 @@ public class TestQueryForUpdate extends BaseTestCase { Query query = DB.find(Customer.class) .forUpdateNoWait() .setMaxRows(1) - .order().desc("id"); + .orderBy().desc("id"); List list = query.findList(); Customer first = list.get(0); diff --git a/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java b/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java index f1b30b586..039c6e617 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java +++ b/ebean-test/src/test/java/org/tests/basic/TestQueryUsingConnection.java @@ -73,9 +73,26 @@ public class TestQueryUsingConnection extends BaseTestCase { assertThat(count).isEqualTo(otherCount + 1); assertThat(otherCount).isEqualTo(masterCount); } - } + @IgnorePlatform({Platform.SQLSERVER, Platform.COCKROACH}) + @Test + public void dtoQueryUsingConnection() { + ResetBasicData.reset(); + + try (Transaction transaction = DB.createTransaction()) { + final CountryDto dto = DB.findDto(CountryDto.class, "select code, name from o_country where code=?") + .usingConnection(transaction.connection()) + .setParameter("NZ") + .findOne(); + + assertThat(dto).isNotNull(); + assertEquals("NZ", dto.code); + transaction.rollback(); + } + } + + public static class CountryDto { final String code; final String name; diff --git a/ebean-test/src/test/java/org/tests/basic/TestUpdate.java b/ebean-test/src/test/java/org/tests/basic/TestUpdate.java index d7973ca59..5f6e9aa43 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestUpdate.java +++ b/ebean-test/src/test/java/org/tests/basic/TestUpdate.java @@ -1,8 +1,8 @@ package org.tests.basic; -import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.Update; +import io.ebean.xtest.BaseTestCase; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,6 +10,7 @@ import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; import java.util.Arrays; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -37,13 +38,17 @@ public class TestUpdate extends BaseTestCase { @Test public void testNormal() { + String sillyContent = "a123456789b123456789c123456789d123456789e123456789"; 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(); + update + .setParameter("name", List.of("testUpdate" + i)) + .setParameter("smallnote", "Note #" + i + sillyContent) + .execute(); } Customer cust = DB.find(Customer.class).where().eq("name", "testUpdate3").findOne(); - assertThat(cust.getSmallnote()).isEqualTo("Note #3"); + assertThat(cust.getSmallnote()).isEqualTo("Note #3" + sillyContent); } @Test diff --git a/ebean-test/src/test/java/org/tests/cache/TestCacheInterceptSaveWhenLazyLoaded.java b/ebean-test/src/test/java/org/tests/cache/TestCacheInterceptSaveWhenLazyLoaded.java index b69276295..941aed9cb 100644 --- a/ebean-test/src/test/java/org/tests/cache/TestCacheInterceptSaveWhenLazyLoaded.java +++ b/ebean-test/src/test/java/org/tests/cache/TestCacheInterceptSaveWhenLazyLoaded.java @@ -1,5 +1,6 @@ package org.tests.cache; +import io.ebean.Transaction; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import org.junit.jupiter.api.Test; @@ -26,8 +27,7 @@ public class TestCacheInterceptSaveWhenLazyLoaded extends BaseTestCase { order.setCustomer(customer); DB.save(order); - DB.beginTransaction(); - try { + try (Transaction txn = DB.beginTransaction()) { Order foundOrder = DB.find(Order.class) .where().eq("id", order.getId()) @@ -47,9 +47,6 @@ public class TestCacheInterceptSaveWhenLazyLoaded extends BaseTestCase { assertSame(foundOrder, order1); assertTrue(DB.beanState(foundOrder).isDirty()); - - } finally { - DB.endTransaction(); } // cleanup diff --git a/ebean-test/src/test/java/org/tests/genkey/TestGeneratedKeys.java b/ebean-test/src/test/java/org/tests/genkey/TestGeneratedKeys.java index a663738c8..f8209e897 100644 --- a/ebean-test/src/test/java/org/tests/genkey/TestGeneratedKeys.java +++ b/ebean-test/src/test/java/org/tests/genkey/TestGeneratedKeys.java @@ -1,6 +1,7 @@ package org.tests.genkey; import io.ebean.DB; +import io.ebean.Transaction; import org.junit.jupiter.api.Test; import org.tests.model.basic.TOne; @@ -19,12 +20,10 @@ public class TestGeneratedKeys { c1.setName("Two"); c1.setDescription("Test Gen Key Two"); - DB.beginTransaction(); - try { + try (Transaction txn = DB.beginTransaction()) { DB.save(c); DB.save(c1); - } finally { - DB.commitTransaction(); + txn.commit(); } Integer id = c.getId(); assertNotNull(id); diff --git a/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java b/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java new file mode 100644 index 000000000..02b69639e --- /dev/null +++ b/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java @@ -0,0 +1,411 @@ +package org.tests.insert; + +import io.ebean.*; +import io.ebean.annotation.Platform; +import io.ebean.test.LoggedSql; +import io.ebean.xtest.BaseTestCase; +import io.ebean.xtest.ForPlatform; +import org.junit.jupiter.api.Test; +import org.tests.update.EPersonOnline; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import static io.ebean.InsertOptions.ON_CONFLICT_NOTHING; +import static io.ebean.InsertOptions.ON_CONFLICT_UPDATE; +import static org.assertj.core.api.Assertions.assertThat; + +class TestInsertOnConflict extends BaseTestCase { + + InsertOptions onConflictDoUpdateAndGetGeneratedKeys = InsertOptions.builder() + .onConflictUpdate() + .getGeneratedKeys(true) + .build(); + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertOnConflictUpdateExplicitTransaction() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a@b.com"); + + try (Transaction txn = DB.createTransaction()) { + db.insert(bean, ON_CONFLICT_UPDATE, txn); + txn.commit(); + } + assertThat(bean.getId()).isNotNull(); + + var bean2 = newBean("a@b.com"); + bean2.setOnlineStatus(false); + try (Transaction txn = DB.createTransaction()) { + db.insert(bean2, ON_CONFLICT_UPDATE, txn); + txn.commit(); + } + assertThat(bean2.getId()).isEqualTo(bean.getId()); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + + List list = db.find(EPersonOnline.class).findList(); + assertThat(list).hasSize(1); + assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated()); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertOnConflictUpdate_when_noIdValue() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a@b.com"); + db.insert(bean, onConflictDoUpdateAndGetGeneratedKeys); + assertThat(bean.getId()).isNotNull(); + + var bean2 = newBean("a@b.com"); + bean2.setOnlineStatus(false); + db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys); + assertThat(bean2.getId()).isEqualTo(bean.getId()); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + + List list = db.find(EPersonOnline.class).findList(); + + assertThat(list).hasSize(1); + assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated()); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertOnConflictUpdate_when_idValueSupplied() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a@b.com"); + bean.setId(40_042L); + db.insert(bean, ON_CONFLICT_UPDATE); + assertThat(bean.getId()).isNotNull(); + + var bean2 = newBean("a@b.com"); + bean2.setId(40_043L); // not expected but can be different + bean2.setOnlineStatus(false); + + db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + + List list = db.find(EPersonOnline.class).findList(); + + assertThat(list).hasSize(1); + assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated()); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertAll_onConflictUpdate_when_noIdValue() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a1@b.com"); + var bean2 = newBean("a2@b.com"); + var bean3 = newBean("a3@b.com"); + db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_UPDATE); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(5); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains(" -- bind"); + assertThat(sql.get(2)).contains(" -- bind"); + assertThat(sql.get(3)).contains(" -- bind"); + assertThat(sql.get(4)).contains(" -- executeBatch()"); + + var bean4 = newBean("a1@b.com"); + var bean5 = newBean("a5@b.com"); + db.insertAll(List.of(bean4, bean5), ON_CONFLICT_UPDATE); + + List list = db.find(EPersonOnline.class).orderBy("id").findList(); + assertThat(list).hasSize(4); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertAll_onConflictUpdate_explicitTransaction() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + try (Transaction txn = DB.createTransaction()) { + txn.setBatchSize(3); + var bean = newBean("a1@b.com"); + var bean2 = newBean("a2@b.com"); + var bean3 = newBean("a3@b.com"); + var bean4 = newBean("a4@b.com"); + db.insertAll(List.of(bean, bean2, bean3, bean4), ON_CONFLICT_UPDATE, txn); + txn.commit(); + } + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(8); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains(" -- bind"); + assertThat(sql.get(2)).contains(" -- bind"); + assertThat(sql.get(3)).contains(" -- bind"); + assertThat(sql.get(4)).contains(" -- executeBatch()"); + assertThat(sql.get(5)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(6)).contains(" -- bind"); + assertThat(sql.get(7)).contains(" -- executeBatch()"); + + List list = db.find(EPersonOnline.class).orderBy("id").findList(); + assertThat(list).hasSize(4); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void explicitConstraint() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a1@b.com"); + var bean2 = newBean("a2@b.com"); + var bean3 = newBean("a3@b.com"); + + var ON_CONFLICT_ = InsertOptions.builder() + .onConflictUpdate() + .constraint("uq_e_person_online_email") + .build(); + + db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(5); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict on constraint uq_e_person_online_email do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains(" -- bind"); + assertThat(sql.get(2)).contains(" -- bind"); + assertThat(sql.get(3)).contains(" -- bind"); + assertThat(sql.get(4)).contains(" -- executeBatch()"); + + List list = db.find(EPersonOnline.class).orderBy("id").findList(); + assertThat(list).hasSize(3); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void explicitUniqueColumns() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a1@b.com"); + var bean2 = newBean("a2@b.com"); + var bean3 = newBean("a3@b.com"); + + var ON_CONFLICT_ = InsertOptions.builder() + .onConflictUpdate() + .uniqueColumns(" email ") + .build(); + + db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(5); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict ( email ) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains(" -- bind"); + assertThat(sql.get(2)).contains(" -- bind"); + assertThat(sql.get(3)).contains(" -- bind"); + assertThat(sql.get(4)).contains(" -- executeBatch()"); + + List list = db.find(EPersonOnline.class).orderBy("id").findList(); + assertThat(list).hasSize(3); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void explicitUpdateSet() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a1@b.com"); + var bean2 = newBean("a2@b.com"); + + var ON_CONFLICT_ = InsertOptions.builder() + .onConflictUpdate() + .updateSet("when_updated=excluded.when_updated, online_status=true") + .build(); + + db.insertAll(List.of(bean, bean2), ON_CONFLICT_); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(4); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set when_updated=excluded.when_updated, online_status=true"); + assertThat(sql.get(1)).contains(" -- bind"); + assertThat(sql.get(2)).contains(" -- bind"); + assertThat(sql.get(3)).contains(" -- executeBatch()"); + + List list = db.find(EPersonOnline.class).orderBy("id").findList(); + assertThat(list).hasSize(2); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertOnConflictNothing_when_noIdValue() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a@b.com"); + db.insert(bean, ON_CONFLICT_NOTHING); + assertThat(bean.getId()).isNotNull(); + + var bean2 = newBean("a@b.com"); + bean2.setOnlineStatus(false); + db.insert(bean2, ON_CONFLICT_NOTHING); + assertThat(bean2.getId()).isNull(); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing"); + assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing"); + + List list = db.find(EPersonOnline.class).findList(); + + assertThat(list).hasSize(1); + assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean.getWhenUpdated()); + } + + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void updateQueryReturning() throws SQLException { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + + var bean1 = newBean("a1@bee.com"); + var bean2 = newBean("a2@cee.com"); + var bean3 = newBean("a3@bee.com"); + db.saveAll(bean1, bean2, bean3); + + String sql = "update e_person_online set email = concat('x',email) where email like ? returning id, email, online_status"; + + try (Transaction txn = db.createTransaction()) { + Connection connection = txn.connection(); + try (PreparedStatement pstmt = connection.prepareStatement(sql)) { + pstmt.setString(1, "%bee.com"); + try (ResultSet resultSet = pstmt.executeQuery()) { + while (resultSet.next()) { + long id = resultSet.getLong(1); + String email = resultSet.getString(2); + boolean status = resultSet.getBoolean(3); + // do something with the id, email and status + assertThat(id).isGreaterThan(0); + assertThat(email).startsWith("x"); + assertThat(status).isTrue(); + } + } + } + txn.commit(); + } + + try (Transaction txn = db.beginTransaction()) { + List sqlRowList = DB.sqlQuery(sql) + .setParameter("%bee.com") + .findList(); + + assertThat(sqlRowList).hasSize(2); + assertThat(sqlRowList.get(0).getString("email")).startsWith("xx"); + + txn.addModification("e_person_online", false, true, false); + txn.commit(); + } + + try (Transaction txn = db.beginTransaction()) { + List dtoList = db.findDto(ReturnDto.class, sql) + .setParameter("%bee.com") + .findList(); + + assertThat(dtoList).hasSize(2); + assertThat(dtoList.get(0).email).startsWith("xxx"); + + txn.addModification("e_person_online", false, true, false); + txn.commit(); + } + + try (Transaction txn = db.beginTransaction()) { + List dtoList2 = db.findDto(ReturnDto2.class, sql) + .setParameter("%bee.com") + .findList(); + + assertThat(dtoList2).hasSize(2); + assertThat(dtoList2.get(0).email).startsWith("xxxx"); + assertThat(dtoList2.get(0).id).isGreaterThan(0); + txn.addModification("e_person_online", false, true, false); + txn.commit(); + } + } + + public static class ReturnDto { + + private final long id; + private final String email; + private final boolean onlineStatus; + + public ReturnDto(long id, String email, boolean onlineStatus) { + this.id = id; + this.email = email; + this.onlineStatus = onlineStatus; + } + } + + public static class ReturnDto2 { + + private long id; + private String email; + private boolean onlineStatus; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public boolean isOnlineStatus() { + return onlineStatus; + } + + public void setOnlineStatus(boolean onlineStatus) { + this.onlineStatus = onlineStatus; + } + } + + private static EPersonOnline newBean(String email) { + EPersonOnline bean = new EPersonOnline(); + bean.setEmail(email); + bean.setOnlineStatus(true); + return bean; + } +} diff --git a/ebean-test/src/test/java/org/tests/iud/TestPersistCascade.java b/ebean-test/src/test/java/org/tests/iud/TestPersistCascade.java index 39136c284..d8d4424a9 100644 --- a/ebean-test/src/test/java/org/tests/iud/TestPersistCascade.java +++ b/ebean-test/src/test/java/org/tests/iud/TestPersistCascade.java @@ -35,11 +35,9 @@ public class TestPersistCascade extends BaseTestCase { assertThat(sql.get(23)).contains("insert into pcf_city"); assertSqlBind(sql, 24, 26); assertThat(sql.get(28)).contains("insert into pcf_event"); - assertSqlBind(sql, 29, 48); - assertThat(sql.get(50)).contains("insert into pcf_event"); - assertSqlBind(sql, 51, 70); - assertThat(sql.get(72)).contains("insert into pcf_event"); - assertSqlBind(sql, 73, 87); + assertSqlBind(sql, 29, 128); + assertThat(sql.get(130)).contains("insert into pcf_event"); + assertSqlBind(sql, 131, 150); } country.deletePermanent(); diff --git a/ebean-test/src/test/java/org/tests/level/test/ManyToManyTest.java b/ebean-test/src/test/java/org/tests/level/test/ManyToManyTest.java index 5973dcf7d..34a71b899 100644 --- a/ebean-test/src/test/java/org/tests/level/test/ManyToManyTest.java +++ b/ebean-test/src/test/java/org/tests/level/test/ManyToManyTest.java @@ -1,5 +1,6 @@ package org.tests.level.test; +import io.ebean.Transaction; import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import org.junit.jupiter.api.Test; @@ -19,8 +20,7 @@ public class ManyToManyTest extends BaseTestCase { @Test public void test() { - DB.beginTransaction(); - try { + try (Transaction txn = DB.beginTransaction()) { Level4 i = new Level4("i"); Level4 ii = new Level4("ii"); Level4 iii = new Level4("iii"); @@ -92,9 +92,6 @@ public class ManyToManyTest extends BaseTestCase { .findList(); validateObjectGraph(i, ii, iii, one, two, x1, x2, x3, x4, x5, things); - - } finally { - DB.endTransaction(); } } diff --git a/ebean-test/src/test/java/org/tests/model/array/EArrayBean.java b/ebean-test/src/test/java/org/tests/model/array/EArrayBean.java index 14cea519e..b24ebaa79 100644 --- a/ebean-test/src/test/java/org/tests/model/array/EArrayBean.java +++ b/ebean-test/src/test/java/org/tests/model/array/EArrayBean.java @@ -9,6 +9,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Version; import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -58,6 +60,12 @@ public class EArrayBean { @DbArray Set status2; + @DbArray + List times; + + @DbArray + List dates; + @Version Long version; @@ -165,6 +173,24 @@ public class EArrayBean { this.status2 = status2; } + public List getTimes() { + return times; + } + + public EArrayBean setTimes(List times) { + this.times = times; + return this; + } + + public List getDates() { + return dates; + } + + public EArrayBean setDates(List dates) { + this.dates = dates; + return this; + } + public Long getVersion() { return version; } 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 89cd68095..dcbbde859 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 @@ -10,6 +10,8 @@ import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.sql.SQLException; +import java.time.Instant; +import java.time.LocalDate; import java.util.*; import static org.assertj.core.api.Assertions.assertThat; @@ -58,6 +60,8 @@ class TestDbArray_basic extends BaseTestCase { bean.setStatus2(new LinkedHashSet<>()); bean.getStatus2().add(EArrayBean.Status.TWO); bean.getStatus2().add(EArrayBean.Status.ONE); + bean.setTimes(List.of(Instant.now(), Instant.now().minusSeconds(60))); + bean.setDates(List.of(LocalDate.now(), LocalDate.now().minusDays(1))); DB.save(bean); @@ -66,6 +70,8 @@ class TestDbArray_basic extends BaseTestCase { assertThat(found.getPhoneNumbers()).containsExactly("4321", "9823"); assertThat(found.getDoubs()).hasSize(2); assertThat(found.getFloats()).hasSize(2); + assertThat(found.getTimes()).hasSize(2); + assertThat(found.getDates()).hasSize(2); if (isPostgresCompatible()) { Query query = DB.find(EArrayBean.class) diff --git a/ebean-test/src/test/java/org/tests/model/basic/CustomerFindController.java b/ebean-test/src/test/java/org/tests/model/basic/CustomerFindController.java new file mode 100644 index 000000000..996c80d9f --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/basic/CustomerFindController.java @@ -0,0 +1,35 @@ +package org.tests.model.basic; + +import io.ebean.bean.BeanCollection; +import io.ebean.event.BeanFindController; +import io.ebean.event.BeanQueryRequest; + +/** + * @author Noemi Praml, FOCONIS AG + */ +public class CustomerFindController implements BeanFindController { + @Override + public boolean isRegisterFor(Class cls) { + return Customer.class.isAssignableFrom(cls); + } + + @Override + public boolean isInterceptFind(BeanQueryRequest request) { + return false; + } + + @Override + public T find(BeanQueryRequest request) { + return null; + } + + @Override + public boolean isInterceptFindMany(BeanQueryRequest request) { + return false; + } + + @Override + public BeanCollection findMany(BeanQueryRequest request) { + return null; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/basic/Product.java b/ebean-test/src/test/java/org/tests/model/basic/Product.java index df57dadd0..4f5557660 100644 --- a/ebean-test/src/test/java/org/tests/model/basic/Product.java +++ b/ebean-test/src/test/java/org/tests/model/basic/Product.java @@ -39,6 +39,13 @@ public class Product implements Serializable { @Version Timestamp updtime; + public Product(Integer id) { + this.id = id; + } + + public Product() { + } + /** * Return id. */ diff --git a/ebean-test/src/test/java/org/tests/model/basic/xtra/TestDeleteUnloadedChildren.java b/ebean-test/src/test/java/org/tests/model/basic/xtra/TestDeleteUnloadedChildren.java index 09c7de91e..467f2121e 100644 --- a/ebean-test/src/test/java/org/tests/model/basic/xtra/TestDeleteUnloadedChildren.java +++ b/ebean-test/src/test/java/org/tests/model/basic/xtra/TestDeleteUnloadedChildren.java @@ -54,7 +54,6 @@ public class TestDeleteUnloadedChildren extends BaseTestCase { @Test public void testCascadeDelete2() { - init(); try (Transaction txn = DB.beginTransaction()) { @@ -68,18 +67,14 @@ public class TestDeleteUnloadedChildren extends BaseTestCase { @Test public void testCascadeDelete3() { - init(); - DB.beginTransaction(); - try { + try (Transaction txn = DB.beginTransaction()) { EdExtendedParent extendedParent = DB.find(EdExtendedParent.class).where() .eq("name", "My second computer").findOne(); extendedParent.getChildren().size(); DB.delete(extendedParent); - DB.commitTransaction(); - } finally { - DB.endTransaction(); + txn.commit(); } } diff --git a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java index c4b07d3cd..7ed80c302 100644 --- a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java +++ b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java @@ -28,6 +28,11 @@ public class OmBeanListChild extends Model { public Long getId() { return id; } + + public OmBeanListChild setId(Long id) { + this.id = id; + return this; + } } diff --git a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java index 1aacb79d6..026199f70 100644 --- a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java +++ b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java @@ -2,10 +2,14 @@ package org.tests.model.orphanremoval; import io.ebean.Model; +import io.ebean.annotation.WhenCreated; +import io.ebean.annotation.WhenModified; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Version; + +import java.time.Instant; import java.util.List; import static jakarta.persistence.CascadeType.ALL; @@ -19,6 +23,13 @@ public class OmBeanListParent extends Model { @Version private long version; + private String name; + + @WhenCreated + private Instant whenCreated; + @WhenModified + private Instant whenModified; + @OneToMany(cascade = ALL, mappedBy = "parent", orphanRemoval = true) private List children; @@ -35,6 +46,42 @@ public class OmBeanListParent extends Model { this.children.clear(); this.children.addAll(children); } + + public void setChildren2(List children) { + this.children = children; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + public Instant getWhenModified() { + return whenModified; + } + + public void setWhenModified(Instant whenModified) { + this.whenModified = whenModified; + } + + public Instant getWhenCreated() { + return whenCreated; + } + + public void setWhenCreated(Instant whenCreated) { + this.whenCreated = whenCreated; + } } diff --git a/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java b/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java index 446fff4b2..d03575e8c 100644 --- a/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java +++ b/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java @@ -1,17 +1,20 @@ package org.tests.model.orphanremoval; +import io.ebean.DB; +import io.ebean.test.LoggedSql; import org.junit.jupiter.api.Test; import java.util.List; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -public class TestOrphanRemovalOverwrite { +class TestOrphanRemovalOverwrite { @Test - public void testOverwritingMapping() { + void testOverwritingMapping() { OmBeanListParent parent = new OmBeanListParent(); parent.save(); @@ -29,8 +32,45 @@ public class TestOrphanRemovalOverwrite { // Refreshing here generates new objects for the associated children that are referred to by the parent. parent.refresh(); - assertNotNull(childList.get(0).getId()); - assertEquals(childList.get(0).getId(), parent.getChildren().get(0).getId()); + OmBeanListChild child = childList.get(0); + assertNotNull(child.getId()); + + OmBeanListChild refreshedChild = parent.getChildren().get(0); + assertEquals(child.getId(), refreshedChild.getId()); + assertEquals(child, refreshedChild); assertEquals(childList, parent.getChildren()); } + + @Test + void clearAddAll() { + OmBeanListChild c1 = new OmBeanListChild("c1"); + OmBeanListChild c2 = new OmBeanListChild("c2"); + + OmBeanListParent parent = new OmBeanListParent(); + parent.getChildren().add(c1); + parent.getChildren().add(c2); + parent.save(); + + + OmBeanListChild c3 = new OmBeanListChild("c3"); + c3.setId(c1.getId()); + + OmBeanListParent p1 = DB.find(OmBeanListParent.class, parent.getId()); + List children = p1.getChildren(); + children.clear(); + children.add(c3); + + LoggedSql.start(); + DB.save(p1); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(7); + assertThat(sql.get(0)).contains("delete from om_bean_list_child where id=?"); + assertThat(sql.get(1)).contains(" -- bind"); + assertThat(sql.get(2)).contains(" -- bind"); + assertThat(sql.get(3)).contains(" -- executeBatch()"); + assertThat(sql.get(4)).contains(" insert into om_bean_list_child"); + assertThat(sql.get(5)).contains(" -- bind"); + assertThat(sql.get(6)).contains(" -- executeBatch()"); + } } diff --git a/ebean-test/src/test/java/org/tests/o2m/OneToManyListMarkAsDirtyTest.java b/ebean-test/src/test/java/org/tests/o2m/OneToManyListMarkAsDirtyTest.java new file mode 100644 index 000000000..ae358b85d --- /dev/null +++ b/ebean-test/src/test/java/org/tests/o2m/OneToManyListMarkAsDirtyTest.java @@ -0,0 +1,114 @@ +package org.tests.o2m; + +import io.ebean.DB; +import io.ebean.xtest.BaseTestCase; +import org.junit.jupiter.api.Test; +import org.tests.model.orphanremoval.OmBeanListChild; +import org.tests.model.orphanremoval.OmBeanListParent; +import java.time.Instant; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import static org.assertj.core.api.Assertions.assertThat; + +class OneToManyListMarkAsDirtyTest extends BaseTestCase { + @Test + void usingNewListWithNonDirtyParent_expect_orphanDeleted_works() { + // setup + var parent = new OmBeanListParent(); + var a_b = new OmBeanListChild("b"); + parent.getChildren().add(a_b); + DB.save(parent); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); + + // act + var secondParent = DB.find(OmBeanListParent.class, parent.getId()); + secondParent.setChildren2(new ArrayList<>()); //