From f3cb7e2cfdd3059eb6c71d9848fcedf624f9894a Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Fri, 1 Feb 2019 22:53:41 +1300 Subject: [PATCH] #1624 - ENH: Create io.ebean.DB as alias for Ebean and io.ebean.Database as alias for EbeanServer --- src/main/java/io/ebean/DB.java | 1194 +++++++++++++ src/main/java/io/ebean/Database.java | 1567 +++++++++++++++++ src/main/java/io/ebean/DatabaseFactory.java | 67 + src/main/java/io/ebean/Ebean.java | 2 +- src/main/java/io/ebean/EbeanServer.java | 1561 +--------------- .../java/io/ebean/config/DatabaseConfig.java | 44 + .../ebean/EbeanServer_deleteAllByIdTest.java | 30 +- .../java/io/ebean/plugin/BeanTypeTest.java | 56 +- .../io/ebean/text/json/JsonContextTest.java | 51 +- .../build/ModelBuildBeanVisitorTest.java | 6 +- .../transaction/JdbcTransactionTest.java | 30 +- 11 files changed, 2956 insertions(+), 1652 deletions(-) create mode 100644 src/main/java/io/ebean/DB.java create mode 100644 src/main/java/io/ebean/Database.java create mode 100644 src/main/java/io/ebean/DatabaseFactory.java create mode 100644 src/main/java/io/ebean/config/DatabaseConfig.java diff --git a/src/main/java/io/ebean/DB.java b/src/main/java/io/ebean/DB.java new file mode 100644 index 000000000..9583861ca --- /dev/null +++ b/src/main/java/io/ebean/DB.java @@ -0,0 +1,1194 @@ +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; + +public class 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 txn = DB.beginTransaction()) {
+   * 	   Order order = DB.find(Order.class,10); ...
+   *
+   * 	   DB.save(order);
+   *
+   * 	   txn.commit();
+   *   }
+   *
+   * }
+ *

+ * If you want to externalise the transaction management then you should be + * able to do this via Database. Specifically with Database you can pass + * the transaction to the various find() and save() execute() methods. This + * gives you the ability to create the transactions yourself externally from + * Ebean and pass those transactions through to 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: + *

+ * + */ + 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. + *

+ *

+ * 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. + *

