diff --git a/.editorconfig b/.editorconfig index 837d975fa..18b710430 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,4 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true spaces_around_operators = true +max_line_length = 130 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..2e27fa4e0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +custom: https://ebean.io/sponsors diff --git a/.gitignore b/.gitignore index 0d96b2f95..1b88dd830 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ profiling/ *.ipr *.iws .idea/ +*uuid.state \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 372ace1ff..5fcaa8a7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,22 +3,34 @@ sudo: required language: java jdk: - oraclejdk8 + - openjdk11 git: depth: 3 addons: postgresql: "9.6" +# apt: +# packages: +# - mysql-server-5.7 +# - mysql-client-core-5.7 +# - mysql-client-5.7 services: + - docker - postgresql + - mysql before_script: - ./.travis/setup_database + - mysql -u root -e 'CREATE DATABASE test_ebean;' + - mysql -u root -e "CREATE USER 'test_ebean'@'localhost' IDENTIFIED BY 'test';" + - mysql -u root -e "GRANT ALL ON test_ebean.* TO 'test_ebean'@'localhost';" env: - EBEAN_DB=h2 - EBEAN_DB=pg +# - EBEAN_DB=mysql install: true diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 000000000..16fa6d698 --- /dev/null +++ b/CONFIGURATION.md @@ -0,0 +1,298 @@ +Overview of ebean.properties file +================================= + + +### DbMigration options + +You can set individual values for different platforms when generating migrations + +`dbmigration.platform..databaseSequenceBatchSize` +For DB's using sequences this is the number of sequence values prefetched. + +`dbmigration.platform..dbuuid` +Control, how UUID generation should work - it affects DDL which column type is generated. Possible values: +- BINARY enforces binary UUID +- VARCHAR enforces varchar UUID +- BINARY_OPTIMIZED enforces binary-optimized UUID (makes sense only with Type1 ID) +- AUTO_BINARY use binary when platform does not support native UUID +- AUTO_BINARY_OPTIMIZED use binary-optimized when platform does not support native UUID +- AUTO_VARCHAR (default) use varchar when platform does not support UUID + +`dbmigration.platform..uuidStoreAsBinary` +Same as setting dbuuid to BINARY + +`dbmigration.platform..geometrySRID` +The Geometry SRID value (default 4326). + +`dbmigration.platform..idType` +The ID type (IDENTITY, SEQUENCE, GENERATOR, EXTERNAL) + +`dbmigration.platform..mapping` +Adjust the mapping. For example `BOOLEAN=integer(32);BIT=tinyint(3)` + + +`ebean.migration.applyPrefix` +Set this to "V" to be compatible with FlywayDB. + +`ebean.migration.applySuffix` +Defaults to '.sql' + +`ebean.migration.dbusername` +DB user used to run the DB migration. + +`ebean.migration.dbpassword` +DB password used to run the DB migration. + +`ebean.migration.generate` +Set to true if the DB migration should be generated on server start. + +`ebean.migration.generatePendingDrop` +The version of a pending drop that should be generated as the next migration. + +`ebean.migration.includeGeneratedFileComment` +TODO + +`ebean.migration.metaTable` +For running migration the DB table that holds migration execution status. Default 'db_migration' + +`ebean.migration.migrationPath` +Resource path for the migration xml and sql. + +`ebean.migration.modelPath` +Subdirectory the model xml files go into. Default 'model' + +`ebean.migration.modelSuffix` +Suffix. Default '.model.xml' + +`ebean.migration.name` +Description text that can be appended to the version to become the ddl script file name + +`ebean.migration.patchInsertOn` +migration versions that should be added to history without running. + +`ebean.migration.patchResetChecksumOn` +migration versions that should have their checksum reset and not run. +Use this if you get a 'Checksum mismatch' error. + +`ebean.migration.placeholders` +A comma and equals delimited placeholders that are substituted in SQL scripts when running migration (used by DB Migration runner only). + +`ebean.migration.platform` +The database platform to generate migration DDL for. + +`ebean.migration.run` +Flag set to true means to run any outstanding migrations on startup. + +`ebean.migration.version` +The migration version name (typically FlywayDb compatible). Example: 1.1.1_2 + + +### Ebean UUID options + +`ebean.uuidVersion` +Controls, how the UUIDs are generated. Possible values +- VERSION4 (default) generate random V4 UUIDs, +- VERSION1 generate rfc4122 compliant Type 1 UUIDs (requires a state file) +- VERSION1RND generate fake Type 1 UUIDs + +Note, that V1 UUIDs in conjunction with AUTO_BINARY_OPTIMIZED will give you the best index performance, but you MUST understand how this works to avoid collisions. + +`ebean.uuidStateFile` +The state file that is Required to generate V1 UUIDs + + +### DocStoreConfig + +`ebean.docstore.active` +True when the Document store integration is active/on. + +`ebean.docstore.allowAllCertificates` +Set to true such that the client allows connections to invalid/self signed SSL certificates. + +`ebean.docstore.bulkBatchSize` +The default batch size to use for the Bulk API calls. + +`ebean.docstore.create` +When true the Document store should create any document indexes that don't already exist. + +`ebean.docstore.dropCreate` +When true the Document store should drop and re-create document indexes. + +`ebean.docstore.generateMapping` +Set to true means Ebean will generate mapping files on startup. + +`ebean.docstore.mappingPath` +Resource path for the Document store mapping files. + +`ebean.docstore.mappingSuffix` +Suffix used for mapping files. + +`ebean.docstore.password` + Password credential that be used for authentication to document store. + +`ebean.docstore.pathToResources` +Location of resources that mapping files are generated into. + +`ebean.docstore.persist` +The default mode used by indexes. `DEFAULT|IGNORE|QUEUE|UPDATE` + +`ebean.docstore.url` +The URL of the Document store server. For example: http://localhost:9200. + +`ebean.docstore.username` +Credential that be used for authentication to document store. + +### Ebean Autotune + +`ebean.autoTune.mode` +The autoTune mode `DEFAULT_OFF|DEFAULT_ON|DEFAULT_ONIFEMPTY` + +`ebean.autoTune.profiling` +TODO + +`ebean.autoTune.profilingBase` +TODO + +`ebean.autoTune.profilingFile` +TODO + +`ebean.autoTune.profilingRate` +TODO + +`ebean.autoTune.profilingUpdateFrequency` +TODO + +`ebean.autoTune.queryTuning` +TODO + +`ebean.autoTune.queryTuningAddVersion` +TODO + +`ebean.autoTune.queryTuningFile` +TODO + +`ebean.autoTune.skipGarbageCollectionOnShutdown` +TODO + +`ebean.autoTune.skipProfileReportingOnShutdown` +TODO + + +### Ebean options +`ebean.allQuotedIdentifiers` +Quote all identifiers + +`ebean.asOfSysPeriod` +Column used to support history and 'As of' queries. This column is a timestamp range or equivalent. + +`ebean.asOfViewSuffix` +Suffix appended to the base table to derive the view that contains the union of the base table and the history table in order to support asOf queries. + +`ebean.autoCommitMode` +Set to true if the DataSource uses autoCommit. Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager. + +`ebean.autoReadOnlyDataSource` +When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig + +`ebean.autostart` +Should the server start all + +`ebean.backgroundExecutorSchedulePoolSize` +TODO + +`ebean.backgroundExecutorShutdownSecs` +TODO + +and some more todos: +`ebean.batch.mode` +`ebean.batch.size` +`ebean.changeLogAsync` +`ebean.changeLogIncludeInserts` +`ebean.classes` +`ebean.collectQueryOrigins` +`ebean.collectQueryStatsByNode` +`ebean.currentUserProvider` +`ebean.dataSourceJndiName` +`ebean.dataTimeZone` +`ebean.databaseBooleanFalse` +`ebean.databaseBooleanTrue` +`ebean.databasePlatform` +`ebean.databasePlatformName` +`ebean.databaseSequenceBatchSize` +ebean.datasource.h2.adminpassword +ebean.datasource.h2.adminusername +`ebean.dbEncrypt` +`ebean.dbOffline` +ebean.ddl.createOnly +ebean.ddl.generate +ebean.ddl.header +ebean.ddl.initSql +ebean.ddl.run +ebean.ddl.seedSql +ebean.defaultDeleteMissingChildren +ebean.defaultOrderById +ebean.disableClasspathSearch +ebean.disableL2Cache +ebean.docStoreOnly +ebean.docstore.active +ebean.docstore.allowAllCertificates +ebean.docstore.bulkBatchSize +ebean.docstore.create +ebean.docstore.dropCreate +ebean.docstore.generateMapping +ebean.docstore.mappingPath +ebean.docstore.mappingSuffix +ebean.docstore.password +ebean.docstore.pathToResources +ebean.docstore.persist +ebean.docstore.url +ebean.docstore.username +ebean.encryptDeployManager +ebean.encryptKeyManager +ebean.encryptor +ebean.explicitTransactionBeginMode +ebean.expressionEqualsWithNullAsNoop +ebean.expressionNativeIlike +ebean.geometrySRID +ebean.historyTableSuffix +ebean.jdbcFetchSizeFindEach +ebean.jdbcFetchSizeFindList +ebean.jodaLocalTimeMode +ebean.jsonDateTime +ebean.jsonInclude +ebean.lazyLoadBatchSize +ebean.localTimeWithNanos +ebean.namingConvention.schema +ebean.namingConvention.sequenceFormat +ebean.namingConvention.useForeignKeyPrefix +ebean.namingconvention +ebean.notifyL2CacheInForeground +ebean.packages +ebean.persistBatch +ebean.persistBatchOnCascade +ebean.persistBatchSize +ebean.persistBatching +ebean.persistenceContextScope +ebean.profiling +ebean.profiling.directory +ebean.profiling.includeProfileIds +ebean.profiling.minimumMicros +ebean.profiling.profilesPerFile +ebean.profiling.verbose +ebean.queryBatchSize +ebean.queryPlanTTLSeconds +ebean.search.packages +ebean.serverCachePlugin +ebean.skipCacheAfterWrite +ebean.slowQueryMillis +ebean.tenant.catalogProvider +ebean.tenant.currentTenantProvider +ebean.tenant.mode +ebean.tenant.partitionColumn +ebean.tenant.schemaProvider +ebean.updateAllPropertiesInBatch +ebean.updateChangesOnly +ebean.updatesDeleteMissingChildren +ebean.useJavaxValidationNotNull +ebean.useJtaTransactionManager + diff --git a/README.md b/README.md index db3cf331e..85c3d36d2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,39 @@ +[![Build Status](https://travis-ci.org/ebean-orm/ebean.svg?branch=master)](https://travis-ci.org/ebean-orm/ebean) +[![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) + +# Sponsors + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ # Need help? Post questions or issues to the Ebean google group - https://groups.google.com/forum/#!forum/ebean # Documentation -Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website") +Goto [https://ebean.io/docs/](https://ebean.io/docs/) ## Maven cental links: @@ -10,8 +41,7 @@ Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Web [Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects") -## Current versions -* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean -* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent -* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin +## Other versions +* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent +* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin diff --git a/howto-test.md b/howto-test.md index 243ed5200..ed159cac2 100644 --- a/howto-test.md +++ b/howto-test.md @@ -48,7 +48,8 @@ Reqirements run: `mvn clean test -Ddatasource.default=pg` Current status: PASS -Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16 +Tests run: 2166, Failures: 0, Errors: 0, Skipped: 25 + 'mysql' platform @@ -61,8 +62,8 @@ Reqirements run: `mvn clean test -Ddatasource.default=mysql` -Current status: FAIL -Tests run: 1986, Failures: 2, Errors: 4, Skipped: 16 +Current status: PASS +Tests run: 2166, Failures: 0, Errors: 0, Skipped: 33 @@ -76,8 +77,8 @@ Reqires an installed sqlserver - e.g. https://hub.docker.com/r/microsoft/mssql-s run: `mvn clean test -Ddatasource.default=mssql` -Current status: FAIL -Tests run: 1986, Failures: 19, Errors: 43, Skipped: 16 +Current status: PASS +Tests run: 2166, Failures: 0, Errors: 0, Skipped: 39 @@ -107,7 +108,7 @@ Reqires a locally installed DB2 Express-C server. run: `mvn clean test -Ddatasource.default=db2 -Pdb2` Current status: FAIL -Tests run: 1986, Failures: 33, Errors: 26, Skipped: 16 +Tests run: 2166, Failures: 29, Errors: 58, Skipped: 30 'sqlite' platform @@ -150,3 +151,18 @@ In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains TODO: not yet tested + +'hana' platform +-------------------- + +Requires an installed SAP HANA server - e.g. https://store.docker.com/images/sap-hana-express-edition + +- Create a user `EBEAN_TEST` with password `Eb3an_test` in the tenant database `HXE` +- Adjust the connection string and/or username in `ebean.properties` + +run: `mvn clean test -Ddatasource.default=hana` + +Current status: PASS +Tests run: 2486, Failures: 0, Errors: 0, Skipped: 84 + + diff --git a/pom.xml b/pom.xml index f3e7f3cb5..2b1fc0bc7 100644 --- a/pom.xml +++ b/pom.xml @@ -9,12 +9,18 @@ io.ebean ebean - 11.10.3-SNAPSHOT + 11.44.2-SNAPSHOT jar ebean http://ebean-orm.github.io/ + + 2.9.9 + 2.9.9.2 + 1.21 + + scm:git:git@github.com:ebean-orm/ebean.git HEAD @@ -83,6 +89,15 @@ provided + + + org.yaml + snakeyaml + ${snakeyaml.version} + + com.fasterxml.jackson.core jackson-core - 2.9.0 + ${jackson-core.version} com.fasterxml.jackson.core jackson-databind - 2.9.0 + ${jackson-databind.version} provided @@ -174,34 +195,49 @@ provided + + + org.glassfish.jaxb + jaxb-runtime + 2.4.0-b180830.0438 + provided + + + + javax.annotation + javax.annotation-api + 1.3.2 + provided + + org.postgresql postgresql - 9.4.1212 + 42.2.2 provided - - - - - - + + com.nuodb.jdbc + nuodb-jdbc + 20.2.0 + test + - - - - + + + + - io.ebean - ebean-docker-run - 1.5.1 + io.ebean.test + ebean-test-docker + 2.10.3 test @@ -212,26 +248,12 @@ test - - org.avaje - avaje-agentloader - 2.1.2 - test - - - - io.ebean - ebean-agent - 11.9.1 - test - - com.h2database h2 - 1.4.196 + 1.4.197 provided @@ -252,15 +274,28 @@ com.microsoft.sqlserver mssql-jdbc - 6.1.3.jre8-preview + 7.2.2.jre8 test mysql mysql-connector-java - - 5.1.43 + 8.0.17 + test + + + + com.sap.cloud.db.jdbc + ngdbc + 2.3.48 + test + + + + org.mockito + mockito-core + 3.0.0 test @@ -295,7 +330,7 @@ io.ebean ebean-maven-plugin - 11.9.1 + 11.44.1 test @@ -313,9 +348,10 @@ org.apache.maven.plugins maven-surefire-plugin - 2.5 + 2.22.1 false + false false **/Test*.java @@ -328,6 +364,11 @@ datasource.default ${datasource.default} + + + dbClockDelta + ${dbClockDelta} + @@ -348,11 +389,11 @@ maven-javadoc-plugin 2.9.1 - Ebean 10 + Ebean 11 src/main/java/io/ebean/overview.html 1.8 org.avaje.doclet.PygmentsDoclet - io.ebeaninternal.*:com.avaje.ebean.util + io.ebeaninternal.*:io.ebeanservice:io.ebean.common:io.ebean.bean:io.ebean.service:io.ebean.metric:io.ebean.util:io.ebean.config.properties:io.ebean.config.dbplatform org.avaje pygments-doclet diff --git a/src/main/java/io/ebean/BackgroundExecutor.java b/src/main/java/io/ebean/BackgroundExecutor.java index b88f8b658..f6439773e 100644 --- a/src/main/java/io/ebean/BackgroundExecutor.java +++ b/src/main/java/io/ebean/BackgroundExecutor.java @@ -1,6 +1,8 @@ package io.ebean; +import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** @@ -37,4 +39,21 @@ public interface BackgroundExecutor { *

