#120 - ENH: Add support for using where() on bulk Update statements

This commit is contained in:
Robin Bygrave
2016-05-23 22:24:07 +12:00
parent 1c162b9b18
commit 6c7074e288
27 changed files with 1093 additions and 348 deletions
+22
View File
@@ -1019,6 +1019,28 @@ public final class Ebean {
return serverMgr.getDefaultServer().find(beanType);
}
/**
* Create an Update query to perform a bulk update.
* <p>
* <pre>{@code
*
* int rows = Ebean.update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param beanType The type of entity bean to update
* @param <T> The type of entity bean
* @return The update query to use
*/
public static <T> UpdateQuery<T> update(Class<T> beanType) {
return serverMgr.getDefaultServer().update(beanType);
}
/**
* Create a filter for sorting and filtering lists of entities locally without
* going back to the database.
+153 -180
View File
@@ -48,20 +48,20 @@ import java.util.Set;
* <p>
* Example: Get a EbeanServer
* </p>
*
* <p>
* <pre>{@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);
* }</pre>
*
* <p>
* <p>
* <b>EbeanServer has more API than Ebean</b><br/>
* EbeanServer provides additional API compared with Ebean. For example it
@@ -78,7 +78,7 @@ import java.util.Set;
* ThreadLocal transaction management you can use the createTransaction()
* method. Example: a single thread requires more than one transaction.
* </p>
*
*
* @see Ebean
* @see EbeanServerFactory
* @see ServerConfig
@@ -95,16 +95,14 @@ public interface EbeanServer {
* also have the option of shutting down the DataSource and deregistering the
* JDBC driver.
* </p>
*
* @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.
*
* @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.
*/
@@ -151,7 +149,8 @@ public interface EbeanServer {
* 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.
* </p>
* @param bean The entity bean to set the id value on.
*
* @param bean The entity bean to set the id value on.
* @param id The id value to set.
*/
Object setBeanId(Object bean, Object id);
@@ -179,6 +178,27 @@ public interface EbeanServer {
*/
<T> CsvReader<T> createCsvReader(Class<T> beanType);
/**
* Create an Update query to perform a bulk update.
* <p>
* <pre>{@code
*
* int rows = ebeanServer
* .update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param beanType The type of entity bean to update
* @param <T> The type of entity bean
* @return The update query to use
*/
<T> UpdateQuery<T> update(Class<T> beanType);
/**
* Create a query for an entity bean and synonym for {@link #find(Class)}.
*
@@ -201,7 +221,7 @@ public interface EbeanServer {
* {@link Query#findSet()} etc will execute against the same EbeanServer from
* which is was created.
* </p>
*
* <p>
* <pre>{@code
*
* // Find order 2 specifying explicitly the parts of the object graph to
@@ -224,7 +244,6 @@ public interface EbeanServer {
* .findList();
*
* }</pre>
*
*/
<T> Query<T> find(Class<T> beanType);
@@ -272,7 +291,7 @@ public interface EbeanServer {
* Note that the sorting uses a Comparator and Collections.sort(); and does
* not invoke a DB query.
* </p>
*
* <p>
* <pre>{@code
*
* // find orders and their customers
@@ -291,10 +310,8 @@ public interface EbeanServer {
*
* }</pre>
*
* @param list
* the list of entity beans
* @param sortByClause
* the properties to sort the list by
* @param list the list of entity beans
* @param sortByClause the properties to sort the list by
*/
<T> void sort(List<T> list, String sortByClause);
@@ -309,7 +326,7 @@ public interface EbeanServer {
* <p>
* An example:
* </p>
*
* <p>
* <pre>{@code
*
* // The bean name and properties - "topic","postCount" and "id"
@@ -362,7 +379,6 @@ public interface EbeanServer {
* 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;
@@ -400,7 +416,7 @@ public interface EbeanServer {
* Example of using a transaction to span multiple calls to find(), save()
* etc.
* </p>
*
* <p>
* <pre>{@code
*
* // start a transaction (stored in a ThreadLocal)
@@ -419,7 +435,7 @@ public interface EbeanServer {
* }
*
* }</pre>
*
* <p>
* <h3>Transaction options:</h3>
* <pre>{@code
*
@@ -452,7 +468,7 @@ public interface EbeanServer {
* }
*
* }</pre>
*
* <p>
* <p>
* If you want to externalise the transaction management then you use
* createTransaction() and pass the transaction around to the various methods on
@@ -468,12 +484,12 @@ public interface EbeanServer {
/**
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
*
* <p>
* <p>
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
* {@link #execute(TxScope, TxRunnable)}.
* </p>
*
* <p>
* <h3>REQUIRES_NEW example:</h3>
* <pre>{@code
* // Start a new transaction. If there is a current transaction
@@ -494,7 +510,7 @@ public interface EbeanServer {
* }
*
* }</pre>
*
* <p>
* <h3>REQUIRED example:</h3>
* <pre>{@code
*
@@ -543,25 +559,24 @@ public interface EbeanServer {
* </p>
* <p>
* Code example:
*
* <p>
* <pre>{@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();
* }
*
* }</pre>
*
* <p>
* </p>
*
*/
void endTransaction();
@@ -576,28 +591,25 @@ public interface EbeanServer {
/**
* 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
*
* @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.
*
* <p>
* <pre>{@code
* // Fetch order 1
* Order order = ebeanServer.find(Order.class, 1);
* }</pre>
*
* <p>
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* </p>
*
* <p>
* <pre>{@code
* // ... additionally fetching customer, customer shipping address,
* // order details, and the product associated with each order detail.
@@ -628,10 +640,8 @@ public interface EbeanServer {
*
* }</pre>
*
* @param beanType
* the type of entity bean to fetch
* @param id
* the id value
* @param beanType the type of entity bean to fetch
* @param id the id value
*/
<T> T find(Class<T> beanType, Object id);
@@ -658,7 +668,7 @@ public interface EbeanServer {
*
*
* }</pre>
*
* <p>
* <h3>Lazy loading characteristics</h3>
* <pre>{@code
*
@@ -673,10 +683,8 @@ public interface EbeanServer {
*
* }</pre>
*
* @param beanType
* the type of entity bean
* @param id
* the id value
* @param beanType the type of entity bean
* @param id the id value
*/
<T> T getReference(Class<T> beanType, Object id);
@@ -707,7 +715,7 @@ public interface EbeanServer {
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
*
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
@@ -741,7 +749,7 @@ public interface EbeanServer {
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
*
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
@@ -766,8 +774,8 @@ public interface EbeanServer {
/**
* Return versions of a @History entity bean.
* <p>
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
@@ -779,7 +787,7 @@ public interface EbeanServer {
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* <p>
* <pre>{@code
*
* List<Customer> customers =
@@ -789,14 +797,10 @@ public interface EbeanServer {
*
* }</pre>
*
* @param <T>
* the type of entity bean to fetch.
* @param query
* the query to execute.
* @param transaction
* the transaction to use (can be null).
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
*
* @see Query#findList()
*/
<T> List<T> findList(Query<T> query, Transaction transaction);
@@ -808,13 +812,10 @@ public interface EbeanServer {
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query
* the query to execute the row count on
* @param transaction
* the transaction (can be null).
* @return a Future object for the row count query
*
* @param query the query to execute the row count on
* @param transaction the transaction (can be null).
* @return a Future object for the row count query
* @see com.avaje.ebean.Query#findFutureRowCount()
*/
<T> FutureRowCount<T> findFutureRowCount(Query<T> query, Transaction transaction);
@@ -826,13 +827,10 @@ public interface EbeanServer {
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query
* the query to execute the fetch Id's on
* @param transaction
* the transaction (can be null).
* @return a Future object for the list of Id's
*
* @param query the query to execute the fetch Id's on
* @param transaction the transaction (can be null).
* @return a Future object for the list of Id's
* @see com.avaje.ebean.Query#findFutureIds()
*/
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
@@ -846,13 +844,9 @@ public interface EbeanServer {
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
*
*
* @param query
* the query to execute in the background
* @param transaction
* the transaction (can be null).
* @param query the query to execute in the background
* @param transaction the transaction (can be null).
* @return a Future object for the list result of the query
*
* @see Query#findFutureList()
*/
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
@@ -867,7 +861,7 @@ public interface EbeanServer {
* If maxRows is not set on the query prior to calling findPagedList() then a
* PersistenceException is thrown.
* </p>
*
* <p>
* <pre>{@code
*
* PagedList<Order> pagedList = Ebean.find(Order.class)
@@ -884,7 +878,6 @@ public interface EbeanServer {
* }</pre>
*
* @return The PagedList
*
* @see Query#findPagedList()
*/
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
@@ -896,7 +889,7 @@ public interface EbeanServer {
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* <p>
* <pre>{@code
*
* Set<Customer> customers =
@@ -905,15 +898,11 @@ public interface EbeanServer {
* .findSet();
*
* }</pre>
*
* @param <T>
* the type of entity bean to fetch.
* @param query
* the query to execute
* @param transaction
* the transaction to use (can be null).
* @return the set of fetched beans.
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute
* @param transaction the transaction to use (can be null).
* @return the set of fetched beans.
* @see Query#findSet()
*/
<T> Set<T> findSet(Query<T> query, Transaction transaction);
@@ -925,15 +914,11 @@ public interface EbeanServer {
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T>
* the type of entity bean to fetch.
* @param query
* the query to execute.
* @param transaction
* the transaction to use (can be null).
* @return the map of fetched beans.
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the map of fetched beans.
* @see Query#findMap()
*/
<T> Map<?, T> findMap(Query<T> query, Transaction transaction);
@@ -950,15 +935,11 @@ public interface EbeanServer {
* explicitly control the transaction used for the query.
* </p>
*
* @param <T>
* the type of entity bean to fetch.
* @param query
* the query to execute.
* @param transaction
* the transaction to use (can be null).
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
*
* @see Query#findUnique()
*/
@Nullable
@@ -979,6 +960,19 @@ public interface EbeanServer {
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* Execute the update query returning the number of rows updated.
* <p>
* The update query must be created using {@link #update(Class)}.
* </p>
*
* @param query the update query to execute
* @param transaction the optional transaction to use for the update (can be null)
* @param <T> the type of entity bean
* @return The number of rows updated
*/
<T> int update(Query<T> query, Transaction transaction);
/**
* Execute the sql query returning a list of MapBean.
* <p>
@@ -986,13 +980,10 @@ public interface EbeanServer {
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query
* the query to execute.
* @param transaction
* the transaction to use (can be null).
* @return the list of fetched MapBean.
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched MapBean.
* @see SqlQuery#findList()
*/
List<SqlRow> findList(SqlQuery query, Transaction transaction);
@@ -1027,13 +1018,10 @@ public interface EbeanServer {
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query
* the query to execute.
* @param transaction
* the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findUnique()
*/
@Nullable
@@ -1054,7 +1042,7 @@ public interface EbeanServer {
* In this example below the details property has a CascadeType.ALL set so
* saving an order will also save all its details.
* </p>
*
* <p>
* <pre>{@code
* public class Order { ...
*
@@ -1063,7 +1051,7 @@ public interface EbeanServer {
* ...
* }
* }</pre>
*
* <p>
* <p>
* 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
@@ -1195,7 +1183,7 @@ public interface EbeanServer {
* <p>
* Example:
* </p>
*
* <p>
* <pre>{@code
*
* // example that uses 'named' parameters
@@ -1212,11 +1200,8 @@ public interface EbeanServer {
*
* }</pre>
*
* @param sqlUpdate
* the update sql potentially with bind values
*
* @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);
@@ -1241,7 +1226,7 @@ public interface EbeanServer {
* <p>
* Example:
* </p>
*
* <p>
* <pre>{@code
*
* String sql = "{call sp_order_modify(?,?,?)}";
@@ -1288,28 +1273,20 @@ public interface EbeanServer {
* information is registered immediately (with the transaction manager).
* </p>
*
* @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
* @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 <T>
* 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)
*
* @param <T> 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> T find(Class<T> beanType, Object id, Transaction transaction);
@@ -1331,20 +1308,20 @@ public interface EbeanServer {
* <p>
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
* dirty so that it is not skipped.
*
* <p>
* <pre>{@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);
*
*
* }</pre>
*/
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().
@@ -1367,17 +1344,17 @@ public interface EbeanServer {
* controls if only the changed properties are included in the update or if all the loaded
* properties are included instead.
* </p>
*
* <p>
* <pre>{@code
*
*
* // A 'stateless update' example
* Customer customer = new Customer();
* customer.setId(7);
* customer.setName("ModifiedNameNoOCC");
* ebeanServer.update(customer);
*
*
* }</pre>
*
*
* @see ServerConfig#setUpdatesDeleteMissingChildren(boolean)
* @see ServerConfig#setUpdateChangesOnly(boolean)
*/
@@ -1390,14 +1367,11 @@ public interface EbeanServer {
/**
* 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.
*
* @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;
@@ -1454,7 +1428,7 @@ public interface EbeanServer {
* The scope can control the transaction type, isolation and rollback
* semantics.
* </p>
*
* <p>
* <pre>{@code
*
* // set specific transactional scope settings
@@ -1477,7 +1451,7 @@ public interface EbeanServer {
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
* </p>
*
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxRunnable() {
@@ -1503,7 +1477,7 @@ public interface EbeanServer {
* The scope can control the transaction type, isolation and rollback
* semantics.
* </p>
*
* <p>
* <pre>{@code
*
* // set specific transactional scope settings
@@ -1531,7 +1505,7 @@ public interface EbeanServer {
* This is basically the same as TxRunnable except that it returns an Object
* (and you specify the return type via generics).
* </p>
*
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxCallable<String>() {
@@ -1555,7 +1529,6 @@ public interface EbeanServer {
/**
* Return the manager of the server cache ("L2" cache).
*
*/
ServerCacheManager getServerCacheManager();
@@ -1571,7 +1544,7 @@ public interface EbeanServer {
* This instance is safe to be used concurrently by multiple threads and this
* method is cheap to call.
* </p>
*
* <p>
* <h3>Simple example:</h3>
* <pre>{@code
*
@@ -1580,7 +1553,7 @@ public interface EbeanServer {
* System.out.println(jsonOutput);
*
* }</pre>
*
* <p>
* <h3>Using PathProperties:</h3>
* <pre>{@code
*
@@ -1630,9 +1603,9 @@ public interface EbeanServer {
* The values are published from the draft to the live bean.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
*/
<T> T publish(Class<T> beanType, Object id);
@@ -1655,8 +1628,8 @@ public interface EbeanServer {
* The values are published from the draft beans to the live beans.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
*/
<T> List<T> publish(Query<T> query);
@@ -1681,9 +1654,9 @@ public interface EbeanServer {
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> 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 <T> 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> T draftRestore(Class<T> beanType, Object id);
@@ -1707,8 +1680,8 @@ public interface EbeanServer {
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
*/
<T> List<T> draftRestore(Query<T> query);
@@ -135,10 +135,18 @@ public interface ExpressionList<T> {
* optimal depending on the database platform.
* </p>
*
* @return the number of beans/rows that were deleted.
* @return the number of rows that were deleted.
*/
int delete();
/**
* Execute as a update query.
*
* @return the number of rows that were updated.
* @see UpdateQuery
*/
int update();
/**
* Execute the query process the beans one at a time.
*
+5
View File
@@ -763,6 +763,11 @@ public interface Query<T> {
*/
int delete();
/**
* Execute the UpdateQuery returning the number of rows updated.
*/
int update();
/**
* Return the count of entities this query should return.
* <p>
@@ -0,0 +1,160 @@
package com.avaje.ebean;
/**
* An update query typically intended to perform a bulk update of many rows that match the query.
* <p>
* Also note that you can also just use a raw SQL update via {@link SqlUpdate} which is pretty light and simple.
* This UpdateQuery is more for the cases where we want to build the where expression of the update using the
* {@link ExpressionList} "Criteria API" that is used with a normal ORM query.
* </p>
*
* <h4>Example: Simple update</h4>
*
* <pre>{@code
*
* int rows = ebeanServer
* .update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
* <pre>{@code sql
*
* update o_customer set status=?, updtime=? where id > ?
*
* }</pre>
*
* <p>
* Note that if the where() clause contains a join then the SQL update changes to use a
* <code> WHERE ID IN () </code> form.
* </p>
*
* <h4>Example: Update with a JOIN</h4>
* <p>
* In this example the expression <code>.eq("billingAddress.country", nz)</code> requires a join
* to the address table.
* </p>
*
* <pre>{@code
*
* int rows = ebeanServer
* .update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .eq("status", Customer.Status.NEW)
* .eq("billingAddress.country", nz)
* .gt("id", 1000)
* .update();
* }</pre>
*
* <pre>{@code sql
*
* update o_customer set status=?, updtime=?
* where id in (
* select t0.id c0
* from o_customer t0
* left outer join o_address t1 on t1.id = t0.billing_address_id
* where t0.status = ?
* and t1.country_code = ?
* and t0.id > ? )
*
* }</pre>
*
* @param <T> The type of entity bean being updated
*
* @see SqlUpdate
*/
public interface UpdateQuery<T> {
/**
* Set the value of a property.
*
*
* <pre>{@code
*
* int rows = ebeanServer
* .update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param property The bean property to be set
* @param value The value to set the property to
*/
UpdateQuery<T> set(String property, Object value);
/**
* Set the property to be null.
*
* <pre>{@code
*
* int rows = ebeanServer
* .update(Customer.class)
* .setNull("notes")
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param property The property to be set to null.
*/
UpdateQuery<T> setNull(String property);
/**
*
* Set using a property expression that does not need any bind values.
* <p>
* The property expression typically contains database functions.
* </p>
*
* <pre>{@code
*
* int rows = ebeanServer
* .update(Customer.class)
* .setRaw("status = coalesce(status, 'A')")
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param propertyExpression A property expression
*/
UpdateQuery<T> setRaw(String propertyExpression);
/**
* Set using a property expression that can contain <code>?</code> bind value placeholders.
* <p>
* For each <code>?</code> in the property expression there should be a matching bind value supplied.
* </p>
* <pre>{@code
*
* int rows = ebeanServer
* .update(Customer.class)
* .setRaw("status = coalesce(status, ?)", Customer.Status.ACTIVE)
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param propertyExpression A raw property expression
* @param values The values to bind with the property expression
*/
UpdateQuery<T> setRaw(String propertyExpression, Object... values);
/**
* Return the query expression list to add predicates to.
*/
ExpressionList<T> where();
}
@@ -18,6 +18,7 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmUpdateProperties;
import java.sql.Timestamp;
import java.util.List;
@@ -91,6 +92,11 @@ public interface SpiQuery<T> extends Query<T> {
* Delete query.
*/
DELETE,
/**
* Update query.
*/
UPDATE,
}
enum TemporalMode {
@@ -677,4 +683,9 @@ public interface SpiQuery<T> extends Query<T> {
*/
Set<String> validate(BeanType<T> desc);
/**
* Return the properties for an update query.
*/
OrmUpdateProperties getUpdateProperties();
}
@@ -53,6 +53,7 @@ import com.avaje.ebeaninternal.server.query.QueryFutureRowCount;
import com.avaje.ebeaninternal.server.querydefn.DefaultOrmQuery;
import com.avaje.ebeaninternal.server.querydefn.DefaultOrmUpdate;
import com.avaje.ebeaninternal.server.querydefn.DefaultRelationalQuery;
import com.avaje.ebeaninternal.server.querydefn.DefaultUpdateQuery;
import com.avaje.ebeaninternal.server.text.csv.TCsvReader;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
@@ -875,11 +876,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new TCsvReader<T>(this, descriptor);
}
public <T> UpdateQuery<T> update(Class<T> beanType) {
return new DefaultUpdateQuery<T>(createQuery(beanType));
}
public <T> Query<T> find(Class<T> beanType) {
return createQuery(beanType);
}
public <T> Query<T> createQuery(Class<T> beanType) {
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType) {
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?");
@@ -1159,6 +1164,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public <T> int update(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.UPDATE, query, t);
try {
request.initTransIfRequired();
request.markNotQueryOnly();
return request.update();
} finally {
request.endTransIfRequired();
}
}
public <T> FutureRowCount<T> findFutureRowCount(Query<T> q, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) q).copy();
@@ -42,4 +42,9 @@ public interface OrmQueryEngine {
* Execute the query as a delete statement.
*/
<T> int delete(OrmQueryRequest<T> request);
/**
* Execute the query as a update statement.
*/
<T> int update(OrmQueryRequest<T> request);
}
@@ -286,6 +286,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return queryEngine.delete(this);
}
/**
* Execute the query as a update.
*/
public int update() {
return queryEngine.update(this);
}
/**
* Execute the query as findById.
*/
@@ -52,6 +52,11 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
*/
int delete();
/**
* Execute the query as a update.
*/
int update();
/**
* Execute the query as findById.
*/
@@ -367,6 +367,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private String deleteByIdSql;
private String deleteByIdInSql;
private String whereIdInSql;
private String softDeleteByIdSql;
private String softDeleteByIdInSql;
@@ -687,7 +688,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
String idEqualsSql = idBinder.getBindIdSql(null);
deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql;
deleteByIdInSql = "delete from " + baseTable + " where " + idBinderInLHSSqlNoAlias + " ";
whereIdInSql = " where " + idBinderInLHSSqlNoAlias + " ";
deleteByIdInSql = "delete from " + baseTable + whereIdInSql;
if (softDelete) {
softDeleteByIdSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idEqualsSql;
@@ -796,6 +798,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
}
/**
* Return the "where id in" sql (for use with UpdateQuery).
*/
public String getWhereIdInSql() {
return whereIdInSql;
}
/**
* Return the "delete by id" sql.
*/
@@ -277,6 +277,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.delete();
}
@Override
public int update() {
return query.update();
}
@Override
public FutureIds<T> findFutureIds() {
return query.findFutureIds();
@@ -279,6 +279,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.delete();
}
@Override
public int update() {
return exprList.update();
}
@Override
public Query<T> asOf(Timestamp asOf) {
return exprList.asOf(asOf);
@@ -88,7 +88,7 @@ public class CQueryBuilder {
/**
* Build the delete query.
*/
public <T> CQueryDelete buildDeleteQuery(OrmQueryRequest<T> request) {
public <T> CQueryUpdate buildUpdateQuery(String type, OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
String rootTableAlias = query.getAlias();
@@ -99,33 +99,54 @@ public class CQueryBuilder {
if (queryPlan != null) {
// skip building the SqlTree and Sql string
predicates.prepare(false);
String sql = queryPlan.getSql();
return new CQueryDelete(request, predicates, sql);
return new CQueryUpdate(type, request, predicates, queryPlan.getSql());
}
predicates.prepare(true);
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
boolean includeJoins = sqlTree.isIncludeJoins();
String sql;
if (!includeJoins) {
// simple - delete from table ...
sql = aliasStrip(buildSql("delete", request, predicates, sqlTree).getSql());
if (type.equals("Delete")) {
sql = buildDeleteSql(request, rootTableAlias, predicates, sqlTree);
} else {
// wrap as - delete from table where id in (select id ...)
sql = buildSql(null, request, predicates, sqlTree).getSql();
sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
String alias = (rootTableAlias == null) ? "t0" : rootTableAlias;
sql = aliasReplace(sql, alias);
sql = buildUpdateSql(request, rootTableAlias, predicates, sqlTree);
}
// cache the query plan
queryPlan = new CQueryPlan(request, sql, sqlTree, false, false, predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryDelete(request, predicates, sql);
return new CQueryUpdate(type, request, predicates, sql);
}
private <T> String buildDeleteSql(OrmQueryRequest<T> request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {
if (!sqlTree.isIncludeJoins()) {
// simple - delete from table ...
return aliasStrip(buildSql("delete", request, predicates, sqlTree).getSql());
}
// wrap as - delete from table where id in (select id ...)
String sql = buildSql(null, request, predicates, sqlTree).getSql();
sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
String alias = (rootTableAlias == null) ? "t0" : rootTableAlias;
sql = aliasReplace(sql, alias);
return sql;
}
private <T> String buildUpdateSql(OrmQueryRequest<T> request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {
String updateClause = "update "+request.getBeanDescriptor().getBaseTable()+" set "+predicates.getDbUpdateClause();
if (!sqlTree.isIncludeJoins()) {
// simple - update table set ... where ...
return aliasStrip(buildSql(updateClause, request, predicates, sqlTree).getSql());
}
// wrap as - update table set ... where id in (select id ...)
String sql = buildSql(null, request, predicates, sqlTree).getSql();
sql = updateClause + " " + request.getBeanDescriptor().getWhereIdInSql() + "in (" + sql + ")";
String alias = (rootTableAlias == null) ? "t0" : rootTableAlias;
sql = aliasReplace(sql, alias);
return sql;
}
/**
@@ -423,11 +444,10 @@ public class CQueryBuilder {
}
}
sb.append(" from ");
// build the from clause potentially with joins
// required only for the predicates
sb.append(select.getFromSql());
if (selectClause == null || !selectClause.startsWith("update")) {
sb.append(" from ");
sb.append(select.getFromSql());
}
String inheritanceWhere = select.getInheritanceWhereSql();
@@ -51,10 +51,18 @@ public class CQueryEngine {
}
public <T> int delete(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery("Delete", request);
return executeUpdate(request, query);
}
CQueryDelete query = queryBuilder.buildDeleteQuery(request);
public <T> int update(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery("Update", request);
return executeUpdate(request, query);
}
private <T> int executeUpdate(OrmQueryRequest<T> request, CQueryUpdate query) {
try {
int rows = query.delete();
int rows = query.execute();
if (request.isLogSql()) {
String logSql = query.getGeneratedSql();
@@ -9,11 +9,12 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DeployParser;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest;
import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import com.avaje.ebeaninternal.server.querydefn.OrmUpdateProperties;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.util.BindParamsParser;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -112,6 +113,8 @@ public class CQueryPredicates {
private String dbOrderBy;
private String dbUpdateClause;
/**
* Includes from where and order by clauses.
*/
@@ -133,6 +136,12 @@ public class CQueryPredicates {
public String bind(DataBind dataBind) throws SQLException {
OrmUpdateProperties updateProperties = query.getUpdateProperties();
if (updateProperties != null) {
// bind the update set clause
updateProperties.bind(binder, dataBind);
}
if (query.isVersionsBetween() && binder.isBindAsOfWithFromClause()) {
// sql2011 based versions between timestamp syntax
Timestamp start = query.getVersionStart();
@@ -196,6 +205,15 @@ public class CQueryPredicates {
return dataBind.log().toString();
}
private void buildUpdateClause(boolean buildSql, DeployParser deployParser) {
if (buildSql) {
OrmUpdateProperties updateProperties = query.getUpdateProperties();
if (updateProperties != null) {
dbUpdateClause = updateProperties.buildSetClause(deployParser);
}
}
}
private void buildBindHavingRawSql(boolean buildSql, boolean parseRaw, DeployParser deployParser) {
if (buildSql || bindParams != null) {
// having clause with named parameters...
@@ -266,15 +284,12 @@ public class CQueryPredicates {
prepare(buildSql, true, deployParser);
}
public void prepareRawSql(DeployParser deployParser) {
prepare(true, false, deployParser);
}
/**
* This combines the sql from named/positioned parameters and expressions.
*/
private void prepare(boolean buildSql, boolean parseRaw, DeployParser deployParser) {
buildUpdateClause(buildSql, deployParser);
buildBindWhereRawSql(buildSql, parseRaw, deployParser);
buildBindHavingRawSql(buildSql, parseRaw, deployParser);
@@ -473,6 +488,13 @@ public class CQueryPredicates {
return where.getBindValues();
}
/**
* Return the db update set clause for an UpdateQuery.
*/
public String getDbUpdateClause() {
return dbUpdateClause;
}
/**
* Return the db column version of the combined where clause.
*/
@@ -4,9 +4,6 @@ import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.type.DataBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.Connection;
@@ -19,8 +16,6 @@ import java.sql.SQLException;
*/
public class CQueryRowCount {
private static final Logger logger = LoggerFactory.getLogger(CQueryRowCount.class);
/**
* The overall find request wrapper object.
*/
@@ -137,29 +132,12 @@ public class CQueryRowCount {
/**
* Close the resources.
* <p>
* The jdbc resultSet and statement need to be closed. Its important that
* this method is called.
* </p>
*/
private void close() {
try {
if (rset != null) {
rset.close();
rset = null;
}
} catch (SQLException e) {
logger.error(null, e);
}
try {
if (pstmt != null) {
pstmt.close();
pstmt = null;
}
} catch (SQLException e) {
logger.error(null, e);
}
UtilJdbc.close(rset);
UtilJdbc.close(pstmt);
rset = null;
pstmt = null;
}
}
@@ -4,9 +4,6 @@ import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.type.DataBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -15,9 +12,7 @@ import java.sql.SQLException;
/**
* Executes the delete query.
*/
public class CQueryDelete {
private static final Logger logger = LoggerFactory.getLogger(CQueryDelete.class);
public class CQueryUpdate {
private final OrmQueryRequest<?> request;
@@ -35,6 +30,8 @@ public class CQueryDelete {
*/
private final String sql;
private final String type;
/**
* The statement used to create the resultSet.
*/
@@ -49,8 +46,8 @@ public class CQueryDelete {
/**
* Create the Sql select based on the request.
*/
public CQueryDelete(OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
public CQueryUpdate(String type, OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
this.type = type;
this.request = request;
this.query = request.getQuery();
this.sql = sql;
@@ -63,14 +60,12 @@ public class CQueryDelete {
* Return a summary description of this query.
*/
public String getSummary() {
//noinspection StringBufferReplaceableByString
StringBuilder sb = new StringBuilder(80);
sb.append("Delete exeMicros[").append(executionTimeMicros)
sb.append(type).append(" exeMicros[").append(executionTimeMicros)
.append("] rows[").append(rowCount)
.append("] type[").append(desc.getName())
.append("] predicates[").append(predicates.getLogWhereSql())
.append("] bind[").append(bindLog).append("]");
return sb.toString();
}
@@ -89,12 +84,11 @@ public class CQueryDelete {
}
/**
* Execute the query returning the row count.
* Execute the update or delete statement returning the row count.
*/
public int delete() throws SQLException {
public int execute() throws SQLException {
long startNano = System.nanoTime();
try {
SpiTransaction t = request.getTransaction();
@@ -122,14 +116,8 @@ public class CQueryDelete {
* Close the resources.
*/
private void close() {
try {
if (pstmt != null) {
pstmt.close();
pstmt = null;
}
} catch (SQLException e) {
logger.error(null, e);
}
UtilJdbc.close(pstmt);
pstmt = null;
}
}
@@ -53,6 +53,12 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
return queryEngine.delete(request);
}
public <T> int update(OrmQueryRequest<T> request) {
flushJdbcBatchOnQuery(request);
return queryEngine.update(request);
}
public <T> int findRowCount(OrmQueryRequest<T> request) {
flushJdbcBatchOnQuery(request);
@@ -0,0 +1,34 @@
package com.avaje.ebeaninternal.server.query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UtilJdbc {
private static final Logger logger = LoggerFactory.getLogger(UtilJdbc.class);
public static void close(ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
logger.error("Error closing ResultSet", e);
}
}
public static void close(PreparedStatement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
logger.error("Error closing PreparedStatement", e);
}
}
}
@@ -230,6 +230,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private boolean useDocStore;
private OrmUpdateProperties updateProperties;
public DefaultOrmQuery(BeanDescriptor<T> desc, EbeanServer server, ExpressionFactory expressionFactory) {
this.beanDescriptor = desc;
this.beanType = desc.getBeanType();
@@ -831,7 +833,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
queryPlanKey = new OrmQueryPlanKey(includeTableJoin, type, detail, maxRows, firstRow,
disableLazyLoading, rawWhereClause, orderBy, query, additionalWhere, additionalHaving,
distinct, sqlDistinct, mapKey, id, bindParams, whereExpressions, havingExpressions,
temporalMode, forUpdate, rootTableAlias, rawSql);
temporalMode, forUpdate, rootTableAlias, rawSql, updateProperties);
return queryPlanKey;
}
@@ -1036,6 +1038,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return server.delete(this, null);
}
@Override
public int update() {
return server.update(this, null);
}
@Override
public List<Object> findIds() {
// a copy of this query is made in the server
@@ -1528,4 +1535,13 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
return validation.getUnknownProperties();
}
public void setUpdateProperties(OrmUpdateProperties updateProperties) {
this.updateProperties = updateProperties;
}
@Override
public OrmUpdateProperties getUpdateProperties() {
return updateProperties;
}
}
@@ -0,0 +1,48 @@
package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.UpdateQuery;
/**
* Default implementation of UpdateQuery.
*/
public class DefaultUpdateQuery<T> implements UpdateQuery<T> {
private final OrmUpdateProperties values = new OrmUpdateProperties();
private final DefaultOrmQuery<T> query;
public DefaultUpdateQuery(DefaultOrmQuery<T> query) {
this.query = query;
query.setUpdateProperties(values);
}
@Override
public UpdateQuery<T> set(String property, Object value) {
values.set(property, value);
return this;
}
@Override
public UpdateQuery<T> setNull(String property) {
values.set(property, null);
return this;
}
@Override
public UpdateQuery<T> setRaw(String propertyExpression) {
values.setRaw(propertyExpression);
return this;
}
@Override
public UpdateQuery<T> setRaw(String propertyExpression, Object... vals) {
values.setRaw(propertyExpression, vals);
return this;
}
@Override
public ExpressionList<T> where() {
return query.where();
}
}
@@ -35,11 +35,12 @@ public class OrmQueryPlanKey implements CQueryPlanKey {
private final SpiQuery.TemporalMode temporalMode;
private final boolean forUpdate;
private final String rootTableAlias;
private final OrmUpdateProperties updateProperties;
private final int planHash;
private final int bindCount;
public OrmQueryPlanKey(TableJoin includeTableJoin, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, String rawWhereClause, OrderBy<?> orderBy, String query, String additionalWhere, String additionalHaving, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, boolean forUpdate, String rootTableAlias, RawSql rawSql) {
public OrmQueryPlanKey(TableJoin includeTableJoin, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, String rawWhereClause, OrderBy<?> orderBy, String query, String additionalWhere, String additionalHaving, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, boolean forUpdate, String rootTableAlias, RawSql rawSql, OrmUpdateProperties updateProperties) {
this.includeTableJoin = includeTableJoin;
this.type = type;
@@ -61,6 +62,7 @@ public class OrmQueryPlanKey implements CQueryPlanKey {
this.temporalMode = temporalMode;
this.forUpdate = forUpdate;
this.rootTableAlias = rootTableAlias;
this.updateProperties = updateProperties;
this.rawSqlKey = (rawSql == null) ? null : rawSql.getKey();
// exclude bind values and things unrelated to the sql being generated
@@ -91,6 +93,9 @@ public class OrmQueryPlanKey implements CQueryPlanKey {
if (having != null) {
having.queryPlanHash(builder);
}
if (updateProperties != null) {
updateProperties.buildQueryPlanHash(builder);
}
this.planHash = builder.getPlanHash();
this.bindCount = builder.getBindCount();
@@ -128,6 +133,7 @@ public class OrmQueryPlanKey implements CQueryPlanKey {
if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null) return false;
if (where != null ? !where.isSameByPlan(that.where) : that.where != null) return false;
if (having != null ? !having.isSameByPlan(that.having) : that.having != null) return false;
if (updateProperties != null ? !updateProperties.isSameByPlan(that.updateProperties) : that.updateProperties != null) return false;
if (rawSqlKey != null ? !rawSqlKey.equals(that.rawSqlKey) : that.rawSqlKey != null) return false;
// if (detail != null ? !detail.equals(that.detail) : that.detail != null) return false;
@@ -0,0 +1,209 @@
package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.server.deploy.DeployParser;
import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.type.DataBind;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* Set properties for a UpdateQuery.
*/
public class OrmUpdateProperties {
private static final NullValue NULL_VALUE = new NullValue();
private static final NoneValue NONE_VALUE = new NoneValue();
/**
* Bind value used in the set clause for update query.
* It may/may not have bind values etc.
*/
public static abstract class Value {
public void bind(Binder binder, DataBind dataBind) throws SQLException {
// default to no bind values
}
public String bindClause() {
return "";
}
public int getBindCount() {
return 0;
}
}
/**
* Set property to null.
*/
static class NullValue extends Value {
@Override
public String bindClause() {
return "=null";
}
}
/**
* Set property to a simple value.
*/
static class SimpleValue extends Value {
final Object value;
SimpleValue(Object value) {
this.value = value;
}
@Override
public int getBindCount() {
return 1;
}
@Override
public String bindClause() {
return "=?";
}
@Override
public void bind(Binder binder, DataBind dataBind) throws SQLException {
binder.bindObject(dataBind, value);
}
}
/**
* Set using an expression with no bind value.
*/
static class NoneValue extends Value {
@Override
public String bindClause() {
return "";
}
}
/**
* Set using an expression with many bind values.
*/
static class RawArrayValue extends Value {
final Object[] bindValues;
public RawArrayValue(Object[] bindValues) {
this.bindValues = bindValues;
}
@Override
public int getBindCount() {
return bindValues.length;
}
@Override
public void bind(Binder binder, DataBind dataBind) throws SQLException {
for (Object val : bindValues) {
binder.bindObject(dataBind, val);
}
}
}
/**
* The set properties/expressions and their bind values.
*/
private LinkedHashMap<String, Value> values = new LinkedHashMap<String, Value>();
/**
* Normal set property.
*/
public void set(String propertyName, Object value) {
if (value == null) {
values.put(propertyName, NULL_VALUE);
} else {
values.put(propertyName, new SimpleValue(value));
}
}
/**
* Set a raw expression with no bind values.
*/
public void setRaw(String propertyName) {
values.put(propertyName, NONE_VALUE);
}
/**
* Set a raw expression with many bind values.
*/
public void setRaw(String propertyExpression, Object... vals) {
if (vals.length == 0) {
setRaw(propertyExpression);
} else {
values.put(propertyExpression, new RawArrayValue(vals));
}
}
/**
* Return true if this update has the same logical set clause.
*/
public boolean isSameByPlan(OrmUpdateProperties that) {
return that.values.size() == values.size()
&& logicalSetClause().equals(that.logicalSetClause());
}
/**
* Build the hash for the query plan caching.
*/
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
builder.add(OrmUpdateProperties.class);
Set<Map.Entry<String, Value>> entries = values.entrySet();
for (Map.Entry<String, Value> entry : entries) {
builder.add(entry.getKey());
builder.bind(entry.getValue().getBindCount());
}
}
/**
* Bind all the bind values for the update set clause.
*/
public void bind(Binder binder, DataBind dataBind) throws SQLException {
for (Value bindValue : values.values()) {
bindValue.bind(binder, dataBind);
}
}
/**
* Build the actual set clause converting logical property names to db columns etc.
*/
public String buildSetClause(DeployParser deployParser) {
int setCount = 0;
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Value> entry : values.entrySet()) {
String property = entry.getKey();
if (setCount++ > 0) {
sb.append(", ");
}
// translate to db columns and remove table alias placeholders
sb.append(deployParser.parse(property).replace("${}", ""));
sb.append(entry.getValue().bindClause());
}
return sb.toString();
}
/**
* Return a logical set clause to use for isSameByPlan() use.
*/
private String logicalSetClause() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Value> entry : values.entrySet()) {
sb.append(", ");
sb.append(entry.getKey()).append(entry.getValue().bindClause());
}
return sb.toString();
}
}