+ */ + 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); + } + + /** + * 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. + *

+ */ + public static SqlQuery createSqlQuery(String sql) { + return getDefault().createSqlQuery(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. + *

+ */ + public static SqlUpdate createSqlUpdate(String sql) { + return getDefault().createSqlUpdate(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(); + } + +} diff --git a/src/main/java/io/ebean/Database.java b/src/main/java/io/ebean/Database.java new file mode 100644 index 000000000..81e1d56d4 --- /dev/null +++ b/src/main/java/io/ebean/Database.java @@ -0,0 +1,1567 @@ +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); + + /** + * 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. + *

+ *

+ *

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); +} diff --git a/src/main/java/io/ebean/DatabaseFactory.java b/src/main/java/io/ebean/DatabaseFactory.java new file mode 100644 index 000000000..8bd1721b8 --- /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 EbeanServers 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/Ebean.java b/src/main/java/io/ebean/Ebean.java index 50300010c..c8cfc1304 100644 --- a/src/main/java/io/ebean/Ebean.java +++ b/src/main/java/io/ebean/Ebean.java @@ -120,7 +120,7 @@ public final class Ebean { private static final Logger logger = LoggerFactory.getLogger(Ebean.class); static { - EbeanVersion.getVersion(); // initalizes the version class and logs the version. + EbeanVersion.getVersion(); // initialises the version class and logs the version. } /** diff --git a/src/main/java/io/ebean/EbeanServer.java b/src/main/java/io/ebean/EbeanServer.java index 75993bc0a..6b56621f4 100644 --- a/src/main/java/io/ebean/EbeanServer.java +++ b/src/main/java/io/ebean/EbeanServer.java @@ -1,1564 +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.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 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 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. - */ - 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 Ebean 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); - - /** - * 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. - *

- *

- *

Using try with resources

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

- *

Using try finally block

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

- *

Transaction options

- *
{@code
-   *
-   *     try (Transaction txn = ebeanServer.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 - * 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
-   * try (Transaction txn = server.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 = server.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
-   *
-   *   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 extended API for EbeanServer. - *

- * 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 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; - - /** - * 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 - 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 = 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; - - /** - * 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);
-   *
-   *   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");
-   *
-   *        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);
-   *
-   *   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 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); } diff --git a/src/main/java/io/ebean/config/DatabaseConfig.java b/src/main/java/io/ebean/config/DatabaseConfig.java new file mode 100644 index 000000000..f314c73e0 --- /dev/null +++ b/src/main/java/io/ebean/config/DatabaseConfig.java @@ -0,0 +1,44 @@ +package io.ebean.config; + +import io.ebean.DatabaseFactory; + +/** + * The configuration used for creating a Database. + *

+ * Used to programmatically construct an Database and optionally register it + * with the DB singleton. + *

+ *

+ * If you just use DB thout this programmatic configuration Ebean will read + * the application.properties file and take the configuration from there. This usually + * includes searching the class path and automatically registering any entity + * classes and listeners etc. + *

+ *
{@code
+ *
+ * DatabaseConfig config = new DatabaseConfig();
+ *
+ * // read the ebean.properties and load
+ * // those settings into this serverConfig object
+ * config.loadFromProperties();
+ *
+ * // explicitly register the entity beans to avoid classpath scanning
+ * config.addClass(Customer.class);
+ * config.addClass(User.class);
+ *
+ * Database db = DatabaseFactory.create(config);
+ *
+ * }
+ * + *

+ * Note that ServerConfigProvider provides a standard Java ServiceLoader mechanism that can + * be used to apply configuration to the DatabaseConfig. + *

+ * + * @author emcgreal + * @author rbygrave + * @see DatabaseFactory + */ +public class DatabaseConfig extends ServerConfig { + +} diff --git a/src/test/java/io/ebean/EbeanServer_deleteAllByIdTest.java b/src/test/java/io/ebean/EbeanServer_deleteAllByIdTest.java index b7c285afb..819419fbc 100644 --- a/src/test/java/io/ebean/EbeanServer_deleteAllByIdTest.java +++ b/src/test/java/io/ebean/EbeanServer_deleteAllByIdTest.java @@ -17,7 +17,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase { List someBeans = beans(3); - Ebean.saveAll(someBeans); + DB.saveAll(someBeans); List ids = new ArrayList<>(); for (EBasicVer someBean : someBeans) { ids.add(someBean.getId()); @@ -25,7 +25,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase { // act LoggedSqlCollector.start(); - Ebean.deleteAll(EBasicVer.class, ids); + DB.deleteAll(EBasicVer.class, ids); List loggedSql = LoggedSqlCollector.stop(); assertThat(loggedSql).hasSize(1); @@ -37,21 +37,18 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase { List someBeans = beans(3); - Ebean.saveAll(someBeans); + DB.saveAll(someBeans); List ids = new ArrayList<>(); for (EBasicVer someBean : someBeans) { ids.add(someBean.getId()); } - EbeanServer server = Ebean.getDefaultServer(); + Database db = DB.getDefault(); // act LoggedSqlCollector.start(); - Transaction txn = server.beginTransaction(); - try { - server.deleteAll(EBasicVer.class, ids, txn); + try (Transaction txn = db.beginTransaction()) { + db.deleteAll(EBasicVer.class, ids, txn); txn.commit(); - } finally { - txn.end(); } List loggedSql = LoggedSqlCollector.stop(); assertThat(loggedSql).hasSize(1); @@ -63,7 +60,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase { List someBeans = beans(3); - Ebean.saveAll(someBeans); + DB.saveAll(someBeans); List ids = new ArrayList<>(); for (EBasicVer someBean : someBeans) { ids.add(someBean.getId()); @@ -71,7 +68,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase { LoggedSqlCollector.start(); - Ebean.deleteAllPermanent(EBasicVer.class, ids); + DB.deleteAllPermanent(EBasicVer.class, ids); List loggedSql = LoggedSqlCollector.stop(); assertThat(loggedSql).hasSize(1); @@ -84,21 +81,18 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase { List someBeans = beans(3); - Ebean.saveAll(someBeans); + DB.saveAll(someBeans); List ids = new ArrayList<>(); for (EBasicVer someBean : someBeans) { ids.add(someBean.getId()); } - EbeanServer server = Ebean.getDefaultServer(); + Database db = DB.getDefault(); // act LoggedSqlCollector.start(); - Transaction txn = server.beginTransaction(); - try { - server.deleteAllPermanent(EBasicVer.class, ids, txn); + try (Transaction txn = db.beginTransaction()) { + db.deleteAllPermanent(EBasicVer.class, ids, txn); txn.commit(); - } finally { - txn.end(); } List loggedSql = LoggedSqlCollector.stop(); assertThat(loggedSql).hasSize(1); diff --git a/src/test/java/io/ebean/plugin/BeanTypeTest.java b/src/test/java/io/ebean/plugin/BeanTypeTest.java index dd910def6..42cfb3aa2 100644 --- a/src/test/java/io/ebean/plugin/BeanTypeTest.java +++ b/src/test/java/io/ebean/plugin/BeanTypeTest.java @@ -1,12 +1,13 @@ package io.ebean.plugin; -import io.ebean.Ebean; -import io.ebean.EbeanServer; +import io.ebean.DB; +import io.ebean.Database; import io.ebean.FetchPath; import io.ebean.Query; import io.ebean.text.PathProperties; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.querydefn.OrmQueryDetail; +import org.junit.Test; import org.tests.inheritance.Stockforecast; import org.tests.model.basic.Car; import org.tests.model.basic.Customer; @@ -15,60 +16,61 @@ import org.tests.model.basic.OrderDetail; import org.tests.model.basic.Person; import org.tests.model.basic.Product; import org.tests.model.basic.Vehicle; -import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class BeanTypeTest { - static EbeanServer server = Ebean.getDefaultServer(); + static Database db = DB.getDefault(); BeanType beanType(Class cls) { - return server.getPluginApi().getBeanType(cls); + return db.getPluginApi().getBeanType(cls); } @Test - public void getBeanType() throws Exception { + public void getBeanType() { assertThat(beanType(Order.class).getBeanType()).isEqualTo(Order.class); } @Test - public void getTypeAtPath_when_ManyToOne() throws Exception { + public void getTypeAtPath_when_ManyToOne() { BeanType orderType = beanType(Order.class); BeanType customerType = orderType.getBeanTypeAtPath("customer"); assertThat(customerType.getBeanType()).isEqualTo(Customer.class); } @Test - public void getTypeAtPath_when_OneToMany() throws Exception { + public void getTypeAtPath_when_OneToMany() { BeanType orderType = beanType(Order.class); BeanType detailsType = orderType.getBeanTypeAtPath("details"); assertThat(detailsType.getBeanType()).isEqualTo(OrderDetail.class); } @Test - public void getTypeAtPath_when_nested() throws Exception { + public void getTypeAtPath_when_nested() { BeanType orderType = beanType(Order.class); BeanType productType = orderType.getBeanTypeAtPath("details.product"); assertThat(productType.getBeanType()).isEqualTo(Product.class); } @Test(expected = RuntimeException.class) - public void getTypeAtPath_when_simpleType() throws Exception { + public void getTypeAtPath_when_simpleType() { beanType(Order.class).getBeanTypeAtPath("status"); } @Test - public void createBean() throws Exception { + public void createBean() { assertThat(beanType(Order.class).createBean()).isNotNull(); } @Test - public void property() throws Exception { + public void property() { Order order = new Order(); order.setStatus(Order.Status.APPROVED); @@ -78,13 +80,13 @@ public class BeanTypeTest { } @Test - public void getBaseTable() throws Exception { + public void getBaseTable() { assertThat(beanType(Order.class).getBaseTable()).isEqualTo("o_order"); } @Test - public void beanId_and_getBeanId() throws Exception { + public void beanId_and_getBeanId() { Order order = new Order(); order.setId(42); @@ -97,7 +99,7 @@ public class BeanTypeTest { } @Test - public void setBeanId() throws Exception { + public void setBeanId() { Order order = new Order(); beanType(Order.class).setBeanId(order, 42); @@ -106,7 +108,7 @@ public class BeanTypeTest { } @Test - public void isDocStoreIndex() throws Exception { + public void isDocStoreIndex() { assertThat(beanType(Order.class).isDocStoreMapped()).isFalse(); assertThat(beanType(Person.class).isDocStoreMapped()).isFalse(); @@ -116,7 +118,7 @@ public class BeanTypeTest { } @Test - public void docStore_getEmbedded() throws Exception { + public void docStore_getEmbedded() { BeanDocType orderDocType = beanType(Order.class).docStore(); FetchPath customer = orderDocType.getEmbedded("customer"); @@ -125,7 +127,7 @@ public class BeanTypeTest { } @Test - public void docStore_getEmbeddedManyRoot() throws Exception { + public void docStore_getEmbeddedManyRoot() { BeanDocType orderDocType = beanType(Order.class).docStore(); @@ -139,28 +141,28 @@ public class BeanTypeTest { } @Test - public void getDocStoreQueueId() throws Exception { + public void getDocStoreQueueId() { assertThat(beanType(Order.class).getDocStoreQueueId()).isEqualTo("order"); assertThat(beanType(Customer.class).getDocStoreQueueId()).isEqualTo("customer"); } @Test - public void getDocStoreIndexType() throws Exception { + public void getDocStoreIndexType() { assertThat(beanType(Order.class).docStore().getIndexType()).isEqualTo("order"); assertThat(beanType(Customer.class).docStore().getIndexType()).isEqualTo("customer"); } @Test - public void getDocStoreIndexName() throws Exception { + public void getDocStoreIndexName() { assertThat(beanType(Order.class).docStore().getIndexType()).isEqualTo("order"); assertThat(beanType(Customer.class).docStore().getIndexType()).isEqualTo("customer"); } @Test - public void docStoreNested() throws Exception { + public void docStoreNested() { FetchPath parse = PathProperties.parse("id,name"); @@ -169,9 +171,9 @@ public class BeanTypeTest { } @Test - public void docStoreApplyPath() throws Exception { + public void docStoreApplyPath() { - SpiQuery orderQuery = (SpiQuery) server.find(Order.class); + SpiQuery orderQuery = (SpiQuery) db.find(Order.class); beanType(Order.class).docStore().applyPath(orderQuery); OrmQueryDetail detail = orderQuery.getDetail(); @@ -228,13 +230,13 @@ public class BeanTypeTest { @Test public void addInheritanceWhere_when_leaf() { - Query query = server.find(Vehicle.class); + Query query = db.find(Vehicle.class); beanType(Car.class).addInheritanceWhere(query); } @Test public void addInheritanceWhere_when_root() { - Query query = server.find(Vehicle.class); + Query query = db.find(Vehicle.class); beanType(Vehicle.class).addInheritanceWhere(query); } diff --git a/src/test/java/io/ebean/text/json/JsonContextTest.java b/src/test/java/io/ebean/text/json/JsonContextTest.java index d6fd854b7..4c1de729b 100644 --- a/src/test/java/io/ebean/text/json/JsonContextTest.java +++ b/src/test/java/io/ebean/text/json/JsonContextTest.java @@ -1,14 +1,13 @@ package io.ebean.text.json; -import io.ebean.Ebean; -import io.ebean.EbeanServer; +import com.fasterxml.jackson.core.JsonGenerator; +import io.ebean.DB; import io.ebean.text.PathProperties; +import org.junit.Test; import org.tests.model.basic.Contact; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; -import com.fasterxml.jackson.core.JsonGenerator; -import org.junit.Test; import java.io.StringReader; import java.io.StringWriter; @@ -16,16 +15,18 @@ import java.util.List; import java.util.Map; import static org.assertj.core.api.StrictAssertions.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; public class JsonContextTest { @Test - public void testIsSupportedType() throws Exception { + public void testIsSupportedType() { - EbeanServer server = Ebean.getDefaultServer(); - - JsonContext json = server.json(); + JsonContext json = DB.json(); assertTrue(json.isSupportedType(Customer.class)); assertFalse(json.isSupportedType(System.class)); } @@ -35,14 +36,14 @@ public class JsonContextTest { ResetBasicData.reset(); - List orders = Ebean.find(Order.class) + List orders = DB.find(Order.class) .fetch("customer", "id, name") .where().eq("customer.id", 1) .findList(); - String json = Ebean.json().toJson(orders); + String json = DB.json().toJson(orders); - List orders1 = Ebean.json().toList(Order.class, json); + List orders1 = DB.json().toList(Order.class, json); Customer customer = null; for (Order order : orders1) { @@ -60,16 +61,16 @@ public class JsonContextTest { ResetBasicData.reset(); - List orders = Ebean.find(Order.class) + List orders = DB.find(Order.class) .select("status") .fetch("customer", "id, name") .findList(); - String json = Ebean.json().toJson(orders); + String json = DB.json().toJson(orders); JsonReadOptions options = new JsonReadOptions().setEnableLazyLoading(true); - List orders1 = Ebean.json().toList(Order.class, json, options); + List orders1 = DB.json().toList(Order.class, json, options); for (Order order : orders1) { Customer customer = order.getCustomer(); @@ -81,11 +82,9 @@ public class JsonContextTest { } @Test - public void test_toObject() throws Exception { + public void test_toObject() { - EbeanServer server = Ebean.getDefaultServer(); - - JsonContext json = server.json(); + JsonContext json = DB.getDefault().json(); Customer customer = new Customer(); customer.setId(1); @@ -111,7 +110,7 @@ public class JsonContextTest { String jsonWithUnknown = "{\"id\":42,\"unknownProp\":\"foo\",\"name\":\"rob\",\"version\":1}"; - Customer customer = Ebean.json().toBean(Customer.class, jsonWithUnknown); + Customer customer = DB.json().toBean(Customer.class, jsonWithUnknown); assertEquals(Integer.valueOf(42), customer.getId()); assertEquals("rob", customer.getName()); } @@ -139,7 +138,7 @@ public class JsonContextTest { options.addRootVisitor(custReadVisitor); - Customer customer = Ebean.json().toBean(Customer.class, jsonWithUnknown, options); + Customer customer = DB.json().toBean(Customer.class, jsonWithUnknown, options); assertEquals(Integer.valueOf(42), customer.getId()); assertEquals("rob", customer.getName()); @@ -160,7 +159,7 @@ public class JsonContextTest { JsonReadOptions options = new JsonReadOptions(); options.addRootVisitor(custReadVisitor); - Customer customer = Ebean.json().toBean(Customer.class, someJsonAllKnown, options); + Customer customer = DB.json().toBean(Customer.class, someJsonAllKnown, options); assertEquals(Integer.valueOf(42), customer.getId()); assertEquals("rob", customer.getName()); @@ -171,10 +170,8 @@ public class JsonContextTest { @Test public void testCreateGenerator() throws Exception { - EbeanServer server = Ebean.getDefaultServer(); - StringWriter writer = new StringWriter(); - JsonContext json = server.json(); + JsonContext json = DB.json(); JsonGenerator generator = json.createGenerator(writer); Customer customer = new Customer(); @@ -198,10 +195,8 @@ public class JsonContextTest { @Test public void testCreateGenerator_writeRaw() throws Exception { - EbeanServer server = Ebean.getDefaultServer(); - StringWriter writer = new StringWriter(); - JsonContext json = server.json(); + JsonContext json = DB.json(); JsonGenerator generator = json.createGenerator(writer); // test that we can write anything via writeRaw() diff --git a/src/test/java/io/ebeaninternal/dbmigration/model/build/ModelBuildBeanVisitorTest.java b/src/test/java/io/ebeaninternal/dbmigration/model/build/ModelBuildBeanVisitorTest.java index a62ba120c..14a87348e 100644 --- a/src/test/java/io/ebeaninternal/dbmigration/model/build/ModelBuildBeanVisitorTest.java +++ b/src/test/java/io/ebeaninternal/dbmigration/model/build/ModelBuildBeanVisitorTest.java @@ -2,14 +2,14 @@ package io.ebeaninternal.dbmigration.model.build; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.config.DbConstraintNaming; +import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.dbmigration.ddlgeneration.platform.DefaultConstraintMaxLength; import io.ebeaninternal.dbmigration.model.MColumn; import io.ebeaninternal.dbmigration.model.MTable; import io.ebeaninternal.dbmigration.model.ModelContainer; import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing; -import io.ebeaninternal.api.SpiEbeanServer; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -19,7 +19,7 @@ public class ModelBuildBeanVisitorTest extends BaseTestCase { @Test public void test() { - SpiEbeanServer defaultServer = (SpiEbeanServer) Ebean.getDefaultServer(); + SpiEbeanServer defaultServer = (SpiEbeanServer) DB.getDefault(); ModelContainer model = new ModelContainer(); diff --git a/src/test/java/io/ebeaninternal/server/transaction/JdbcTransactionTest.java b/src/test/java/io/ebeaninternal/server/transaction/JdbcTransactionTest.java index f0dcbd69d..06ffb49f0 100644 --- a/src/test/java/io/ebeaninternal/server/transaction/JdbcTransactionTest.java +++ b/src/test/java/io/ebeaninternal/server/transaction/JdbcTransactionTest.java @@ -1,28 +1,27 @@ package io.ebeaninternal.server.transaction; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Transaction; import io.ebean.cache.ServerCache; import io.ebean.cache.ServerCacheManager; +import org.junit.Test; import org.tests.model.basic.Contact; import org.tests.model.basic.Customer; import org.tests.model.basic.EBasic; -import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; public class JdbcTransactionTest { @Test - public void isSkipCache() throws Exception { + public void isSkipCache() { - Transaction transaction = Ebean.beginTransaction(); - try { + try (Transaction transaction = DB.beginTransaction()) { // implicitly skip false due to query only at this point assertThat(transaction.isSkipCache()).isFalse(); EBasic basic = new EBasic("b1"); - Ebean.save(basic); + DB.save(basic); // implicitly skip true due to save assertThat(transaction.isSkipCache()).isTrue(); @@ -35,42 +34,35 @@ public class JdbcTransactionTest { transaction.setSkipCache(true); assertThat(transaction.isSkipCache()).isTrue(); - - } finally { - transaction.end(); } } @Test - public void skipCacheAfterSave() throws Exception { + public void skipCacheAfterSave() { - ServerCacheManager cacheManager = Ebean.getDefaultServer().getServerCacheManager(); + ServerCacheManager cacheManager = DB.getDefault().getServerCacheManager(); ServerCache customerBeanCache = cacheManager.getBeanCache(Customer.class); ServerCache contactNatKeyCache = cacheManager.getNaturalKeyCache(Contact.class); - Transaction transaction = Ebean.beginTransaction(); - try { + try (Transaction transaction = DB.beginTransaction()) { customerBeanCache.getStatistics(true); contactNatKeyCache.getStatistics(true); Customer.find.byId(19898989); - Ebean.find(Contact.class).where().eq("email", "junk@foo.com").findOne(); + DB.find(Contact.class).where().eq("email", "junk@foo.com").findOne(); assertThat(customerBeanCache.getStatistics(true).getMissCount()).isEqualTo(1); assertThat(contactNatKeyCache.getStatistics(true).getMissCount()).isEqualTo(1); EBasic basic = new EBasic("b1"); - Ebean.save(basic); + DB.save(basic); // these don't hit L2 cache due to the save of b1 Customer.find.byId(29898989); - Ebean.find(Contact.class).where().eq("email", "junk2@foo.com").findOne(); + DB.find(Contact.class).where().eq("email", "junk2@foo.com").findOne(); assertThat(customerBeanCache.getStatistics(true).getMissCount()).isEqualTo(0); assertThat(contactNatKeyCache.getStatistics(true).getMissCount()).isEqualTo(0); - - } finally { - transaction.end(); } }