*/ void executePeriodically(Runnable r, long delay, TimeUnit unit); + + /** + * Schedules a Runnable for one-shot action that becomes enabled after the given delay. + * + * @return a ScheduledFuture representing pending completion of the task and + * whose get() method will return null upon completion + */ + ScheduledFuture schedule(Runnable r, long delay, TimeUnit unit); + + /** + * Schedules a Callable for one-shot action that becomes enabled after the given delay. + * + * @return a ScheduledFuture that can be used to extract result or cancel + */ + ScheduledFuture schedule(Callable c, long delay, TimeUnit unit); + + } diff --git a/src/main/java/io/ebean/BeanFinder.java b/src/main/java/io/ebean/BeanFinder.java new file mode 100644 index 000000000..2c8e04eec --- /dev/null +++ b/src/main/java/io/ebean/BeanFinder.java @@ -0,0 +1,164 @@ +package io.ebean; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; + +/** + * Provides finder functionality for use with "Dependency Injection style" use of Ebean. + *

+ * Note that typically users would extend BeanRepository rather than BeanFinder. + *

+ *
{@code
+ *
+ * public class CustomerFinder extends BeanFinder {
+ *
+ *   @Inject
+ *   public CustomerFinder(Database database) {
+ *     super(Customer.class, database);
+ *   }
+ *
+ *   // ... add customer specific finders
+ * }
+ *
+ * }
+ * + * @param The ID type + * @param The Bean type + */ +public abstract class BeanFinder { + + protected final Database server; + + protected final Class type; + + /** + * Create with the given bean type and Database instance. + * + * @param type The bean type + * @param server The Database instance typically created via Spring factory or equivalent. + */ + protected BeanFinder(Class type, Database server) { + this.type = type; + this.server = server; + } + + /** + * Return the Database to use. + */ + public Database db() { + return server; + } + + /** + * Return the current transaction. + */ + public Transaction currentTransaction() { + return db().currentTransaction(); + } + + /** + * Flush the JDBC batch on the current transaction. + */ + public void flush() { + db().flush(); + } + + /** + * Return typically a different Database to the default. + *

+ * This is equivalent to {@link DB#byName(String)} + * + * @param server The name of the Database. If this is null then the default Database is returned. + */ + public Database db(String server) { + return DB.byName(server); + } + + /** + * Creates an entity reference for this ID. + *

+ * Equivalent to {@link Database#getReference(Class, Object)} + */ + @Nonnull + public T ref(I id) { + return db().getReference(type, id); + } + + /** + * Retrieves an entity by ID. + */ + @Nullable + public T findById(I id) { + return db().find(type, id); + } + + /** + * Find an entity by ID returning an Optional. + */ + @Nullable + public Optional findByIdOrEmpty(I id) { + return db().find(type).setId(id).findOneOrEmpty(); + } + + /** + * Delete a bean by Id. + */ + public void deleteById(I id) { + db().delete(type, id); + } + + /** + * Retrieves all entities of the given type. + */ + @Nonnull + public List findAll() { + return query().findList(); + } + + /** + * Creates an update query. + * + *

{@code
+   *
+   *  int rows =
+   *      updateQuery()
+   *      .set("status", Customer.Status.ACTIVE)
+   *      .set("updtime", new Timestamp(System.currentTimeMillis()))
+   *      .where()
+   *        .gt("id", 1000)
+   *        .update();
+   *
+   * }
+ * + *

+ * Equivalent to {@link Database#update(Class)} + */ + protected UpdateQuery updateQuery() { + return db().update(type); + } + + /** + * Creates a query. + *

+ * Equivalent to {@link Database#find(Class)} + */ + protected Query query() { + return db().find(type); + } + + /** + * Creates a native sql query. + */ + protected Query nativeSql(String nativeSql) { + return db().findNative(type, nativeSql); + } + + /** + * Creates a query using the ORM query language. + */ + protected Query query(String ormQuery) { + return db().createQuery(type, ormQuery); + } +} diff --git a/src/main/java/io/ebean/BeanRepository.java b/src/main/java/io/ebean/BeanRepository.java new file mode 100644 index 000000000..b89a49b39 --- /dev/null +++ b/src/main/java/io/ebean/BeanRepository.java @@ -0,0 +1,218 @@ +package io.ebean; + +import io.ebean.bean.EntityBean; + +import java.util.Collection; + +/** + * Provides finder functionality for use with "Dependency Injection style" use of Ebean. + *

+ *

{@code
+ *
+ * @Repository
+ * public class CustomerRepository extends BeanRepository {
+ *
+ *   @Inject
+ *   public CustomerRepository(Database server) {
+ *     super(Customer.class, server);
+ *   }
+ *
+ *   // ... add customer specific finders and persist logic
+ *
+ *   public List findByName(String nameStart) {
+ *     return query().where()
+ *             .istartsWith("name", nameStart)
+ *             .findList();
+ *   }
+ *
+ * }
+ * }
+ * + * @param The ID type + * @param The Bean type + */ +public abstract class BeanRepository extends BeanFinder { + + /** + * Create with the given bean type and Database instance. + *

+ * Typically users would extend BeanRepository rather than BeanFinder. + *

+ *
{@code
+   *
+   *   @Inject
+   *   public CustomerRepository(Database server) {
+   *     super(Customer.class, server);
+   *   }
+   *
+   * }
+ * + * @param type The bean type + * @param server The Database instance typically created via Spring factory or equivalent + */ + protected BeanRepository(Class type, Database server) { + super(type, server); + } + + /** + * Marks the entity bean as dirty. + *

+ * This is used so that when a bean that is otherwise unmodified is updated the version + * property is updated. + *

+ * An unmodified bean that is saved or updated is normally skipped and this marks the bean as + * dirty so that it is not skipped. + *

+ *

{@code
+   *
+   * Customer customer = customerRepository.byId(id);
+   *
+   * // mark the bean as dirty so that a save() or update() will
+   * // increment the version property
+   *
+   * customerRepository.markAsDirty(customer);
+   * customerRepository.save(customer);
+   *
+   * }
+ * + * @see Database#markAsDirty(Object) + */ + public void markAsDirty(T bean) { + db().markAsDirty(bean); + } + + /** + * Mark the property as unset or 'not loaded'. + *

+ * This would be used to specify a property that we did not wish to include in a stateless update. + *

+ *
{@code
+   *
+   *   // populate an entity bean from JSON or whatever
+   *   Customer customer = ...;
+   *
+   *   // mark the email property as 'unset' so that it is not
+   *   // included in a 'stateless update'
+   *   customerRepository.markPropertyUnset(customer, "email");
+   *
+   *   customerRepository.update(customer);
+   *
+   * }
+ * + * @param propertyName the name of the property on the bean to be marked as 'unset' + */ + public void markPropertyUnset(T bean, String propertyName) { + ((EntityBean) bean)._ebean_getIntercept().setPropertyLoaded(propertyName, false); + } + + /** + * Insert or update this entity depending on its state. + *

+ * Ebean will detect if this is a new bean or a previously fetched bean and perform either an + * insert or an update based on that. + * + * @see Database#save(Object) + */ + public void save(T bean) { + db().save(bean); + } + + /** + * Save all the beans in the collection. + */ + public int saveAll(Collection bean) { + return db().saveAll(bean); + } + + /** + * Update this entity. + * + * @see Database#update(Object) + */ + public void update(T bean) { + db().update(bean); + } + + /** + * Insert this entity. + * + * @see Database#insert(Object) + */ + public void insert(T bean) { + db().insert(bean); + } + + /** + * Delete this bean. + *

+ * This will return true if the bean was deleted successfully or JDBC batch is being used. + *

+ *

+ * If there is no current transaction one will be created and committed for + * you automatically. + *

+ *

+ * If the Bean does not have a version property (or loaded version property) and + * the bean does not exist then this returns false indicating that nothing was + * deleted. Note that, if JDBC batch mode is used then this always returns true. + *

+ * + * @see Database#delete(Object) + */ + public boolean delete(T bean) { + return db().delete(bean); + } + + /** + * Delete all the beans in the collection. + */ + public int deleteAll(Collection beans) { + return db().deleteAll(beans); + } + + /** + * Delete a bean permanently without soft delete. + *

+ * This is used when the bean contains a @SoftDelete property and we + * want to perform a hard/permanent delete. + *

+ * + * @see Database#deletePermanent(Object) + */ + public boolean deletePermanent(T bean) { + return db().deletePermanent(bean); + } + + /** + * Merge this entity using the default merge options. + *

+ * Ebean will detect if this is a new bean or a previously fetched bean and perform either an + * insert or an update based on that. + * + * @see Database#merge(Object) + */ + public void merge(T bean) { + db().merge(bean); + } + + /** + * Merge this entity using the specified merge options. + *

+ * Ebean will detect if this is a new bean or a previously fetched bean and perform either an + * insert or an update based on that. + * + * @see Database#merge(Object, MergeOptions) + */ + public void merge(T bean, MergeOptions options) { + db().merge(bean, options); + } + + /** + * Refreshes this entity from the database. + * + * @see Database#refresh(Object) + */ + public void refresh(T bean) { + db().refresh(bean); + } +} diff --git a/src/main/java/io/ebean/BeanState.java b/src/main/java/io/ebean/BeanState.java index ddb76d358..da8c24d81 100644 --- a/src/main/java/io/ebean/BeanState.java +++ b/src/main/java/io/ebean/BeanState.java @@ -1,5 +1,6 @@ package io.ebean; +import javax.annotation.Nullable; import java.util.Map; import java.util.Set; @@ -56,7 +57,7 @@ public interface BeanState { * * // set loaded state on the email property to false so that * // the email property is not included in a stateless update - * Ebean.getBeanState(user).setPropertyLoaded("email", false); + * DB.getBeanState(user).setPropertyLoaded("email", false); * * user.update(); * @@ -101,7 +102,7 @@ public interface BeanState { /** * Advanced - Used to programmatically build a partially or fully loaded * entity bean. First create an entity bean via - * {@link EbeanServer#createEntityBean(Class)}, then populate its properties + * {@link Database#createEntityBean(Class)}, then populate its properties * and then call this method specifying which properties where loaded or null * for a fully loaded entity bean. */ @@ -111,4 +112,10 @@ public interface BeanState { * Reset the bean putting it into NEW state such that a save() results in an insert. */ void resetForInsert(); + + /** + * Returns a map with load erros. + */ + @Nullable + Map getLoadErrors(); } diff --git a/src/main/java/io/ebean/CacheMode.java b/src/main/java/io/ebean/CacheMode.java index 7d8d549f2..b74753e21 100644 --- a/src/main/java/io/ebean/CacheMode.java +++ b/src/main/java/io/ebean/CacheMode.java @@ -69,8 +69,8 @@ public enum CacheMode { */ GET(true, false); - private boolean get; - private boolean put; + private final boolean get; + private final boolean put; CacheMode(boolean get, boolean put) { this.get = get; diff --git a/src/main/java/io/ebean/CallableSql.java b/src/main/java/io/ebean/CallableSql.java index 0ff90d181..604f5f105 100644 --- a/src/main/java/io/ebean/CallableSql.java +++ b/src/main/java/io/ebean/CallableSql.java @@ -17,11 +17,11 @@ import java.sql.SQLException; * * String sql = "{call sp_order_mod(?,?)}"; * - * CallableSql cs = Ebean.createCallableSql(sql); + * CallableSql cs = DB.createCallableSql(sql); * cs.setParameter(1, "turbo"); * cs.registerOut(2, Types.INTEGER); * - * Ebean.execute(cs); + * DB.execute(cs); * * // read the out parameter * Integer returnValue = (Integer) cs.getObject(2); @@ -38,7 +38,7 @@ import java.sql.SQLException; * * String sql = "{call sp_insert_order(?,?)}"; * - * CallableSql cs = Ebean.createCallableSql(sql); + * CallableSql cs = DB.createCallableSql(sql); * * // Inform Ebean this stored procedure inserts into the * // oe_order table and inserts + updates the oe_order_detail table. @@ -46,35 +46,33 @@ import java.sql.SQLException; * cs.addModification("oe_order", true, false, false); * cs.addModification("oe_order_detail", true, true, false); * - * Transaction t = Ebean.startTransaction(); * - * // execute using JDBC batching 10 statements at a time - * t.setBatchMode(true); - * t.setBatchSize(10); - * try { + * try (Transaction t = DB.beginTransaction()) { + * + * // execute using JDBC batching 10 statements at a time + * t.setBatchMode(true); + * t.setBatchSize(10); + * * cs.setParameter(1, "Was"); * cs.setParameter(2, "Banana"); - * Ebean.execute(cs); + * DB.execute(cs); * * cs.setParameter(1, "Here"); * cs.setParameter(2, "Kumera"); - * Ebean.execute(cs); + * DB.execute(cs); * * cs.setParameter(1, "More"); * cs.setParameter(2, "Apple"); - * Ebean.execute(cs); + * DB.execute(cs); * - * // Ebean.externalModification("oe_order",true,false,false); - * // Ebean.externalModification("oe_order_detail",true,true,false); - * Ebean.commitTransaction(); + * // DB.externalModification("oe_order",true,false,false); + * // DB.externalModification("oe_order_detail",true,true,false); + * t.commit(); * - * } finally { - * Ebean.endTransaction(); * } * } * * @see SqlUpdate - * @see Ebean#execute(CallableSql) */ public interface CallableSql { @@ -173,7 +171,7 @@ public interface CallableSql { * Add table modification information to the TransactionEvent. *

* This would be similar to using the - * Ebean.externalModification() method. It may be easier and make + * DB.externalModification() method. It may be easier and make * more sense to set it here with the CallableSql. *

*

diff --git a/src/main/java/io/ebean/CountDistinctOrder.java b/src/main/java/io/ebean/CountDistinctOrder.java new file mode 100644 index 000000000..e23ac4fd4 --- /dev/null +++ b/src/main/java/io/ebean/CountDistinctOrder.java @@ -0,0 +1,27 @@ +package io.ebean; +/** + * Enumeration to use with {@link Query#setCountDistinct(CountDistinctOrder)}. + * @author Roland Praml, FOCONIS AG + * + */ +public enum CountDistinctOrder { + NO_ORDERING, + + /** order by attribute ascending */ + ATTR_ASC, + + /** order by attribute descending */ + ATTR_DESC, + + /** order by count ascending and attribute ascending */ + COUNT_ASC_ATTR_ASC, + + /** order by count ascending and attribute descending */ + COUNT_ASC_ATTR_DESC, + + /** order by count descending and attribute ascending */ + COUNT_DESC_ATTR_ASC, + + /** order by count descending and attribute descending */ + COUNT_DESC_ATTR_DESC, +} diff --git a/src/main/java/io/ebean/CountedValue.java b/src/main/java/io/ebean/CountedValue.java new file mode 100644 index 000000000..3752f8e51 --- /dev/null +++ b/src/main/java/io/ebean/CountedValue.java @@ -0,0 +1,33 @@ +package io.ebean; + +import java.io.Serializable; +/** + * Holds a distinct value with it's count. + * (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.) + * @author Roland Praml, FOCONIS AG + */ +public class CountedValue implements Serializable { + private static final long serialVersionUID = -2267971668356749695L; + + private final A value; + private final long count; + + public CountedValue(A value, long count) { + this.value = value; + this.count = count; + } + + public long getCount() { + return count; + } + + public A getValue() { + return value; + } + + @Override + public String toString() { + return count + ": " + value; + } + +} diff --git a/src/main/java/io/ebean/DB.java b/src/main/java/io/ebean/DB.java new file mode 100644 index 000000000..0379bae6d --- /dev/null +++ b/src/main/java/io/ebean/DB.java @@ -0,0 +1,1254 @@ +package io.ebean; + +import io.ebean.annotation.TxIsolation; +import io.ebean.cache.ServerCacheManager; +import io.ebean.config.DatabaseConfig; +import io.ebean.plugin.Property; +import io.ebean.text.csv.CsvReader; +import io.ebean.text.json.JsonContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +/** + * DB is a registry of {@link Database} by name. + *

+ * DB additionally provides a convenient way to use the 'default' Database. + *

+ *

Default database

+ *

+ * One of the Database instances can be registered as the "default database" + * and can be obtained using DB.getDefault() + *

+ *
{@code
+ *
+ * Database database = DB.getDefault();
+ *
+ * }
+ * + *

Named database

+ *

+ * Multiple database instances can be registered with DB and we can obtain them + * using DB.byName() + *

+ *
{@code
+ *
+ * Database hrDatabase = DB.byName("hr");
+ *
+ * }
+ * + *

Convenience methods

+ *

+ * DB has methods like {@link #find(Class)} and {@link #save(Object)} which are + * just convenience for using the default database. + *

+ * + *
{@code
+ *
+ * // fetch using the default database
+ * Order order = DB.find(Order.class, 10);
+ *
+ * // is the same as
+ * Database database = DB.getDefault();
+ * Order order = database.find(Order.class, 10);
+ *
+ * }
+ */ +public class DB { + + /** + * Hide constructor. + */ + private DB() { + } + + /** + * Return the default database. + */ + public static Database getDefault() { + return Ebean.getDefaultServer(); + } + + /** + * Return the database for the given name. + * + * @param name The name of the database + */ + public static Database byName(String name) { + return Ebean.getServer(name); + } + +// /** +// * Register the server with this Ebean singleton. Specify if the registered +// * server is the primary/default server. +// */ +// public static void register(EbeanServer server, boolean defaultServer) { +// serverMgr.register(server, defaultServer); +// } +// +// /** +// * Backdoor for registering a mock implementation of EbeanServer as the default server. +// */ +// protected static EbeanServer mock(String name, EbeanServer server, boolean defaultServer) { +// EbeanServer originalPrimaryServer = serverMgr.defaultServer; +// serverMgr.registerWithName(name, server, defaultServer); +// return originalPrimaryServer; +// } + + /** + * Return the ExpressionFactory from the default database. + *

+ * The ExpressionFactory is used internally by the query and ExpressionList to + * build the WHERE and HAVING clauses. Alternatively you can use the + * ExpressionFactory directly to create expressions to add to the query where + * clause. + *

+ *

+ * Alternatively you can use the {@link Expr} as a shortcut to the + * ExpressionFactory of the 'Default' database. + *

+ *

+ * You generally need to the an ExpressionFactory (or {@link Expr}) to build + * an expression that uses OR like Expression e = Expr.or(..., ...); + *

+ */ + public static ExpressionFactory getExpressionFactory() { + return getDefault().getExpressionFactory(); + } + + /** + * Return the next identity value for a given bean type. + *

+ * This will only work when a IdGenerator is on this bean type such as a DB + * sequence or UUID. + *

+ *

+ * For DB's supporting getGeneratedKeys and sequences such as Oracle10 you do + * not need to use this method generally. It is made available for more + * complex cases where it is useful to get an ID prior to some processing. + *

+ */ + public static Object nextId(Class beanType) { + return getDefault().nextId(beanType); + } + + /** + * Start a transaction with 'REQUIRED' semantics. + *

+ * With REQUIRED semantics if an active transaction already exists that transaction will be used. + *

+ *

+ * The transaction is stored in a ThreadLocal variable and typically you only + * need to use the returned Transaction IF you wish to do things like + * use batch mode, change the transaction isolation level, use savepoints or + * log comments to the transaction log. + *

+ *

+ * Example of using a transaction to span multiple calls to find(), save() + * etc. + *

+ *
{@code
+   *
+   *   try (Transaction transaction = DB.beginTransaction()) {
+   *
+   *     Order order = DB.find(Order.class, 42);
+   *     order.setStatus(Status.COMPLETE);
+   *     order.save();
+   *
+   *     transaction.commit();
+   *   }
+   *
+   * }
+ *

+ * If we want to externalise the transaction management then we do this via Database. + * With Database we can pass the transaction to the various find(), save() and execute() + * methods. This gives us the ability to create the transactions externally from Ebean + * and use the transaction explicitly via the various methods available on Database. + *

+ */ + public static Transaction beginTransaction() { + return getDefault().beginTransaction(); + } + + /** + * Start a transaction additionally specifying the isolation level. + * + * @param isolation the Transaction isolation level + */ + public static Transaction beginTransaction(TxIsolation isolation) { + return getDefault().beginTransaction(isolation); + } + + /** + * Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics. + *

+ * Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or + * {@link #execute(TxScope, Runnable)}. + *

+ *

+ *

REQUIRES_NEW example:

+ *
{@code
+   * // Start a new transaction. If there is a current transaction
+   * // suspend it until this transaction ends
+   *
+   * try (Transaction txn = DB.beginTransaction(TxScope.requiresNew())) {
+   *   ...
+   *
+   *   // commit the transaction
+   *   txn.commit();
+   * }
+   * }
+ * + *

REQUIRED example:

+ *
{@code
+   * // start a new transaction if there is not a current transaction
+   *
+   * try (Transaction txn = DB.beginTransaction(TxScope.required())) {
+   *   ...
+   *
+   *   // commit the transaction if it was created or
+   *   // do nothing if there was already a current transaction
+   *   txn.commit();
+   * }
+   * }
+ */ + public static Transaction beginTransaction(TxScope scope) { + return getDefault().beginTransaction(scope); + } + + /** + * Returns the current transaction or null if there is no current transaction in scope. + */ + public static Transaction currentTransaction() { + return getDefault().currentTransaction(); + } + + /** + * The batch will be flushing automatically but you can use this to explicitly + * flush the batch if you like. + *

+ * Flushing occurs automatically when: + *

+ *
    + *
  • the batch size is reached
  • + *
  • A query is executed on the same transaction
  • + *
  • UpdateSql or CallableSql are mixed with bean save and delete
  • + *
  • Transaction commit occurs
  • + *
  • A getter method is called on a batched bean
  • + *
+ */ + public static void flush() { + currentTransaction().flush(); + } + + /** + * Register a TransactionCallback on the currently active transaction. + *

+ * If there is no currently active transaction then a PersistenceException is thrown. + * + * @param transactionCallback the transaction callback to be registered with the current transaction + * @throws PersistenceException if there is no currently active transaction + */ + public static void register(TransactionCallback transactionCallback) throws PersistenceException { + getDefault().register(transactionCallback); + } + + /** + * Commit the current transaction. + */ + public static void commitTransaction() { + getDefault().commitTransaction(); + } + + /** + * Rollback the current transaction. + */ + 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. + */ + public static void setRollbackOnly() { + getDefault().currentTransaction().setRollbackOnly(); + } + + /** + * Return a map of the differences between two objects of the same type. + *

+ * When null is passed in for b, then the 'OldValues' of a is used for the + * difference comparison. + *

+ */ + public static Map diff(Object a, Object b) { + return getDefault().diff(a, b); + } + + /** + * Either Insert or Update the bean depending on its state. + *

+ * If there is no current transaction one will be created and committed for + * you automatically. + *

+ *

+ * Save can cascade along relationships. For this to happen you need to + * specify a cascade of CascadeType.ALL or CascadeType.PERSIST on the + * OneToMany, OneToOne or ManyToMany annotation. + *

+ *

+ * When a save cascades via a OneToMany or ManyToMany Ebean will automatically + * set the 'parent' object to the 'detail' object. In the example below in + * saving the order and cascade saving the order details the 'parent' order + * will be set against each order detail when it is saved. + *

+ */ + public static void save(Object bean) throws OptimisticLockException { + getDefault().save(bean); + } + + /** + * Insert the bean. This is useful when you set the Id property on a bean and + * want to explicitly insert it. + */ + public static void insert(Object bean) { + getDefault().insert(bean); + } + + /** + * Insert a collection of beans. + */ + public static void insertAll(Collection beans) { + getDefault().insertAll(beans); + } + + /** + * Marks the entity bean as dirty. + *

+ * This is used so that when a bean that is otherwise unmodified is updated with the version + * property updated. + *

+ * An unmodified bean that is saved or updated is normally skipped and this marks the bean as + * dirty so that it is not skipped. + *

{@code
+   *
+   *   Customer customer = DB.find(Customer, id);
+   *
+   *   // mark the bean as dirty so that a save() or update() will
+   *   // increment the version property
+   *   DB.markAsDirty(customer);
+   *   DB.save(customer);
+   *
+   * }
+ */ + public static void markAsDirty(Object bean) throws OptimisticLockException { + getDefault().markAsDirty(bean); + } + + /** + * Saves the bean using an update. If you know you are updating a bean then it is preferrable to + * use this update() method rather than save(). + *

+ * Stateless updates: Note that the bean does not have to be previously fetched to call + * update().You can create a new instance and set some of its properties programmatically for via + * JSON/XML marshalling etc. This is described as a 'stateless update'. + *

+ *

+ * Optimistic Locking: Note that if the version property is not set when update() is + * called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used). + *

+ *

+ * {@link DatabaseConfig#setUpdatesDeleteMissingChildren(boolean)}: When cascade saving to a + * OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children + * that are in the database but are not in the collection are deleted. + *

+ *

+ * {@link DatabaseConfig#setUpdateChangesOnly(boolean)}: The updateChangesOnly setting + * controls if only the changed properties are included in the update or if all the loaded + * properties are included instead. + *

+ *
{@code
+   *
+   *   // A 'stateless update' example
+   *   Customer customer = new Customer();
+   *   customer.setId(7);
+   *   customer.setName("ModifiedNameNoOCC");
+   *   database.update(customer);
+   *
+   * }
+ * + * @see DatabaseConfig#setUpdatesDeleteMissingChildren(boolean) + * @see DatabaseConfig#setUpdateChangesOnly(boolean) + */ + public static void update(Object bean) throws OptimisticLockException { + getDefault().update(bean); + } + + /** + * Update the beans in the collection. + */ + public static void updateAll(Collection beans) throws OptimisticLockException { + getDefault().updateAll(beans); + } + + /** + * Merge the bean using the default merge options. + * + * @param bean The bean to merge + */ + public static void merge(Object bean) { + getDefault().merge(bean); + } + + /** + * Merge the bean using the given merge options. + * + * @param bean The bean to merge + * @param options The options to control the merge + */ + public static void merge(Object bean, MergeOptions options) { + getDefault().merge(bean, options); + } + + /** + * Save all the beans from a Collection. + */ + public static int saveAll(Collection beans) throws OptimisticLockException { + return getDefault().saveAll(beans); + } + + /** + * This method checks the uniqueness of a bean. I.e. if the save will work. It will return the + * properties that violates an unique / primary key. This may be done in an UI save action to + * validate if the user has entered correct values. + *

+ * Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update. + *

+ * Note: This checks only the root bean! + *

+ *

{@code
+   *
+   *   // there is a unique constraint on title
+   *
+   *   Document doc = new Document();
+   *   doc.setTitle("One flew over the cuckoo's nest");
+   *   doc.setBody("clashes with doc1");
+   *
+   *   Set properties = DB.checkUniqueness(doc);
+   *
+   *   if (properties.isEmpty()) {
+   *     // it is unique ... carry on
+   *
+   *   } else {
+   *     // build a user friendly message
+   *     // to return message back to user
+   *
+   *     String uniqueProperties = properties.toString();
+   *
+   *     StringBuilder msg = new StringBuilder();
+   *
+   *     properties.forEach((it)-> {
+   *       Object propertyValue = it.getVal(doc);
+   *       String propertyName = it.getName();
+   *       msg.append(" property["+propertyName+"] value["+propertyValue+"]");
+   *     });
+   *
+   *     // uniqueProperties > [title]
+   *     //       custom msg > property[title] value[One flew over the cuckoo's nest]
+   *
+   *  }
+   *
+   * }
+ * + * @param bean The entity bean to check uniqueness on + * @return a set of Properties if constraint validation was detected or empty list. + */ + @Nonnull + public static Set checkUniqueness(Object bean) { + return getDefault().checkUniqueness(bean); + } + + /** + * Same as {@link #checkUniqueness(Object)}. but with given transaction. + */ + @Nonnull + public static Set checkUniqueness(Object bean, Transaction transaction) { + return getDefault().checkUniqueness(bean, transaction); + } + + /** + * Delete the bean. + *

+ * This will return true if the bean was deleted successfully or JDBC batch is being used. + *

+ *

+ * If there is no current transaction one will be created and committed for + * you automatically. + *

+ *

+ * If the bean is configured with @SoftDelete then this will perform a soft + * delete rather than a hard/permanent delete. + *

+ *

+ * If the Bean does not have a version property (or loaded version property) and + * the bean does not exist then this returns false indicating that nothing was + * deleted. Note that, if JDBC batch mode is used then this always returns true. + *

+ */ + public static boolean delete(Object bean) throws OptimisticLockException { + return getDefault().delete(bean); + } + + /** + * Delete the bean in permanent fashion (will not use soft delete). + */ + public static boolean deletePermanent(Object bean) throws OptimisticLockException { + return getDefault().deletePermanent(bean); + } + + /** + * Delete the bean given its type and id. + */ + public static int delete(Class beanType, Object id) { + return getDefault().delete(beanType, id); + } + + /** + * Delete permanent the bean given its type and id. + */ + public static int deletePermanent(Class beanType, Object id) { + return getDefault().deletePermanent(beanType, id); + } + + /** + * Delete several beans given their type and id values. + */ + public static int deleteAll(Class beanType, Collection ids) { + return getDefault().deleteAll(beanType, ids); + } + + /** + * Delete permanent several beans given their type and id values. + */ + public static int deleteAllPermanent(Class beanType, Collection ids) { + return getDefault().deleteAllPermanent(beanType, ids); + } + + /** + * Delete all the beans in the Collection. + */ + public static int deleteAll(Collection beans) throws OptimisticLockException { + return getDefault().deleteAll(beans); + } + + /** + * Delete permanent all the beans in the Collection (will not use soft delete). + */ + public static int deleteAllPermanent(Collection beans) throws OptimisticLockException { + return getDefault().deleteAllPermanent(beans); + } + + /** + * Refresh the values of a bean. + *

+ * Note that this resets OneToMany and ManyToMany properties so that if they + * are accessed a lazy load will refresh the many property. + *

+ */ + public static void refresh(Object bean) { + getDefault().refresh(bean); + } + + /** + * Refresh a 'many' property of a bean. + *
{@code
+   *
+   *   Order order = ...;
+   *   ...
+   *   // refresh the order details...
+   *   DB.refreshMany(order, "details");
+   *
+   * }
+ * + * @param bean the entity bean containing the List Set or Map to refresh. + * @param manyPropertyName the property name of the List Set or Map to refresh. + */ + public static void refreshMany(Object bean, String manyPropertyName) { + getDefault().refreshMany(bean, manyPropertyName); + } + + /** + * Get a reference object. + *

+ * This is sometimes described as a proxy (with lazy loading). + *

+ *
{@code
+   *
+   *   Product product = DB.getReference(Product.class, 1);
+   *
+   *   // You can get the id without causing a fetch/lazy load
+   *   Integer productId = product.getId();
+   *
+   *   // If you try to get any other property a fetch/lazy loading will occur
+   *   // This will cause a query to execute...
+   *   String name = product.getName();
+   *
+   * }
+ * + * @param beanType the type of entity bean + * @param id the id value + */ + public static T getReference(Class beanType, Object id) { + return getDefault().getReference(beanType, id); + } + + /** + * Sort the list using the sortByClause which can contain a comma delimited + * list of property names and keywords asc, desc, nullsHigh and nullsLow. + *
    + *
  • asc - ascending order (which is the default)
  • + *
  • desc - Descending order
  • + *
  • nullsHigh - Treat null values as high/large values (which is the + * default)
  • + *
  • nullsLow- Treat null values as low/very small values
  • + *
+ *

+ * If you leave off any keywords the defaults are ascending order and treating + * nulls as high values. + *

+ *

+ * Note that the sorting uses a Comparator and Collections.sort(); and does + * not invoke a DB query. + *

+ *
{@code
+   *
+   *   // find orders and their customers
+   *   List list = DB.find(Order.class)
+   *     .fetch("customer")
+   *     .orderBy("id")
+   *     .findList();
+   *
+   *   // sort by customer name ascending, then by order shipDate
+   *   // ... then by the order status descending
+   *   DB.sort(list, "customer.name, shipDate, status desc");
+   *
+   *   // sort by customer name descending (with nulls low)
+   *   // ... then by the order id
+   *   DB.sort(list, "customer.name desc nullsLow, id");
+   *
+   * }
+ * + * @param list the list of entity beans + * @param sortByClause the properties to sort the list by + */ + public static void sort(List list, String sortByClause) { + getDefault().sort(list, sortByClause); + } + + /** + * Find a bean using its unique id. This will not use caching. + *
{@code
+   *
+   *   // Fetch order 1
+   *   Order order = DB.find(Order.class, 1);
+   *
+   * }
+ *

+ * If you want more control over the query then you can use createQuery() and + * Query.findOne(); + *

+ *
{@code
+   *
+   *   // ... additionally fetching customer, customer shipping address,
+   *   // order details, and the product associated with each order detail.
+   *   // note: only product id and name is fetch (its a "partial object").
+   *   // note: all other objects use "*" and have all their properties fetched.
+   *
+   *   Query query = DB.find(Order.class)
+   *     .setId(1)
+   *     .fetch("customer")
+   *     .fetch("customer.shippingAddress")
+   *     .fetch("details")
+   *     .query();
+   *
+   *   // fetch associated products but only fetch their product id and name
+   *   query.fetch("details.product", "name");
+   *
+   *   // traverse the object graph...
+   *
+   *   Order order = query.findOne();
+   *
+   *   Customer customer = order.getCustomer();
+   *   Address shippingAddress = customer.getShippingAddress();
+   *   List details = order.getDetails();
+   *   OrderDetail detail0 = details.get(0);
+   *   Product product = detail0.getProduct();
+   *   String productName = product.getName();
+   *
+   * }
+ * + * @param beanType the type of entity bean to fetch + * @param id the id value + */ + @Nullable + public static T find(Class beanType, Object id) { + return getDefault().find(beanType, id); + } + + /** + * Look to execute a native sql query that does not returns beans but instead + * returns SqlRow or direct access to ResultSet (see {@link SqlQuery#findList(RowMapper)}. + * + *

+ * Refer to {@link DtoQuery} for native sql queries returning DTO beans. + *

+ *

+ * Refer to {@link #findNative(Class, String)} for native sql queries returning entity beans. + *

+ */ + public static SqlQuery sqlQuery(String sql) { + return getDefault().sqlQuery(sql); + } + + /** + * This is an alias for {@link #sqlQuery(String)}. + */ + public static SqlQuery createSqlQuery(String sql) { + return sqlQuery(sql); + } + + /** + * Look to execute a native sql insert update or delete statement. + *

+ * Use this to execute a Insert Update or Delete statement. The statement will + * be native to the database and contain database table and column names. + *

+ * + *

+ * See {@link SqlUpdate} for example usage. + *

+ * + * @return The SqlUpdate instance to set parameters and execute + */ + public static SqlUpdate sqlUpdate(String sql) { + return getDefault().sqlUpdate(sql); + } + + /** + * This is an alias for {@link #sqlUpdate(String)}. + */ + public static SqlUpdate createSqlUpdate(String sql) { + return sqlUpdate(sql); + } + + /** + * Create a CallableSql to execute a given stored procedure. + * + * @see CallableSql + */ + public static CallableSql createCallableSql(String sql) { + return getDefault().createCallableSql(sql); + } + + /** + * Create a orm update where you will supply the insert/update or delete + * statement (rather than using a named one that is already defined using the + * @NamedUpdates annotation). + *

+ * The orm update differs from the sql update in that it you can use the bean + * name and bean property names rather than table and column names. + *

+ *

+ * An example: + *

+ *
{@code
+   *
+   *   // The bean name and properties - "topic","postCount" and "id"
+   *
+   *   // will be converted into their associated table and column names
+   *   String updStatement = "update topic set postCount = :pc where id = :id";
+   *
+   *   Update update = DB.createUpdate(Topic.class, updStatement);
+   *
+   *   update.set("pc", 9);
+   *   update.set("id", 3);
+   *
+   *   int rows = update.execute();
+   *   System.out.println("rows updated:" + rows);
+   *
+   * }
+ */ + public static Update createUpdate(Class beanType, String ormUpdate) { + + return getDefault().createUpdate(beanType, ormUpdate); + } + + /** + * Create a CsvReader for a given beanType. + */ + public static CsvReader createCsvReader(Class beanType) { + + return getDefault().createCsvReader(beanType); + } + + /** + * Create a named query. + *

+ * For RawSql the named query is expected to be in ebean.xml. + *

+ * + * @param beanType The type of entity bean + * @param namedQuery The name of the query + * @param The type of entity bean + * @return The query + */ + public static Query createNamedQuery(Class beanType, String namedQuery) { + return getDefault().createNamedQuery(beanType, namedQuery); + } + + /** + * Create a query for a type of entity bean. + *

+ * You can use the methods on the Query object to specify fetch paths, + * predicates, order by, limits etc. + *

+ *

+ * You then use findList(), findSet(), findMap() and findOne() to execute + * the query and return the collection or bean. + *

+ *

+ * Note that a query executed by {@link Query#findList()} etc will execute against + * the same database from which is was created. + *

+ * + * @param beanType the class of entity to be fetched + * @return A ORM Query for this beanType + */ + public static Query createQuery(Class beanType) { + + return getDefault().createQuery(beanType); + } + + /** + * Parse the Ebean query language statement returning the query which can then + * be modified (add expressions, change order by clause, change maxRows, change + * fetch and select paths etc). + *

+ *

Example

+ *
{@code
+   *
+   *   // Find order additionally fetching the customer, details and details.product name.
+   *
+   *   String eql = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
+   *
+   *   Query query = DB.createQuery(Order.class, eql);
+   *   query.setParameter("orderId", 2);
+   *
+   *   Order order = query.findOne();
+   *
+   *   // This is the same as:
+   *
+   *   Order order = DB.find(Order.class)
+   *     .fetch("customer")
+   *     .fetch("details")
+   *     .fetch("detail.product", "name")
+   *     .setId(2)
+   *     .findOne();
+   *
+   * }
+ * + * @param beanType The type of bean to fetch + * @param eql The Ebean query + * @param The type of the entity bean + * @return The query with expressions defined as per the parsed query statement + */ + public static Query createQuery(Class beanType, String eql) { + + return getDefault().createQuery(beanType, eql); + } + + /** + * Create a query for a type of entity bean. + *

+ * This is actually the same as {@link #createQuery(Class)}. The reason it + * exists is that people used to JPA will probably be looking for a + * createQuery method (the same as entityManager). + *

+ * + * @param beanType the type of entity bean to find + * @return A ORM Query object for this beanType + */ + public static Query find(Class beanType) { + + return getDefault().find(beanType); + } + + /** + * Create a query using native SQL. + *

+ * The native SQL can contain named parameters or positioned parameters. + *

+ *
{@code
+   *
+   *   String sql = "select c.id, c.name from customer c where c.name like ? order by c.name";
+   *
+   *   Query query = database.findNative(Customer.class, sql);
+   *   query.setParameter(1, "Rob%");
+   *
+   *   List customers = query.findList();
+   *
+   * }
+ * + * @param beanType The type of entity bean to fetch + * @param nativeSql The SQL that can contain named or positioned parameters + * @return The query to set parameters and execute + */ + public static Query findNative(Class beanType, String nativeSql) { + return getDefault().findNative(beanType, nativeSql); + } + + /** + * Create a Query for DTO beans. + *

+ * DTO beans are just normal bean like classes with public constructor(s) and setters. + * They do not need to be registered with Ebean before use. + *

+ * + * @param dtoType The type of the DTO bean the rows will be mapped into. + * @param sql The SQL query to execute. + * @param The type of the DTO bean. + */ + public static DtoQuery findDto(Class dtoType, String sql) { + return getDefault().findDto(dtoType, sql); + } + + /** + * Create an Update query to perform a bulk update. + *

+ *

{@code
+   *
+   *  int rows = DB.update(Customer.class)
+   *      .set("status", Customer.Status.ACTIVE)
+   *      .set("updtime", new Timestamp(System.currentTimeMillis()))
+   *      .where()
+   *        .gt("id", 1000)
+   *        .update();
+   *
+   * }
+ * + * @param beanType The type of entity bean to update + * @param The type of entity bean + * @return The update query to use + */ + public static UpdateQuery update(Class beanType) { + return getDefault().update(beanType); + } + + /** + * Create a filter for sorting and filtering lists of entities locally without + * going back to the database. + *

+ * This produces and returns a new list with the sort and filters applied. + *

+ *

+ * Refer to {@link Filter} for an example of its use. + *

+ */ + public static Filter filter(Class beanType) { + return getDefault().filter(beanType); + } + +// /** +// * Execute a Sql Update Delete or Insert statement. This returns the number of +// * rows that where updated, deleted or inserted. If is executed in batch then +// * this returns -1. You can get the actual rowCount after commit() from +// * updateSql.getRowCount(). +// *

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

+// *

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

+// *

+// * Example: +// *

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

+// * Example: +// *

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

+ * The scope can control the transaction type, isolation and rollback + * semantics. + *

+ *
{@code
+   *
+   * // set specific transactional scope settings
+   * TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
+   *
+   * DB.execute(scope, new TxRunnable() {
+   *   public void run() {
+   * 	   User u1 = DB.find(User.class, 1);
+   *     ...
+   *   }
+   * });
+   *
+   * }
+ */ + public static void execute(TxScope scope, Runnable r) { + getDefault().execute(scope, r); + } + + /** + * Execute a Runnable in a Transaction with the default scope. + *

+ * The default scope runs with REQUIRED and by default will rollback on any + * exception (checked or runtime). + *

+ *
{@code
+   *
+   * DB.execute(() -> {
+   *
+   *   User u1 = DB.find(User.class, 1);
+   *   User u2 = DB.find(User.class, 2);
+   *
+   *   u1.setName("u1 mod");
+   *   u2.setName("u2 mod");
+   *
+   *   DB.save(u1);
+   *   DB.save(u2);
+   * });
+   * }
+ */ + public static void execute(Runnable r) { + getDefault().execute(r); + } + + /** + * Execute a Callable in a Transaction with an explicit scope. + *

+ * The scope can control the transaction type, isolation and rollback + * semantics. + *

+ *
{@code
+   *
+   * // set specific transactional scope settings
+   * TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
+   *
+   * DB.executeCall(scope, new Callable() {
+   *   public String call() {
+   * 	   User u1 = DB.find(User.class, 1);
+   * 		 ...
+   * 		 return u1.getEmail();
+   *   }
+   * });
+   * }
+ */ + public static T executeCall(TxScope scope, Callable c) { + return getDefault().executeCall(scope, c); + } + + /** + * Execute a Callable in a Transaction with the default scope. + *

+ * The default scope runs with REQUIRED and by default will rollback on any + * exception (checked or runtime). + *

+ *

+ * This is basically the same as TxRunnable except that it returns an Object + * (and you specify the return type via generics). + *

+ *
{@code
+   *
+   * DB.executeCall(() -> {
+   *
+   *   User u1 = DB.find(User.class, 1);
+   *   User u2 = DB.find(User.class, 2);
+   *
+   *   u1.setName("u1 mod");
+   *   u2.setName("u2 mod");
+   *
+   *   DB.save(u1);
+   *   DB.save(u2);
+   *
+   *   return u1.getEmail();
+   * });
+   * }
+ */ + public static T executeCall(Callable c) { + return getDefault().executeCall(c); + } + + /** + * Inform Ebean that tables have been modified externally. These could be the + * result of from calling a stored procedure, other JDBC calls or external + * programs including other frameworks. + *

+ * If you use DB.execute(UpdateSql) then the table modification information + * is automatically deduced and you do not need to call this method yourself. + *

+ *

+ * This information is used to invalidate objects out of the cache and + * potentially text indexes. This information is also automatically broadcast + * across the cluster. + *

+ *

+ * If there is a transaction then this information is placed into the current + * transactions event information. When the transaction is committed this + * information is registered (with the transaction manager). If this + * transaction is rolled back then none of the transaction event information + * registers including the information you put in via this method. + *

+ *

+ * If there is NO current transaction when you call this method then this + * information is registered immediately (with the transaction manager). + *

+ * + * @param tableName the name of the table that was modified + * @param inserts true if rows where inserted into the table + * @param updates true if rows on the table where updated + * @param deletes true if rows on the table where deleted + */ + public static void externalModification(String tableName, boolean inserts, boolean updates, boolean deletes) { + getDefault().externalModification(tableName, inserts, updates, deletes); + } + + /** + * Return the BeanState for a given entity bean. + *

+ * This will return null if the bean is not an enhanced entity bean. + *

+ */ + public static BeanState getBeanState(Object bean) { + return getDefault().getBeanState(bean); + } + + /** + * Return the manager of the level 2 cache ("L2" cache). + */ + public static ServerCacheManager getServerCacheManager() { + return getDefault().getServerCacheManager(); + } + + /** + * Return the BackgroundExecutor service for asynchronous processing of + * queries. + */ + public static BackgroundExecutor getBackgroundExecutor() { + return getDefault().getBackgroundExecutor(); + } + + /** + * Return the JsonContext for reading/writing JSON. + */ + public static JsonContext json() { + return getDefault().json(); + } + + /** + * Truncate the base tables for the given bean types. + */ + public static void truncate(Class... types) { + getDefault().truncate(types); + } + + /** + * Truncate the given tables. + */ + public static void truncate(String... tables) { + getDefault().truncate(tables); + } + +} diff --git a/src/main/java/io/ebean/Database.java b/src/main/java/io/ebean/Database.java new file mode 100644 index 000000000..ee9ece29d --- /dev/null +++ b/src/main/java/io/ebean/Database.java @@ -0,0 +1,1594 @@ +package io.ebean; + +import io.ebean.annotation.TxIsolation; +import io.ebean.cache.ServerCacheManager; +import io.ebean.config.DatabaseConfig; +import io.ebean.config.ServerConfig; +import io.ebean.meta.MetaInfoManager; +import io.ebean.plugin.Property; +import io.ebean.plugin.SpiServer; +import io.ebean.text.csv.CsvReader; +import io.ebean.text.json.JsonContext; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +/** + * Provides the API for fetching and saving beans to a particular database. + * + *
Registration with the DB singleton
+ *

+ * When a Database instance is created it can be registered with the DB + * singleton (see {@link DatabaseConfig#setRegister(boolean)}). The DB + * singleton is essentially a map of Database's that have been registered + * with it. + *

+ *

+ * The Database can then be retrieved later via {@link DB#byName(String)}. + *

+ * + *
The 'default' Database
+ *

+ * One Database can be designated as the 'default' or 'primary' Database + * (see {@link DatabaseConfig#setDefaultServer(boolean)}. Many methods on DB + * such as {@link DB#find(Class)} etc are actually just a convenient way to + * call methods on the 'default/primary' Database. + *

+ * + *
Constructing a Database
+ *

+ * Database's are constructed by the DatabaseFactory. They can be created + * programmatically via {@link DatabaseFactory#create(DatabaseConfig)} or they + * can be automatically constructed on demand using configuration information in + * the application.properties file. + *

+ * + *
Example: Get a Database
+ *

+ *

{@code
+ *
+ *   // Get access to the Human Resources Database
+ *   Database hrDatabase = DB.byName("hr");
+ *
+ *
+ *   // fetch contact 3 from the HR database
+ *   Contact contact = hrDatabase.find(Contact.class, new Integer(3));
+ *
+ *   contact.setStatus("INACTIVE"); ...
+ *
+ *   // save the contact back to the HR database
+ *   hrDatabase.save(contact);
+ *
+ * }
+ * + *
Database vs DB API
+ *

+ * Database provides additional API compared with DB. For example it + * provides more control over the use of Transactions that is not available in + * the DB API. + *

+ * + *

+ * External Transactions: If you wanted to use transactions created + * externally to Ebean then Database provides additional methods where you + * can explicitly pass a transaction (that can be created externally). + *

+ * + *

+ * Bypass ThreadLocal Mechanism: If you want to bypass the built in + * ThreadLocal transaction management you can use the createTransaction() + * method. Example: a single thread requires more than one transaction. + *

+ * + * @see DB + * @see DatabaseFactory + * @see DatabaseConfig + */ +public interface Database { + + /** + * Shutdown the Database instance programmatically. + *

+ * This method is not normally required. Ebean registers a shutdown hook and shuts down cleanly. + *

+ *

+ * If the under underlying DataSource is the Ebean implementation then you + * also have the option of shutting down the DataSource and deregistering the + * JDBC driver. + *

+ * + * @param shutdownDataSource if true then shutdown the underlying DataSource if it is the Ebean + * DataSource implementation. + * @param deregisterDriver if true then deregister the JDBC driver if it is the Ebean + * DataSource implementation. + */ + void shutdown(boolean shutdownDataSource, boolean deregisterDriver); + + /** + * Return AutoTune which is used to control the AutoTune service at runtime. + */ + AutoTune getAutoTune(); + + /** + * Return the name. This is used with {@link DB#byName(String)} to get a + * Database that was registered with the DB singleton. + */ + String getName(); + + /** + * Return the ExpressionFactory for this server. + */ + ExpressionFactory getExpressionFactory(); + + /** + * Return the MetaInfoManager which is used to get meta data from the Database + * such as query execution statistics. + */ + MetaInfoManager getMetaInfoManager(); + + /** + * Return the extended API intended for use by plugins. + */ + SpiServer getPluginApi(); + + /** + * Return the BeanState for a given entity bean. + *

+ * This will return null if the bean is not an enhanced entity bean. + *

+ */ + BeanState getBeanState(Object bean); + + /** + * Return the value of the Id property for a given bean. + */ + Object getBeanId(Object bean); + + /** + * Set the Id value onto the bean converting the type of the id value if necessary. + *

+ * For example, if the id value passed in is a String but ought to be a Long or UUID etc + * then it will automatically be converted. + *

+ * + * @param bean The entity bean to set the id value on. + * @param id The id value to set. + */ + Object setBeanId(Object bean, Object id); + + /** + * Return a map of the differences between two objects of the same type. + *

+ * When null is passed in for b, then the 'OldValues' of a is used for the + * difference comparison. + *

+ */ + Map diff(Object newBean, Object oldBean); + + /** + * Create a new instance of T that is an EntityBean. + *

+ * Useful if you use BeanPostConstructListeners or @PostConstruct Annotations. + * In this case you should not use "new Bean...()". Making all bean construtors protected + * could be a good idea here. + *

+ */ + T createEntityBean(Class type); + + /** + * Create a CsvReader for a given beanType. + */ + CsvReader createCsvReader(Class beanType); + + /** + * Create an Update query to perform a bulk update. + *

+ *

{@code
+   *
+   *  int rows = database
+   *      .update(Customer.class)
+   *      .set("status", Customer.Status.ACTIVE)
+   *      .set("updtime", new Timestamp(System.currentTimeMillis()))
+   *      .where()
+   *        .gt("id", 1000)
+   *        .update();
+   *
+   * }
+ * + * @param beanType The type of entity bean to update + * @param The type of entity bean + * @return The update query to use + */ + UpdateQuery update(Class beanType); + + /** + * Create a named query. + *

+ * For RawSql the named query is expected to be in ebean.xml. + *

+ * + * @param beanType The type of entity bean + * @param namedQuery The name of the query + * @param The type of entity bean + * @return The query + */ + Query createNamedQuery(Class beanType, String namedQuery); + + /** + * Create a query for an entity bean and synonym for {@link #find(Class)}. + * + * @see #find(Class) + */ + Query createQuery(Class beanType); + + /** + * Parse the Ebean query language statement returning the query which can then + * be modified (add expressions, change order by clause, change maxRows, change + * fetch and select paths etc). + *

+ *

Example

+ *
{@code
+   *
+   *   // Find order additionally fetching the customer, details and details.product name.
+   *
+   *   String ormQuery = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
+   *
+   *   Query query = DB.createQuery(Order.class, ormQuery);
+   *   query.setParameter("orderId", 2);
+   *
+   *   Order order = query.findOne();
+   *
+   *   // This is the same as:
+   *
+   *   Order order = DB.find(Order.class)
+   *     .fetch("customer")
+   *     .fetch("details")
+   *     .fetch("detail.product", "name")
+   *     .setId(2)
+   *     .findOne();
+   *
+   * }
+ * + * @param beanType The type of bean to fetch + * @param ormQuery The Ebean ORM query + * @param The type of the entity bean + * @return The query with expressions defined as per the parsed query statement + */ + Query createQuery(Class beanType, String ormQuery); + + /** + * Create a query for a type of entity bean. + *

+ * You can use the methods on the Query object to specify fetch paths, + * predicates, order by, limits etc. + *

+ *

+ * You then use findList(), findSet(), findMap() and findOne() to execute + * the query and return the collection or bean. + *

+ *

+ * Note that a query executed by {@link Query#findList()} + * {@link Query#findSet()} etc will execute against the same Database from + * which is was created. + *

+ *

+ *

{@code
+   *
+   *   // Find order 2 specifying explicitly the parts of the object graph to
+   *   // eagerly fetch. In this case eagerly fetch the associated customer,
+   *   // details and details.product.name
+   *
+   *   Order order = database.find(Order.class)
+   *     .fetch("customer")
+   *     .fetch("details")
+   *     .fetch("detail.product", "name")
+   *     .setId(2)
+   *     .findOne();
+   *
+   *   // find some new orders ... with firstRow/maxRows
+   *   List orders =
+   *     database.find(Order.class)
+   *       .where().eq("status", Order.Status.NEW)
+   *       .setFirstRow(20)
+   *       .setMaxRows(10)
+   *       .findList();
+   *
+   * }
+ */ + Query find(Class beanType); + + /** + * Create a query using native SQL. + *

+ * The native SQL can contain named parameters or positioned parameters. + *

+ *
{@code
+   *
+   *   String sql = "select c.id, c.name from customer c where c.name like ? order by c.name";
+   *
+   *   Query query = database.findNative(Customer.class, sql);
+   *   query.setParameter(1, "Rob%");
+   *
+   *   List customers = query.findList();
+   *
+   * }
+ * + * @param beanType The type of entity bean to fetch + * @param nativeSql The SQL that can contain named or positioned parameters + * @return The query to set parameters and execute + */ + Query findNative(Class beanType, String nativeSql); + + /** + * Return the next unique identity value for a given bean type. + *

+ * This will only work when a IdGenerator is on the bean such as for beans + * that use a DB sequence or UUID. + *

+ *

+ * For DB's supporting getGeneratedKeys and sequences such as Oracle10 you do + * not need to use this method generally. It is made available for more + * complex cases where it is useful to get an ID prior to some processing. + *

+ */ + Object nextId(Class beanType); + + /** + * Create a filter for sorting and filtering lists of entities locally without + * going back to the database. + *

+ * This produces and returns a new list with the sort and filters applied. + *

+ *

+ * Refer to {@link Filter} for an example of its use. + *

+ */ + Filter filter(Class beanType); + + /** + * Sort the list in memory using the sortByClause which can contain a comma delimited + * list of property names and keywords asc, desc, nullsHigh and nullsLow. + *
    + *
  • asc - ascending order (which is the default)
  • + *
  • desc - Descending order
  • + *
  • nullsHigh - Treat null values as high/large values (which is the + * default)
  • + *
  • nullsLow- Treat null values as low/very small values
  • + *
+ *

+ * If you leave off any keywords the defaults are ascending order and treating + * nulls as high values. + *

+ *

+ * Note that the sorting uses a Comparator and Collections.sort(); and does + * not invoke a DB query. + *

+ *

+ *

{@code
+   *
+   *   // find orders and their customers
+   *   List list = database.find(Order.class)
+   *     .fetch("customer")
+   *     .orderBy("id")
+   *     .findList();
+   *
+   *   // sort by customer name ascending, then by order shipDate
+   *   // ... then by the order status descending
+   *   database.sort(list, "customer.name, shipDate, status desc");
+   *
+   *   // sort by customer name descending (with nulls low)
+   *   // ... then by the order id
+   *   database.sort(list, "customer.name desc nullsLow, id");
+   *
+   * }
+ * + * @param list the list of entity beans + * @param sortByClause the properties to sort the list by + */ + void sort(List list, String sortByClause); + + /** + * Create a orm update where you will supply the insert/update or delete + * statement (rather than using a named one that is already defined using the + * @NamedUpdates annotation). + *

+ * The orm update differs from the sql update in that it you can use the bean + * name and bean property names rather than table and column names. + *

+ *

+ * An example: + *

+ *

+ *

{@code
+   *
+   *   // The bean name and properties - "topic","postCount" and "id"
+   *
+   *   // will be converted into their associated table and column names
+   *   String updStatement = "update topic set postCount = :pc where id = :id";
+   *
+   *   Update update = database.createUpdate(Topic.class, updStatement);
+   *
+   *   update.set("pc", 9);
+   *   update.set("id", 3);
+   *
+   *   int rows = update.execute();
+   *   System.out.println("rows updated:" + rows);
+   *
+   * }
+ */ + Update createUpdate(Class beanType, String ormUpdate); + + /** + * Create a Query for DTO beans. + *

+ * DTO beans are just normal bean like classes with public constructor(s) and setters. + * They do not need to be registered with DB before use. + *

+ * + * @param dtoType The type of the DTO bean the rows will be mapped into. + * @param sql The SQL query to execute. + * @param The type of the DTO bean. + */ + DtoQuery findDto(Class dtoType, String sql); + + /** + * Create a named Query for DTO beans. + *

+ * DTO beans are just normal bean like classes with public constructor(s) and setters. + * They do not need to be registered with DB before use. + *

+ * + * @param dtoType The type of the DTO bean the rows will be mapped into. + * @param namedQuery The name of the query + * @param The type of the DTO bean. + */ + DtoQuery createNamedDtoQuery(Class dtoType, String namedQuery); + + /** + * Look to execute a native sql query that does not returns beans but instead + * returns SqlRow or direct access to ResultSet (see {@link SqlQuery#findList(RowMapper)}. + * + *

+ * Refer to {@link DtoQuery} for native sql queries returning DTO beans. + *

+ *

+ * Refer to {@link #findNative(Class, String)} for native sql queries returning entity beans. + *

+ */ + SqlQuery sqlQuery(String sql); + + /** + * This is an alias for {@link #sqlQuery(String)}. + */ + SqlQuery createSqlQuery(String sql); + + /** + * Look to execute a native sql insert update or delete statement. + *

+ * Use this to execute a Insert Update or Delete statement. The statement will + * be native to the database and contain database table and column names. + *

+ * + *

+ * See {@link SqlUpdate} for example usage. + *

+ * + * @return The SqlUpdate instance to set parameters and execute + */ + SqlUpdate sqlUpdate(String sql); + + /** + * This is an alias for {@link #sqlUpdate(String)}. + */ + SqlUpdate createSqlUpdate(String sql); + + /** + * Create a CallableSql to execute a given stored procedure. + */ + CallableSql createCallableSql(String callableSql); + + /** + * Register a TransactionCallback on the currently active transaction. + *

+ * If there is no currently active transaction then a PersistenceException is thrown. + * + * @param transactionCallback The transaction callback to be registered with the current transaction. + * @throws PersistenceException If there is no currently active transaction + */ + void register(TransactionCallback transactionCallback) throws PersistenceException; + + /** + * Create a new transaction that is not held in TransactionThreadLocal. + *

+ * You will want to do this if you want multiple Transactions in a single + * thread or generally use transactions outside of the TransactionThreadLocal + * management. + *

+ */ + Transaction createTransaction(); + + /** + * Create a new transaction additionally specifying the isolation level. + *

+ * Note that this transaction is NOT stored in a thread local. + *

+ */ + Transaction createTransaction(TxIsolation isolation); + + /** + * Start a transaction with 'REQUIRED' semantics. + *

+ * With REQUIRED semantics if an active transaction already exists that transaction will be used. + *

+ *

+ * The transaction is stored in a ThreadLocal variable and typically you only + * need to use the returned Transaction IF you wish to do things like + * use batch mode, change the transaction isolation level, use savepoints or + * log comments to the transaction log. + *

+ *

+ * Example of using a transaction to span multiple calls to find(), save() + * etc. + *

+ *

+ *

Using try with resources

+ *
{@code
+   *
+   *    // start a transaction (stored in a ThreadLocal)
+   *
+   *    try (Transaction txn = database.beginTransaction()) {
+   *
+   * 	    Order order = database.find(Order.class, 10);
+   * 	    ...
+   * 	    database.save(order);
+   *
+   * 	    txn.commit();
+   *    }
+   *
+   * }
+ *

+ *

Using try finally block

+ *
{@code
+   *
+   *    // start a transaction (stored in a ThreadLocal)
+   *    Transaction txn = database.beginTransaction();
+   *    try {
+   * 	    Order order = database.find(Order.class,10);
+   *
+   * 	    database.save(order);
+   *
+   * 	    txn.commit();
+   *
+   *    } finally {
+   * 	    txn.end();
+   *    }
+   *
+   * }
+ *

+ *

Transaction options

+ *
{@code
+   *
+   *     try (Transaction txn = database.beginTransaction()) {
+   *
+   *       // explicitly turn on/off JDBC batch use
+   *       txn.setBatchMode(true);
+   *       txn.setBatchSize(50);
+   *
+   *       // control flushing when mixing save and queries
+   *       txn.setBatchFlushOnQuery(false);
+   *
+   *       // turn off persist cascade if needed
+   *       txn.setPersistCascade(false);
+   *
+   *       // for large batch insert processing when we do not
+   *       // ... need the generatedKeys, don't get them
+   *       txn.setBatchGetGeneratedKeys(false);
+   *
+   *       // explicitly flush the JDBC batch buffer
+   *       txn.flush();
+   *
+   *       ...
+   *
+   *       txn.commit();
+   *    }
+   *
+   * }
+ *

+ *

+ * If you want to externalise the transaction management then you use + * createTransaction() and pass the transaction around to the various methods on + * Database yourself. + *

+ */ + Transaction beginTransaction(); + + /** + * Start a transaction additionally specifying the isolation level. + */ + Transaction beginTransaction(TxIsolation isolation); + + /** + * Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics. + *

+ *

+ * Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or + * {@link #execute(TxScope, Runnable)}. + *

+ *

+ *

REQUIRES_NEW example:

+ *
{@code
+   * // Start a new transaction. If there is a current transaction
+   * // suspend it until this transaction ends
+   * try (Transaction txn = database.beginTransaction(TxScope.requiresNew())) {
+   *
+   *   ...
+   *
+   *   // commit the transaction
+   *   txn.commit();
+   *
+   *   // At end this transaction will:
+   *   //  A) will rollback transaction if it has not been committed
+   *   //  B) will restore a previously suspended transaction
+   * }
+   *
+   * }
+ *

+ *

REQUIRED example:

+ *
{@code
+   *
+   * // start a new transaction if there is not a current transaction
+   * try (Transaction txn = database.beginTransaction(TxScope.required())) {
+   *
+   *   ...
+   *
+   *   // commit the transaction if it was created or
+   *   // do nothing if there was already a current transaction
+   *   txn.commit();
+   * }
+   *
+   * }
+ */ + Transaction beginTransaction(TxScope scope); + + /** + * Returns the current transaction or null if there is no current transaction in scope. + */ + Transaction currentTransaction(); + + /** + * Flush the JDBC batch on the current transaction. + *

+ * This only is useful when JDBC batch is used. Flush occurs automatically when the + * transaction commits or batch size is reached. This manually flushes the JDBC batch + * buffer. + *

+ *

+ * This is the same as currentTransaction().flush(). + *

+ */ + void flush(); + + /** + * Commit the current transaction. + */ + void commitTransaction(); + + /** + * Rollback the current transaction. + */ + 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. + *

+ * Note that this resets OneToMany and ManyToMany properties so that if they + * are accessed a lazy load will refresh the many property. + *

+ */ + void refresh(Object bean); + + /** + * Refresh a many property of an entity bean. + * + * @param bean the entity bean containing the 'many' property + * @param propertyName the 'many' property to be refreshed + */ + void refreshMany(Object bean, String propertyName); + + /** + * Find a bean using its unique id. + *

+ *

{@code
+   *   // Fetch order 1
+   *   Order order = database.find(Order.class, 1);
+   * }
+ *

+ *

+ * If you want more control over the query then you can use createQuery() and + * Query.findOne(); + *

+ *

+ *

{@code
+   *   // ... additionally fetching customer, customer shipping address,
+   *   // order details, and the product associated with each order detail.
+   *   // note: only product id and name is fetch (its a "partial object").
+   *   // note: all other objects use "*" and have all their properties fetched.
+   *
+   *   Query query = database.find(Order.class)
+   *     .setId(1)
+   *     .fetch("customer")
+   *     .fetch("customer.shippingAddress")
+   *     .fetch("details")
+   *     .query();
+   *
+   *   // fetch associated products but only fetch their product id and name
+   *   query.fetch("details.product", "name");
+   *
+   *
+   *   Order order = query.findOne();
+   *
+   *   // traverse the object graph...
+   *
+   *   Customer customer = order.getCustomer();
+   *   Address shippingAddress = customer.getShippingAddress();
+   *   List details = order.getDetails();
+   *   OrderDetail detail0 = details.get(0);
+   *   Product product = detail0.getProduct();
+   *   String productName = product.getName();
+   *
+   * }
+ * + * @param beanType the type of entity bean to fetch + * @param id the id value + */ + @Nullable + T find(Class beanType, Object id); + + /** + * Get a reference object. + *

+ * This will not perform a query against the database unless some property other + * that the id property is accessed. + *

+ *

+ * It is most commonly used to set a 'foreign key' on another bean like: + *

+ *
{@code
+   *
+   *   Product product = database.getReference(Product.class, 1);
+   *
+   *   OrderDetail orderDetail = new OrderDetail();
+   *   // set the product 'foreign key'
+   *   orderDetail.setProduct(product);
+   *   orderDetail.setQuantity(42);
+   *   ...
+   *
+   *   database.save(orderDetail);
+   *
+   *
+   * }
+ *

+ *

Lazy loading characteristics

+ *
{@code
+   *
+   *   Product product = database.getReference(Product.class, 1);
+   *
+   *   // You can get the id without causing a fetch/lazy load
+   *   Long productId = product.getId();
+   *
+   *   // If you try to get any other property a fetch/lazy loading will occur
+   *   // This will cause a query to execute...
+   *   String name = product.getName();
+   *
+   * }
+ * + * @param beanType the type of entity bean + * @param id the id value + */ + @Nonnull + T getReference(Class beanType, Object id); + + /** + * Return the extended API for Database. + *

+ * The extended API has the options for executing queries that take an explicit + * transaction as an argument. + *

+ *

+ * Typically we only need to use the extended API when we do NOT want to use the + * usual ThreadLocal based mechanism to obtain the current transaction but instead + * supply the transaction explicitly. + *

+ */ + ExtendedServer extended(); + + /** + * Either Insert or Update the bean depending on its state. + *

+ * If there is no current transaction one will be created and committed for + * you automatically. + *

+ *

+ * Save can cascade along relationships. For this to happen you need to + * specify a cascade of CascadeType.ALL or CascadeType.PERSIST on the + * OneToMany, OneToOne or ManyToMany annotation. + *

+ *

+ * In this example below the details property has a CascadeType.ALL set so + * saving an order will also save all its details. + *

+ *

+ *

{@code
+   *   public class Order { ...
+   *
+   * 	   @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
+   * 	   List details;
+   * 	   ...
+   *   }
+   * }
+ *

+ *

+ * When a save cascades via a OneToMany or ManyToMany Ebean will automatically + * set the 'parent' object to the 'detail' object. In the example below in + * saving the order and cascade saving the order details the 'parent' order + * will be set against each order detail when it is saved. + *

+ */ + void save(Object bean) throws OptimisticLockException; + + /** + * Save all the beans in the collection. + */ + int saveAll(Collection beans) throws OptimisticLockException; + + /** + * Delete the bean. + *

+ * This will return true if the bean was deleted successfully or JDBC batch is being used. + *

+ *

+ * If there is no current transaction one will be created and committed for + * you automatically. + *

+ *

+ * If the Bean does not have a version property (or loaded version property) and + * the bean does not exist then this returns false indicating that nothing was + * deleted. Note that, if JDBC batch mode is used then this always returns true. + *

+ */ + boolean delete(Object bean) throws OptimisticLockException; + + /** + * Delete the bean with an explicit transaction. + *

+ * This will return true if the bean was deleted successfully or JDBC batch is being used. + *

+ *

+ * If the Bean does not have a version property (or loaded version property) and + * the bean does not exist then this returns false indicating that nothing was + * deleted. However, if JDBC batch mode is used then this always returns true. + *

+ */ + boolean delete(Object bean, Transaction transaction) throws OptimisticLockException; + + /** + * Delete a bean permanently without soft delete. + */ + boolean deletePermanent(Object bean) throws OptimisticLockException; + + /** + * Delete a bean permanently without soft delete using an explicit transaction. + */ + boolean deletePermanent(Object bean, Transaction transaction) throws OptimisticLockException; + + /** + * Delete all the beans in the collection permanently without soft delete. + */ + int deleteAllPermanent(Collection beans) throws OptimisticLockException; + + /** + * Delete all the beans in the collection permanently without soft delete using an explicit transaction. + */ + int deleteAllPermanent(Collection beans, Transaction transaction) throws OptimisticLockException; + + /** + * Delete the bean given its type and id. + */ + int delete(Class beanType, Object id); + + /** + * Delete the bean given its type and id with an explicit transaction. + */ + int delete(Class beanType, Object id, Transaction transaction); + + /** + * Delete permanent given the bean type and id. + */ + int deletePermanent(Class beanType, Object id); + + /** + * Delete permanent given the bean type and id with an explicit transaction. + */ + int deletePermanent(Class beanType, Object id, Transaction transaction); + + /** + * Delete all the beans in the collection. + */ + int deleteAll(Collection beans) throws OptimisticLockException; + + /** + * Delete all the beans in the collection using an explicit transaction. + */ + int deleteAll(Collection beans, Transaction transaction) throws OptimisticLockException; + + /** + * Delete several beans given their type and id values. + */ + int deleteAll(Class beanType, Collection ids); + + /** + * Delete several beans given their type and id values with an explicit transaction. + */ + int deleteAll(Class beanType, Collection ids, Transaction transaction); + + /** + * Delete permanent for several beans given their type and id values. + */ + int deleteAllPermanent(Class beanType, Collection ids); + + /** + * Delete permanent for several beans given their type and id values with an explicit transaction. + */ + int deleteAllPermanent(Class beanType, Collection ids, Transaction transaction); + + /** + * Execute a Sql Update Delete or Insert statement. This returns the number of + * rows that where updated, deleted or inserted. If is executed in batch then + * this returns -1. You can get the actual rowCount after commit() from + * updateSql.getRowCount(). + *

+ * If you wish to execute a Sql Select natively then you should use the + * SqlQuery object or DtoQuery. + *

+ *

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

+ *

+ * Example: + *

+ *

+ *

{@code
+   *
+   *   // example that uses 'named' parameters
+   *   String s = "UPDATE f_topic set post_count = :count where id = :id"
+   *
+   *   SqlUpdate update = database.createSqlUpdate(s);
+   *
+   *   update.setParameter("id", 1);
+   *   update.setParameter("count", 50);
+   *
+   *   int modifiedCount = database.execute(update);
+   *
+   *   String msg = "There where " + modifiedCount + "rows updated";
+   *
+   * }
+ * + * @param sqlUpdate the update sql potentially with bind values + * @return the number of rows updated or deleted. -1 if executed in batch. + * @see CallableSql + */ + int execute(SqlUpdate sqlUpdate); + + /** + * Execute a ORM insert update or delete statement using the current + * transaction. + *

+ * This returns the number of rows that where inserted, updated or deleted. + *

+ */ + int execute(Update update); + + /** + * Execute a ORM insert update or delete statement with an explicit + * transaction. + */ + int execute(Update update, Transaction transaction); + + /** + * For making calls to stored procedures. + *

+ * Example: + *

+ *

+ *

{@code
+   *
+   *   String sql = "{call sp_order_modify(?,?,?)}";
+   *
+   *   CallableSql cs = database.createCallableSql(sql);
+   *   cs.setParameter(1, 27);
+   *   cs.setParameter(2, "SHIPPED");
+   *   cs.registerOut(3, Types.INTEGER);
+   *   cs.execute();
+   *
+   *   // read the out parameter
+   *   Integer returnValue = (Integer) cs.getObject(3);
+   *
+   * }
+ * + */ + int execute(CallableSql callableSql); + + /** + * Inform Ebean that tables have been modified externally. These could be the + * result of from calling a stored procedure, other JDBC calls or external + * programs including other frameworks. + *

+ * If you use database.execute(UpdateSql) then the table modification information + * is automatically deduced and you do not need to call this method yourself. + *

+ *

+ * This information is used to invalidate objects out of the cache and + * potentially text indexes. This information is also automatically broadcast + * across the cluster. + *

+ *

+ * If there is a transaction then this information is placed into the current + * transactions event information. When the transaction is committed this + * information is registered (with the transaction manager). If this + * transaction is rolled back then none of the transaction event information + * registers including the information you put in via this method. + *

+ *

+ * If there is NO current transaction when you call this method then this + * information is registered immediately (with the transaction manager). + *

+ * + * @param tableName the name of the table that was modified + * @param inserted true if rows where inserted into the table + * @param updated true if rows on the table where updated + * @param deleted true if rows on the table where deleted + */ + void externalModification(String tableName, boolean inserted, boolean updated, boolean deleted); + + /** + * Find a entity bean with an explicit transaction. + * + * @param the type of entity bean to find + * @param beanType the type of entity bean to find + * @param id the bean id value + * @param transaction the transaction to use (can be null) + */ + T find(Class beanType, Object id, Transaction transaction); + + /** + * Insert or update a bean with an explicit transaction. + */ + void save(Object bean, Transaction transaction) throws OptimisticLockException; + + /** + * Save all the beans in the collection with an explicit transaction. + */ + int saveAll(Collection beans, Transaction transaction) throws OptimisticLockException; + + /** + * This method checks the uniqueness of a bean. I.e. if the save will work. It will return the + * properties that violates an unique / primary key. This may be done in an UI save action to + * validate if the user has entered correct values. + *

+ * Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update. + *

+ * Note: This checks only the root bean! + *

+ *

{@code
+   *
+   *   // there is a unique constraint on title
+   *
+   *   Document doc = new Document();
+   *   doc.setTitle("One flew over the cuckoo's nest");
+   *   doc.setBody("clashes with doc1");
+   *
+   *   Set properties = DB.checkUniqueness(doc);
+   *
+   *   if (properties.isEmpty()) {
+   *     // it is unique ... carry on
+   *
+   *   } else {
+   *     // build a user friendly message
+   *     // to return message back to user
+   *
+   *     String uniqueProperties = properties.toString();
+   *
+   *     StringBuilder msg = new StringBuilder();
+   *
+   *     properties.forEach((it)-> {
+   *       Object propertyValue = it.getVal(doc);
+   *       String propertyName = it.getName();
+   *       msg.append(" property["+propertyName+"] value["+propertyValue+"]");
+   *     });
+   *
+   *     // uniqueProperties > [title]
+   *     //       custom msg > property[title] value[One flew over the cuckoo's nest]
+   *
+   *  }
+   *
+   * }
+ * + * @param bean The entity bean to check uniqueness on + * @return a set of Properties if constraint validation was detected or empty list. + */ + @Nonnull + Set checkUniqueness(Object bean); + + /** + * Same as {@link #checkUniqueness(Object)}. but with given transaction. + */ + @Nonnull + Set checkUniqueness(Object bean, Transaction transaction); + + /** + * Marks the entity bean as dirty. + *

+ * This is used so that when a bean that is otherwise unmodified is updated the version + * property is updated. + *

+ * An unmodified bean that is saved or updated is normally skipped and this marks the bean as + * dirty so that it is not skipped. + *

+ *

{@code
+   *
+   * Customer customer = database.find(Customer, id);
+   *
+   * // mark the bean as dirty so that a save() or update() will
+   * // increment the version property
+   * database.markAsDirty(customer);
+   * database.save(customer);
+   *
+   * }
+ */ + void markAsDirty(Object bean); + + /** + * Saves the bean using an update. If you know you are updating a bean then it is preferable to + * use this update() method rather than save(). + *

+ * Stateless updates: Note that the bean does not have to be previously fetched to call + * update().You can create a new instance and set some of its properties programmatically for via + * JSON/XML marshalling etc. This is described as a 'stateless update'. + *

+ *

+ * Optimistic Locking: Note that if the version property is not set when update() is + * called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used). + *

+ *

+ * {@link ServerConfig#setUpdatesDeleteMissingChildren(boolean)}: When cascade saving to a + * OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children + * that are in the database but are not in the collection are deleted. + *

+ *

+ * {@link ServerConfig#setUpdateChangesOnly(boolean)}: The updateChangesOnly setting + * controls if only the changed properties are included in the update or if all the loaded + * properties are included instead. + *

+ *

+ *

{@code
+   *
+   * // A 'stateless update' example
+   * Customer customer = new Customer();
+   * customer.setId(7);
+   * customer.setName("ModifiedNameNoOCC");
+   * database.update(customer);
+   *
+   * }
+ * + * @see ServerConfig#setUpdatesDeleteMissingChildren(boolean) + * @see ServerConfig#setUpdateChangesOnly(boolean) + */ + void update(Object bean) throws OptimisticLockException; + + /** + * Update a bean additionally specifying a transaction. + */ + void update(Object bean, Transaction transaction) throws OptimisticLockException; + + /** + * Update a bean additionally specifying a transaction and the deleteMissingChildren setting. + * + * @param bean the bean to update + * @param transaction the transaction to use (can be null). + * @param deleteMissingChildren specify false if you do not want 'missing children' of a OneToMany + * or ManyToMany to be automatically deleted. + */ + void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException; + + /** + * Update a collection of beans. If there is no current transaction one is created and used to + * update all the beans in the collection. + */ + void updateAll(Collection beans) throws OptimisticLockException; + + /** + * Update a collection of beans with an explicit transaction. + */ + void updateAll(Collection beans, Transaction transaction) throws OptimisticLockException; + + /** + * Merge the bean using the default merge options (no paths specified, default delete). + * + * @param bean The bean to merge + */ + void merge(Object bean); + + /** + * Merge the bean using the given merge options. + * + * @param bean The bean to merge + * @param options The options to control the merge + */ + void merge(Object bean, MergeOptions options); + + /** + * Merge the bean using the given merge options and a transaction. + * + * @param bean The bean to merge + * @param options The options to control the merge + */ + void merge(Object bean, MergeOptions options, Transaction transaction); + + /** + * Insert the bean. + *

+ * Compared to save() this forces bean to perform an insert rather than trying to decide + * based on the bean state. As such this is useful when you fetch beans from one database + * and want to insert them into another database (and you want to explicitly insert them). + *

+ */ + void insert(Object bean); + + /** + * Insert the bean with a transaction. + */ + void insert(Object bean, 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 a collection of beans with an explicit transaction. + */ + void insertAll(Collection beans, Transaction transaction); + + /** + * Execute explicitly passing a transaction. + */ + int execute(SqlUpdate updSql, Transaction transaction); + + /** + * Execute explicitly passing a transaction. + */ + int execute(CallableSql callableSql, Transaction transaction); + + /** + * Execute a Runnable in a Transaction with an explicit scope. + *

+ * The scope can control the transaction type, isolation and rollback + * semantics. + *

+ *

+ *

{@code
+   *
+   *   // set specific transactional scope settings
+   *   TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
+   *
+   *   database.execute(scope, new Runnable() {
+   * 	   public void run() {
+   * 		   User u1 = database.find(User.class, 1);
+   * 		   ...
+   * 	   }
+   *   });
+   *
+   * }
+ */ + void execute(TxScope scope, Runnable runnable); + + /** + * Execute a Runnable in a Transaction with the default scope. + *

+ * The default scope runs with REQUIRED and by default will rollback on any + * exception (checked or runtime). + *

+ *

+ *

{@code
+   *
+   *    database.execute(() -> {
+   *
+   *        User u1 = database.find(User.class, 1);
+   *        User u2 = database.find(User.class, 2);
+   *
+   *        u1.setName("u1 mod");
+   *        u2.setName("u2 mod");
+   *
+   *        u1.save();
+   *        u2.save();
+   *    });
+   *
+   * }
+ */ + void execute(Runnable runnable); + + /** + * Execute a TxCallable in a Transaction with an explicit scope. + *

+ * The scope can control the transaction type, isolation and rollback + * semantics. + *

+ *

+ *

{@code
+   *
+   *   // set specific transactional scope settings
+   *   TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
+   *
+   *   database.executeCall(scope, new Callable() {
+   * 	   public String call() {
+   * 		   User u1 = database.find(User.class, 1);
+   * 		   ...
+   * 		   return u1.getEmail();
+   * 	   }
+   *   });
+   *
+   * }
+ */ + T executeCall(TxScope scope, Callable callable); + + /** + * Execute a TxCallable in a Transaction with the default scope. + *

+ * The default scope runs with REQUIRED and by default will rollback on any + * exception (checked or runtime). + *

+ *

+ *

{@code
+   *
+   *   database.executeCall(new Callable() {
+   *     public String call() {
+   *       User u1 = database.find(User.class, 1);
+   *       User u2 = database.find(User.class, 2);
+   *
+   *       u1.setName("u1 mod");
+   *       u2.setName("u2 mod");
+   *
+   *       database.save(u1);
+   *       database.save(u2);
+   *
+   *       return u1.getEmail();
+   *     }
+   *   });
+   *
+   * }
+ */ + T executeCall(Callable callable); + + /** + * Return the manager of the server cache ("L2" cache). + */ + ServerCacheManager getServerCacheManager(); + + /** + * Return the BackgroundExecutor service for asynchronous processing of + * queries. + */ + BackgroundExecutor getBackgroundExecutor(); + + /** + * Return the JsonContext for reading/writing JSON. + *

+ * This instance is safe to be used concurrently by multiple threads and this + * method is cheap to call. + *

+ *

+ *

Simple example:

+ *
{@code
+   *
+   *     JsonContext json = database.json();
+   *     String jsonOutput = json.toJson(list);
+   *     System.out.println(jsonOutput);
+   *
+   * }
+ *

+ *

Using PathProperties:

+ *
{@code
+   *
+   *     // specify just the properties we want
+   *     PathProperties paths = PathProperties.parse("name, status, anniversary");
+   *
+   *     List customers =
+   *       database.find(Customer.class)
+   *         // apply those paths to the query (only fetch what we need)
+   *         .apply(paths)
+   *         .where().ilike("name", "rob%")
+   *         .findList();
+   *
+   *     // ... get the json
+   *     JsonContext jsonContext = database.json();
+   *     String json = jsonContext.toJson(customers, paths);
+   *
+   * }
+ * + * @see FetchPath + * @see Query#apply(FetchPath) + */ + JsonContext json(); + + /** + * Return a ScriptRunner for running SQL or DDL scripts. + *

+ * Intended to use mostly in testing to run seed SQL scripts or truncate table scripts etc. + */ + ScriptRunner script(); + + /** + * Return the Document store. + */ + DocumentStore docStore(); + + /** + * Publish a single bean given its type and id returning the resulting live bean. + *

+ * The values are published from the draft to the live bean. + *

+ * + * @param the type of the entity bean + * @param beanType the type of the entity bean + * @param id the id of the entity bean + * @param transaction the transaction the publish process should use (can be null) + */ + T publish(Class beanType, Object id, Transaction transaction); + + /** + * Publish a single bean given its type and id returning the resulting live bean. + * This will use the current transaction or create one if required. + *

+ * The values are published from the draft to the live bean. + *

+ * + * @param the type of the entity bean + * @param beanType the type of the entity bean + * @param id the id of the entity bean + */ + T publish(Class beanType, Object id); + + /** + * Publish the beans that match the query returning the resulting published beans. + *

+ * The values are published from the draft beans to the live beans. + *

+ * + * @param the type of the entity bean + * @param query the query used to select the draft beans to publish + * @param transaction the transaction the publish process should use (can be null) + */ + List publish(Query query, Transaction transaction); + + /** + * Publish the beans that match the query returning the resulting published beans. + * This will use the current transaction or create one if required. + *

+ * The values are published from the draft beans to the live beans. + *

+ * + * @param the type of the entity bean + * @param query the query used to select the draft beans to publish + */ + List publish(Query query); + + /** + * Restore the draft bean back to the live state. + *

+ * The values from the live beans are set back to the draft bean and the + * @DraftDirty and @DraftReset properties are reset. + *

+ * + * @param the type of the entity bean + * @param beanType the type of the entity bean + * @param id the id of the entity bean to restore + * @param transaction the transaction the restore process should use (can be null) + */ + T draftRestore(Class beanType, Object id, Transaction transaction); + + /** + * Restore the draft bean back to the live state. + *

+ * The values from the live beans are set back to the draft bean and the + * @DraftDirty and @DraftReset properties are reset. + *

+ * + * @param the type of the entity bean + * @param beanType the type of the entity bean + * @param id the id of the entity bean to restore + */ + T draftRestore(Class beanType, Object id); + + /** + * Restore the draft beans matching the query back to the live state. + *

+ * The values from the live beans are set back to the draft bean and the + * @DraftDirty and @DraftReset properties are reset. + *

+ * + * @param the type of the entity bean + * @param query the query used to select the draft beans to restore + * @param transaction the transaction the restore process should use (can be null) + */ + List draftRestore(Query query, Transaction transaction); + + /** + * Restore the draft beans matching the query back to the live state. + *

+ * The values from the live beans are set back to the draft bean and the + * @DraftDirty and @DraftReset properties are reset. + *

+ * + * @param the type of the entity bean + * @param query the query used to select the draft beans to restore + */ + List draftRestore(Query query); + + /** + * Returns the set of properties/paths that are unknown (do not map to known properties or paths). + *

+ * Validate the query checking the where and orderBy expression paths to confirm if + * they represent valid properties/path for the given bean type. + *

+ */ + Set validateQuery(Query query); + + /** + * Truncate all the given tables. + */ + void truncate(String... tables); + + /** + * Truncate the base tables for the given bean types. + */ + void truncate(Class... tables); + +} diff --git a/src/main/java/io/ebean/DatabaseFactory.java b/src/main/java/io/ebean/DatabaseFactory.java new file mode 100644 index 000000000..d050c7e24 --- /dev/null +++ b/src/main/java/io/ebean/DatabaseFactory.java @@ -0,0 +1,67 @@ +package io.ebean; + +import io.ebean.config.ContainerConfig; +import io.ebean.config.DatabaseConfig; + +/** + * Creates Database instances. + *

+ * This uses either DatabaseConfig or properties in the application.properties file to + * configure and create a Database instance. + *

+ *

+ * The Database instance can either be registered with the DB singleton or + * not. The DB singleton effectively holds a map of Database by a name. + * If the Database is registered with the DB singleton you can retrieve it + * later via {@link DB#byName(String)}. + *

+ *

+ * One Database can be nominated as the 'default/primary' Database. Many + * methods on the DB singleton such as {@link DB#find(Class)} are just a + * convenient way of using the 'default/primary' Database. + *

+ */ +public class DatabaseFactory { + + /** + * Initialise the container with clustering configuration. + *

+ * Call this prior to creating any Database instances or alternatively set the + * ContainerConfig on the ServerConfig when creating the first Database instance. + */ + public static synchronized void initialiseContainer(ContainerConfig containerConfig) { + EbeanServerFactory.initialiseContainer(containerConfig); + } + + /** + * Create using ebean.properties to configure the server. + */ + public static synchronized Database create(String name) { + return EbeanServerFactory.create(name); + } + + /** + * Create using the ServerConfig object to configure the server. + */ + public static synchronized Database create(DatabaseConfig config) { + return EbeanServerFactory.create(config); + } + + /** + * Create using the ServerConfig additionally specifying a classLoader to use as the context class loader. + */ + public static synchronized Database createWithContextClassLoader(DatabaseConfig config, ClassLoader classLoader) { + return EbeanServerFactory.createWithContextClassLoader(config, classLoader); + } + + /** + * Shutdown gracefully all Database instances cleaning up any resources as required. + *

+ * This is typically invoked via JVM shutdown hook and not explicitly called. + *

+ */ + public static synchronized void shutdown() { + EbeanServerFactory.shutdown(); + } + +} diff --git a/src/main/java/io/ebean/DtoQuery.java b/src/main/java/io/ebean/DtoQuery.java new file mode 100644 index 000000000..61f7f3d86 --- /dev/null +++ b/src/main/java/io/ebean/DtoQuery.java @@ -0,0 +1,139 @@ +package io.ebean; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * Query for performing native SQL queries that return DTO Bean's. + *

+ * These beans are just normal classes. They must have public constructors + * and setters. + *

+ * Constructors with arguments are used if the number of constructor arguments + * matches the number of columns in the resultSet. + *

+ *

+ * If the number of columns in the resultSet is greater than the largest constructor + * then the largest constructor is used for the first columns and remaining columns + * are mapped by setter methods. + *

+ * + *
{@code
+ *
+ *   // CustomerDto is just a 'bean like' class
+ *   // with public constructor(s) and public setter methods
+ *
+ *   String sql = "select id, name from customer where name like :name and status_code = :status";
+ *
+ *   List beans =
+ *     DB.findDto(CustomerDto.class, sql)
+ *     .setParameter("name", "Acme%")
+ *     .setParameter("status", "ACTIVE")
+ *     .findList();
+ *
+ * }
+ */ +public interface DtoQuery { + + /** + * Execute the query returning a list. + */ + @Nonnull + List findList(); + + /** + * Execute the query iterating a row at a time. + *

+ * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. + *

+ */ + void findEach(Consumer consumer); + + /** + * Execute the query iterating a row at a time with the ability to stop consuming part way through. + *

+ * Returning false after processing a row stops the iteration through the query results. + *

+ *

+ * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. + *

+ */ + void findEachWhile(Predicate consumer); + + /** + * Execute the query returning a single bean. + */ + @Nullable + T findOne(); + + /** + * Execute the query returning an optional bean. + */ + @Nonnull + Optional findOneOrEmpty(); + + /** + * The same as bind for named parameters. + */ + DtoQuery setParameter(String name, Object value); + + /** + * The same as bind for positioned parameters. + */ + DtoQuery setParameter(int position, Object value); + + /** + * Set the index of the first row of the results to return. + */ + DtoQuery setFirstRow(int firstRow); + + /** + * Set the maximum number of query results to return. + */ + DtoQuery setMaxRows(int maxRows); + + /** + * When resultSet columns are not able to be mapped to a bean property then instead of + * throwing effectively skip reading that column. + */ + DtoQuery setRelaxedMode(); + + /** + * Set a label on the query to make it easier to identify queries related to query execution statistics. + * + * @param label A label that is unique to the DTO bean type. + */ + DtoQuery setLabel(String label); + + /** + * Set the profile location of this query. This is used to relate query execution metrics + * back to a location like a specific line of code. + */ + DtoQuery setProfileLocation(ProfileLocation profileLocation); + + /** + * Set a timeout on this query. + *

+ * This will typically result in a call to setQueryTimeout() on a + * preparedStatement. If the timeout occurs an exception will be thrown - this + * will be a SQLException wrapped up in a PersistenceException. + *

+ * + * @param secs the query timeout limit in seconds. Zero means there is no limit. + */ + DtoQuery setTimeout(int secs); + + /** + * A hint which for JDBC translates to the Statement.fetchSize(). + *

+ * Gives the JDBC driver a hint as to the number of rows that should be + * fetched from the database when more rows are needed for ResultSet. + *

+ */ + DtoQuery setBufferFetchSizeHint(int bufferFetchSizeHint); + +} diff --git a/src/main/java/io/ebean/Ebean.java b/src/main/java/io/ebean/Ebean.java index edb4a5d6f..2fc69a7bf 100644 --- a/src/main/java/io/ebean/Ebean.java +++ b/src/main/java/io/ebean/Ebean.java @@ -2,126 +2,48 @@ package io.ebean; import io.ebean.annotation.TxIsolation; import io.ebean.cache.ServerCacheManager; +import io.ebean.config.BeanNotEnhancedException; import io.ebean.config.ServerConfig; +import io.ebean.datasource.DataSourceConfigurationException; +import io.ebean.plugin.Property; import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; -import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; /** - * This Ebean object is effectively a singleton that holds a map of registered - * {@link EbeanServer}s. It additionally provides a convenient way to use the - * 'default' EbeanServer. + * Ebean is a registry of {@link Database} by name. Ebean has now been renamed to {@link DB}. *

- * If you are using a Dependency Injection framework such as - * Spring or Guice you will probably - * NOT use this Ebean singleton object. Instead you will - * configure and construct EbeanServer instances using {@link ServerConfig} and - * {@link EbeanServerFactory} and inject those EbeanServer instances into your - * data access objects. - *

+ * Ebean is effectively this is an alias for {@link DB} which is the new and improved name for Ebean. *

- * In documentation "Ebean singleton" refers to this object. - *

- *
    - *
  • There is one EbeanServer per Database (javax.sql.DataSource).
  • - *
  • EbeanServers can be 'registered' with the Ebean singleton (put into its - * map). Registered EbeanServer's can later be retrieved via - * {@link #getServer(String)}.
  • - *
  • One EbeanServer can be referred to as the 'default' EbeanServer. For - * convenience, the Ebean singleton (this object) provides methods such as - * {@link #find(Class)} that proxy through to the 'default' EbeanServer. This - * can be useful for applications that use a single database.
  • - *
- *

- * For developer convenience Ebean has static methods that proxy through to the - * methods on the 'default' EbeanServer. These methods are provided for - * developers who are mostly using a single database. Many developers will be - * able to use the methods on Ebean rather than get a EbeanServer. - *

- *

- * EbeanServers can be created and used without ever needing or using the Ebean - * singleton. Refer to {@link ServerConfig#setRegister(boolean)}. - *

- *

- * You can either programmatically create/register EbeanServers via - * {@link EbeanServerFactory} or they can automatically be created and - * registered when you first use the Ebean singleton. When EbeanServers are - * created automatically they are configured using information in the - * ebean.properties file. - *

- *
{@code
- *
- *   // fetch shipped orders (and also their customer)
- *   List list = Ebean.find(Order.class)
- * 	  .fetch("customer")
- * 	  .where()
- * 	  .eq("status.code", Order.Status.SHIPPED)
- * 	  .findList();
- *
- *   // read/use the order list ...
- *   for (Order order : list) {
- * 	   Customer customer = order.getCustomer();
- * 	   ...
- *   }
- *
- * }
- *
{@code
- *
- *   // fetch order 10, modify and save
- *   Order order = Ebean.find(Order.class, 10);
- *
- *   OrderStatus shipped = Ebean.getReference(OrderStatus.class,"SHIPPED");
- *   order.setStatus(shipped);
- *   order.setShippedDate(shippedDate);
- *   ...
- *
- *   // implicitly creates a transaction and commits
- *   Ebean.save(order);
- *
- * }
- *

- * When you have multiple databases and need access to a specific one the - * {@link #getServer(String)} method provides access to the EbeanServer for that - * specific database. - *

- *
{@code
- *
- *   // Get access to the Human Resources EbeanServer/Database
- *   EbeanServer hrDb = Ebean.getServer("hr");
- *
- *   // fetch contact 3 from the HR database
- *   Contact contact = hrDb.find(Contact.class, 3);
- *
- *   contact.setName("I'm going to change");
- *   ...
- *
- *   // save the contact back to the HR database
- *   hrDb.save(contact);
- *
- * }
+ * The preference is to use DB and Database rather than Ebean and EbeanServer. */ public final class Ebean { private static final Logger logger = LoggerFactory.getLogger(Ebean.class); + static { + EbeanVersion.getVersion(); // initialises the version class and logs the version. + } + /** - * Manages creation and cache of EbeanServers. + * Manages creation and cache of Databases. */ private static final Ebean.ServerManager serverMgr = new Ebean.ServerManager(); /** - * Helper class for managing fast and safe access and creation of - * EbeanServers. + * Helper class for managing fast and safe access and creation of Databases. */ private static final class ServerManager { @@ -131,29 +53,21 @@ public final class Ebean { private final ConcurrentHashMap concMap = new ConcurrentHashMap<>(); /** - * Cache for synchronized read, creation and put. Protected by the monitor - * object. + * Cache for synchronized read, creation and put. Protected by the monitor object. */ private final HashMap syncMap = new HashMap<>(); private final Object monitor = new Object(); /** - * The 'default' EbeanServer. + * The 'default' Database. */ private EbeanServer defaultServer; private ServerManager() { try { - // skipDefaultServer is set by EbeanServerFactory - // ... when it is creating the primaryServer - if (PrimaryServer.isSkip()) { - // primary server being created by EbeanServerFactory - // ... so we should not try and create it here - logger.debug("PrimaryServer.isSkip()"); - - } else { + if (!PrimaryServer.isSkip()) { // look to see if there is a default server defined String defaultName = PrimaryServer.getDefaultServerName(); logger.debug("defaultName:{}", defaultName); @@ -161,15 +75,24 @@ public final class Ebean { defaultServer = getWithCreate(defaultName.trim()); } } + } catch (BeanNotEnhancedException e) { + throw e; + + } catch (DataSourceConfigurationException e) { + String msg = "Configuration error creating DataSource for the default Database." + + " This typically means a missing application-test.yaml or missing ebean-test-config dependency." + + " See https://ebean.io/docs/trouble-shooting#datasource"; + throw new DataSourceConfigurationException(msg, e); + } catch (Throwable e) { - logger.error("Error trying to create the default EbeanServer", e); + logger.error("Error trying to create the default Database", e); throw new RuntimeException(e); } } private EbeanServer getDefaultServer() { if (defaultServer == null) { - String msg = "The default EbeanServer has not been defined?"; + String msg = "The default Database has not been defined?"; msg += " This is normally set via the ebean.datasource.default property."; msg += " Otherwise it should be registered programmatically via registerServer()"; throw new PersistenceException(msg); @@ -191,7 +114,7 @@ public final class Ebean { } /** - * Synchronized read, create and put of EbeanServers. + * Synchronized read, create and put of Databases. */ private EbeanServer getWithCreate(String name) { @@ -230,7 +153,7 @@ public final class Ebean { } /** - * Get the EbeanServer for a given DataSource. If name is null this will + * Get the Database for a given DataSource. If name is null this will * return the 'default' EbeanServer. *

* This is provided to access EbeanServer for databases other than the @@ -376,7 +299,7 @@ public final class Ebean { * Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or * {@link #execute(TxScope, Runnable)}. *

- * + *

*

REQUIRES_NEW example:

*
{@code
    * // Start a new transaction. If there is a current transaction
@@ -621,7 +544,8 @@ public final class Ebean {
    *   Customer customer = new Customer();
    *   customer.setId(7);
    *   customer.setName("ModifiedNameNoOCC");
-   *   ebeanServer.update(customer);
+   *
+   *   DB.update(customer);
    *
    * }
* @@ -639,6 +563,25 @@ public final class Ebean { serverMgr.getDefaultServer().updateAll(beans); } + /** + * Merge the bean using the default merge options. + * + * @param bean The bean to merge + */ + public static void merge(Object bean) { + serverMgr.getDefaultServer().merge(bean); + } + + /** + * Merge the bean using the given merge options. + * + * @param bean The bean to merge + * @param options The options to control the merge + */ + public static void merge(Object bean, MergeOptions options) { + serverMgr.getDefaultServer().merge(bean, options); + } + /** * Save all the beans from a Collection. */ @@ -646,6 +589,65 @@ public final class Ebean { return serverMgr.getDefaultServer().saveAll(beans); } + /** + * This method checks the uniqueness of a bean. I.e. if the save will work. It will return the + * properties that violates an unique / primary key. This may be done in an UI save action to + * validate if the user has entered correct values. + *

+ * Note: This method queries the DB for uniqueness of all indices, so do not use it in a batch update. + *

+ * Note: This checks only the root bean! + *

+ *

{@code
+   *
+   *   // there is a unique constraint on title
+   *
+   *   Document doc = new Document();
+   *   doc.setTitle("One flew over the cuckoo's nest");
+   *   doc.setBody("clashes with doc1");
+   *
+   *   Set properties = server().checkUniqueness(doc);
+   *
+   *   if (properties.isEmpty()) {
+   *     // it is unique ... carry on
+   *
+   *   } else {
+   *     // build a user friendly message
+   *     // to return message back to user
+   *
+   *     String uniqueProperties = properties.toString();
+   *
+   *     StringBuilder msg = new StringBuilder();
+   *
+   *     properties.forEach((it)-> {
+   *       Object propertyValue = it.getVal(doc);
+   *       String propertyName = it.getName();
+   *       msg.append(" property["+propertyName+"] value["+propertyValue+"]");
+   *     });
+   *
+   *     // uniqueProperties > [title]
+   *     //       custom msg > property[title] value[One flew over the cuckoo's nest]
+   *
+   *  }
+   *
+   * }
+ * + * @param bean The entity bean to check uniqueness on + * @return a set of Properties if constraint validation was detected or empty list. + */ + @Nonnull + public static Set checkUniqueness(Object bean) { + return serverMgr.getDefaultServer().checkUniqueness(bean); + } + + /** + * Same as {@link #checkUniqueness(Object)}. but with given transaction. + */ + @Nonnull + public static Set checkUniqueness(Object bean, Transaction transaction) { + return serverMgr.getDefaultServer().checkUniqueness(bean, transaction); + } + /** * Delete the bean. *

@@ -1043,10 +1045,9 @@ public final class Ebean { * * String sql = "select c.id, c.name from customer c where c.name like ? order by c.name"; * - * Query query = ebeanServer.findNative(Customer.class, sql); - * query.setParameter(1, "Rob%"); - * - * List customers = query.findList(); + * List customers = DB.findNative(Customer.class, sql) + * .setParameter(1, "Rob%") + * .findList() * * } * @@ -1058,6 +1059,21 @@ public final class Ebean { return serverMgr.getDefaultServer().findNative(beanType, nativeSql); } + /** + * Create a Query for DTO beans. + *

+ * DTO beans are just normal bean like classes with public constructor(s) and setters. + * They do not need to be registered with Ebean before use. + *

+ * + * @param dtoType The type of the DTO bean the rows will be mapped into. + * @param sql The SQL query to execute. + * @param The type of the DTO bean. + */ + public static DtoQuery findDto(Class dtoType, String sql) { + return serverMgr.getDefaultServer().findDto(dtoType, sql); + } + /** * Create an Update query to perform a bulk update. *

diff --git a/src/main/java/io/ebean/EbeanServer.java b/src/main/java/io/ebean/EbeanServer.java index 4ca3e7c6b..6b56621f4 100644 --- a/src/main/java/io/ebean/EbeanServer.java +++ b/src/main/java/io/ebean/EbeanServer.java @@ -1,1853 +1,13 @@ package io.ebean; -import io.ebean.annotation.TxIsolation; -import io.ebean.cache.ServerCacheManager; -import io.ebean.config.ServerConfig; -import io.ebean.meta.MetaInfoManager; -import io.ebean.plugin.SpiServer; -import io.ebean.text.csv.CsvReader; -import io.ebean.text.json.JsonContext; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import javax.persistence.NonUniqueResultException; -import javax.persistence.OptimisticLockException; -import javax.persistence.PersistenceException; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.function.Consumer; -import java.util.function.Predicate; - /** - * Provides the API for fetching and saving beans to a particular DataSource. + * Provides the API for fetching and saving beans to a particular Database. *

- * Registration with the Ebean Singleton:
- * When a EbeanServer is constructed it can be registered with the Ebean - * singleton (see {@link ServerConfig#setRegister(boolean)}). The Ebean - * singleton is essentially a map of EbeanServer's that have been registered - * with it. The EbeanServer can then be retrieved later via - * {@link Ebean#getServer(String)}. - *

+ * Effectively this is an alias for {@link Database} which is now the new + * and improved name for EbeanServer. *

- * The 'default' EbeanServer
- * One EbeanServer can be designated as the 'default' or 'primary' EbeanServer - * (see {@link ServerConfig#setDefaultServer(boolean)}. Many methods on Ebean - * such as {@link Ebean#find(Class)} etc are actually just a convenient way to - * call methods on the 'default/primary' EbeanServer. This is handy for - * applications that use a single DataSource. - *

- * There is one EbeanServer per Database (javax.sql.DataSource). One EbeanServer - * is referred to as the 'default' server and that is the one that - * Ebean methods such as {@link Ebean#find(Class)} use. - *

- *

- * Constructing a EbeanServer
- * EbeanServer's are constructed by the EbeanServerFactory. They can be created - * programmatically via {@link EbeanServerFactory#create(ServerConfig)} or they - * can be automatically constructed on demand using configuration information in - * the ebean.properties file. - *

- *

- * Example: Get a EbeanServer - *

- *

- *

{@code
- * // Get access to the Human Resources EbeanServer/Database
- * EbeanServer hrServer = Ebean.getServer("HR");
- *
- *
- * // fetch contact 3 from the HR database Contact contact =
- * hrServer.find(Contact.class, new Integer(3));
- *
- * contact.setStatus("INACTIVE"); ...
- *
- * // save the contact back to the HR database hrServer.save(contact);
- * }
- *

- *

- * EbeanServer has more API than Ebean
- * EbeanServer provides additional API compared with Ebean. For example it - * provides more control over the use of Transactions that is not available in - * the Ebean API. - *

- *

- * External Transactions: If you wanted to use transactions created - * externally to eBean then EbeanServer provides additional methods where you - * can explicitly pass a transaction (that can be created externally). - *

- *

- * Bypass ThreadLocal Mechanism: If you want to bypass the built in - * ThreadLocal transaction management you can use the createTransaction() - * method. Example: a single thread requires more than one transaction. - *

- * - * @see Ebean - * @see EbeanServerFactory - * @see ServerConfig + * The preference is to use DB and Database rather than Ebean and EbeanServer. */ -public interface EbeanServer { +public interface EbeanServer extends Database { - /** - * Shutdown the EbeanServer programmatically. - *

- * This method is not normally required. Ebean registers a shutdown hook and shuts down cleanly. - *

- *

- * If the under underlying DataSource is the Ebean implementation then you - * also have the option of shutting down the DataSource and deregistering the - * JDBC driver. - *

- * - * @param shutdownDataSource if true then shutdown the underlying DataSource if it is the EbeanORM - * DataSource implementation. - * @param deregisterDriver if true then deregister the JDBC driver if it is the EbeanORM - * DataSource implementation. - */ - void shutdown(boolean shutdownDataSource, boolean deregisterDriver); - - /** - * Return AutoTune which is used to control the AutoTune service at runtime. - */ - AutoTune getAutoTune(); - - /** - * Return the name. This is used with {@link Ebean#getServer(String)} to get a - * EbeanServer that was registered with the Ebean singleton. - */ - String getName(); - - /** - * Return the ExpressionFactory for this server. - */ - ExpressionFactory getExpressionFactory(); - - /** - * Return the MetaInfoManager which is used to get meta data from the EbeanServer - * such as query execution statistics. - */ - MetaInfoManager getMetaInfoManager(); - - /** - * Return the extended API intended for use by plugins. - */ - SpiServer getPluginApi(); - - /** - * Return the BeanState for a given entity bean. - *

- * This will return null if the bean is not an enhanced entity bean. - *

- */ - BeanState getBeanState(Object bean); - - /** - * Return the value of the Id property for a given bean. - */ - Object getBeanId(Object bean); - - /** - * Set the Id value onto the bean converting the type of the id value if necessary. - *

- * For example, if the id value passed in is a String but ought to be a Long or UUID etc - * then it will automatically be converted. - *

- * - * @param bean The entity bean to set the id value on. - * @param id The id value to set. - */ - Object setBeanId(Object bean, Object id); - - /** - * Return a map of the differences between two objects of the same type. - *

- * When null is passed in for b, then the 'OldValues' of a is used for the - * difference comparison. - *

- */ - Map diff(Object newBean, Object oldBean); - - /** - * Create a new instance of T that is an EntityBean. - *

- * Useful if you use BeanPostConstructListeners or @PostConstruct Annotations. - * In this case you should not use "new Bean...()". Making all bean construtors protected - * could be a good idea here. - *

- */ - T createEntityBean(Class type); - - /** - * Create a CsvReader for a given beanType. - */ - CsvReader createCsvReader(Class beanType); - - /** - * Create an Update query to perform a bulk update. - *

- *

{@code
-   *
-   *  int rows = ebeanServer
-   *      .update(Customer.class)
-   *      .set("status", Customer.Status.ACTIVE)
-   *      .set("updtime", new Timestamp(System.currentTimeMillis()))
-   *      .where()
-   *        .gt("id", 1000)
-   *        .update();
-   *
-   * }
- * - * @param beanType The type of entity bean to update - * @param The type of entity bean - * @return The update query to use - */ - UpdateQuery update(Class beanType); - - /** - * Create a named query. - *

- * For RawSql the named query is expected to be in ebean.xml. - *

- * - * @param beanType The type of entity bean - * @param namedQuery The name of the query - * @param The type of entity bean - * @return The query - */ - Query createNamedQuery(Class beanType, String namedQuery); - - /** - * Create a query for an entity bean and synonym for {@link #find(Class)}. - * - * @see #find(Class) - */ - Query createQuery(Class beanType); - - /** - * Parse the Ebean query language statement returning the query which can then - * be modified (add expressions, change order by clause, change maxRows, change - * fetch and select paths etc). - *

- *

Example

- *
{@code
-   *
-   *   // Find order additionally fetching the customer, details and details.product name.
-   *
-   *   String ormQuery = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
-   *
-   *   Query query = Ebean.createQuery(Order.class, ormQuery);
-   *   query.setParameter("orderId", 2);
-   *
-   *   Order order = query.findOne();
-   *
-   *   // This is the same as:
-   *
-   *   Order order = Ebean.find(Order.class)
-   *     .fetch("customer")
-   *     .fetch("details")
-   *     .fetch("detail.product", "name")
-   *     .setId(2)
-   *     .findOne();
-   *
-   * }
- * - * @param beanType The type of bean to fetch - * @param ormQuery The Ebean ORM query - * @param The type of the entity bean - * @return The query with expressions defined as per the parsed query statement - */ - Query createQuery(Class beanType, String ormQuery); - - /** - * Create a query for a type of entity bean. - *

- * You can use the methods on the Query object to specify fetch paths, - * predicates, order by, limits etc. - *

- *

- * You then use findList(), findSet(), findMap() and findOne() to execute - * the query and return the collection or bean. - *

- *

- * Note that a query executed by {@link Query#findList()} - * {@link Query#findSet()} etc will execute against the same EbeanServer from - * which is was created. - *

- *

- *

{@code
-   *
-   *   // Find order 2 specifying explicitly the parts of the object graph to
-   *   // eagerly fetch. In this case eagerly fetch the associated customer,
-   *   // details and details.product.name
-   *
-   *   Order order = ebeanServer.find(Order.class)
-   *     .fetch("customer")
-   *     .fetch("details")
-   *     .fetch("detail.product", "name")
-   *     .setId(2)
-   *     .findOne();
-   *
-   *   // find some new orders ... with firstRow/maxRows
-   *   List orders =
-   *     ebeanServer.find(Order.class)
-   *       .where().eq("status", Order.Status.NEW)
-   *       .setFirstRow(20)
-   *       .setMaxRows(10)
-   *       .findList();
-   *
-   * }
- */ - Query find(Class beanType); - - /** - * Create a query using native SQL. - *

- * The native SQL can contain named parameters or positioned parameters. - *

- *
{@code
-   *
-   *   String sql = "select c.id, c.name from customer c where c.name like ? order by c.name";
-   *
-   *   Query query = ebeanServer.findNative(Customer.class, sql);
-   *   query.setParameter(1, "Rob%");
-   *
-   *   List customers = query.findList();
-   *
-   * }
- * - * @param beanType The type of entity bean to fetch - * @param nativeSql The SQL that can contain named or positioned parameters - * @return The query to set parameters and execute - */ - Query findNative(Class beanType, String nativeSql); - - /** - * Return the next unique identity value for a given bean type. - *

- * This will only work when a IdGenerator is on the bean such as for beans - * that use a DB sequence or UUID. - *

- *

- * For DB's supporting getGeneratedKeys and sequences such as Oracle10 you do - * not need to use this method generally. It is made available for more - * complex cases where it is useful to get an ID prior to some processing. - *

- */ - Object nextId(Class beanType); - - /** - * Create a filter for sorting and filtering lists of entities locally without - * going back to the database. - *

- * This produces and returns a new list with the sort and filters applied. - *

- *

- * Refer to {@link Filter} for an example of its use. - *

- */ - Filter filter(Class beanType); - - /** - * Sort the list in memory using the sortByClause which can contain a comma delimited - * list of property names and keywords asc, desc, nullsHigh and nullsLow. - *
    - *
  • asc - ascending order (which is the default)
  • - *
  • desc - Descending order
  • - *
  • nullsHigh - Treat null values as high/large values (which is the - * default)
  • - *
  • nullsLow- Treat null values as low/very small values
  • - *
- *

- * If you leave off any keywords the defaults are ascending order and treating - * nulls as high values. - *

- *

- * Note that the sorting uses a Comparator and Collections.sort(); and does - * not invoke a DB query. - *

- *

- *

{@code
-   *
-   *   // find orders and their customers
-   *   List list = ebeanServer.find(Order.class)
-   *     .fetch("customer")
-   *     .orderBy("id")
-   *     .findList();
-   *
-   *   // sort by customer name ascending, then by order shipDate
-   *   // ... then by the order status descending
-   *   ebeanServer.sort(list, "customer.name, shipDate, status desc");
-   *
-   *   // sort by customer name descending (with nulls low)
-   *   // ... then by the order id
-   *   ebeanServer.sort(list, "customer.name desc nullsLow, id");
-   *
-   * }
- * - * @param list the list of entity beans - * @param sortByClause the properties to sort the list by - */ - void sort(List list, String sortByClause); - - /** - * Create a orm update where you will supply the insert/update or delete - * statement (rather than using a named one that is already defined using the - * @NamedUpdates annotation). - *

- * The orm update differs from the sql update in that it you can use the bean - * name and bean property names rather than table and column names. - *

- *

- * An example: - *

- *

- *

{@code
-   *
-   *   // The bean name and properties - "topic","postCount" and "id"
-   *
-   *   // will be converted into their associated table and column names
-   *   String updStatement = "update topic set postCount = :pc where id = :id";
-   *
-   *   Update update = ebeanServer.createUpdate(Topic.class, updStatement);
-   *
-   *   update.set("pc", 9);
-   *   update.set("id", 3);
-   *
-   *   int rows = update.execute();
-   *   System.out.println("rows updated:" + rows);
-   *
-   * }
- */ - Update createUpdate(Class beanType, String ormUpdate); - - /** - * Create a SqlQuery for executing native sql - * query statements. - *

- * Note that you can use raw SQL with entity beans, refer to the SqlSelect - * annotation for examples. - *

- */ - SqlQuery createSqlQuery(String sql); - - /** - * Create a sql update for executing native dml statements. - *

- * Use this to execute a Insert Update or Delete statement. The statement will - * be native to the database and contain database table and column names. - *

- *

- * See {@link SqlUpdate} for example usage. - *

- */ - SqlUpdate createSqlUpdate(String sql); - - /** - * Create a CallableSql to execute a given stored procedure. - */ - CallableSql createCallableSql(String callableSql); - - /** - * Register a TransactionCallback on the currently active transaction. - *

- * If there is no currently active transaction then a PersistenceException is thrown. - * - * @param transactionCallback The transaction callback to be registered with the current transaction. - * @throws PersistenceException If there is no currently active transaction - */ - void register(TransactionCallback transactionCallback) throws PersistenceException; - - /** - * Create a new transaction that is not held in TransactionThreadLocal. - *

- * You will want to do this if you want multiple Transactions in a single - * thread or generally use transactions outside of the TransactionThreadLocal - * management. - *

- */ - Transaction createTransaction(); - - /** - * Create a new transaction additionally specifying the isolation level. - *

- * Note that this transaction is NOT stored in a thread local. - *

- */ - Transaction createTransaction(TxIsolation isolation); - - /** - * Start a transaction with 'REQUIRED' semantics. - *

- * With REQUIRED semantics if an active transaction already exists that transaction will be used. - *

- *

- * The transaction is stored in a ThreadLocal variable and typically you only - * need to use the returned Transaction IF you wish to do things like - * use batch mode, change the transaction isolation level, use savepoints or - * log comments to the transaction log. - *

- *

- * Example of using a transaction to span multiple calls to find(), save() - * etc. - *

- *

- *

{@code
-   *
-   *    // start a transaction (stored in a ThreadLocal)
-   *    ebeanServer.beginTransaction();
-   *    try {
-   * 	    Order order = ebeanServer.find(Order.class,10);
-   *
-   * 	    ebeanServer.save(order);
-   *
-   * 	    ebeanServer.commitTransaction();
-   *
-   *    } finally {
-   * 	    // rollback if we didn't commit
-   * 	    // i.e. an exception occurred before commitTransaction().
-   * 	    ebeanServer.endTransaction();
-   *    }
-   *
-   * }
- *

- *

Transaction options:

- *
{@code
-   *
-   *     Transaction txn = ebeanServer.beginTransaction();
-   *     try {
-   *       // explicitly turn on/off JDBC batch use
-   *       txn.setBatchMode(true);
-   *       txn.setBatchSize(50);
-   *
-   *       // control flushing when mixing save and queries
-   *       txn.setBatchFlushOnQuery(false);
-   *
-   *       // turn off persist cascade if needed
-   *       txn.setPersistCascade(false);
-   *
-   *       // for large batch insert processing when we do not
-   *       // ... need the generatedKeys, don't get them
-   *       txn.setBatchGetGeneratedKeys(false);
-   *
-   *       // explicitly flush the JDBC batch buffer
-   *       txn.flushBatch();
-   *
-   *       ...
-   *
-   *       txn.commit();
-   *
-   *    } finally {
-   *       // rollback if necessary
-   *       txn.end();
-   *    }
-   *
-   * }
- *

- *

- * If you want to externalise the transaction management then you use - * createTransaction() and pass the transaction around to the various methods on - * EbeanServer yourself. - *

- */ - Transaction beginTransaction(); - - /** - * Start a transaction additionally specifying the isolation level. - */ - Transaction beginTransaction(TxIsolation isolation); - - /** - * Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics. - *

- *

- * Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or - * {@link #execute(TxScope, Runnable)}. - *

- *

- *

REQUIRES_NEW example:

- *
{@code
-   * // Start a new transaction. If there is a current transaction
-   * // suspend it until this transaction ends
-   * Transaction txn = server.beginTransaction(TxScope.requiresNew());
-   * try {
-   *
-   *   ...
-   *
-   *   // commit the transaction
-   *   txn.commit();
-   *
-   * } finally {
-   *   // end this transaction which:
-   *   //  A) will rollback transaction if it has not been committed already
-   *   //  B) will restore a previously suspended transaction
-   *   txn.end();
-   * }
-   *
-   * }
- *

- *

REQUIRED example:

- *
{@code
-   *
-   * // start a new transaction if there is not a current transaction
-   * Transaction txn = server.beginTransaction(TxScope.required());
-   * try {
-   *
-   *   ...
-   *
-   *   // commit the transaction if it was created or
-   *   // do nothing if there was already a current transaction
-   *   txn.commit();
-   *
-   * } finally {
-   *   // end this transaction which will rollback the transaction
-   *   // if it was created for this try finally scope and has not
-   *   // already been committed
-   *   txn.end();
-   * }
-   *
-   * }
- */ - Transaction beginTransaction(TxScope scope); - - /** - * Returns the current transaction or null if there is no current transaction in scope. - */ - Transaction currentTransaction(); - - /** - * Flush the JDBC batch on the current transaction. - *

- * This only is useful when JDBC batch is used. Flush occurs automatically when the - * transaction commits or batch size is reached. This manually flushes the JDBC batch - * buffer. - *

- *

- * This is the same as currentTransaction().flush(). - *

- */ - void flush(); - - /** - * Commit the current transaction. - */ - void commitTransaction(); - - /** - * Rollback the current transaction. - */ - 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
-   *
-   *   ebeanServer.beginTransaction();
-   *   try {
-   *     // do some fetching and or persisting ...
-   *
-   *     // commit at the end
-   *     ebeanServer.commitTransaction();
-   *
-   *   } finally {
-   *     // if commit didn't occur then rollback the transaction
-   *     ebeanServer.endTransaction();
-   *   }
-   *
-   * }
- *

- *

- */ - void endTransaction(); - - /** - * Refresh the values of a bean. - *

- * Note that this resets OneToMany and ManyToMany properties so that if they - * are accessed a lazy load will refresh the many property. - *

- */ - void refresh(Object bean); - - /** - * Refresh a many property of an entity bean. - * - * @param bean the entity bean containing the 'many' property - * @param propertyName the 'many' property to be refreshed - */ - void refreshMany(Object bean, String propertyName); - - /** - * Find a bean using its unique id. - *

- *

{@code
-   *   // Fetch order 1
-   *   Order order = ebeanServer.find(Order.class, 1);
-   * }
- *

- *

- * If you want more control over the query then you can use createQuery() and - * Query.findOne(); - *

- *

- *

{@code
-   *   // ... additionally fetching customer, customer shipping address,
-   *   // order details, and the product associated with each order detail.
-   *   // note: only product id and name is fetch (its a "partial object").
-   *   // note: all other objects use "*" and have all their properties fetched.
-   *
-   *   Query query = ebeanServer.find(Order.class)
-   *     .setId(1)
-   *     .fetch("customer")
-   *     .fetch("customer.shippingAddress")
-   *     .fetch("details")
-   *     .query();
-   *
-   *   // fetch associated products but only fetch their product id and name
-   *   query.fetch("details.product", "name");
-   *
-   *
-   *   Order order = query.findOne();
-   *
-   *   // traverse the object graph...
-   *
-   *   Customer customer = order.getCustomer();
-   *   Address shippingAddress = customer.getShippingAddress();
-   *   List details = order.getDetails();
-   *   OrderDetail detail0 = details.get(0);
-   *   Product product = detail0.getProduct();
-   *   String productName = product.getName();
-   *
-   * }
- * - * @param beanType the type of entity bean to fetch - * @param id the id value - */ - @Nullable - T find(Class beanType, Object id); - - /** - * Get a reference object. - *

- * This will not perform a query against the database unless some property other - * that the id property is accessed. - *

- *

- * It is most commonly used to set a 'foreign key' on another bean like: - *

- *
{@code
-   *
-   *   Product product = ebeanServer.getReference(Product.class, 1);
-   *
-   *   OrderDetail orderDetail = new OrderDetail();
-   *   // set the product 'foreign key'
-   *   orderDetail.setProduct(product);
-   *   orderDetail.setQuantity(42);
-   *   ...
-   *
-   *   ebeanServer.save(orderDetail);
-   *
-   *
-   * }
- *

- *

Lazy loading characteristics

- *
{@code
-   *
-   *   Product product = ebeanServer.getReference(Product.class, 1);
-   *
-   *   // You can get the id without causing a fetch/lazy load
-   *   Long productId = product.getId();
-   *
-   *   // If you try to get any other property a fetch/lazy loading will occur
-   *   // This will cause a query to execute...
-   *   String name = product.getName();
-   *
-   * }
- * - * @param beanType the type of entity bean - * @param id the id value - */ - @Nonnull - T getReference(Class beanType, Object id); - - /** - * Return the number of 'top level' or 'root' entities this query should return. - * - * @see Query#findCount() - * @see Query#findFutureCount() - */ - int findCount(Query query, Transaction transaction); - - /** - * Return the Id values of the query as a List. - * - * @see Query#findIds() - */ - @Nonnull - List
findIds(Query query, Transaction transaction); - - /** - * Return a QueryIterator for the query. - *

- * Generally using {@link #findEach(Query, Consumer, Transaction)} or - * {@link #findEachWhile(Query, Predicate, Transaction)} is preferred - * to findIterate(). The reason is that those methods automatically take care of - * closing the queryIterator (and the underlying jdbc statement and resultSet). - *

- *

- * This is similar to findEach in that not all the result beans need to be held - * in memory at the same time and as such is good for processing large queries. - *

- * - * @see Query#findIterate() - * @see Query#findEach(Consumer) - * @see Query#findEachWhile(Predicate) - */ - @Nonnull - QueryIterator findIterate(Query query, Transaction transaction); - - /** - * Execute the query visiting the each bean one at a time. - *

- * Unlike findList() this is suitable for processing a query that will return - * a very large resultSet. The reason is that not all the result beans need to be - * held in memory at the same time and instead processed one at a time. - *

- *

- * Internally this query using a PersistenceContext scoped to each bean (and the - * beans associated object graph). - *

- *

- *

{@code
-   *
-   *     ebeanServer.find(Order.class)
-   *       .where().eq("status", Order.Status.NEW)
-   *       .order().asc("id")
-   *       .findEach((Order order) -> {
-   *
-   *         // do something with the order bean
-   *         System.out.println(" -- processing order ... " + order);
-   *       });
-   *
-   * }
- * - * @see Query#findEach(Consumer) - * @see Query#findEachWhile(Predicate) - */ - void findEach(Query query, Consumer consumer, Transaction transaction); - - /** - * Execute the query visiting the each bean one at a time. - *

- * Compared to findEach() this provides the ability to stop processing the query - * results early by returning false for the Predicate. - *

- *

- * Unlike findList() this is suitable for processing a query that will return - * a very large resultSet. The reason is that not all the result beans need to be - * held in memory at the same time and instead processed one at a time. - *

- *

- * Internally this query using a PersistenceContext scoped to each bean (and the - * beans associated object graph). - *

- *

- *

{@code
-   *
-   *     ebeanServer.find(Order.class)
-   *       .where().eq("status", Order.Status.NEW)
-   *       .order().asc("id")
-   *       .findEachWhile((Order order) -> {
-   *
-   *         // do something with the order bean
-   *         System.out.println(" -- processing order ... " + order);
-   *
-   *         boolean carryOnProcessing = ...
-   *         return carryOnProcessing;
-   *       });
-   *
-   * }
- * - * @see Query#findEach(Consumer) - * @see Query#findEachWhile(Predicate) - */ - void findEachWhile(Query query, Predicate consumer, Transaction transaction); - - /** - * Return versions of a @History entity bean. - *

- * Generally this query is expected to be a find by id or unique predicates query. - * It will execute the query against the history returning the versions of the bean. - *

- */ - @Nonnull - List> findVersions(Query query, Transaction transaction); - - /** - * Execute a query returning a list of beans. - *

- * Generally you are able to use {@link Query#findList()} rather than - * explicitly calling this method. You could use this method if you wish to - * explicitly control the transaction used for the query. - *

- *

- *

{@code
-   *
-   * List customers =
-   *     ebeanServer.find(Customer.class)
-   *     .where().ilike("name", "rob%")
-   *     .findList();
-   *
-   * }
- * - * @param the type of entity bean to fetch. - * @param query the query to execute. - * @param transaction the transaction to use (can be null). - * @return the list of fetched beans. - * @see Query#findList() - */ - @Nonnull - List findList(Query query, Transaction transaction); - - /** - * Execute find row count query in a background thread. - *

- * This returns a Future object which can be used to cancel, check the - * execution status (isDone etc) and get the value (with or without a - * timeout). - *

- * - * @param query the query to execute the row count on - * @param transaction the transaction (can be null). - * @return a Future object for the row count query - * @see Query#findFutureCount() - */ - @Nonnull - FutureRowCount findFutureCount(Query query, Transaction transaction); - - /** - * Execute find Id's query in a background thread. - *

- * This returns a Future object which can be used to cancel, check the - * execution status (isDone etc) and get the value (with or without a - * timeout). - *

- * - * @param query the query to execute the fetch Id's on - * @param transaction the transaction (can be null). - * @return a Future object for the list of Id's - * @see Query#findFutureIds() - */ - @Nonnull - FutureIds findFutureIds(Query query, Transaction transaction); - - /** - * Execute find list query in a background thread returning a FutureList object. - *

- * This returns a Future object which can be used to cancel, check the - * execution status (isDone etc) and get the value (with or without a timeout). - *

- * This query will execute in it's own PersistenceContext and using its own transaction. - * What that means is that it will not share any bean instances with other queries. - * - * @param query the query to execute in the background - * @param transaction the transaction (can be null). - * @return a Future object for the list result of the query - * @see Query#findFutureList() - */ - @Nonnull - FutureList findFutureList(Query query, Transaction transaction); - - /** - * Return a PagedList for this query using firstRow and maxRows. - *

- * The benefit of using this over findList() is that it provides functionality to get the - * total row count etc. - *

- *

- * If maxRows is not set on the query prior to calling findPagedList() then a - * PersistenceException is thrown. - *

- *

- *

{@code
-   *
-   *  PagedList pagedList = Ebean.find(Order.class)
-   *       .setFirstRow(50)
-   *       .setMaxRows(20)
-   *       .findPagedList();
-   *
-   *       // fetch the total row count in the background
-   *       pagedList.loadRowCount();
-   *
-   *       List orders = pagedList.getList();
-   *       int totalRowCount = pagedList.getTotalRowCount();
-   *
-   * }
- * - * @return The PagedList - * @see Query#findPagedList() - */ - @Nonnull - PagedList findPagedList(Query query, Transaction transaction); - - /** - * Execute the query returning a set of entity beans. - *

- * Generally you are able to use {@link Query#findSet()} rather than - * explicitly calling this method. You could use this method if you wish to - * explicitly control the transaction used for the query. - *

- *

- *

{@code
-   *
-   * Set customers =
-   *     ebeanServer.find(Customer.class)
-   *     .where().ilike("name", "rob%")
-   *     .findSet();
-   *
-   * }
- * - * @param the type of entity bean to fetch. - * @param query the query to execute - * @param transaction the transaction to use (can be null). - * @return the set of fetched beans. - * @see Query#findSet() - */ - @Nonnull - Set findSet(Query query, Transaction transaction); - - /** - * Execute the query returning the entity beans in a Map. - *

- * Generally you are able to use {@link Query#findMap()} rather than - * explicitly calling this method. You could use this method if you wish to - * explicitly control the transaction used for the query. - *

- * - * @param the type of entity bean to fetch. - * @param query the query to execute. - * @param transaction the transaction to use (can be null). - * @return the map of fetched beans. - * @see Query#findMap() - */ - @Nonnull - Map findMap(Query query, Transaction transaction); - - /** - * Execute the query returning a list of values for a single property. - *

- *

Example 1:

- *
{@code
-   *
-   *  List names =
-   *    Ebean.find(Customer.class)
-   *      .select("name")
-   *      .orderBy().asc("name")
-   *      .findSingleAttributeList();
-   *
-   * }
- *

Example 2:

- *
{@code
-   *
-   *  List names =
-   *    Ebean.find(Customer.class)
-   *      .setDistinct(true)
-   *      .select("name")
-   *      .where().eq("status", Customer.Status.NEW)
-   *      .orderBy().asc("name")
-   *      .setMaxRows(100)
-   *      .findSingleAttributeList();
-   *
-   * }
- * - * @return the list of values for the selected property - * @see Query#findSingleAttributeList() - */ - @Nonnull - List
findSingleAttributeList(Query query, Transaction transaction); - - /** - * Execute the query returning at most one entity bean or null (if no matching - * bean is found). - *

- * This will throw a NonUniqueResultException if the query finds more than one result. - *

- *

- * Generally you are able to use {@link Query#findOne()} rather than - * explicitly calling this method. You could use this method if you wish to - * explicitly control the transaction used for the query. - *

- * - * @param the type of entity bean to fetch. - * @param query the query to execute. - * @param transaction the transaction to use (can be null). - * @return the list of fetched beans. - * @throws NonUniqueResultException if more than one result was found - * @see Query#findOne() - */ - @Nullable - T findOne(Query query, Transaction transaction); - - /** - * Similar to findOne() but returns an Optional (rather than nullable). - */ - @Nonnull - Optional findOneOrEmpty(Query query, Transaction transaction); - - /** - * Execute as a delete query deleting the 'root level' beans that match the predicates - * in the query. - *

- * Note that if the query includes joins then the generated delete statement may not be - * optimal depending on the database platform. - *

- * - * @param query the query used for the delete - * @param transaction the transaction to use (can be null) - * @param the type of entity bean to fetch. - * @return the number of beans/rows that were deleted - */ - int delete(Query query, Transaction transaction); - - /** - * Execute the update query returning the number of rows updated. - *

- * The update query must be created using {@link #update(Class)}. - *

- * - * @param query the update query to execute - * @param transaction the optional transaction to use for the update (can be null) - * @param the type of entity bean - * @return The number of rows updated - */ - int update(Query query, Transaction transaction); - - /** - * Execute the sql query returning a list of MapBean. - *

- * Generally you are able to use {@link SqlQuery#findList()} rather than - * explicitly calling this method. You could use this method if you wish to - * explicitly control the transaction used for the query. - *

- * - * @param query the query to execute. - * @param transaction the transaction to use (can be null). - * @return the list of fetched MapBean. - * @see SqlQuery#findList() - */ - @Nonnull - List findList(SqlQuery query, Transaction transaction); - - /** - * Execute the SqlQuery iterating a row at a time. - *

- * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. - *

- */ - void findEach(SqlQuery query, Consumer consumer, Transaction transaction); - - /** - * Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through. - *

- * Returning false after processing a row stops the iteration through the query results. - *

- *

- * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. - *

- */ - void findEachWhile(SqlQuery query, Predicate consumer, Transaction transaction); - - /** - * Execute the sql query returning a single MapBean or null. - *

- * This will throw a PersistenceException if the query found more than one - * result. - *

- *

- * Generally you are able to use {@link SqlQuery#findOne()} rather than - * explicitly calling this method. You could use this method if you wish to - * explicitly control the transaction used for the query. - *

- * - * @param query the query to execute. - * @param transaction the transaction to use (can be null). - * @return the fetched MapBean or null if none was found. - * @see SqlQuery#findOne() - */ - @Nullable - SqlRow findOne(SqlQuery query, Transaction transaction); - - /** - * Either Insert or Update the bean depending on its state. - *

- * If there is no current transaction one will be created and committed for - * you automatically. - *

- *

- * Save can cascade along relationships. For this to happen you need to - * specify a cascade of CascadeType.ALL or CascadeType.PERSIST on the - * OneToMany, OneToOne or ManyToMany annotation. - *

- *

- * In this example below the details property has a CascadeType.ALL set so - * saving an order will also save all its details. - *

- *

- *

{@code
-   *   public class Order { ...
-   *
-   * 	   @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
-   * 	   List details;
-   * 	   ...
-   *   }
-   * }
- *

- *

- * When a save cascades via a OneToMany or ManyToMany Ebean will automatically - * set the 'parent' object to the 'detail' object. In the example below in - * saving the order and cascade saving the order details the 'parent' order - * will be set against each order detail when it is saved. - *

- */ - void save(Object bean) throws OptimisticLockException; - - /** - * Save all the beans in the collection. - */ - int saveAll(Collection beans) throws OptimisticLockException; - - /** - * Delete the bean. - *

- * This will return true if the bean was deleted successfully or JDBC batch is being used. - *

- *

- * If there is no current transaction one will be created and committed for - * you automatically. - *

- *

- * If the Bean does not have a version property (or loaded version property) and - * the bean does not exist then this returns false indicating that nothing was - * deleted. Note that, if JDBC batch mode is used then this always returns true. - *

- */ - boolean delete(Object bean) throws OptimisticLockException; - - /** - * Delete the bean with an explicit transaction. - *

- * This will return true if the bean was deleted successfully or JDBC batch is being used. - *

- *

- * If the Bean does not have a version property (or loaded version property) and - * the bean does not exist then this returns false indicating that nothing was - * deleted. However, if JDBC batch mode is used then this always returns true. - *

- */ - boolean delete(Object bean, Transaction transaction) throws OptimisticLockException; - - /** - * Delete a bean permanently without soft delete. - */ - boolean deletePermanent(Object bean) throws OptimisticLockException; - - /** - * Delete a bean permanently without soft delete using an explicit transaction. - */ - boolean deletePermanent(Object bean, Transaction transaction) throws OptimisticLockException; - - /** - * Delete all the beans in the collection permanently without soft delete. - */ - int deleteAllPermanent(Collection beans) throws OptimisticLockException; - - /** - * Delete all the beans in the collection permanently without soft delete using an explicit transaction. - */ - int deleteAllPermanent(Collection beans, Transaction transaction) throws OptimisticLockException; - - /** - * Delete the bean given its type and id. - */ - int delete(Class beanType, Object id); - - /** - * Delete the bean given its type and id with an explicit transaction. - */ - int delete(Class beanType, Object id, Transaction transaction); - - /** - * Delete permanent given the bean type and id. - */ - int deletePermanent(Class beanType, Object id); - - /** - * Delete permanent given the bean type and id with an explicit transaction. - */ - int deletePermanent(Class beanType, Object id, Transaction transaction); - - /** - * Delete all the beans in the collection. - */ - int deleteAll(Collection beans) throws OptimisticLockException; - - /** - * Delete all the beans in the collection using an explicit transaction. - */ - int deleteAll(Collection beans, Transaction transaction) throws OptimisticLockException; - - /** - * Delete several beans given their type and id values. - */ - int deleteAll(Class beanType, Collection ids); - - /** - * Delete several beans given their type and id values with an explicit transaction. - */ - int deleteAll(Class beanType, Collection ids, Transaction transaction); - - /** - * Delete permanent for several beans given their type and id values. - */ - int deleteAllPermanent(Class beanType, Collection ids); - - /** - * Delete permanent for several beans given their type and id values with an explicit transaction. - */ - int deleteAllPermanent(Class beanType, Collection ids, Transaction transaction); - - /** - * Execute a Sql Update Delete or Insert statement. This returns the number of - * rows that where updated, deleted or inserted. If is executed in batch then - * this returns -1. You can get the actual rowCount after commit() from - * updateSql.getRowCount(). - *

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

- *

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

- *

- * Example: - *

- *

- *

{@code
-   *
-   *   // example that uses 'named' parameters
-   *   String s = "UPDATE f_topic set post_count = :count where id = :id"
-   *
-   *   SqlUpdate update = ebeanServer.createSqlUpdate(s);
-   *
-   *   update.setParameter("id", 1);
-   *   update.setParameter("count", 50);
-   *
-   *   int modifiedCount = ebeanServer.execute(update);
-   *
-   *   String msg = "There where " + modifiedCount + "rows updated";
-   *
-   * }
- * - * @param sqlUpdate the update sql potentially with bind values - * @return the number of rows updated or deleted. -1 if executed in batch. - * @see CallableSql - */ - int execute(SqlUpdate sqlUpdate); - - /** - * Execute a ORM insert update or delete statement using the current - * transaction. - *

- * This returns the number of rows that where inserted, updated or deleted. - *

- */ - int execute(Update update); - - /** - * Execute a ORM insert update or delete statement with an explicit - * transaction. - */ - int execute(Update update, Transaction transaction); - - /** - * For making calls to stored procedures. - *

- * Example: - *

- *

- *

{@code
-   *
-   *   String sql = "{call sp_order_modify(?,?,?)}";
-   *
-   *   CallableSql cs = ebeanServer.createCallableSql(sql);
-   *   cs.setParameter(1, 27);
-   *   cs.setParameter(2, "SHIPPED");
-   *   cs.registerOut(3, Types.INTEGER);
-   *
-   *   ebeanServer.execute(cs);
-   *
-   *   // read the out parameter
-   *   Integer returnValue = (Integer) cs.getObject(3);
-   *
-   * }
- * - * @see CallableSql - * @see Ebean#execute(SqlUpdate) - */ - int execute(CallableSql callableSql); - - /** - * Inform Ebean that tables have been modified externally. These could be the - * result of from calling a stored procedure, other JDBC calls or external - * programs including other frameworks. - *

- * If you use ebeanServer.execute(UpdateSql) then the table modification information - * is automatically deduced and you do not need to call this method yourself. - *

- *

- * This information is used to invalidate objects out of the cache and - * potentially text indexes. This information is also automatically broadcast - * across the cluster. - *

- *

- * If there is a transaction then this information is placed into the current - * transactions event information. When the transaction is committed this - * information is registered (with the transaction manager). If this - * transaction is rolled back then none of the transaction event information - * registers including the information you put in via this method. - *

- *

- * If there is NO current transaction when you call this method then this - * information is registered immediately (with the transaction manager). - *

- * - * @param tableName the name of the table that was modified - * @param inserted true if rows where inserted into the table - * @param updated true if rows on the table where updated - * @param deleted true if rows on the table where deleted - */ - void externalModification(String tableName, boolean inserted, boolean updated, boolean deleted); - - /** - * Find a entity bean with an explicit transaction. - * - * @param the type of entity bean to find - * @param beanType the type of entity bean to find - * @param id the bean id value - * @param transaction the transaction to use (can be null) - */ - T find(Class beanType, Object id, Transaction transaction); - - /** - * Insert or update a bean with an explicit transaction. - */ - void save(Object bean, Transaction transaction) throws OptimisticLockException; - - /** - * Save all the beans in the collection with an explicit transaction. - */ - int saveAll(Collection beans, Transaction transaction) throws OptimisticLockException; - - /** - * Marks the entity bean as dirty. - *

- * This is used so that when a bean that is otherwise unmodified is updated the version - * property is updated. - *

- * An unmodified bean that is saved or updated is normally skipped and this marks the bean as - * dirty so that it is not skipped. - *

- *

{@code
-   *
-   * Customer customer = ebeanServer.find(Customer, id);
-   *
-   * // mark the bean as dirty so that a save() or update() will
-   * // increment the version property
-   * ebeanServer.markAsDirty(customer);
-   * ebeanServer.save(customer);
-   *
-   * }
- */ - void markAsDirty(Object bean); - - /** - * Saves the bean using an update. If you know you are updating a bean then it is preferable to - * use this update() method rather than save(). - *

- * Stateless updates: Note that the bean does not have to be previously fetched to call - * update().You can create a new instance and set some of its properties programmatically for via - * JSON/XML marshalling etc. This is described as a 'stateless update'. - *

- *

- * Optimistic Locking: Note that if the version property is not set when update() is - * called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used). - *

- *

- * {@link ServerConfig#setUpdatesDeleteMissingChildren(boolean)}: When cascade saving to a - * OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children - * that are in the database but are not in the collection are deleted. - *

- *

- * {@link ServerConfig#setUpdateChangesOnly(boolean)}: The updateChangesOnly setting - * controls if only the changed properties are included in the update or if all the loaded - * properties are included instead. - *

- *

- *

{@code
-   *
-   * // A 'stateless update' example
-   * Customer customer = new Customer();
-   * customer.setId(7);
-   * customer.setName("ModifiedNameNoOCC");
-   * ebeanServer.update(customer);
-   *
-   * }
- * - * @see ServerConfig#setUpdatesDeleteMissingChildren(boolean) - * @see ServerConfig#setUpdateChangesOnly(boolean) - */ - void update(Object bean) throws OptimisticLockException; - - /** - * Update a bean additionally specifying a transaction. - */ - void update(Object bean, Transaction transaction) throws OptimisticLockException; - - /** - * Update a bean additionally specifying a transaction and the deleteMissingChildren setting. - * - * @param bean the bean to update - * @param transaction the transaction to use (can be null). - * @param deleteMissingChildren specify false if you do not want 'missing children' of a OneToMany - * or ManyToMany to be automatically deleted. - */ - void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException; - - /** - * Update a collection of beans. If there is no current transaction one is created and used to - * update all the beans in the collection. - */ - void updateAll(Collection beans) throws OptimisticLockException; - - /** - * Update a collection of beans with an explicit transaction. - */ - void updateAll(Collection beans, Transaction transaction) throws OptimisticLockException; - - /** - * Insert the bean. - *

- * Compared to save() this forces bean to perform an insert rather than trying to decide - * based on the bean state. As such this is useful when you fetch beans from one database - * and want to insert them into another database (and you want to explicitly insert them). - *

- */ - void insert(Object bean); - - /** - * Insert the bean with a transaction. - */ - void insert(Object bean, 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 a collection of beans with an explicit transaction. - */ - void insertAll(Collection beans, Transaction transaction); - - /** - * Execute explicitly passing a transaction. - */ - int execute(SqlUpdate updSql, Transaction transaction); - - /** - * Execute explicitly passing a transaction. - */ - int execute(CallableSql callableSql, Transaction transaction); - - /** - * Execute a Runnable in a Transaction with an explicit scope. - *

- * The scope can control the transaction type, isolation and rollback - * semantics. - *

- *

- *

{@code
-   *
-   *   // set specific transactional scope settings
-   *   TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
-   *
-   *   ebeanServer.execute(scope, new Runnable() {
-   * 	   public void run() {
-   * 		   User u1 = Ebean.find(User.class, 1);
-   * 		   ...
-   * 	   }
-   *   });
-   *
-   * }
- */ - void execute(TxScope scope, Runnable runnable); - - /** - * Execute a Runnable in a Transaction with the default scope. - *

- * The default scope runs with REQUIRED and by default will rollback on any - * exception (checked or runtime). - *

- *

- *

{@code
-   *
-   *    ebeanServer.execute(() -> {
-   *
-   *        User u1 = ebeanServer.find(User.class, 1);
-   *        User u2 = ebeanServer.find(User.class, 2);
-   *
-   *        u1.setName("u1 mod");
-   *        u2.setName("u2 mod");
-   *
-   *        ebeanServer.save(u1);
-   *        ebeanServer.save(u2);
-   *
-   *    });
-   *
-   * }
- */ - void execute(Runnable runnable); - - /** - * Execute a TxCallable in a Transaction with an explicit scope. - *

- * The scope can control the transaction type, isolation and rollback - * semantics. - *

- *

- *

{@code
-   *
-   *   // set specific transactional scope settings
-   *   TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
-   *
-   *   ebeanServer.executeCall(scope, new Callable() {
-   * 	   public String call() {
-   * 		   User u1 = ebeanServer.find(User.class, 1);
-   * 		   ...
-   * 		   return u1.getEmail();
-   * 	   }
-   *   });
-   *
-   * }
- */ - T executeCall(TxScope scope, Callable callable); - - /** - * Execute a TxCallable in a Transaction with the default scope. - *

- * The default scope runs with REQUIRED and by default will rollback on any - * exception (checked or runtime). - *

- *

- *

{@code
-   *
-   *   ebeanServer.executeCall(new Callable() {
-   *     public String call() {
-   *       User u1 = ebeanServer.find(User.class, 1);
-   *       User u2 = ebeanServer.find(User.class, 2);
-   *
-   *       u1.setName("u1 mod");
-   *       u2.setName("u2 mod");
-   *
-   *       ebeanServer.save(u1);
-   *       ebeanServer.save(u2);
-   *
-   *       return u1.getEmail();
-   *     }
-   *   });
-   *
-   * }
- */ - T executeCall(Callable callable); - - /** - * Return the manager of the server cache ("L2" cache). - */ - ServerCacheManager getServerCacheManager(); - - /** - * Return the BackgroundExecutor service for asynchronous processing of - * queries. - */ - BackgroundExecutor getBackgroundExecutor(); - - /** - * Return the JsonContext for reading/writing JSON. - *

- * This instance is safe to be used concurrently by multiple threads and this - * method is cheap to call. - *

- *

- *

Simple example:

- *
{@code
-   *
-   *     JsonContext json = ebeanServer.json();
-   *     String jsonOutput = json.toJson(list);
-   *     System.out.println(jsonOutput);
-   *
-   * }
- *

- *

Using PathProperties:

- *
{@code
-   *
-   *     // specify just the properties we want
-   *     PathProperties paths = PathProperties.parse("name, status, anniversary");
-   *
-   *     List customers =
-   *       ebeanServer.find(Customer.class)
-   *         // apply those paths to the query (only fetch what we need)
-   *         .apply(paths)
-   *         .where().ilike("name", "rob%")
-   *         .findList();
-   *
-   *     // ... get the json
-   *     JsonContext jsonContext = ebeanServer.json();
-   *     String json = jsonContext.toJson(customers, paths);
-   *
-   * }
- * - * @see FetchPath - * @see Query#apply(FetchPath) - */ - JsonContext json(); - - /** - * Return the Document store. - */ - DocumentStore docStore(); - - /** - * Publish a single bean given its type and id returning the resulting live bean. - *

- * The values are published from the draft to the live bean. - *

- * - * @param the type of the entity bean - * @param beanType the type of the entity bean - * @param id the id of the entity bean - * @param transaction the transaction the publish process should use (can be null) - */ - T publish(Class beanType, Object id, Transaction transaction); - - /** - * Publish a single bean given its type and id returning the resulting live bean. - * This will use the current transaction or create one if required. - *

- * The values are published from the draft to the live bean. - *

- * - * @param the type of the entity bean - * @param beanType the type of the entity bean - * @param id the id of the entity bean - */ - T publish(Class beanType, Object id); - - /** - * Publish the beans that match the query returning the resulting published beans. - *

- * The values are published from the draft beans to the live beans. - *

- * - * @param the type of the entity bean - * @param query the query used to select the draft beans to publish - * @param transaction the transaction the publish process should use (can be null) - */ - List publish(Query query, Transaction transaction); - - /** - * Publish the beans that match the query returning the resulting published beans. - * This will use the current transaction or create one if required. - *

- * The values are published from the draft beans to the live beans. - *

- * - * @param the type of the entity bean - * @param query the query used to select the draft beans to publish - */ - List publish(Query query); - - /** - * Restore the draft bean back to the live state. - *

- * The values from the live beans are set back to the draft bean and the - * @DraftDirty and @DraftReset properties are reset. - *

- * - * @param the type of the entity bean - * @param beanType the type of the entity bean - * @param id the id of the entity bean to restore - * @param transaction the transaction the restore process should use (can be null) - */ - T draftRestore(Class beanType, Object id, Transaction transaction); - - /** - * Restore the draft bean back to the live state. - *

- * The values from the live beans are set back to the draft bean and the - * @DraftDirty and @DraftReset properties are reset. - *

- * - * @param the type of the entity bean - * @param beanType the type of the entity bean - * @param id the id of the entity bean to restore - */ - T draftRestore(Class beanType, Object id); - - /** - * Restore the draft beans matching the query back to the live state. - *

- * The values from the live beans are set back to the draft bean and the - * @DraftDirty and @DraftReset properties are reset. - *

- * - * @param the type of the entity bean - * @param query the query used to select the draft beans to restore - * @param transaction the transaction the restore process should use (can be null) - */ - List draftRestore(Query query, Transaction transaction); - - /** - * Restore the draft beans matching the query back to the live state. - *

- * The values from the live beans are set back to the draft bean and the - * @DraftDirty and @DraftReset properties are reset. - *

- * - * @param the type of the entity bean - * @param query the query used to select the draft beans to restore - */ - List draftRestore(Query query); - - /** - * Returns the set of properties/paths that are unknown (do not map to known properties or paths). - *

- * Validate the query checking the where and orderBy expression paths to confirm if - * they represent valid properties/path for the given bean type. - *

- */ - Set validateQuery(Query query); } diff --git a/src/main/java/io/ebean/EbeanServerFactory.java b/src/main/java/io/ebean/EbeanServerFactory.java index 090e23e9a..0880dd25f 100644 --- a/src/main/java/io/ebean/EbeanServerFactory.java +++ b/src/main/java/io/ebean/EbeanServerFactory.java @@ -33,6 +33,10 @@ public class EbeanServerFactory { private static SpiContainer container; + static { + EbeanVersion.getVersion(); // initalizes the version class and logs the version. + } + /** * Initialise the container with clustering configuration. *

@@ -65,10 +69,8 @@ public class EbeanServerFactory { EbeanServer server = createInternal(config); - if (config.isDefaultServer()) { - PrimaryServer.setSkip(true); - } if (config.isRegister()) { + PrimaryServer.setSkip(true); Ebean.register(server, config.isDefaultServer()); } diff --git a/src/main/java/io/ebean/EbeanVersion.java b/src/main/java/io/ebean/EbeanVersion.java new file mode 100644 index 000000000..c9ca35b77 --- /dev/null +++ b/src/main/java/io/ebean/EbeanVersion.java @@ -0,0 +1,44 @@ +package io.ebean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +/** + * Class to determine the ebean version. ( + * @author Roland Praml, FOCONIS AG + * + */ +public class EbeanVersion { + private EbeanVersion() { + + } + private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class); + + private static String version = "unknown"; + static { + try { + Properties prop = new Properties(); + try (InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) { + if (in != null) { + prop.load(in); + in.close(); + version = prop.getProperty("version"); + } + } + logger.info("ebean version: {}", version); + } catch (IOException e) { + logger.warn("Could not determine ebean version: {}", e.getMessage()); + } + } + + /** + * Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties) + */ + public static String getVersion() { + return version; + } + +} diff --git a/src/main/java/io/ebean/ExampleExpression.java b/src/main/java/io/ebean/ExampleExpression.java index 21e40fd35..873f235ab 100644 --- a/src/main/java/io/ebean/ExampleExpression.java +++ b/src/main/java/io/ebean/ExampleExpression.java @@ -22,8 +22,8 @@ package io.ebean; * example.setName("Rob%"); * example.setNotes("%something%"); * - * List<Customer> list = - * Ebean.find(Customer.class) + * List list = + * DB.find(Customer.class) * .where() * // pass the bean into the where() clause * .exampleLike(example) @@ -46,7 +46,7 @@ package io.ebean; * .includeZeros(); * * List list = - * Ebean.find(Customer.class) + * DB.find(Customer.class) * .where() * .add(qbe) * .findList(); diff --git a/src/main/java/io/ebean/Expr.java b/src/main/java/io/ebean/Expr.java index 08c828fc8..ab8887c49 100644 --- a/src/main/java/io/ebean/Expr.java +++ b/src/main/java/io/ebean/Expr.java @@ -13,27 +13,15 @@ import java.util.Map; * {@link Query#where()}. *

*

- * This provides a convenient way to create expressions for the 'Default' - * server. It is actually a short cut for using the ExpressionFactory of the - * 'default' EbeanServer. + * This provides a convenient way to create expressions for the default + * database. *

- * See also {@link Ebean#getExpressionFactory()} + * See also {@link DB#getExpressionFactory()} *

*

* Creates standard common expressions for using in a Query Where or Having * clause. *

- *
{@code
- *
- * // Example: Using an Expr.or() method
- * Query query = Ebean.createQuery(Order.class);
- * query.where(
- *     Expr.or(Expr.eq("status", Order.NEW),
- *             Expr.gt("orderDate", lastWeek));
- *
- * List list = query.findList();
- * ...
- * }
* * @see Query#where() */ @@ -64,11 +52,20 @@ public class Expr { return Ebean.getExpressionFactory().ieq(propertyName, value); } + /** + * In Range - property >= value1 and property < value2. + *

+ * Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps. + *

+ */ + public static Expression inRange(String propertyName, Object value1, Object value2) { + return Ebean.getExpressionFactory().inRange(propertyName, value1, value2); + } + /** * Between - property between the two given values. */ public static Expression between(String propertyName, Object value1, Object value2) { - return Ebean.getExpressionFactory().between(propertyName, value1, value2); } @@ -76,7 +73,6 @@ public class Expr { * Between - value between two given properties. */ public static Expression between(String lowProperty, String highProperty, Object value) { - return Ebean.getExpressionFactory().betweenProperties(lowProperty, highProperty, value); } @@ -242,6 +238,43 @@ public class Expr { return Ebean.getExpressionFactory().in(propertyName, values); } + /** + * In where null or empty values means that no predicate is added to the query. + *

+ * That is, only add the IN predicate if the values are not null or empty. + *

+ * Without this we typically need to code an if block to only add + * the IN predicate if the collection is not empty like: + *

+ * + *

Without inOrEmpty()

+ *
{@code
+   *
+   *   query.where() // add some predicates
+   *     .eq("status", Status.NEW);
+   *
+   *   if (ids != null && !ids.isEmpty()) {
+   *     query.where().in("customer.id", ids);
+   *   }
+   *
+   *   query.findList();
+   *
+   * }
+ * + *

Using inOrEmpty()

+ *
{@code
+   *
+   *   query.where()
+   *     .eq("status", Status.NEW)
+   *     .inOrEmpty("customer.id", ids)
+   *     .findList();
+   *
+   * }
+ */ + public static Expression inOrEmpty(String propertyName, Collection values) { + return Ebean.getExpressionFactory().inOrEmpty(propertyName, values); + } + /** * Id Equal to - ID property is equal to the value. */ diff --git a/src/main/java/io/ebean/ExpressionFactory.java b/src/main/java/io/ebean/ExpressionFactory.java index a2c44cffb..a27955d92 100644 --- a/src/main/java/io/ebean/ExpressionFactory.java +++ b/src/main/java/io/ebean/ExpressionFactory.java @@ -30,9 +30,9 @@ import java.util.Map; * Expr.or(Expr.eq("status", Order.Status.NEW), * Expr.gt("orderDate", lastWeek)); * - * Query query = Ebean.createQuery(Order.class); - * query.where().add(newOrLastWeek); - * List list = query.findList(); + * List list = DB.find(Order.class) + * .where().add(newOrLastWeek) + * .findList(); * ... * } * @@ -122,6 +122,11 @@ public interface ExpressionFactory { */ Expression eq(String propertyName, Object value); + /** + * Equal To or Null - property equal to the given value or null. + */ + Expression eqOrNull(String propertyName, Object value); + /** * Not Equal To - property not equal to the given value. */ @@ -133,11 +138,49 @@ public interface ExpressionFactory { */ Expression ieq(String propertyName, String value); + /** + * Case Insensitive Not Equal To - property not equal to the given value (typically + * using a lower() function to make it case insensitive). + */ + Expression ine(String propertyName, String value); + /** * Case Insensitive Equal To that allows for named parameter use. */ Expression ieqObject(String propertyName, Object value); + /** + * Case Insensitive Not Equal To that allows for named parameter use. + */ + Expression ineObject(String propertyName, Object value); + + /** + * In Range - property >= value1 and property < value2. + *

+ * Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps. + *

+ */ + Expression inRange(String propertyName, Object value1, Object value2); + + /** + * Value in Range between 2 properties. + * + *
{@code
+   *
+   *    .startDate.inRangeWith(endDate, now)
+   *
+   *    // which equates to
+   *    startDate <= now and (endDate > now or endDate is null)
+   *
+   * }
+ * + *

+ * This is a convenience expression combining a number of simple expressions. + * The most common use of this could be called "effective dating" where 2 date or + * timestamp columns represent the date range in which + */ + Expression inRangeWith(String lowProperty, String highProperty, Object value); + /** * Between - property between the two given values. */ @@ -148,6 +191,14 @@ public interface ExpressionFactory { */ Expression betweenProperties(String lowProperty, String highProperty, Object value); + /** + * Greater Than Or Null - property greater than the given value or null. + *

+ * A convenient expression combining GT and Is Null. Most often useful for range + * expressions where the top range value is nullable. + */ + Expression gtOrNull(String propertyName, Object value); + /** * Greater Than - property greater than the given value. */ @@ -159,6 +210,14 @@ public interface ExpressionFactory { */ Expression ge(String propertyName, Object value); + /** + * Less Than or Null - property less than the given value or null. + *

+ * A convenient expression combining LT and Is Null. Most often useful for range + * expressions where the bottom range value is nullable. + */ + Expression ltOrNull(String propertyName, Object value); + /** * Less Than - property less than the given value. */ @@ -266,6 +325,41 @@ public interface ExpressionFactory { */ Expression in(String propertyName, Collection values); + /** + * In where null or empty values means that no predicate is added to the query. + *

+ * That is, only add the IN predicate if the values are not null or empty. + *

+ * Without this we typically need to code an if block to only add + * the IN predicate if the collection is not empty like: + *

+ * + *

Without inOrEmpty()

+ *
{@code
+   *
+   *   query.where() // add some predicates
+   *     .eq("status", Status.NEW);
+   *
+   *   if (ids != null && !ids.isEmpty()) {
+   *     query.where().in("customer.id", ids);
+   *   }
+   *
+   *   query.findList();
+   *
+   * }
+ * + *

Using inOrEmpty()

+ *
{@code
+   *
+   *   query.where()
+   *     .eq("status", Status.NEW)
+   *     .inOrEmpty("customer.id", ids)
+   *     .findList();
+   *
+   * }
+ */ + Expression inOrEmpty(String propertyName, Collection values); + /** * Not In - property has a value in the array of values. */ @@ -327,6 +421,30 @@ public interface ExpressionFactory { */ Expression allEq(Map propertyMap); + /** + * Add expression for ANY of the given bit flags to be set. + * + * @param propertyName The property that holds the flags value + * @param flags The flags we are looking for + */ + Expression bitwiseAny(String propertyName, long flags); + + /** + * Add expression for ALL of the given bit flags to be set. + * + * @param propertyName The property that holds the flags value + * @param flags The flags we are looking for + */ + Expression bitwiseAll(String propertyName, long flags); + + /** + * Add bitwise AND expression of the given bit flags to compare with the match/mask. + * + * @param propertyName The property that holds the flags value + * @param flags The flags we are looking for + */ + Expression bitwiseAnd(String propertyName, long flags, long match); + /** * Add raw expression with a single parameter. *

@@ -418,4 +536,12 @@ public interface ExpressionFactory { */ Junction junction(Junction.Type type, Query query, ExpressionList parent); + /** + * Add the expressions to the given expression list. + * + * @param where The expression list to add the expressions to + * @param expressions The expressions that are parsed + * @param params Bind parameters to match ? or ?1 bind positions. + */ + void where(ExpressionList where, String expressions, Object[] params); } diff --git a/src/main/java/io/ebean/ExpressionList.java b/src/main/java/io/ebean/ExpressionList.java index 1a5db3a21..c40cd0845 100644 --- a/src/main/java/io/ebean/ExpressionList.java +++ b/src/main/java/io/ebean/ExpressionList.java @@ -9,6 +9,7 @@ import io.ebean.search.TextSimple; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.persistence.NonUniqueResultException; +import java.sql.Connection; import java.sql.Timestamp; import java.util.Collection; import java.util.List; @@ -120,6 +121,46 @@ public interface ExpressionList { */ Query asDraft(); + /** + * Convert the query to a DTO bean query. + *

+ * We effectively use the underlying ORM query to build the SQL and then execute + * and map it into DTO beans. + */ + DtoQuery asDto(Class dtoClass); + + /** + * Return the underlying query as an UpdateQuery. + *

+ * Typically this is used with query beans to covert a query bean + * query into an UpdateQuery like the examples below. + *

+ * + *
{@code
+   *
+   *  int rowsUpdated = new QCustomer()
+   *       .name.startsWith("Rob")
+   *       .asUpdate()
+   *       .set("active", false)
+   *       .update();;
+   *
+   * }
+ * + *
{@code
+   *
+   *   int rowsUpdated = new QContact()
+   *       .notes.note.startsWith("Make Inactive")
+   *       .email.endsWith("@foo.com")
+   *       .customer.id.equalTo(42)
+   *       .asUpdate()
+   *       .set("inactive", true)
+   *       .setRaw("email = lower(email)")
+   *       .update();
+   *
+   * }
+ */ + UpdateQuery asUpdate(); + /** * Execute using "for update" clause which results in the DB locking the record. */ @@ -146,6 +187,16 @@ public interface ExpressionList { */ Query setIncludeSoftDeletes(); + /** + * Execute the query using the given transaction. + */ + Query usingTransaction(Transaction transaction); + + /** + * Execute the query using the given connection. + */ + Query usingConnection(Connection connection); + /** * Execute as a delete query deleting the 'root level' beans that match the predicates * in the query. @@ -158,6 +209,18 @@ public interface ExpressionList { */ int delete(); + /** + * Execute as a delete query deleting the 'root level' beans that match the predicates + * in the query. + *

+ * Note that if the query includes joins then the generated delete statement may not be + * optimal depending on the database platform. + *

+ * + * @return the number of rows that were deleted. + */ + int delete(Transaction transaction); + /** * Execute as a update query. * @@ -166,6 +229,40 @@ public interface ExpressionList { */ int update(); + /** + * Execute as a update query with the given transaction. + * + * @return the number of rows that were updated. + * @see UpdateQuery + */ + int update(Transaction transaction); + + /** + * Execute the query returning true if a row is found. + *

+ * The query is executed using max rows of 1 and will only select the id property. + * This method is really just a convenient way to optimise a query to perform a + * 'does a row exist in the db' check. + *

+ * + *

Example:

+ *
{@code
+   *
+   *   boolean userExists = query().where().eq("email", "rob@foo.com").exists();
+   *
+   * }
+ * + *

Example using a query bean:

+ *
{@code
+   *
+   *   boolean userExists = new QContact().email.equalTo("rob@foo.com").exists();
+   *
+   * }
+ * + * @return True if the query finds a matching row in the database + */ + boolean exists(); + /** * Execute the query iterating over the results. * @@ -230,23 +327,23 @@ public interface ExpressionList { /** * Execute the query returning a list of values for a single property. - *

+ * *

Example 1:

*
{@code
    *
    *  List names =
-   *    Ebean.find(Customer.class)
+   *    DB.find(Customer.class)
    *      .select("name")
    *      .orderBy().asc("name")
    *      .findSingleAttributeList();
    *
    * }
- *

+ * *

Example 2:

*
{@code
    *
    *  List names =
-   *    Ebean.find(Customer.class)
+   *    DB.find(Customer.class)
    *      .setDistinct(true)
    *      .select("name")
    *      .where().eq("status", Customer.Status.NEW)
@@ -263,11 +360,10 @@ public interface ExpressionList {
 
   /**
    * Execute a query returning a single value of a single property/column.
-   * 

*

{@code
    *
    *  String name =
-   *    Ebean.find(Customer.class)
+   *    DB.find(Customer.class)
    *      .select("name")
    *      .where().eq("id", 42)
    *      .findSingleAttribute();
@@ -348,10 +444,9 @@ public interface ExpressionList {
    * If maxRows is not set on the query prior to calling findPagedList() then a
    * PersistenceException is thrown.
    * 

- *

*

{@code
    *
-   *  PagedList pagedList = Ebean.find(Order.class)
+   *  PagedList pagedList = DB.find(Order.class)
    *       .setFirstRow(50)
    *       .setMaxRows(20)
    *       .findPagedList();
@@ -394,7 +489,26 @@ public interface ExpressionList {
    * Add some filter predicate expressions to the many property.
    */
   @Nonnull
-  ExpressionList filterMany(String prop);
+  ExpressionList filterMany(String manyProperty);
+
+  /**
+   * Add filter expressions to the many property.
+   *
+   * 
{@code
+   *
+   *   DB.find(Customer.class)
+   *   .where()
+   *   .eq("name", "Rob")
+   *   .filterMany("orders", "status = ?", Status.NEW)
+   *   .findList();
+   *
+   * }
+ * + * @param manyProperty The many property + * @param expressions Filter expressions with and, or and ? or ?1 type bind parameters + * @param params Bind parameters used in the expressions + */ + Query filterMany(String manyProperty, String expressions, Object... params); /** * Specify specific properties to fetch on the main/root bean (aka partial @@ -404,6 +518,11 @@ public interface ExpressionList { */ Query select(String properties); + /** + * Apply the fetchGroup which defines what part of the object graph to load. + */ + Query select(FetchGroup fetchGroup); + /** * Set whether this query uses DISTINCT. *

@@ -413,7 +532,7 @@ public interface ExpressionList { *

{@code
    *
    *   List customers =
-   *       Ebean.find(Customer.class)
+   *       DB.find(Customer.class)
    *          .setDistinct(true)
    *          .select("name")     // only select the customer name
    *          .findList();
@@ -480,6 +599,35 @@ public interface ExpressionList {
    */
   Query setUseQueryCache(CacheMode useCache);
 
+  /**
+   * Extended version for setDistinct in conjunction with "findSingleAttributeList";
+   * 
{@code
+   *
+   *  List> orderStatusCount =
+   *
+   *     DB.find(Order.class)
+   *      .select("status")
+   *      .where()
+   *      .gt("orderDate", LocalDate.now().minusMonths(3))
+   *
+   *      // fetch as single attribute with a COUNT
+   *      .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
+   *      .findSingleAttributeList();
+   *
+   *     for (CountedValue entry : orderStatusCount) {
+   *       System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
+   *     }
+   *
+   *   // produces
+   *
+   *   count:3 orderStatus:NEW
+   *   count:1 orderStatus:SHIPPED
+   *   count:1 orderStatus:COMPLETE
+   *
+   * }
+ */ + Query setCountDistinct(CountDistinctOrder orderBy); + /** * Calls {@link #setUseQueryCache(CacheMode)} with ON or OFF. * @@ -515,6 +663,11 @@ public interface ExpressionList { */ Query setDisableReadAuditing(); + /** + * Set a label on the query (to help identify query execution statistics). + */ + Query setLabel(String label); + /** * Add expressions to the having clause. *

@@ -529,6 +682,14 @@ public interface ExpressionList { */ ExpressionList where(); + /** + * Add the expressions to this expression list. + * + * @param expressions The expressions that are parsed and added to this expression list + * @param params Bind parameters to match ? or ?1 bind positions. + */ + ExpressionList where(String expressions, Object... params); + /** * Path exists - for the given path in a JSON document. *

{@code
@@ -557,7 +718,6 @@ public interface ExpressionList {
 
   /**
    * Equal to expression for the value at the given path in the JSON document.
-   * 

*

{@code
    *
    *   where().jsonEqualTo("content", "path.other", 34)
@@ -572,7 +732,6 @@ public interface ExpressionList {
 
   /**
    * Not Equal to - for the given path in a JSON document.
-   * 

*

{@code
    *
    *   where().jsonNotEqualTo("content", "path.other", 34)
@@ -587,7 +746,6 @@ public interface ExpressionList {
 
   /**
    * Greater than - for the given path in a JSON document.
-   * 

*

{@code
    *
    *   where().jsonGreaterThan("content", "path.other", 34)
@@ -598,7 +756,6 @@ public interface ExpressionList {
 
   /**
    * Greater than or equal to - for the given path in a JSON document.
-   * 

*

{@code
    *
    *   where().jsonGreaterOrEqual("content", "path.other", 34)
@@ -609,7 +766,6 @@ public interface ExpressionList {
 
   /**
    * Less than - for the given path in a JSON document.
-   * 

*

{@code
    *
    *   where().jsonLessThan("content", "path.other", 34)
@@ -620,7 +776,6 @@ public interface ExpressionList {
 
   /**
    * Less than or equal to - for the given path in a JSON document.
-   * 

*

{@code
    *
    *   where().jsonLessOrEqualTo("content", "path.other", 34)
@@ -631,7 +786,6 @@ public interface ExpressionList {
 
   /**
    * Between - for the given path in a JSON document.
-   * 

*

{@code
    *
    *   where().jsonBetween("content", "orderDate", lowerDateTime, upperDateTime)
@@ -642,21 +796,6 @@ public interface ExpressionList {
 
   /**
    * Add an Expression to the list.
-   * 

- * This returns the list so that add() can be chained. - *

- *

- *

{@code
-   *
-   * Query query = Ebean.find(Customer.class);
-   * query.where()
-   *     .like("name","Rob%")
-   *     .eq("status", Customer.ACTIVE);
-   *
-   * List list = query.findList();
-   * ...
-   *
-   * }
*/ ExpressionList add(Expression expr); @@ -670,6 +809,11 @@ public interface ExpressionList { */ ExpressionList eq(String propertyName, Object value); + /** + * Equal To or Null - property is equal to a given value or null. + */ + ExpressionList eqOrNull(String propertyName, Object value); + /** * Not Equal To - property not equal to the given value. */ @@ -681,6 +825,39 @@ public interface ExpressionList { */ ExpressionList ieq(String propertyName, String value); + /** + * Case Insensitive Not Equal To - property not equal to the given value (typically + * using a lower() function to make it case insensitive). + */ + ExpressionList ine(String propertyName, String value); + + /** + * Value in Range between 2 properties. + * + *
{@code
+   *
+   *    .startDate.inRangeWith(endDate, now)
+   *
+   *    // which equates to
+   *    startDate <= now and (endDate > now or endDate is null)
+   *
+   * }
+ * + *

+ * This is a convenience expression combining a number of simple expressions. + * The most common use of this could be called "effective dating" where 2 date or + * timestamp columns represent the date range in which + */ + ExpressionList inRangeWith(String lowProperty, String highProperty, Object value); + + /** + * In Range - property >= value1 and property < value2. + *

+ * Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps. + *

+ */ + ExpressionList inRange(String propertyName, Object value1, Object value2); + /** * Between - property between the two given values. */ @@ -696,6 +873,11 @@ public interface ExpressionList { */ ExpressionList gt(String propertyName, Object value); + /** + * Greater Than or Null - property greater than the given value or null. + */ + ExpressionList gtOrNull(String propertyName, Object value); + /** * Greater Than or Equal to - property greater than or equal to the given * value. @@ -707,6 +889,11 @@ public interface ExpressionList { */ ExpressionList lt(String propertyName, Object value); + /** + * Less Than or Null - property less than the given value or null. + */ + ExpressionList ltOrNull(String propertyName, Object value); + /** * Less Than or Equal to - property less than or equal to the given value. */ @@ -736,7 +923,6 @@ public interface ExpressionList { * To get control over the options you can create an ExampleExpression and set * those options such as case insensitive etc. *

- *

*

{@code
    *
    * // create an example bean and set the properties
@@ -745,16 +931,15 @@ public interface ExpressionList {
    * example.setName("Rob%");
    * example.setNotes("%something%");
    *
-   * List<Customer> list = Ebean.find(Customer.class).where()
-   *     // pass the bean into the where() clause
-   *     .exampleLike(example)
-   *     // you can add other expressions to the same query
-   *     .gt("id", 2).findList();
+   * List list =
+   *   DB.find(Customer.class)
+   *     .where().exampleLike(example)
+   *     .findList();
    *
    * }
*

* Similarly you can create an ExampleExpression - *

+ *

*
{@code
    *
    * Customer example = new Customer();
@@ -764,7 +949,7 @@ public interface ExpressionList {
    * // create a ExampleExpression with more control
    * ExampleExpression qbe = new ExampleExpression(example, true, LikeType.EQUAL_TO).includeZeros();
    *
-   * List list = Ebean.find(Customer.class).where().add(qbe).findList();
+   * List list = DB.find(Customer.class).where().add(qbe).findList();
    *
    * }
*/ @@ -842,8 +1027,43 @@ public interface ExpressionList { ExpressionList in(String propertyName, Collection values); /** - * In - using a subQuery. + * In where null or empty values means that no predicate is added to the query. + *

+ * That is, only add the IN predicate if the values are not null or empty. + *

+ * Without this we typically need to code an if block to only add + * the IN predicate if the collection is not empty like: + *

* + *

Without inOrEmpty()

+ *
{@code
+   *
+   *   query.where() // add some predicates
+   *     .eq("status", Status.NEW);
+   *
+   *   if (ids != null && !ids.isEmpty()) {
+   *     query.where().in("customer.id", ids);
+   *   }
+   *
+   *   query.findList();
+   *
+   * }
+ * + *

Using inOrEmpty()

+ *
{@code
+   *
+   *   query.where()
+   *     .eq("status", Status.NEW)
+   *     .inOrEmpty("customer.id", ids)
+   *     .findList();
+   *
+   * }
+ */ + ExpressionList inOrEmpty(String propertyName, Collection values); + + /** + * In - using a subQuery. + *

* This is exactly the same as in() and provided due to "in" being a Kotlin keyword * (and hence to avoid the slightly ugly escaping when using in() in Kotlin) */ @@ -853,7 +1073,7 @@ public interface ExpressionList { /** * In - property has a value in the array of values. - * + *

* This is exactly the same as in() and provided due to "in" being a Kotlin keyword * (and hence to avoid the slightly ugly escaping when using in() in Kotlin) */ @@ -863,7 +1083,7 @@ public interface ExpressionList { /** * In - property has a value in the collection of values. - * + *

* This is exactly the same as in() and provided due to "in" being a Kotlin keyword * (and hence to avoid the slightly ugly escaping when using in() in Kotlin) */ @@ -961,6 +1181,65 @@ public interface ExpressionList { */ ExpressionList arrayIsNotEmpty(String propertyName); + /** + * Add expression for ANY of the given bit flags to be set. + *

{@code
+   *
+   * where().bitwiseAny("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+   *
+   * }
+ * + * @param propertyName The property that holds the flags value + * @param flags The flags we are looking for + */ + ExpressionList bitwiseAny(String propertyName, long flags); + + /** + * Add expression for ALL of the given bit flags to be set. + *
{@code
+   *
+   * where().bitwiseAll("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+   *
+   * }
+ * + * @param propertyName The property that holds the flags value + * @param flags The flags we are looking for + */ + ExpressionList bitwiseAll(String propertyName, long flags); + + /** + * Add expression for the given bit flags to be NOT set. + *
{@code
+   *
+   * where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
+   *
+   * }
+ * + * @param propertyName The property that holds the flags value + * @param flags The flags we are looking for + */ + ExpressionList bitwiseNot(String propertyName, long flags); + + /** + * Add bitwise AND expression of the given bit flags to compare with the match/mask. + *

+ *

{@code
+   *
+   * // Flags Bulk + Size = Size
+   * // ... meaning Bulk is not set and Size is set
+   *
+   * long selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
+   * long mask = BwFlags.HAS_SIZE; // Only Size flag set
+   *
+   * where().bitwiseAnd("flags", selectedFlags, mask)
+   *
+   * }
+ * + * @param propertyName The property that holds the flags value + * @param flags The flags we are looking for + */ + ExpressionList bitwiseAnd(String propertyName, long flags, long match); + /** * Add raw expression with a single parameter. *

@@ -980,6 +1259,13 @@ public interface ExpressionList { * raw("add_days(orderDate, 10) < ?", someDate) * * }

+ * + *

Subquery example:

+ *
{@code
+   *
+   *   .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
+   *
+   * }
*/ ExpressionList raw(String raw, Object value); @@ -1004,15 +1290,83 @@ public interface ExpressionList { * then they are not translated. logical property name names (not fully * qualified) will still be translated to their physical name. *

- *

*

{@code
    *
    *   raw("orderQty < shipQty")
    *
    * }
+ * + *

Subquery example:

+ *
{@code
+   *
+   *   .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
+   *
+   * }
*/ ExpressionList raw(String raw); + /** + * Only add the raw expression if the values is not null or empty. + *

+ * This is a pure convenience expression to make it nicer to deal with the pattern where we use + * raw() expression with a subquery and only want to add the subquery predicate when the collection + * of values is not empty. + *

+ *

Without inOrEmpty()

+ *
{@code
+   *
+   *   query.where() // add some predicates
+   *     .eq("status", Status.NEW);
+   *
+   *   // common pattern - we can use rawOrEmpty() instead
+   *   if (orderIds != null && !orderIds.isEmpty()) {
+   *     query.where().raw("t0.customer_id in (select o.customer_id from orders o where o.id in (?1))", orderIds);
+   *   }
+   *
+   *   query.findList();
+   *
+   * }
+ * + *

Using rawOrEmpty()

+ * Note that in the example below we use the ?1 bind parameter to get "parameter expansion" + * for each element in the collection. + * + *
{@code
+   *
+   *   query.where()
+   *     .eq("status", Status.NEW)
+   *     // only add the expression if orderIds is not empty
+   *     .rawOrEmpty("t0.customer_id in (select o.customer_id from orders o where o.id in (?1))", orderIds);
+   *     .findList();
+   *
+   * }
+ * + *

Postgres ANY

+ * With Postgres we would often use the SQL ANY expression and array parameter binding + * rather than IN. + * + *
{@code
+   *
+   *   query.where()
+   *     .eq("status", Status.NEW)
+   *     .rawOrEmpty("t0.customer_id in (select o.customer_id from orders o where o.id = any(?))", orderIds);
+   *     .findList();
+   *
+   * }
+ *

+ * Note that we need to cast the Postgres array for UUID types like: + *

+ *
{@code
+   *
+   *   " ... = any(?::uuid[])"
+   *
+   * }
+ * + * @param raw The raw expression that is typically a subquery + * @param values The values which is typically a list or set of id values. + */ + ExpressionList rawOrEmpty(String raw, Collection values); + /** * Add a match expression. * @@ -1083,12 +1437,10 @@ public interface ExpressionList { * typically you only explicitly need to use the and() junction * when it is nested inside an or() or not() junction. *

- *

*

{@code
    *
    *  // Example: Nested and()
    *
-   *  Ebean.find(Customer.class)
    *    .where()
    *    .or()
    *      .and() // nested and
@@ -1109,16 +1461,27 @@ public interface ExpressionList {
    * Return a list of expressions that will be joined by OR's.
    * This is exactly the same as disjunction();
    * 

- *

* Use endOr() or endJunction() to end the OR junction. *

- *

+ * + *

{@code
+   *
+   *  // Example: (status active OR anniversary is null)
+   *
+   *    .where()
+   *    .or()
+   *      .eq("status", Customer.Status.ACTIVE)
+   *      .isNull("anniversary")
+   *    .orderBy().asc("name")
+   *    .findList();
+   *
+   * }
+ * *
{@code
    *
    *  // Example: Use or() to join
    *  // two nested and() expressions
    *
-   *  Ebean.find(Customer.class)
    *    .where()
    *    .or()
    *      .and()
@@ -1142,8 +1505,8 @@ public interface ExpressionList {
    * Use endNot() or endJunction() to end expressions being added to the
    * NOT expression list.
    * 

- *

- *

@{code
+   *
+   * 
{@code
    *
    *    .where()
    *      .not()
@@ -1152,12 +1515,11 @@ public interface ExpressionList {
    *        .endNot()
    *
    * }
- *

- *

@{code
+   *
+   * 
{@code
    *
    * // Example: nested not()
    *
-   * Ebean.find(Customer.class)
    *   .where()
    *     .eq("status", Customer.Status.ACTIVE)
    *     .not()
diff --git a/src/main/java/io/ebean/ExtendedServer.java b/src/main/java/io/ebean/ExtendedServer.java
new file mode 100644
index 000000000..ae1c1a6b6
--- /dev/null
+++ b/src/main/java/io/ebean/ExtendedServer.java
@@ -0,0 +1,479 @@
+package io.ebean;
+
+import io.ebeaninternal.api.SpiQuery;
+import io.ebeaninternal.api.SpiTransaction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.persistence.NonUniqueResultException;
+import java.time.Clock;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * The extended API for Database.
+ * 

+ * This provides the finder methods that take an explicit transaction rather than obtaining + * the transaction from the usual mechanism (which is ThreadLocal based). + *

+ *

+ * In general we only want to use this ExtendedServer API when we want to avoid / bypass + * the use of the mechanism to get the current transaction and instead explicitly supply + * the transaction to use. + *

+ *

+ * Note that in all cases the transaction supplied can be null and in this case the Database + * will use the normal mechanism to obtain the transaction to use. + *

+ */ +public interface ExtendedServer { + + /** + * Return the NOW time from the Clock. + */ + long clockNow(); + + /** + * Set the Clock to use for @WhenCreated and @WhenModified. + *

+ * Note that we only expect to change the Clock for testing purposes. + *

+ */ + void setClock(Clock clock); + + /** + * Execute the query returning true if a row is found. + *

+ * The query is executed using max rows of 1 and will only select the id property. + * This method is really just a convenient way to optimise a query to perform a + * 'does a row exist in the db' check. + *

+ * + *

Example:

+ *
{@code
+   *
+   *   boolean userExists = query().where().eq("email", "rob@foo.com").exists();
+   *
+   * }
+ * + *

Example using a query bean:

+ *
{@code
+   *
+   *   boolean userExists = new QContact().email.equalTo("rob@foo.com").exists();
+   *
+   * }
+ * + * @return True if the query finds a matching row in the database + */ + boolean exists(SpiQuery ormQuery, SpiTransaction transaction); + + /** + * Return the number of 'top level' or 'root' entities this query should return. + * + * @see Query#findCount() + * @see Query#findFutureCount() + */ + int findCount(Query query, Transaction transaction); + + /** + * Return the Id values of the query as a List. + * + * @see Query#findIds() + */ + @Nonnull + List findIds(Query query, Transaction transaction); + + /** + * Return a QueryIterator for the query. + *

+ * Generally using {@link #findEach(Query, Consumer, Transaction)} or + * {@link #findEachWhile(Query, Predicate, Transaction)} is preferred + * to findIterate(). The reason is that those methods automatically take care of + * closing the queryIterator (and the underlying jdbc statement and resultSet). + *

+ *

+ * This is similar to findEach in that not all the result beans need to be held + * in memory at the same time and as such is good for processing large queries. + *

+ * + * @see Query#findIterate() + * @see Query#findEach(Consumer) + * @see Query#findEachWhile(Predicate) + */ + @Nonnull + QueryIterator findIterate(Query query, Transaction transaction); + + /** + * Execute the query visiting the each bean one at a time. + *

+ * Unlike findList() this is suitable for processing a query that will return + * a very large resultSet. The reason is that not all the result beans need to be + * held in memory at the same time and instead processed one at a time. + *

+ *

+ * Internally this query using a PersistenceContext scoped to each bean (and the + * beans associated object graph). + *

+ *

+ *

{@code
+   *
+   *     DB.find(Order.class)
+   *       .where().eq("status", Order.Status.NEW)
+   *       .order().asc("id")
+   *       .findEach((Order order) -> {
+   *
+   *         // do something with the order bean
+   *         System.out.println(" -- processing order ... " + order);
+   *       });
+   *
+   * }
+ * + * @see Query#findEach(Consumer) + * @see Query#findEachWhile(Predicate) + */ + void findEach(Query query, Consumer consumer, Transaction transaction); + + /** + * Execute the query visiting the each bean one at a time. + *

+ * Compared to findEach() this provides the ability to stop processing the query + * results early by returning false for the Predicate. + *

+ *

+ * Unlike findList() this is suitable for processing a query that will return + * a very large resultSet. The reason is that not all the result beans need to be + * held in memory at the same time and instead processed one at a time. + *

+ *

+ * Internally this query using a PersistenceContext scoped to each bean (and the + * beans associated object graph). + *

+ *

+ *

{@code
+   *
+   *     DB.find(Order.class)
+   *       .where().eq("status", Order.Status.NEW)
+   *       .order().asc("id")
+   *       .findEachWhile((Order order) -> {
+   *
+   *         // do something with the order bean
+   *         System.out.println(" -- processing order ... " + order);
+   *
+   *         boolean carryOnProcessing = ...
+   *         return carryOnProcessing;
+   *       });
+   *
+   * }
+ * + * @see Query#findEach(Consumer) + * @see Query#findEachWhile(Predicate) + */ + void findEachWhile(Query query, Predicate consumer, Transaction transaction); + + /** + * Return versions of a @History entity bean. + *

+ * Generally this query is expected to be a find by id or unique predicates query. + * It will execute the query against the history returning the versions of the bean. + *

+ */ + @Nonnull + List> findVersions(Query query, Transaction transaction); + + /** + * Execute a query returning a list of beans. + *

+ * Generally you are able to use {@link Query#findList()} rather than + * explicitly calling this method. You could use this method if you wish to + * explicitly control the transaction used for the query. + *

+ *

+ *

{@code
+   *
+   * List customers = DB.find(Customer.class)
+   *     .where().ilike("name", "rob%")
+   *     .findList();
+   *
+   * }
+ * + * @param the type of entity bean to fetch. + * @param query the query to execute. + * @param transaction the transaction to use (can be null). + * @return the list of fetched beans. + * @see Query#findList() + */ + @Nonnull + List findList(Query query, Transaction transaction); + + /** + * Execute find row count query in a background thread. + *

+ * This returns a Future object which can be used to cancel, check the + * execution status (isDone etc) and get the value (with or without a + * timeout). + *

+ * + * @param query the query to execute the row count on + * @param transaction the transaction (can be null). + * @return a Future object for the row count query + * @see Query#findFutureCount() + */ + @Nonnull + FutureRowCount findFutureCount(Query query, Transaction transaction); + + /** + * Execute find Id's query in a background thread. + *

+ * This returns a Future object which can be used to cancel, check the + * execution status (isDone etc) and get the value (with or without a + * timeout). + *

+ * + * @param query the query to execute the fetch Id's on + * @param transaction the transaction (can be null). + * @return a Future object for the list of Id's + * @see Query#findFutureIds() + */ + @Nonnull + FutureIds findFutureIds(Query query, Transaction transaction); + + /** + * Execute find list query in a background thread returning a FutureList object. + *

+ * This returns a Future object which can be used to cancel, check the + * execution status (isDone etc) and get the value (with or without a timeout). + *

+ * This query will execute in it's own PersistenceContext and using its own transaction. + * What that means is that it will not share any bean instances with other queries. + * + * @param query the query to execute in the background + * @param transaction the transaction (can be null). + * @return a Future object for the list result of the query + * @see Query#findFutureList() + */ + @Nonnull + FutureList findFutureList(Query query, Transaction transaction); + + /** + * Return a PagedList for this query using firstRow and maxRows. + *

+ * The benefit of using this over findList() is that it provides functionality to get the + * total row count etc. + *

+ *

+ * If maxRows is not set on the query prior to calling findPagedList() then a + * PersistenceException is thrown. + *

+ *

+ *

{@code
+   *
+   *  PagedList pagedList = DB.find(Order.class)
+   *       .setFirstRow(50)
+   *       .setMaxRows(20)
+   *       .findPagedList();
+   *
+   *       // fetch the total row count in the background
+   *       pagedList.loadRowCount();
+   *
+   *       List orders = pagedList.getList();
+   *       int totalRowCount = pagedList.getTotalRowCount();
+   *
+   * }
+ * + * @return The PagedList + * @see Query#findPagedList() + */ + @Nonnull + PagedList findPagedList(Query query, Transaction transaction); + + /** + * Execute the query returning a set of entity beans. + *

+ * Generally you are able to use {@link Query#findSet()} rather than + * explicitly calling this method. You could use this method if you wish to + * explicitly control the transaction used for the query. + *

+ *

+ *

{@code
+   *
+   * Set customers = DB.find(Customer.class)
+   *     .where().ilike("name", "rob%")
+   *     .findSet();
+   *
+   * }
+ * + * @param the type of entity bean to fetch. + * @param query the query to execute + * @param transaction the transaction to use (can be null). + * @return the set of fetched beans. + * @see Query#findSet() + */ + @Nonnull + Set findSet(Query query, Transaction transaction); + + /** + * Execute the query returning the entity beans in a Map. + *

+ * Generally you are able to use {@link Query#findMap()} rather than + * explicitly calling this method. You could use this method if you wish to + * explicitly control the transaction used for the query. + *

+ * + * @param the type of entity bean to fetch. + * @param query the query to execute. + * @param transaction the transaction to use (can be null). + * @return the map of fetched beans. + * @see Query#findMap() + */ + @Nonnull + Map findMap(Query query, Transaction transaction); + + /** + * Execute the query returning a list of values for a single property. + *

+ *

Example 1:

+ *
{@code
+   *
+   *  List names =
+   *    DB.find(Customer.class)
+   *      .select("name")
+   *      .orderBy().asc("name")
+   *      .findSingleAttributeList();
+   *
+   * }
+ *

Example 2:

+ *
{@code
+   *
+   *  List names =
+   *    DB.find(Customer.class)
+   *      .setDistinct(true)
+   *      .select("name")
+   *      .where().eq("status", Customer.Status.NEW)
+   *      .orderBy().asc("name")
+   *      .setMaxRows(100)
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + * @return the list of values for the selected property + * @see Query#findSingleAttributeList() + */ + @Nonnull + List
findSingleAttributeList(Query query, Transaction transaction); + + /** + * Execute the query returning at most one entity bean or null (if no matching + * bean is found). + *

+ * This will throw a NonUniqueResultException if the query finds more than one result. + *

+ *

+ * Generally you are able to use {@link Query#findOne()} rather than + * explicitly calling this method. You could use this method if you wish to + * explicitly control the transaction used for the query. + *

+ * + * @param the type of entity bean to fetch. + * @param query the query to execute. + * @param transaction the transaction to use (can be null). + * @return the list of fetched beans. + * @throws NonUniqueResultException if more than one result was found + * @see Query#findOne() + */ + @Nullable + T findOne(Query query, Transaction transaction); + + /** + * Similar to findOne() but returns an Optional (rather than nullable). + */ + @Nonnull + Optional findOneOrEmpty(Query query, Transaction transaction); + + /** + * Execute as a delete query deleting the 'root level' beans that match the predicates + * in the query. + *

+ * Note that if the query includes joins then the generated delete statement may not be + * optimal depending on the database platform. + *

+ * + * @param query the query used for the delete + * @param transaction the transaction to use (can be null) + * @param the type of entity bean to fetch. + * @return the number of beans/rows that were deleted + */ + int delete(Query query, Transaction transaction); + + /** + * Execute the update query returning the number of rows updated. + *

+ * The update query must be created using {@link Database#update(Class)}. + *

+ * + * @param query the update query to execute + * @param transaction the optional transaction to use for the update (can be null) + * @param the type of entity bean + * @return The number of rows updated + */ + int update(Query query, Transaction transaction); + + /** + * Execute the sql query returning a list of MapBean. + *

+ * Generally you are able to use {@link SqlQuery#findList()} rather than + * explicitly calling this method. You could use this method if you wish to + * explicitly control the transaction used for the query. + *

+ * + * @param query the query to execute. + * @param transaction the transaction to use (can be null). + * @return the list of fetched MapBean. + * @see SqlQuery#findList() + */ + @Nonnull + List findList(SqlQuery query, Transaction transaction); + + /** + * Execute the SqlQuery iterating a row at a time. + *

+ * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. + *

+ */ + void findEach(SqlQuery query, Consumer consumer, Transaction transaction); + + /** + * Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through. + *

+ * Returning false after processing a row stops the iteration through the query results. + *

+ *

+ * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. + *

+ */ + void findEachWhile(SqlQuery query, Predicate consumer, Transaction transaction); + + /** + * Execute the sql query returning a single MapBean or null. + *

+ * This will throw a PersistenceException if the query found more than one + * result. + *

+ *

+ * Generally you are able to use {@link SqlQuery#findOne()} rather than + * explicitly calling this method. You could use this method if you wish to + * explicitly control the transaction used for the query. + *

+ * + * @param query the query to execute. + * @param transaction the transaction to use (can be null). + * @return the fetched MapBean or null if none was found. + * @see SqlQuery#findOne() + */ + @Nullable + SqlRow findOne(SqlQuery query, Transaction transaction); + +} diff --git a/src/main/java/io/ebean/FetchConfig.java b/src/main/java/io/ebean/FetchConfig.java index 6e6d14517..39823b2c7 100644 --- a/src/main/java/io/ebean/FetchConfig.java +++ b/src/main/java/io/ebean/FetchConfig.java @@ -24,7 +24,7 @@ import java.io.Serializable; *

*

{@code
  * // Normal fetch join results in a single SQL query
- * List list = Ebean.find(Order.class).fetch("details").findList();
+ * List list = DB.find(Order.class).fetch("details").findList();
  *
  * // Find Orders join details using a single SQL query
  * }
@@ -36,7 +36,7 @@ import java.io.Serializable; * * // This will use 2 SQL queries to build this object graph * List list = - * Ebean.find(Order.class) + * DB.find(Order.class) * .fetch("details", new FetchConfig().query()) * .findList(); * @@ -52,7 +52,7 @@ import java.io.Serializable; * * // This will use 3 SQL queries to build this object graph * List list = - * Ebean.find(Order.class) + * DB.find(Order.class) * .fetch("details", new FetchConfig().query()) * .fetch("customer", new FetchConfig().queryFirst(5)) * .findList(); @@ -70,7 +70,7 @@ import java.io.Serializable; *
{@code
  * // This will use 3 SQL queries to build this object graph
  * List list =
- *     Ebean.find(Order.class)
+ *     DB.find(Order.class)
  *         .select("status, shipDate")
  *         .fetch("details", "quantity, price", new FetchConfig().query())
  *         .fetch("details.product", "sku, name")
@@ -100,7 +100,7 @@ import java.io.Serializable;
  * 
{@code
  *
  * List list =
- *     Ebean.find(Order.class)
+ *     DB.find(Order.class)
  *         .fetch("customer", new FetchConfig().query(10).lazy(5))
  *         .findList();
  *
@@ -121,7 +121,7 @@ import java.io.Serializable;
  * 

*

{@code
  *
- * List list = Ebean.find(Order.class)
+ * List list = DB.find(Order.class)
  *   .fetch("customer","name", new FetchConfig().lazy(5))
  *   .fetch("customer.contacts","contactName, phone, email")
  *   .fetch("customer.shippingAddress")
@@ -148,6 +148,8 @@ public class FetchConfig implements Serializable {
 
   private boolean queryAll;
 
+  private boolean cache;
+
   /**
    * Construct the fetch configuration object.
    */
@@ -188,6 +190,17 @@ public class FetchConfig implements Serializable {
     return this;
   }
 
+  /**
+   * Eagerly fetch the beans fetching the beans from the L2 bean cache
+   * and using the DB for beans not in the cache.
+   */
+  public FetchConfig cache() {
+    this.cache = true;
+    this.queryBatchSize = 0;
+    this.queryAll = true;
+    return this;
+  }
+
   /**
    * Eagerly fetch the beans in this path as a separate query (rather than as
    * part of the main query).
@@ -247,6 +260,13 @@ public class FetchConfig implements Serializable {
     return queryAll;
   }
 
+  /**
+   * Return true if this uses L2 bean cache.
+   */
+  public boolean isCache() {
+    return cache;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;
@@ -255,6 +275,7 @@ public class FetchConfig implements Serializable {
     FetchConfig that = (FetchConfig) o;
     if (lazyBatchSize != that.lazyBatchSize) return false;
     if (queryBatchSize != that.queryBatchSize) return false;
+    if (cache != that.cache) return false;
     return queryAll == that.queryAll;
   }
 
@@ -263,6 +284,7 @@ public class FetchConfig implements Serializable {
     int result = lazyBatchSize;
     result = 92821 * result + queryBatchSize;
     result = 92821 * result + (queryAll ? 1 : 0);
+    result = 92821 * result + (cache ? 1 : 0);
     return result;
   }
 }
diff --git a/src/main/java/io/ebean/FetchGroup.java b/src/main/java/io/ebean/FetchGroup.java
new file mode 100644
index 000000000..95ea70412
--- /dev/null
+++ b/src/main/java/io/ebean/FetchGroup.java
@@ -0,0 +1,123 @@
+package io.ebean;
+
+import io.ebean.service.SpiFetchGroupQuery;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Defines what part of the object graph to load (select and fetch clauses).
+ * 

+ * Using a FetchGroup effectively sets the select() and fetch() clauses for a query. It is alternative + * to specifying the select() and fetch() clauses on the query allowing for more re-use of "what to load" + * that can be defined separately from the query and combined with other FetchGroups. + *

+ * + *

Select example

+ *
{@code
+ *
+ * FetchGroup fetchGroup = FetchGroup.of(Customer.class, "name, status");
+ *
+ * Customer.query()
+ *   .select(fetchGroup)
+ *   .findList();
+ *
+ * }
+ * + *

Select and fetch example

+ *
{@code
+ *
+ * FetchGroup fetchGroup = FetchGroup.of(Customer.class)
+ *   .select("name, status")
+ *   .fetch("contacts", "firstName, lastName, email")
+ *   .build();
+ *
+ * Customer.query()
+ *   .select(fetchGroup)
+ *   .findList();
+ *
+ * }
+ * + *

Combining FetchGroups

+ *

+ * FetchGroups can be combined together to form another FetchGroup. + *

+ *
{@code
+ *
+ *  FetchGroup
FG_ADDRESS = FetchGroup.of(Address.class) + * .select("line1, line2, city") + * .fetch("country", "name") + * .build(); + * + * FetchGroup FG_CUSTOMER = FetchGroup.of(Customer.class) + * .select("name, version") + * .fetch("billingAddress", FG_ADDRESS) + * .build(); + * + * + * Customer.query() + * .select(FG_CUSTOMER) + * .findList(); + * + * }
+ * + * @param The bean type the Fetch group can be applied to + */ +public interface FetchGroup { + + /** + * Return the FetchGroup with the given select clause. + *

+ * We use this for simple FetchGroup that only select() properties and do not have additional fetch() clause. + *

+ *
{@code
+   *
+   * FetchGroup fetchGroup = FetchGroup.of(Customer.class, "name, status");
+   *
+   * Customer.query()
+   *   .select(fetchGroup)
+   *   .findList();
+   *
+   * }
+ * + * @param select The select clause of the FetchGroup + * + * @return The FetchGroup with the given select clause + */ + @Nonnull + static FetchGroup of(Class cls, String select) { + return XServiceProvider.fetchGroupOf(cls, select); + } + + /** + * Return the FetchGroupBuilder with the given select clause that we can add fetch clauses to. + *

+ * We chain select() with one or more fetch() clauses to define the object graph to load. + *

+ *
{@code
+   *
+   * FetchGroup fetchGroup = FetchGroup.of(Customer.class)
+   *   .select("name, status")
+   *   .fetch("contacts", "firstName, lastName, email")
+   *   .build();
+   *
+   * Customer.query()
+   *   .select(fetchGroup)
+   *   .findList();
+   *
+   * }
+ * + * @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to + */ + @Nonnull + static FetchGroupBuilder of(Class cls) { + return XServiceProvider.fetchGroupOf(cls); + } + + /** + * Return a query to be used by query beans for constructing FetchGroup. + */ + static SpiFetchGroupQuery queryFor(Class beanType) { + return XServiceProvider.fetchGroupQueryFor(beanType); + } + +} diff --git a/src/main/java/io/ebean/FetchGroupBuilder.java b/src/main/java/io/ebean/FetchGroupBuilder.java new file mode 100644 index 000000000..121cceb53 --- /dev/null +++ b/src/main/java/io/ebean/FetchGroupBuilder.java @@ -0,0 +1,107 @@ +package io.ebean; + +import javax.annotation.Nonnull; + +/** + * Builds a FetchGroup by adding fetch clauses. + *

+ * We add select() and fetch() clauses to define the object graph we want to load. + *

+ * + *
{@code
+ *
+ * FetchGroup fetchGroup = FetchGroup
+ *   .select("name, status")
+ *   .fetch("contacts", "firstName, lastName, email")
+ *   .build();
+ *
+ * Customer.query()
+ *   .select(fetchGroup)
+ *   .where()
+ *   ...
+ *   .findList();
+ *
+ * }
+ */ +public interface FetchGroupBuilder { + + /** + * Specify specific properties to select (top level properties). + */ + @Nonnull + FetchGroupBuilder select(String select); + + /** + * Fetch all the properties at the given path. + */ + @Nonnull + FetchGroupBuilder fetch(String path); + + /** + * Fetch the path with the nested fetch group. + */ + @Nonnull + FetchGroupBuilder fetch(String path, FetchGroup nestedGroup); + + /** + * Fetch the path using a query join with the nested fetch group. + */ + @Nonnull + FetchGroupBuilder fetchQuery(String path, FetchGroup nestedGroup); + + /** + * Fetch the path lazily with the nested fetch group. + */ + @Nonnull + FetchGroupBuilder fetchLazy(String path, FetchGroup nestedGroup); + + /** + * Fetch the path including specified properties. + */ + @Nonnull + FetchGroupBuilder fetch(String path, String properties); + + /** + * Fetch the path including all its properties using a query join. + */ + @Nonnull + FetchGroupBuilder fetchQuery(String path); + + /** + * Fetch the path including all its properties using L2 cache. + * Cache misses fallback to fetchQuery(). + */ + @Nonnull + FetchGroupBuilder fetchCache(String path); + + /** + * Fetch the path including specified properties using a query join. + */ + @Nonnull + FetchGroupBuilder fetchQuery(String path, String properties); + + /** + * Fetch the path including specified properties using L2 cache. + * Cache misses fallback to fetchQuery(). + */ + @Nonnull + FetchGroupBuilder fetchCache(String path, String properties); + + /** + * Fetch the path including all its properties lazily. + */ + @Nonnull + FetchGroupBuilder fetchLazy(String path); + + /** + * Fetch the path including specified properties lazily. + */ + @Nonnull + FetchGroupBuilder fetchLazy(String path, String properties); + + /** + * Build and return the FetchGroup. + */ + @Nonnull + FetchGroup build(); +} diff --git a/src/main/java/io/ebean/Filter.java b/src/main/java/io/ebean/Filter.java index 5a9ebf593..3b7cdc07a 100644 --- a/src/main/java/io/ebean/Filter.java +++ b/src/main/java/io/ebean/Filter.java @@ -25,14 +25,14 @@ import java.util.Set; * // get a list of entities (query execution statistics in this case) * * List list = - * Ebean.find(MetaQueryStatistic.class).findList(); + * DB.find(MetaQueryStatistic.class).findList(); * * long nowMinus24Hrs = System.currentTimeMillis() - 24 * (1000 * 60 * 60); * * // sort and filter the list returning a filtered list... * * List filteredList = - * Ebean.filter(MetaQueryStatistic.class) + * DB.filter(MetaQueryStatistic.class) * .sort("avgTimeMicros desc") * .gt("executionCount", 0) * .gt("lastQueryTime", nowMinus24Hrs) @@ -63,12 +63,12 @@ import java.util.Set; * // get a list of entities (query execution statistics) * * List orders = - * Ebean.find(Order.class).findList(); + * DB.find(Order.class).findList(); * * // Apply a filter... * * List filteredOrders = - * Ebean.filter(Order.class) + * DB.filter(Order.class) * .startsWith("customer.name", "Rob") * .eq("customer.shippingAddress.city", "Auckland") * .filter(orders); diff --git a/src/main/java/io/ebean/Finder.java b/src/main/java/io/ebean/Finder.java index bcba9667a..fbe4e5668 100644 --- a/src/main/java/io/ebean/Finder.java +++ b/src/main/java/io/ebean/Finder.java @@ -2,7 +2,6 @@ package io.ebean; import javax.annotation.Nonnull; import javax.annotation.Nullable; - import java.util.List; /** @@ -14,8 +13,7 @@ import java.util.List; *

*

Testing

*

- * For testing the mocki-ebean project has the ability to replace the finder implementation - *

+ * For testing the mocki-ebean project has the ability to replace the finder implementation. *

*
{@code
  *
@@ -25,7 +23,7 @@ import java.util.List;
  *     super(Customer.class);
  *   }
  *
- *   // Add your customer finder methods ...
+ *   // Add finder methods ...
  *
  *   public Customer byName(String name) {
  *     return query().eq("name", name).findOne();
@@ -46,6 +44,15 @@ import java.util.List;
  *   ...
  *
  * }
+ *

+ * When the Finder is registered as a field on Customer it can then be used like: + *

+ *
{@code
+ *
+ *   Customer rob = Customer.find.byName("Rob");
+ *
+ * }
+ * */ public class Finder { @@ -55,9 +62,9 @@ public class Finder { private final Class type; /** - * The name of the EbeanServer, null for the default server. + * The name of the database this finder will use, null for the default database. */ - private final String serverName; + private final String _$dbName; /** * Create with the type of the entity bean. @@ -82,15 +89,15 @@ public class Finder { */ public Finder(Class type) { this.type = type; - this.serverName = null; + this._$dbName = null; } /** - * Create with the type of the entity bean and specific server name. + * Create with the type of the entity bean and specific database name. */ - public Finder(Class type, String serverName) { + public Finder(Class type, String databaseName) { this.type = type; - this.serverName = serverName; + this._$dbName = databaseName; } /** @@ -108,30 +115,27 @@ public class Finder { } /** - * Return the underlying 'default' EbeanServer. - *

- * This provides full access to the API such as explicit transaction demarcation etc. + * Return the Database this finder will use. */ - public EbeanServer db() { - return Ebean.getServer(serverName); + public Database db() { + return DB.byName(_$dbName); } /** - * Return typically a different EbeanServer to the default. + * Return typically a different Database to the default. *

- * This is equivalent to {@link Ebean#getServer(String)} + * This is equivalent to {@link DB#byName(String)} * - * @param server The name of the EbeanServer. If this is null then the default EbeanServer is - * returned. + * @param databaseName The name of the Database. If this is null then the default database is returned. */ - public EbeanServer db(String server) { - return Ebean.getServer(server); + public Database db(String databaseName) { + return DB.byName(databaseName); } /** * Creates an entity reference for this ID. *

- * Equivalent to {@link EbeanServer#getReference(Class, Object)} + * Equivalent to {@link Database#getReference(Class, Object)} */ @Nonnull public T ref(I id) { @@ -141,7 +145,7 @@ public class Finder { /** * Retrieves an entity by ID. *

- * Equivalent to {@link EbeanServer#find(Class, Object)} + * Equivalent to {@link Database#find(Class, Object)} */ @Nullable public T byId(I id) { @@ -151,7 +155,7 @@ public class Finder { /** * Delete a bean by Id. *

- * Equivalent to {@link EbeanServer#delete(Class, Object)} + * Equivalent to {@link Database#delete(Class, Object)} */ public void deleteById(I id) { db().delete(type, id); @@ -181,7 +185,7 @@ public class Finder { * }

* *

- * Equivalent to {@link EbeanServer#update(Class)} + * Equivalent to {@link Database#update(Class)} */ public UpdateQuery update() { return db().update(type); @@ -190,7 +194,7 @@ public class Finder { /** * Creates a query. *

- * Equivalent to {@link EbeanServer#find(Class)} + * Equivalent to {@link Database#find(Class)} */ public Query query() { return db().find(type); diff --git a/src/main/java/io/ebean/FutureList.java b/src/main/java/io/ebean/FutureList.java index e6d4c6620..ad7c70485 100644 --- a/src/main/java/io/ebean/FutureList.java +++ b/src/main/java/io/ebean/FutureList.java @@ -20,7 +20,7 @@ import java.util.concurrent.TimeoutException; *

{@code
  *
  *  // create a query to find all orders
- * Query query = Ebean.find(Order.class);
+ * Query query = DB.find(Order.class);
  *
  *  // execute the query in a background thread
  *  // immediately returning the futureList
diff --git a/src/main/java/io/ebean/Junction.java b/src/main/java/io/ebean/Junction.java
index c2a76f196..0ab001b37 100644
--- a/src/main/java/io/ebean/Junction.java
+++ b/src/main/java/io/ebean/Junction.java
@@ -11,7 +11,7 @@ package io.ebean;
  * 

*
{@code
  * Query q =
- *     Ebean.find(Person.class)
+ *     DB.find(Person.class)
  *       .where()
  *         .or()
  *           .like("name", "Rob%")
@@ -30,7 +30,7 @@ package io.ebean;
  * 
{@code
  *
  * Query q =
- *     Ebean.find(Person.class)
+ *     DB.find(Person.class)
  *       .where()
  *         .or()
  *           .like("name", "Rob%")
@@ -50,7 +50,7 @@ package io.ebean;
  * 

*
{@code
  * Query q =
- *  Ebean.find(Customer.class)
+ *  DB.find(Customer.class)
  *      .where()
  *        .or()
  *          .and()
diff --git a/src/main/java/io/ebean/MergeOptions.java b/src/main/java/io/ebean/MergeOptions.java
new file mode 100644
index 000000000..0d1fae70b
--- /dev/null
+++ b/src/main/java/io/ebean/MergeOptions.java
@@ -0,0 +1,29 @@
+package io.ebean;
+
+import java.util.Set;
+
+/**
+ * Options used to control a merge. Use MergeOptionsBuilder to create an instance.
+ * 

+ * Instances of MergeOptions are thread safe and safe to share across threads. + */ +public interface MergeOptions { + + /** + * Returns true if Id values are supplied by the client. + *

+ * This would be the case when for example a mobile creates data in it's own local database + * and then sync's. In this case often the id values are UUID. + */ + boolean isClientGeneratedIds(); + + /** + * Return true if delete permanent should be used and false for 'normal' delete that allows soft deletes. + */ + boolean isDeletePermanent(); + + /** + * Return the paths included in the merge. + */ + Set paths(); +} diff --git a/src/main/java/io/ebean/MergeOptionsBuilder.java b/src/main/java/io/ebean/MergeOptionsBuilder.java new file mode 100644 index 000000000..98173ed8d --- /dev/null +++ b/src/main/java/io/ebean/MergeOptionsBuilder.java @@ -0,0 +1,96 @@ +package io.ebean; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Builds a MergeOptions which is immutable and thread safe. + */ +public class MergeOptionsBuilder { + + private static final MOptions DEFAULT_OPTIONS = new MOptions(); + + private Set paths = new LinkedHashSet<>(); + + private boolean clientGeneratedIds; + + private boolean deletePermanent; + + /** + * Return the default options. + */ + public static MergeOptions defaultOptions() { + return DEFAULT_OPTIONS; + } + + /** + * Add a path that will included in the merge. + * + * @param path The path relative to the root type. + * @return The builder to chain another addPath() or build(). + */ + public MergeOptionsBuilder addPath(String path) { + paths.add(path); + return this; + } + + /** + * Set to true if Id values are supplied by the client. + *

+ * This would be the case when for example a mobile creates data in it's own local database + * and then sync's. In this case often the id values are UUID. + */ + public MergeOptionsBuilder setClientGeneratedIds() { + this.clientGeneratedIds = true; + return this; + } + + /** + * Set that deletions should use delete permanent (rather than default which allows soft deletes). + */ + public MergeOptionsBuilder setDeletePermanent() { + this.deletePermanent = true; + return this; + } + + /** + * Build and return the MergeOptions instance. + */ + public MergeOptions build() { + return new MOptions(paths, clientGeneratedIds, deletePermanent); + } + + private static class MOptions implements MergeOptions { + + private final boolean clientGeneratedIds; + private final boolean deletePermanent; + private final Set paths; + + private MOptions(){ + this.clientGeneratedIds = false; + this.paths = new LinkedHashSet<>(); + this.deletePermanent = false; + } + + private MOptions(Set paths, boolean clientGeneratedIds, boolean deletePermanent) { + this.paths = paths; + this.clientGeneratedIds = clientGeneratedIds; + this.deletePermanent = deletePermanent; + } + + @Override + public Set paths() { + return paths; + } + + @Override + public boolean isClientGeneratedIds() { + return clientGeneratedIds; + } + + @Override + public boolean isDeletePermanent() { + return deletePermanent; + } + } +} diff --git a/src/main/java/io/ebean/Model.java b/src/main/java/io/ebean/Model.java index b0f29b968..9eae9b444 100644 --- a/src/main/java/io/ebean/Model.java +++ b/src/main/java/io/ebean/Model.java @@ -2,7 +2,6 @@ package io.ebean; import io.ebean.bean.EntityBean; -import javax.persistence.MappedSuperclass; /** * A MappedSuperclass base class that provides convenience methods for inserting, updating and @@ -12,15 +11,15 @@ import javax.persistence.MappedSuperclass; * Ebean users. *

* Note that there is a ebean-mocker project that enables you to use Mockito or similar - * tools to still mock out the underlying 'default EbeanServer' for testing purposes. + * tools to still mock out the underlying 'default Database' for testing purposes. *

* You may choose not use this Model mapped superclass if you don't like the 'Active Record' style * or if you believe it 'pollutes' your entity beans. *

- * You can use Dependency Injection like Guice or Spring to construct and wire a EbeanServer instance + * You can use Dependency Injection like Guice or Spring to construct and wire a Database instance * and have that same instance used with this Model and Finder. The way that works is that when the - * DI container creates the EbeanServer instance it can be registered with the Ebean singleton. In this - * way the EbeanServer instance can be injected as per normal Guice / Spring dependency injection and + * DI container creates the Database instance it can be registered with DB. In this + * way the Database instance can be injected as per normal Guice / Spring dependency injection and * that same instance also used to support the Model and Finder active record style. *

* If you choose to use the Model mapped superclass you will probably also chose to additionally add @@ -74,19 +73,36 @@ import javax.persistence.MappedSuperclass; * * }

*/ -@MappedSuperclass public abstract class Model { /** - * Return the underlying 'default' EbeanServer. + * The name of the database this entity will use, null for the default database. + */ + private final String _$dbName; + + /** + * Create using the default database. + */ + public Model() { + this._$dbName = null; + } + + /** + * Create with a named database (typically not the default database). + */ + public Model(String dbName) { + this._$dbName = dbName; + } + + /** + * Return the underlying 'default' Database. *

* This provides full access to the API such as explicit transaction demarcation etc. *

* Example: *

{@code
    *
-   * Transaction transaction = Customer.db().beginTransaction();
-   * try {
+   * try (Transaction transaction = Customer.db().beginTransaction()) {
    *
    *   // turn off cascade persist for this transaction
    *   transaction.setPersistCascade(false);
@@ -106,26 +122,12 @@ public abstract class Model {
    *
    *   transaction.commit();
    *
-   * } finally {
-   *   transaction.end();
    * }
    *
    * }
*/ - public static EbeanServer db() { - return Ebean.getDefaultServer(); - } - - /** - * Return a named EbeanServer that is typically different to the default server. - *

- * If you are using multiple databases then each database has a name and maps to a single - * EbeanServer. You can use this method to get an EbeanServer for another database. - * - * @param server The name of the EbeanServer. If this is null then the default EbeanServer is returned. - */ - public static EbeanServer db(String server) { - return Ebean.getServer(server); + public Database db() { + return DB.byName(_$dbName); } /** @@ -148,7 +150,7 @@ public abstract class Model { * * }

* - * @see EbeanServer#markAsDirty(Object) + * @see Database#markAsDirty(Object) */ public void markAsDirty() { db().markAsDirty(this); @@ -184,7 +186,7 @@ public abstract class Model { * Ebean will detect if this is a new bean or a previously fetched bean and perform either an * insert or an update based on that. * - * @see EbeanServer#save(Object) + * @see Database#save(Object) */ public void save() { db().save(this); @@ -204,7 +206,7 @@ public abstract class Model { /** * Update this entity. * - * @see EbeanServer#update(Object) + * @see Database#update(Object) */ public void update() { db().update(this); @@ -213,7 +215,7 @@ public abstract class Model { /** * Insert this entity. * - * @see EbeanServer#insert(Object) + * @see Database#insert(Object) */ public void insert() { db().insert(this); @@ -234,7 +236,7 @@ public abstract class Model { * deleted. Note that, if JDBC batch mode is used then this always returns true. *

* - * @see EbeanServer#delete(Object) + * @see Database#delete(Object) */ public boolean delete() { return db().delete(this); @@ -247,37 +249,16 @@ public abstract class Model { * want to perform a hard/permanent delete. *

* - * @see EbeanServer#deletePermanent(Object) + * @see Database#deletePermanent(Object) */ public boolean deletePermanent() { return db().deletePermanent(this); } - /** - * Perform an update using this entity against the specified server. - */ - public void update(String server) { - db(server).update(this); - } - - /** - * Perform an insert using this entity against the specified server. - */ - public void insert(String server) { - db(server).insert(this); - } - - /** - * Perform a delete using this entity against the specified server. - */ - public boolean delete(String server) { - return db(server).delete(this); - } - /** * Refreshes this entity from the database. * - * @see EbeanServer#refresh(Object) + * @see Database#refresh(Object) */ public void refresh() { db().refresh(this); diff --git a/src/main/java/io/ebean/OrderBy.java b/src/main/java/io/ebean/OrderBy.java index e9f68f6f5..c7bacf7aa 100644 --- a/src/main/java/io/ebean/OrderBy.java +++ b/src/main/java/io/ebean/OrderBy.java @@ -1,9 +1,11 @@ package io.ebean; +import io.ebean.util.StringHelper; + import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * Represents an Order By for a Query. @@ -74,20 +76,35 @@ public final class OrderBy implements Serializable { return query; } + /** + * Add a property with ascending order to this OrderBy. + */ + public Query asc(String propertyName, String collation) { + list.add(new Property(propertyName, true, collation)); + return query; + } + /** * Add a property with descending order to this OrderBy. */ public Query desc(String propertyName) { - list.add(new Property(propertyName, false)); return query; } + /** + * Add a property with descending order to this OrderBy. + */ + public Query desc(String propertyName, String collation) { + list.add(new Property(propertyName, false, collation)); + return query; + } + + /** * Return true if the property is known to be contained in the order by clause. */ public boolean containsProperty(String propertyName) { - for (Property aList : list) { if (propertyName.equals(aList.getProperty())) { return true; @@ -140,10 +157,9 @@ public final class OrderBy implements Serializable { * Return a copy of the OrderBy. */ public OrderBy copy() { - OrderBy copy = new OrderBy<>(); - for (Property aList : list) { - copy.add(aList.copy()); + for (Property property : list) { + copy.add(property.copy()); } return copy; } @@ -220,6 +236,18 @@ public final class OrderBy implements Serializable { return this; } + /** + * Return true if this order by can be used in select clause. + */ + public boolean supportsSelect() { + for (Property property : list) { + if (!property.supportsSelect()) { + return false; + } + } + return true; + } + /** * A property and its ascending descending order. */ @@ -231,6 +259,8 @@ public final class OrderBy implements Serializable { private boolean ascending; + private String collation; + private String nulls; private String highLow; @@ -247,17 +277,32 @@ public final class OrderBy implements Serializable { this.highLow = highLow; } + public Property(String property, boolean ascending, String collation) { + this.property = property; + this.ascending = ascending; + this.collation = collation; + } + + public Property(String property, boolean ascending, String collation, String nulls, String highLow) { + this.property = property; + this.ascending = ascending; + this.collation = collation; + this.nulls = nulls; + this.highLow = highLow; + } + /** * Return a copy of this Property with the path trimmed. */ public Property copyWithTrim(String path) { - return new Property(property.substring(path.length() + 1), ascending, nulls, highLow); + return new Property(property.substring(path.length() + 1), ascending, collation, nulls, highLow); } @Override public int hashCode() { int hc = property.hashCode(); hc = hc * 92821 + (ascending ? 0 : 1); + hc = hc * 92821 + (collation == null ? 0 : collation.hashCode()); hc = hc * 92821 + (nulls == null ? 0 : nulls.hashCode()); hc = hc * 92821 + (highLow == null ? 0 : highLow.hashCode()); return hc; @@ -274,8 +319,9 @@ public final class OrderBy implements Serializable { Property e = (Property) obj; if (ascending != e.ascending) return false; if (!property.equals(e.property)) return false; - if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false; - return highLow != null ? highLow.equals(e.highLow) : e.highLow == null; + if (!Objects.equals(collation, e.collation)) return false; + if (!Objects.equals(nulls, e.nulls)) return false; + return Objects.equals(highLow, e.highLow); } @Override @@ -284,7 +330,7 @@ public final class OrderBy implements Serializable { } public String toStringFormat() { - if (nulls == null) { + if (nulls == null && collation == null) { if (ascending) { return property; } else { @@ -292,11 +338,23 @@ public final class OrderBy implements Serializable { } } else { StringBuilder sb = new StringBuilder(); - sb.append(property); + if (collation != null) { + if (collation.contains("${}")) { + // this is a complex collation, e.g. DB2 - we must replace the property + sb.append(StringHelper.replaceString(collation, "${}", property)); + } else { + sb.append(property); + sb.append(" collate ").append(collation); + } + } else { + sb.append(property); + } if (!ascending) { sb.append(" ").append("desc"); } - sb.append(" ").append(nulls).append(" ").append(highLow); + if (nulls != null) { + sb.append(" ").append(nulls).append(" ").append(highLow); + } return sb.toString(); } } @@ -319,7 +377,7 @@ public final class OrderBy implements Serializable { * Return a copy of this property. */ public Property copy() { - return new Property(property, ascending, nulls, highLow); + return new Property(property, ascending, collation, nulls, highLow); } /** @@ -350,6 +408,12 @@ public final class OrderBy implements Serializable { this.ascending = ascending; } + /** + * Support use in select clause if no collation or nulls ordering. + */ + boolean supportsSelect() { + return nulls == null; + } } private void parse(String orderByClause) { @@ -360,15 +424,15 @@ public final class OrderBy implements Serializable { String[] chunks = orderByClause.split(","); for (String chunk : chunks) { - String[] pairs = chunk.split(" "); - Property p = parseProperty(pairs); + Property p = parseProperty(chunk); if (p != null) { list.add(p); } } } - private Property parseProperty(String[] pairs) { + private Property parseProperty(String chunk) { + String[] pairs = chunk.split(" "); if (pairs.length == 0) { return null; } @@ -394,8 +458,7 @@ public final class OrderBy implements Serializable { boolean asc = isAscending(wordList.get(1)); return new Property(wordList.get(0), asc, wordList.get(2), wordList.get(3)); } - String m = "Expecting a 1, 2 or 4 words in [" + Arrays.toString(pairs) + "] but got " + wordList; - throw new RuntimeException(m); + return new Property(chunk.trim(), true); } private boolean isAscending(String s) { diff --git a/src/main/java/io/ebean/PagedList.java b/src/main/java/io/ebean/PagedList.java index 421fde776..6b0a92ef1 100644 --- a/src/main/java/io/ebean/PagedList.java +++ b/src/main/java/io/ebean/PagedList.java @@ -24,8 +24,7 @@ import java.util.concurrent.Future; * // We want to find the first 50 new orders * // ... so we don't really need setFirstRow(0) * - * PagedList pagedList - * = ebeanServer.find(Order.class) + * PagedList pagedList = DB.find(Order.class) * .where().eq("status", Order.Status.NEW) * .order().asc("id") * .setFirstRow(0) diff --git a/src/main/java/io/ebean/Pairs.java b/src/main/java/io/ebean/Pairs.java index 8929e3dd1..3e7f125ae 100644 --- a/src/main/java/io/ebean/Pairs.java +++ b/src/main/java/io/ebean/Pairs.java @@ -1,6 +1,7 @@ package io.ebean; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -25,7 +26,7 @@ import java.util.List; * pairs.add("sj2", 1001); * pairs.add("pf3", 1000); * - * List list = Ebean.find(OCachedNatKeyBean3.class) + * List list = DB.find(OCachedNatKeyBean3.class) * .where() * .eq("store", "def") * .inPairs(pairs) // IN clause with 'pairs' of values @@ -125,7 +126,7 @@ public class Pairs { * Return all the value pairs. */ public List getEntries() { - return entries; + return Collections.unmodifiableList(entries); } /** @@ -158,6 +159,7 @@ public class Pairs { return this; } + @Override public String toString() { return "p0:" + property0 + " p1:" + property1 + " entries:" + entries; } @@ -183,6 +185,7 @@ public class Pairs { this.b = b; } + @Override public String toString() { return "{" + a + "," + b + "}"; } diff --git a/src/main/java/io/ebean/PrimaryServer.java b/src/main/java/io/ebean/PrimaryServer.java index 4265b7022..eb4204a1b 100644 --- a/src/main/java/io/ebean/PrimaryServer.java +++ b/src/main/java/io/ebean/PrimaryServer.java @@ -1,6 +1,6 @@ package io.ebean; -import io.ebean.config.PropertyMap; +import io.ebean.config.properties.PropertiesLoader; import java.util.Properties; @@ -44,7 +44,7 @@ class PrimaryServer { */ static synchronized Properties getProperties() { if (globalProperties == null) { - globalProperties = PropertyMap.defaultProperties(); + globalProperties = PropertiesLoader.load(); } if (defaultServerName == null) { defaultServerName = determineDefaultServerName(); @@ -76,7 +76,6 @@ class PrimaryServer { if (defaultServerName == null) { defaultServerName = "db"; } - System.setProperty("ebean_db", defaultServerName); return defaultServerName; } diff --git a/src/main/java/io/ebean/ProfileLocation.java b/src/main/java/io/ebean/ProfileLocation.java index d433f4c3d..7b4b778b8 100644 --- a/src/main/java/io/ebean/ProfileLocation.java +++ b/src/main/java/io/ebean/ProfileLocation.java @@ -40,6 +40,11 @@ public interface ProfileLocation { */ String shortDescription(); + /** + * Return the short label. + */ + String label(); + /** * Add execution time. */ diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java index 53499f9c6..0ed9f4622 100644 --- a/src/main/java/io/ebean/Query.java +++ b/src/main/java/io/ebean/Query.java @@ -3,6 +3,7 @@ package io.ebean; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.persistence.NonUniqueResultException; +import java.sql.Connection; import java.sql.Timestamp; import java.util.List; import java.util.Map; @@ -19,10 +20,7 @@ import java.util.function.Predicate; *

*

{@code
  *
- * List orderList =
- *   ebeanServer.find(Order.class)
- *     .fetch("customer")
- *     .fetch("details")
+ * List orderList = DB.find(Order.class)
  *     .where()
  *       .like("customer.name","rob%")
  *       .gt("orderDate",lastWeek)
@@ -38,29 +36,14 @@ import java.util.function.Predicate;
  * 
{@code
  *
  * String oql =
- *   	+" fetch customer "
- *   	+" fetch details "
  *   	+" where customer.name like :custName and orderDate > :minOrderDate "
  *   	+" order by customer.id, id desc "
  *   	+" limit 50 ";
  *
- * Query query = ebeanServer.createQuery(Order.class, oql);
- * query.setParameter("custName", "Rob%");
- * query.setParameter("minOrderDate", lastWeek);
- *
- * List orderList = query.findList();
- * ...
- * }
- *

- * Example: Using a named query called "with.cust.and.details" - *

- *
{@code
- *
- * Query query = ebeanServer.createNamedQuery(Order.class,"with.cust.and.details");
- * query.setParameter("custName", "Rob%");
- * query.setParameter("minOrderDate", lastWeek);
- *
- * List orderList = query.findList();
+ * List orderList = DB.createQuery(Order.class, oql)
+ *   .setParameter("custName", "Rob%")
+ *   .setParameter("minOrderDate", lastWeek)
+ *   .findList();
  * ...
  * }
*

AutoTune

@@ -236,6 +219,46 @@ public interface Query { */ Query asDraft(); + /** + * Convert the query to a DTO bean query. + *

+ * We effectively use the underlying ORM query to build the SQL and then execute + * and map it into DTO beans. + */ + DtoQuery asDto(Class dtoClass); + + /** + * Convert the query to a UpdateQuery. + *

+ * Typically this is used with query beans to covert a query bean + * query into an UpdateQuery like the examples below. + *

+ * + *
{@code
+   *
+   *  int rowsUpdated = new QCustomer()
+   *       .name.startsWith("Rob")
+   *       .asUpdate()
+   *       .set("active", false)
+   *       .update();;
+   *
+   * }
+ * + *
{@code
+   *
+   *   int rowsUpdated = new QContact()
+   *       .notes.note.startsWith("Make Inactive")
+   *       .email.endsWith("@foo.com")
+   *       .customer.id.equalTo(42)
+   *       .asUpdate()
+   *       .set("inactive", true)
+   *       .setRaw("email = lower(email)")
+   *       .update();
+   *
+   * }
+ */ + UpdateQuery asUpdate(); + /** * Cancel the query execution if supported by the underlying database and * driver. @@ -332,6 +355,27 @@ public interface Query { */ Query setAutoTune(boolean autoTune); + /** + * Execute the query allowing properties with invalid JSON to be collected and not fail the query. + *
{@code
+   *
+   *   // fetch a bean with JSON content
+   *   EBasicJsonList bean= DB.find(EBasicJsonList.class)
+   *       .setId(42)
+   *       .setAllowLoadErrors()  // collect errors into bean state if we have invalid JSON
+   *       .findOne();
+   *
+   *
+   *   // get the invalid JSON errors from the bean state
+   *   Map errors = server().getBeanState(bean).getLoadErrors();
+   *
+   *   // If this map is not empty tell we have invalid JSON
+   *   // and should try and fix the JSON content or inform the user
+   *
+   * }
+ */ + Query setAllowLoadErrors(); + /** * Set the default lazy loading batch size to use. *

@@ -375,8 +419,7 @@ public interface Query { *

*
{@code
    *
-   * List customers =
-   *     ebeanServer.find(Customer.class)
+   * List customers = DB.find(Customer.class)
    *     // Only fetch the customer id, name and status.
    *     // This is described as a "Partial Object"
    *     .select("name, status")
@@ -389,6 +432,11 @@ public interface Query {
    */
   Query select(String fetchProperties);
 
+  /**
+   * Apply the fetchGroup which defines what part of the object graph to load.
+   */
+  Query select(FetchGroup fetchGroup);
+
   /**
    * Specify a path to fetch eagerly including specific properties.
    * 

@@ -399,8 +447,7 @@ public interface Query { *

{@code
    *
    * // query orders...
-   * List orders =
-   *     ebeanServer.find(Order.class)
+   * List orders = DB.find(Order.class)
    *       // fetch the customer...
    *       // ... getting the customers name and phone number
    *       .fetch("customer", "name, phoneNumber")
@@ -415,8 +462,7 @@ public interface Query {
    * 
{@code
    *
    * // fetch customers (their id, name and status)
-   * List customers =
-   *     ebeanServer.find(Customer.class)
+   * List customers = DB.find(Customer.class)
    *     .select("name, status")
    *     .fetch("contacts", "firstName,lastName,email")
    *     .findList();
@@ -455,6 +501,14 @@ public interface Query {
    */
   Query fetchQuery(String path, String fetchProperties);
 
+  /**
+   * Fetch the path and properties using L2 bean cache.
+   *
+   * @param path            The path of the beans we are fetching from L2 cache.
+   * @param fetchProperties The properties that should be loaded.
+   */
+  Query fetchCache(String path, String fetchProperties);
+
   /**
    * Fetch the path and properties lazily (via batch lazy loading).
    * 

@@ -485,8 +539,7 @@ public interface Query { *

{@code
    *
    * // fetch customers (their id, name and status)
-   * List customers =
-   *     ebeanServer.find(Customer.class)
+   * List customers = DB.find(Customer.class)
    *     .select("name, status")
    *     .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
    *     .findList();
@@ -507,8 +560,7 @@ public interface Query {
    * 
{@code
    *
    * // fetch customers (their id, name and status)
-   * List customers =
-   *     ebeanServer.find(Customer.class)
+   * List customers = DB.find(Customer.class)
    *     // eager fetch the contacts
    *     .fetch("contacts")
    *     .findList();
@@ -543,6 +595,11 @@ public interface Query {
    */
   Query fetchQuery(String path);
 
+  /**
+   * Fetch the path eagerly using L2 cache.
+   */
+  Query fetchCache(String path);
+
   /**
    * Fetch the path lazily (via batch lazy loading).
    * 

@@ -571,8 +628,7 @@ public interface Query { *

{@code
    *
    * // fetch customers (their id, name and status)
-   * List customers =
-   *     ebeanServer.find(Customer.class)
+   * List customers = DB.find(Customer.class)
    *     // lazy fetch contacts with a batch size of 100
    *     .fetch("contacts", new FetchConfig().lazy(100))
    *     .findList();
@@ -589,13 +645,21 @@ public interface Query {
    */
   Query apply(FetchPath fetchPath);
 
+  /**
+   * Execute the query using the given transaction.
+   */
+  Query usingTransaction(Transaction transaction);
+
+  /**
+   * Execute the query using the given connection.
+   */
+  Query usingConnection(Connection connection);
+
   /**
    * Execute the query returning the list of Id's.
    * 

- * This query will execute against the EbeanServer that was used to create it. + * This query will execute against the Database that was used to create it. *

- * - * @see EbeanServer#findIds(Query, Transaction) */ @Nonnull List findIds(); @@ -616,24 +680,21 @@ public interface Query { * the jdbc statement and resultSet are closed at the end of the iteration. *

*

- * This query will execute against the EbeanServer that was used to create it. + * This query will execute against the Database that was used to create it. *

*
{@code
    *
-   *  Query query =
-   *    ebeanServer.find(Customer.class)
+   *  Query query = DB.find(Customer.class)
    *     .where().eq("status", Status.NEW)
    *     .order().asc("id");
    *
-   *  QueryIterator it = query.findIterate();
-   *  try {
+   *  // use try with resources to ensure QueryIterator is closed
+   *
+   *  try (QueryIterator it = query.findIterate()) {
    *    while (it.hasNext()) {
    *      Customer customer = it.next();
    *      // do something with customer ...
    *    }
-   *  } finally {
-   *    // close the underlying resources
-   *    it.close();
    *  }
    *
    * }
@@ -670,7 +731,7 @@ public interface Query { *

*
{@code
    *
-   *  ebeanServer.find(Customer.class)
+   *  DB.find(Customer.class)
    *     .where().eq("status", Status.NEW)
    *     .order().asc("id")
    *     .findEach((Customer customer) -> {
@@ -699,7 +760,7 @@ public interface Query {
    * 

*
{@code
    *
-   *  ebeanServer.find(Customer.class)
+   *  DB.find(Customer.class)
    *     .fetch("contacts", new FetchConfig().query(2))
    *     .where().eq("status", Status.NEW)
    *     .order().asc("id")
@@ -722,18 +783,15 @@ public interface Query {
   /**
    * Execute the query returning the list of objects.
    * 

- * This query will execute against the EbeanServer that was used to create it. + * This query will execute against the Database that was used to create it. *

*
{@code
    *
-   * List customers =
-   *     ebeanServer.find(Customer.class)
+   * List customers = DB.find(Customer.class)
    *     .where().ilike("name", "rob%")
    *     .findList();
    *
    * }
- * - * @see EbeanServer#findList(Query, Transaction) */ @Nonnull List findList(); @@ -741,18 +799,15 @@ public interface Query { /** * Execute the query returning the set of objects. *

- * This query will execute against the EbeanServer that was used to create it. + * This query will execute against the Database that was used to create it. *

*
{@code
    *
-   * Set customers =
-   *     ebeanServer.find(Customer.class)
+   * Set customers = DB.find(Customer.class)
    *     .where().ilike("name", "rob%")
    *     .findSet();
    *
    * }
- * - * @see EbeanServer#findSet(Query, Transaction) */ @Nonnull Set findSet(); @@ -760,7 +815,7 @@ public interface Query { /** * Execute the query returning a map of the objects. *

- * This query will execute against the EbeanServer that was used to create it. + * This query will execute against the Database that was used to create it. *

*

* You can use setMapKey() so specify the property values to be used as keys @@ -768,14 +823,11 @@ public interface Query { *

*
{@code
    *
-   * Map map =
-   *   ebeanServer.find(Product.class)
+   * Map map = DB.find(Product.class)
    *     .setMapKey("sku")
    *     .findMap();
    *
    * }
- * - * @see EbeanServer#findMap(Query, Transaction) */ @Nonnull Map findMap(); @@ -787,7 +839,7 @@ public interface Query { *
{@code
    *
    *  List names =
-   *    Ebean.find(Customer.class)
+   *    DB.find(Customer.class)
    *      .select("name")
    *      .orderBy().asc("name")
    *      .findSingleAttributeList();
@@ -798,7 +850,7 @@ public interface Query {
    * 
{@code
    *
    *  List names =
-   *    Ebean.find(Customer.class)
+   *    DB.find(Customer.class)
    *      .setDistinct(true)
    *      .select("name")
    *      .where().eq("status", Customer.Status.NEW)
@@ -819,7 +871,7 @@ public interface Query {
    * 
{@code
    *
    *  String name =
-   *    Ebean.find(Customer.class)
+   *    DB.find(Customer.class)
    *      .select("name")
    *      .where().eq("id", 42)
    *      .findSingleAttribute();
@@ -828,6 +880,42 @@ public interface Query {
    */
    A findSingleAttribute();
 
+  /**
+   * Return true if this is countDistinct query.
+   */
+  boolean isCountDistinct();
+
+  /**
+   * Execute the query returning true if a row is found.
+   * 

+ * The query is executed using max rows of 1 and will only select the id property. + * This method is really just a convenient way to optimise a query to perform a + * 'does a row exist in the db' check. + *

+ * + *

Example using a query bean:

+ *
{@code
+   *
+   *   boolean userExists =
+   *     new QContact()
+   *       .email.equalTo("rob@foo.com")
+   *       .exists();
+   *
+   * }
+ * + *

Example:

+ *
{@code
+   *
+   *   boolean userExists = query()
+   *     .where().eq("email", "rob@foo.com")
+   *     .exists();
+   *
+   * }
+ * + * @return True if the query finds a matching row in the database + */ + boolean exists(); + /** * Execute the query returning either a single bean or null (if no matching * bean is found). @@ -842,8 +930,7 @@ public interface Query { *
{@code
    *
    * // assuming the sku of products is unique...
-   * Product product =
-   *     ebeanServer.find(Product.class)
+   * Product product = DB.find(Product.class)
    *         .where().eq("sku", "aa113")
    *         .findOne();
    * ...
@@ -855,8 +942,7 @@ public interface Query {
    * 
{@code
    *
    * // Fetch order 1 and additionally fetch join its order details...
-   * Order order =
-   *     ebeanServer.find(Order.class)
+   * Order order = DB.find(Order.class)
    *       .setId(1)
    *       .fetch("details")
    *       .findOne();
@@ -914,11 +1000,31 @@ public interface Query {
    */
   int delete();
 
+  /**
+   * Execute as a delete query returning the number of rows deleted using the given transaction.
+   * 

+ * Note that if the query includes joins then the generated delete statement may not be + * optimal depending on the database platform. + *

+ * + * @return the number of beans/rows that were deleted. + */ + int delete(Transaction transaction); + /** * Execute the UpdateQuery returning the number of rows updated. + * + * @return the number of beans/rows updated. */ int update(); + /** + * Execute the UpdateQuery returning the number of rows updated using the given transaction. + * + * @return the number of beans/rows updated. + */ + int update(Transaction transaction); + /** * Return the count of entities this query should return. *

@@ -977,7 +1083,7 @@ public interface Query { *

*
{@code
    *
-   *  PagedList pagedList = Ebean.find(Order.class)
+   *  PagedList pagedList = DB.find(Order.class)
    *       .setFirstRow(50)
    *       .setMaxRows(20)
    *       .findPagedList();
@@ -1002,11 +1108,9 @@ public interface Query {
    * // a query with a named parameter
    * String oql = "find order where status = :orderStatus";
    *
-   * Query query = ebeanServer.find(Order.class, oql);
-   *
-   * // bind the named parameter
-   * query.bind("orderStatus", OrderStatus.NEW);
-   * List list = query.findList();
+   * List list = DB.find(Order.class, oql)
+   *   .setParameter("orderStatus", OrderStatus.NEW)
+   *   .findList();
    *
    * }
* @@ -1024,12 +1128,9 @@ public interface Query { * // a query with a positioned parameter * String oql = "where status = ? order by id desc"; * - * Query query = ebeanServer.createQuery(Order.class, oql); - * - * // bind the parameter - * query.setParameter(1, OrderStatus.NEW); - * - * List list = query.findList(); + * List list = DB.createQuery(Order.class, oql) + * .setParameter(1, OrderStatus.NEW) + * .findList(); * * }
* @@ -1046,8 +1147,7 @@ public interface Query { *

*
{@code
    *
-   * Order order =
-   *     ebeanServer.find(Order.class)
+   * Order order = DB.find(Order.class)
    *     .setId(1)
    *     .fetch("details")
    *     .findOne();
@@ -1068,8 +1168,7 @@ public interface Query {
    * Add a single Expression to the where clause returning the query.
    * 
{@code
    *
-   * List newOrders =
-   *     ebeanServer.find(Order.class)
+   * List newOrders = DB.find(Order.class)
    * 		.where().eq("status", Order.NEW)
    * 		.findList();
    * ...
@@ -1084,8 +1183,7 @@ public interface Query {
    * where clause.
    * 
{@code
    *
-   * List orders =
-   *     ebeanServer.find(Order.class)
+   * List orders = DB.find(Order.class)
    *     .where()
    *       .eq("status", Order.NEW)
    *       .ilike("customer.name","rob%")
@@ -1131,10 +1229,7 @@ public interface Query {
    * 

*
{@code
    *
-   * List list =
-   *     ebeanServer.find(Customer.class)
-   *     // .fetch("orders", new FetchConfig().lazy())
-   *     // .fetch("orders", new FetchConfig().query())
+   * List list = DB.find(Customer.class)
    *     .fetch("orders")
    *     .where().ilike("name", "rob%")
    *     .filterMany("orders").eq("status", Order.Status.NEW).gt("orderDate", lastWeek)
@@ -1148,8 +1243,7 @@ public interface Query {
    * 

* * @param propertyName the name of the many property that you want to have a filter on. - * @return the expression list that you add filter expressions for the many - * to. + * @return the expression list that you add filter expressions for the many to. */ ExpressionList filterMany(String propertyName); @@ -1263,7 +1357,7 @@ public interface Query { *
{@code
    *
    *   List customers =
-   *       Ebean.find(Customer.class)
+   *       DB.find(Customer.class)
    *          .setDistinct(true)
    *          .select("name")
    *          .findList();
@@ -1272,6 +1366,36 @@ public interface Query {
    */
   Query setDistinct(boolean isDistinct);
 
+  /**
+   * Extended version for setDistinct in conjunction with "findSingleAttributeList";
+   *
+   * 
{@code
+   *
+   *  List> orderStatusCount =
+   *
+   *     DB.find(Order.class)
+   *      .select("status")
+   *      .where()
+   *      .gt("orderDate", LocalDate.now().minusMonths(3))
+   *
+   *      // fetch as single attribute with a COUNT
+   *      .setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
+   *      .findSingleAttributeList();
+   *
+   *     for (CountedValue entry : orderStatusCount) {
+   *       System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
+   *     }
+   *
+   *   // produces
+   *
+   *   count:3 orderStatus:NEW
+   *   count:1 orderStatus:SHIPPED
+   *   count:1 orderStatus:COMPLETE
+   *
+   * }
+ */ + Query setCountDistinct(CountDistinctOrder orderBy); + /** * Return the first row value. */ @@ -1305,10 +1429,8 @@ public interface Query { * * // Assuming sku is unique for products... * - * Map productMap = - * ebeanServer.find(Product.class) - * // use sku for keys... - * .setMapKey("sku") + * Map productMap = DB.find(Product.class) + * .setMapKey("sku") // sku map keys... * .findMap(); * * }
@@ -1375,6 +1497,15 @@ public interface Query { */ Query setProfileLocation(ProfileLocation profileLocation); + /** + * Set a label on the query. + *

+ * This label can be used to help identify query performance metrics but we can also use + * profile location enhancement on Finders so for some that would be a better option. + *

+ */ + Query setLabel(String label); + /** * Set to true if this query should execute against the doc store. *

@@ -1435,12 +1566,6 @@ public interface Query { */ String getGeneratedSql(); - /** - * executed the select with "for update" which should lock the record - * "on read" - */ - Query setForUpdate(boolean forUpdate); - /** * Execute using "for update" clause which results in the DB locking the record. */ @@ -1477,11 +1602,55 @@ public interface Query { */ Query alias(String alias); + /** + * Set the base table to use for this query. + *

+ * Typically this is used when a table has partitioning and we wish to specify a specific + * partition/table to query against. + *

+ *
{@code
+   *
+   *   QOrder()
+   *   .setBaseTable("order_2019_05")
+   *   .status.equalTo(Status.NEW)
+   *   .findList();
+   *
+   * }
+ */ + Query setBaseTable(String baseTable); + /** * Return the type of beans being queried. */ Class getBeanType(); + /** + * Restrict the query to only return subtypes of the given inherit type. + * + *
{@code
+   *
+   *   List animals =
+   *     new QAnimal()
+   *       .name.startsWith("Fluffy")
+   *       .setInheritType(Cat.class)
+   *       .findList();
+   *
+   * }
+ * + * @param type An inheritance subtype of the + */ + Query setInheritType(Class type); + + /** + * Returns the inherit type. This is normally the same as getBeanType() returns as long as no other type is set. + */ + Class getInheritType(); + + /** + * Return the type of query being executed. + */ + QueryType getQueryType(); + /** * Set true if you want to disable lazy loading. *

@@ -1499,4 +1668,12 @@ public interface Query { */ Set validate(); + /** + * Controls, if paginated queries should always append an 'order by id' statement at the end to + * guarantee a deterministic sort result. This may affect performance. + * If this is not enabled, and an orderBy is set on the query, it's up to the programmer that + * this query provides a deterministic result. + */ + Query orderById(boolean orderById); + } diff --git a/src/main/java/io/ebean/QueryType.java b/src/main/java/io/ebean/QueryType.java new file mode 100644 index 000000000..4c167ac7d --- /dev/null +++ b/src/main/java/io/ebean/QueryType.java @@ -0,0 +1,22 @@ +package io.ebean; + +/** + * The type of the query being executed. + */ +public enum QueryType { + + /** + * A find query. + */ + FIND, + + /** + * An update query. + */ + UPDATE, + + /** + * A delete query. + */ + DELETE +} diff --git a/src/main/java/io/ebean/RawSql.java b/src/main/java/io/ebean/RawSql.java index 857ce7328..66781155b 100644 --- a/src/main/java/io/ebean/RawSql.java +++ b/src/main/java/io/ebean/RawSql.java @@ -87,7 +87,7 @@ package io.ebean; * // .columnMapping("sum(d.order_qty*d.unit_price)", "totalAmount") * .create(); * - * List list = Ebean.find(OrderAggregate.class) + * List list = DB.find(OrderAggregate.class) * .setRawSql(rawSql) * .where().gt("order.id", 0) * .having().gt("totalAmount", 20) @@ -114,7 +114,7 @@ package io.ebean; * .columnMappingIgnore("'ignoreMe'") * .create(); * - * List orders = Ebean.find(OrderAggregate.class) + * List orders = DB.find(OrderAggregate.class) * .setRawSql(rawSql) * .fetch("order", "status,orderDate", new FetchConfig().query()) * .fetch("order.customer", "name") @@ -146,7 +146,7 @@ package io.ebean; * .tableAliasMapping("p", "details.product") * .create(); * - * List ordersFromRaw = Ebean.find(Order.class) + * List ordersFromRaw = DB.find(Order.class) * .setRawSql(rawSql) * .setParameter("maxOrderId", 2) * .setParameter("productId", 1) diff --git a/src/main/java/io/ebean/RawSqlBuilder.java b/src/main/java/io/ebean/RawSqlBuilder.java index 3d1e4f4ca..0df4ac184 100644 --- a/src/main/java/io/ebean/RawSqlBuilder.java +++ b/src/main/java/io/ebean/RawSqlBuilder.java @@ -1,6 +1,7 @@ package io.ebean; import java.sql.ResultSet; +import java.sql.SQLException; /** * Builds RawSql instances from a SQL string and column mappings. @@ -24,6 +25,13 @@ public interface RawSqlBuilder { return XServiceProvider.rawSql().resultSet(resultSet, propertyNames); } + /** + * Create and return a SqlRow based on the resultSet with dbTrueValue and binaryOptimizedUUID options. + */ + static SqlRow sqlRow(ResultSet resultSet, final String dbTrueValue, boolean binaryOptimizedUUID) throws SQLException { + return XServiceProvider.rawSql().sqlRow(resultSet, dbTrueValue, binaryOptimizedUUID); + } + /** * Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be * modified - so no additional WHERE or HAVING expressions can be added to diff --git a/src/main/java/io/ebean/RowConsumer.java b/src/main/java/io/ebean/RowConsumer.java new file mode 100644 index 000000000..3a02964f2 --- /dev/null +++ b/src/main/java/io/ebean/RowConsumer.java @@ -0,0 +1,40 @@ +package io.ebean; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet. + *

+ * This provides a low level option that reads directly from the JDBC ResultSet. + *

+ * + *
{@code
+ *
+ *  String sql = "select id, name, status from o_customer order by name desc";
+ *
+ *  DB.sqlQuery(sql)
+ *    .findEachRow((resultSet, rowNum) -> {
+ *
+ *      // read directly from ResultSet
+ *
+ *      long id = resultSet.getLong(1);
+ *      String name = resultSet.getString(2);
+ *
+ *      // do something interesting with the data
+ *
+ *    });
+ *
+ * }
+ */ +@FunctionalInterface +public interface RowConsumer { + + /** + * Read the data from the ResultSet and process it. + * + * @param resultSet The JDBC ResultSet positioned to the current row + * @param rowNum The number of the current row being mapped. + */ + void accept(ResultSet resultSet, int rowNum) throws SQLException; +} diff --git a/src/main/java/io/ebean/RowMapper.java b/src/main/java/io/ebean/RowMapper.java new file mode 100644 index 000000000..5ef6e64de --- /dev/null +++ b/src/main/java/io/ebean/RowMapper.java @@ -0,0 +1,62 @@ +package io.ebean; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Used with SqlQuery to map raw JDBC ResultSet to objects. + *

+ * This provides a low level mapping option with direct use of JDBC ResultSet + * with the option of having logic in the mapping. For example, only map some + * columns depending on the values read from other columns. + *

+ *

+ * For straight mapping into beans then DtoQuery would be the first choice as + * it can automatically map the ResultSet into beans. + *

+ * + *
{@code
+ *
+ *    //
+ *    // A mapper from ResultSet into our CustomerDto bean
+ *    //
+ *    class CustomerMapper implements RowMapper {
+ *
+ *     @Override
+ *     public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
+ *
+ *       long id = rset.getLong(1);
+ *       String name = rset.getString(2);
+ *       String status = rset.getString(3);
+ *
+ *       return new CustomerDto(id, name, status);
+ *     }
+ *   }
+ *
+ *
+ *   //
+ *   // Then use the mapper
+ *   //
+ *
+ *   String sql = "select id, name, status from o_customer where name = ?";
+ *
+ *  CustomerDto rob = DB.sqlQuery(sql)
+ *    .setParameter(1, "Rob")
+ *    .findOne(CUSTOMER_MAPPER);
+ *
+ *
+ * }
+ * + * @param The type the row data is mapped into. + */ +@FunctionalInterface +public interface RowMapper { + + /** + * Read the data from the ResultSet and map to the return type. + * + * @param resultSet The JDBC ResultSet positioned to the current row + * @param rowNum The number of the current row being mapped. + */ + T map(ResultSet resultSet, int rowNum) throws SQLException; +} diff --git a/src/main/java/io/ebean/ScriptRunner.java b/src/main/java/io/ebean/ScriptRunner.java new file mode 100644 index 000000000..2336d1628 --- /dev/null +++ b/src/main/java/io/ebean/ScriptRunner.java @@ -0,0 +1,53 @@ +package io.ebean; + +import java.net.URL; +import java.util.Map; + +/** + * Runs DDL and SQL scripts. + *

+ * Typically these are scripts used for testing such as seed SQL scripts or truncate SQL scripts. + *

+ * Scripts are executed in their own transaction and committed on successful completion. + * + *

Example of simple use

+ *
{@code
+ *
+ *   Database database = DB.getDefault();
+ *   database.script().run("/scripts/test-script.sql");
+ *
+ * }
+ */ +public interface ScriptRunner { + + /** + * Run a script given the resource path (that should start with "/"). + */ + void run(String path); + + /** + * Run a script given the resource path (that should start with "/") and place holders. + * + *
{@code
+   *
+   *   Map placeholders = new HashMap<>();
+   *   placeholders.put("tableName", "e_basic");
+   *
+   *   Database database = DB.getDefault();
+   *   database.script().run("/scripts/test-script.sql", placeholders);
+   *
+   * }
+ */ + void run(String path, Map placeholderMap); + + /** + * Run a DDL or SQL script given the resource. + */ + void run(URL resource); + + /** + * Run a DDL or SQL script given the resource and place holders. + */ + void run(URL resource, Map placeholderMap); + +} diff --git a/src/main/java/io/ebean/SerializableConflictException.java b/src/main/java/io/ebean/SerializableConflictException.java new file mode 100644 index 000000000..a1ee9fee4 --- /dev/null +++ b/src/main/java/io/ebean/SerializableConflictException.java @@ -0,0 +1,18 @@ +package io.ebean; + +import javax.persistence.OptimisticLockException; + +/** + * Thrown at SERIALIZABLE isolation level for non-recoverable concurrent conflict. + */ +public class SerializableConflictException extends OptimisticLockException { + + private static final long serialVersionUID = 1L; + + /** + * Create with a message and cause. + */ + public SerializableConflictException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/ebean/SqlQuery.java b/src/main/java/io/ebean/SqlQuery.java index a83aa9319..a35ef7eb4 100644 --- a/src/main/java/io/ebean/SqlQuery.java +++ b/src/main/java/io/ebean/SqlQuery.java @@ -3,40 +3,37 @@ package io.ebean; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Serializable; +import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; /** - * Query object for performing native SQL queries that return SqlRow's. - *

- * Firstly note that you can use your own sql queries with entity beans - * by using the SqlSelect annotation. This should be your first approach when - * wanting to use your own SQL queries. - *

- *

- * If ORM Mapping is too tight and constraining for your problem then SqlQuery - * could be a good approach. - *

+ * Query object for performing native SQL queries that return SqlRow or directly read + * ResultSet using a RowMapper. *

* The returned SqlRow objects are similar to a LinkedHashMap with some type * conversion support added. *

*

+ * Refer to {@link DtoQuery} for native sql queries returning DTO beans. + *

+ *

+ * Refer to {@link Database#findNative(Class, String)} for native sql queries returning entity beans. + *

+ * *
{@code
  *
- *   // its typically a good idea to use a named query
- *   // and put the sql in the orm.xml instead of in your code
+ *   // example using named parameters
  *
  *   String sql = "select id, name from customer where name like :name and status_code = :status";
  *
- *   SqlQuery sqlQuery = Ebean.createSqlQuery(sql);
- *   sqlQuery.setParameter("name", "Acme%");
- *   sqlQuery.setParameter("status", "ACTIVE");
- *
- *   // execute the query returning a List of MapBean objects
- *   List list = sqlQuery.findList();
+ *   List list =
+ *     DB.sqlQuery(sql)
+ *       .setParameter("name", "Acme%")
+ *       .setParameter("status", "ACTIVE")
+ *       .findList();
  *
  * }
*/ @@ -77,17 +74,151 @@ public interface SqlQuery extends Serializable { @Nullable SqlRow findOne(); + /** + * Execute the query returning a single result using the mapper. + * + * @param mapper Used to map each ResultSet row into the result object. + */ + T findOne(RowMapper mapper); + + /** + * Execute the query returning a list using the mapper. + * + * @param mapper Used to map each ResultSet row into the result object. + */ + List findList(RowMapper mapper); + + /** + * Execute the query reading each row from ResultSet using the RowConsumer. + *

+ * This provides a low level option that reads directly from the JDBC ResultSet + * and is good for processing very large results where (unlike findList) we don't + * hold all the results in memory but instead can process row by row. + *

+ * + *
{@code
+   *
+   *  String sql = "select id, name, status from customer order by name desc";
+   *
+   *  DB.sqlQuery(sql)
+   *    .findEachRow((resultSet, rowNum) -> {
+   *
+   *      // read directly from ResultSet
+   *
+   *      long id = resultSet.getLong(1);
+   *      String name = resultSet.getString(2);
+   *
+   *      // do something interesting with the data
+   *
+   *    });
+   *
+   * }
+ * + * @param consumer Used to read and process each ResultSet row. + */ + void findEachRow(RowConsumer consumer); + /** * Execute the query returning an optional row. */ @Nonnull Optional findOneOrEmpty(); + /** + * Execute the query returning a single scalar attribute. + *
@{code
+   *
+   *   String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
+   *
+   *   BigDecimal maxPrice = DB.sqlQuery(sql)
+   *     .setParameter(1, 2)
+   *     .findSingleAttribute(BigDecimal.class);
+   *
+   * }
+ * + *

+ * The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc). + *

+ * + * @param attributeType The type of the returned value + */ + T findSingleAttribute(Class attributeType); + + /** + * Execute the query returning a single BigDecimal value. + *

+ * This is an alias for findSingleAttribute(BigDecimal.class) + *

+ */ + BigDecimal findSingleDecimal(); + + /** + * Execute the query returning a single Long value. + *

+ * This is an alias for findSingleAttribute(Long.class) + *

+ */ + Long findSingleLong(); + + /** + * Execute the query returning a list of scalar attribute values. + * + *
{@code
+   *
+   *   String sql =
+   *   " select (unit_price * order_qty) " +
+   *   " from o_order_detail " +
+   *   " where unit_price > ? " +
+   *   " order by (unit_price * order_qty) desc";
+   *
+   *   //
+   *   List lineAmounts =
+   *     DB.sqlQuery(sql)
+   *       .setParameter(1, 3)
+   *       .findSingleAttributeList(BigDecimal.class);
+   *
+   * }
+ * + *

+ * The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc). + *

+ * + * @param attributeType The type of the returned value + */ + List findSingleAttributeList(Class attributeType); + /** * The same as bind for named parameters. */ SqlQuery setParameter(String name, Object value); + /** + * Set one of more positioned parameters. + *

+ * This is a convenient alternative to multiple calls setParameter(). + * + *

{@code
+   *
+   *   String sql = "select id, name from customer where name like ? and status = ?";
+   *
+   *   List list =
+   *     DB.sqlQuery(sql)
+   *       .setParams("Rob", Status.NEW)
+   *       .findList();
+   *
+   *
+   *   // is the same as ...
+   *
+   *   List list =
+   *     DB.sqlQuery(sql)
+   *       .setParameter(1, "Rob")
+   *       .setParameter(2, "Status.NEW)
+   *       .findList();
+   *
+   * }
+ */ + SqlQuery setParams(Object... values); + /** * The same as bind for positioned parameters. */ @@ -115,6 +246,11 @@ public interface SqlQuery extends Serializable { */ SqlQuery setTimeout(int secs); + /** + * Set a label that can be put on performance metrics that are collected. + */ + SqlQuery setLabel(String label); + /** * A hint which for JDBC translates to the Statement.fetchSize(). *

diff --git a/src/main/java/io/ebean/SqlUpdate.java b/src/main/java/io/ebean/SqlUpdate.java index 1f8401eed..392d6f09f 100644 --- a/src/main/java/io/ebean/SqlUpdate.java +++ b/src/main/java/io/ebean/SqlUpdate.java @@ -16,18 +16,90 @@ package io.ebean; * notify Ebean of external changes and enable Ebean to maintain it's "L2" * server cache. *

- *

+ * *

{@code
  *
- * // example that uses 'named' parameters
- * String s = "UPDATE f_topic set post_count = :count where id = :id";
- * SqlUpdate update = Ebean.createSqlUpdate(s);
- * update.setParameter("id", 1);
- * update.setParameter("count", 50);
+ *   // example using 'positioned' parameters
  *
- * int modifiedCount = Ebean.execute(update);
+ *   String sql = "insert into audit_log (group, title, description) values (?, ?, ?);
  *
- * String msg = "There were " + modifiedCount + " rows updated";
+ *   int rows =
+ *     DB.sqlUpdate(sql)
+ *       .setParams("login", "new user", "user rob was created")
+ *       .executeNow();
+ *
+ * }
+ * + *
{@code
+ *
+ *   // example using 'named' parameters
+ *
+ *   String sql = "update topic set post_count = :count where id = :id";
+ *
+ *   int rows =
+ *     DB.sqlUpdate(sql)
+ *       .setParameter("id", 1)
+ *       .setParameter("count", 50)
+ *       .execute();
+ *
+ *   String msg = "There were " + rows + " rows updated";
+ *
+ * }
+ *

+ *

Example: Using setNextParameter()

+ *
{@code
+ *
+ *  String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
+ *
+ *  SqlUpdate insert = DB.sqlUpdate(sql);
+ *
+ *  try (Transaction txn = DB.beginTransaction()) {
+ *    txn.setBatchMode(true);
+ *
+ *    insert.setNextParameter(10000);
+ *    insert.setNextParameter("hello");
+ *    insert.setNextParameter("rob");
+ *    insert.execute();
+ *
+ *    insert.setNextParameter(10001);
+ *    insert.setNextParameter("goodbye");
+ *    insert.setNextParameter("rob");
+ *    insert.execute();
+ *
+ *    insert.setNextParameter(10002);
+ *    insert.setNextParameter("chow");
+ *    insert.setNextParameter("bob");
+ *    insert.execute();
+ *
+ *    txn.commit();
+ *  }
+ * }
+ *

+ * An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like: + *

+ *
{@code
+ *
+ *   try (Transaction txn = DB.beginTransaction()) {
+ *
+ *     insert.setNextParameter(10000);
+ *     insert.setNextParameter("hello");
+ *     insert.setNextParameter("rob");
+ *     insert.addBatch();
+ *
+ *     insert.setNextParameter(10001);
+ *     insert.setNextParameter("goodbye");
+ *     insert.setNextParameter("rob");
+ *     insert.addBatch();
+ *
+ *     insert.setNextParameter(10002);
+ *     insert.setNextParameter("chow");
+ *     insert.setNextParameter("bob");
+ *     insert.addBatch();
+ *
+ *     int[] rows = insert.executeBatch();
+ *
+ *     txn.commit();
+ *   }
  *
  * }
* @@ -40,6 +112,11 @@ public interface SqlUpdate { /** * Execute the update returning the number of rows modified. *

+ * Note that if the transaction has batch mode on then this update will use JDBC batch and may not execute until + * later - at commit time or a transaction flush. In this case this method returns -1 indicating that the + * update has been batched for later execution. + *

+ *

* After you have executed the SqlUpdate you can bind new variables using * {@link #setParameter(String, Object)} etc and then execute the SqlUpdate * again. @@ -54,6 +131,42 @@ public interface SqlUpdate { */ int execute(); + /** + * Execute the statement now regardless of the JDBC batch mode of the transaction. + */ + int executeNow(); + + /** + * Execute when addBatch() has been used to batch multiple bind executions. + * + * @return The row counts for each of the batched statements. + */ + int[] executeBatch(); + + /** + * Add the statement to batch processing to then later execute via executeBatch(). + */ + void addBatch(); + + /** + * Return the generated key value. + */ + Object getGeneratedKey(); + + /** + * Execute and return the generated key. This is effectively a short cut for: + *

+ *

{@code
+   *
+   *   sqlUpdate.execute();
+   *   Object key = sqlUpdate.getGeneratedKey();
+   *
+   * }
+ * + * @return The generated key value + */ + Object executeGetKey(); + /** * Return true if eBean should automatically deduce the table modification * information and process it. @@ -88,6 +201,11 @@ public interface SqlUpdate { */ SqlUpdate setLabel(String label); + /** + * Set to true when we want to use getGeneratedKeys with this statement. + */ + SqlUpdate setGetGeneratedKeys(boolean getGeneratedKeys); + /** * Return the sql statement. */ @@ -113,6 +231,40 @@ public interface SqlUpdate { */ SqlUpdate setTimeout(int secs); + /** + * Set one of more positioned parameters. + *

+ * This is a convenient alternative to multiple setParameter() calls. + * + *

{@code
+   *
+   *   String sql = "insert into audit_log (id, name, version) values (?,?,?)";
+   *
+   *   DB.sqlUpdate(sql)
+   *       .setParams(UUID.randomUUID(), "Hello", 1)
+   *       .executeNow();
+   *
+   *
+   *   // is the same as ...
+   *
+   *   DB.sqlUpdate(sql)
+   *       .setParameter(1, UUID.randomUUID())
+   *       .setParameter(2, "Hello")
+   *       .setParameter(3, 1)
+   *       .executeNow();
+   *
+   * }
+ * + */ + SqlUpdate setParams(Object... values); + + /** + * Set the next positioned parameter. + * + * @param value The value to bind + */ + SqlUpdate setNextParameter(Object value); + /** * Set a parameter via its index position. */ diff --git a/src/main/java/io/ebean/Transaction.java b/src/main/java/io/ebean/Transaction.java index 743fcc138..e9bd914c0 100644 --- a/src/main/java/io/ebean/Transaction.java +++ b/src/main/java/io/ebean/Transaction.java @@ -14,17 +14,17 @@ import java.sql.Connection; public interface Transaction extends AutoCloseable { /** - * Return the current transaction (of the default server) or null if there is + * Return the current transaction (of the default database) or null if there is * no current transaction in scope. *

- * This is the same as Ebean.currentTransaction() + * This is the same as DB.currentTransaction() *

*

- * This returns the current transaction for the 'default server'. If you are using - * multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}. + * This returns the current transaction for the default database. *

- * @see Ebean#currentTransaction() - * @see EbeanServer#currentTransaction() + * + * @see DB#currentTransaction() + * @see Database#currentTransaction() */ static Transaction current() { return Ebean.currentTransaction(); @@ -83,7 +83,7 @@ public interface Transaction extends AutoCloseable { *

* This is similar to commit() but leaves the transaction "Active". *

- *

Functions/h3> + *

Functions

*