Compare commits

..
Author SHA1 Message Date
Robin Bygrave c7818e7f23 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.1 2015-12-03 21:41:20 +13:00
Robin Bygrave 8318bbc4ca #118 - Additional: Add query includeSoftDeletes() asDraft() to ExpressionList (for fluid style when added after predicates) 2015-12-03 17:42:26 +13:00
Robin Bygrave 337646a6ce No effective change - javadoc update 2015-12-03 16:47:19 +13:00
Robin Bygrave fa6ee93e5b Bump pom to 6.13.1-SNAPSHOT 2015-12-03 16:35:43 +13:00
Robin Bygrave 0fe691112f Merge branch 'softDelete1' into softDelete2 2015-12-03 00:33:47 +13:00
Robin Bygrave 6946ff07e0 [maven-release-plugin] prepare for next development iteration 2015-12-02 23:48:15 +13:00
Robin Bygrave 7c3d052da0 [maven-release-plugin] prepare release avaje-ebeanorm-6.12.3 2015-12-02 23:47:49 +13:00
Robin Bygrave aea7a7fd65 #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... 2015-12-02 23:46:26 +13:00
Robin Bygrave f39a684c8e #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... 2015-12-02 21:38:34 +13:00
Robin Bygrave 3cb5854786 #475 - Unique constraint name not unique when using @Draftable 2015-12-02 07:57:27 +13:00
Robin Bygrave e8192723cc [maven-release-plugin] prepare for next development iteration 2015-12-01 23:25:26 +13:00
Robin Bygrave 3787748eae [maven-release-plugin] prepare release avaje-ebeanorm-6.12.2 2015-12-01 23:25:00 +13:00
Robin Bygrave 0ad52cf6a7 #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... - initial 2015-12-01 23:21:11 +13:00
Robin Bygrave 51bee836d4 #472 - DDL column ordering for @WhoCreated / @WhoModified. These columns should appear in the DDL with the @WhenCreated/@WhenModified columns. 2015-12-01 10:19:36 +13:00
Robin Bygrave 9193b6b971 No effective change - remove whitespace 2015-12-01 09:32:09 +13:00
Robin Bygrave e48b9fab0f No effective change - spelling in javadoc 2015-12-01 08:08:14 +13:00
Robin Bygrave a14ca24b33 [maven-release-plugin] prepare for next development iteration 2015-11-30 23:25:13 +13:00
Robin Bygrave 761f43d6a1 [maven-release-plugin] prepare release avaje-ebeanorm-6.12.1 2015-11-30 23:24:46 +13:00
Robin Bygrave 737f9a893e Bump pom - 6.12.1-SNAPSHOT 2015-11-30 23:23:58 +13:00
Robin Bygrave 579812d717 #455 - Should not generate "FOR UPDATE" for delete query 2015-11-30 16:50:26 +13:00
Robin Bygrave fae39fe650 #291 - ENH: Add ability to validate a query or property expression - Was Please check column exist on entity 2015-11-30 15:10:22 +13:00
Robin Bygrave debd8ad617 #465 - The encrypt key is logged out in sql statement - when included in where predicate 2015-11-30 12:25:25 +13:00
Robin Bygrave 6450ba219c #469 - Embedded bean made dirty after a prior insert does not trigger dirty state on 'outer' bean 2015-11-27 18:19:59 +13:00
Robin Bygrave a578eda2b1 #467 - Delete bean with unloaded version property throws OptimisticLockException - API Change, return boolean 2015-11-26 16:09:25 +13:00
Robin Bygrave a2ee0bfdbe #467 - Delete bean with unloaded version property throws OptimisticLockException 2015-11-26 15:12:59 +13:00
Robin Bygrave 872f0e21cb #466 - Delete of @Draftable bean ... means also delete matching live bean (aka delete has automatic publish behavior) 2015-11-26 00:49:18 +13:00
Robin Bygrave 8620dbf337 #464 - ENH: Add draftRestore(Class<?> beanType, Object id) ... to refresh a draft from the live (@Draftable feature) 2015-11-25 23:36:17 +13:00
Robin Bygrave 4f51a3b130 #463 - ENH: Add @DraftReset support - property that is set to null on the draft been when it is published (@Draftable feature) 2015-11-25 11:06:14 +13:00
Robin Bygrave 5f194f066a [maven-release-plugin] prepare for next development iteration 2015-11-24 01:04:26 +13:00
97 changed files with 2479 additions and 432 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.11.1</version>
<version>6.13.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
+28 -2
View File
@@ -697,12 +697,31 @@ public final class Ebean {
/**
* Delete the bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the bean is configured with <code>@SoftDelete</code> then this will perform a soft
* delete rather than a hard/permanent delete.
* </p>
* <p>
* 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.
* </p>
*/
public static void delete(Object bean) throws OptimisticLockException {
serverMgr.getDefaultServer().delete(bean);
public static boolean delete(Object bean) throws OptimisticLockException {
return serverMgr.getDefaultServer().delete(bean);
}
/**
* Delete the bean in permanent fashion (will not use soft delete).
*/
public static boolean deletePermanent(Object bean) throws OptimisticLockException {
return serverMgr.getDefaultServer().deletePermanent(bean);
}
/**
@@ -726,6 +745,13 @@ public final class Ebean {
return serverMgr.getDefaultServer().deleteAll(beans);
}
/**
* Delete permanent all the beans in the Collection (will not use soft delete).
*/
public static int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException {
return serverMgr.getDefaultServer().deleteAllPermanent(beans);
}
/**
* Refresh the values of a bean.
* <p>
+99 -4
View File
@@ -1246,16 +1246,52 @@ public interface EbeanServer {
/**
* Delete the bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* 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.
* </p>
*/
void delete(Object bean) throws OptimisticLockException;
boolean delete(Object bean) throws OptimisticLockException;
/**
* Delete the bean with an explicit transaction.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* 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.
* </p>
*/
void delete(Object bean, Transaction transaction) throws OptimisticLockException;
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.
@@ -1456,7 +1492,7 @@ public interface EbeanServer {
void markAsDirty(Object bean);
/**
* Saves the bean using an update. If you know you are updating a bean then it is preferrable to
* 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().
* <p>
* <b>Stateless updates:</b> Note that the bean does not have to be previously fetched to call
@@ -1508,7 +1544,6 @@ public interface EbeanServer {
* @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;
@@ -1869,4 +1904,64 @@ public interface EbeanServer {
*/
<T> List<T> publish(Query<T> query);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <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 transaction the transaction the restore process should use (can be null)
*/
<T> T draftRestore(Class<T> beanType, Object id, Transaction transaction);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <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
*/
<T> T draftRestore(Class<T> beanType, Object id);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <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 transaction the transaction the restore process should use (can be null)
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <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
*/
<T> List<T> draftRestore(Query<T> query);
/**
* Returns the set of properties/paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties/path for the given bean type.
* </p>
*/
<T> Set<String> validateQuery(Query<T> query);
}
@@ -107,6 +107,16 @@ public interface ExpressionList<T> extends Serializable {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
Query<T> asDraft();
/**
* Execute the query including soft deleted rows.
*/
Query<T> includeSoftDeletes();
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
+17 -5
View File
@@ -251,12 +251,24 @@ public abstract class Model {
}
/**
* Delete this entity.
* Delete this bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* 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.
* </p>
*
* @see EbeanServer#delete(Object)
*/
public void delete() {
db().delete(this);
public boolean delete() {
return db().delete(this);
}
/**
@@ -276,8 +288,8 @@ public abstract class Model {
/**
* Perform a delete using this entity against the specified server.
*/
public void delete(String server) {
db(server).delete(this);
public boolean delete(String server) {
return db(server).delete(this);
}
/**
+14
View File
@@ -302,6 +302,11 @@ public interface Query<T> extends Serializable {
*/
Query<T> asDraft();
/**
* Execute the query including soft deleted rows.
*/
Query<T> includeSoftDeletes();
/**
* Cancel the query execution if supported by the underlying database and
* driver.
@@ -1318,4 +1323,13 @@ public interface Query<T> extends Serializable {
* </p>
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
/**
* Returns the set of properties or paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties or paths for the given bean type.
* </p>
*/
Set<String> validate();
}
@@ -9,6 +9,10 @@ import java.lang.annotation.Target;
* For a timestamp property that is set to the datetime when the entity is
* created/inserted.
* <p>
* This is effectively an alias for @WhenCreated which was added as it hints
* towards a better naming convention (WhenCreated, WhenModified).
* </p>
* <p>
* An alternative to using this annotation would be to use insertable=false,
* updateable=false with @Column and have the DB insert the current time
* (default value on the DB column is SYSTIME etc).
@@ -0,0 +1,19 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a property on a @Draftable bean that is set to null on the 'draft bean' on publish.
* <p>
* This is expected to be put on properties that get 'reset' or 'cleared' after a publish.
* These properties might represent a publish comment or publish timestamp.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DraftReset {
}
@@ -0,0 +1,25 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to indicate a property on an entity bean used to control 'soft delete'
* (also known as 'logical delete').
* <p>
* The property should be of type boolean.
* </p>
* <pre>{@code
*
* @SoftDelete
* boolean deleted;
*
* }</pre>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SoftDelete {
}
@@ -8,6 +8,10 @@ import java.lang.annotation.Target;
/**
* For a timestamp property that is set to the datetime when the entity was last
* updated.
* <p>
* This is effectively an alias for @WhenModified which was added as it hints
* towards a better naming convention (WhenCreated, WhenModified).
* </p>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@@ -11,7 +11,6 @@ import java.lang.annotation.Target;
* This is effectively an alias for @UpdatedTimestamp and added to hint
* towards a better naming convention (WhenCreated, WhenModified).
* </p>
* </p>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@@ -507,7 +507,7 @@ public final class EntityBeanIntercept implements Serializable {
setDirty(true);
}
private void setChangedProperty(int propertyIndex) {
public void setChangedProperty(int propertyIndex) {
if (changedProps == null) {
changedProps = new boolean[owner._ebean_getPropertyNames().length];
}
@@ -141,6 +141,7 @@ public class ModelBuildContext {
int fkCount = 0;
int ixCount = 0;
int uqCount = 0;
Collection<MColumn> cols = draftTable.getColumns().values();
for (MColumn col: cols) {
if (col.getForeignKeyName() != null) {
@@ -152,6 +153,13 @@ public class ModelBuildContext {
String[] indexCols = {col.getName()};
col.setForeignKeyIndex(foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount));
}
// adjust the unique constraint names
if (col.getUnique() != null){
col.setUnique(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
}
if (col.getUniqueOneToOne() != null){
col.setUniqueOneToOne(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
}
}
addTable(draftTable);
@@ -17,8 +17,13 @@ public interface SpiBeanType<T> {
Class<T> getBeanType();
/**
* Return the base table this bean type maps to.
* Return true if the property is a valid known property or path for the given bean type.
*/
boolean isValidExpression(String property);
/**
* Return the base table this bean type maps to.
*/
String getBaseTable();
/**
@@ -66,4 +66,9 @@ public interface SpiExpression extends Expression {
* the associated request.
*/
void addBindValues(SpiExpressionRequest request);
/**
* Validate all the properties/paths associated with this expression.
*/
void validate(SpiExpressionValidation validation);
}
@@ -67,4 +67,8 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
/**
* Validate all the properties/paths used in this expression list.
*/
void validate(SpiExpressionValidation validation);
}
@@ -14,7 +14,7 @@ public interface SpiExpressionRequest {
/**
* Return the DB specific JSON expression handler.
*/
JsonExpressionHandler getJsonHander();
JsonExpressionHandler getJsonHandler();
/**
* Parse the logical property name to the deployment name.
@@ -35,7 +35,12 @@ public interface SpiExpressionRequest {
* Append to the expression sql.
*/
SpiExpressionRequest append(String sql);
/**
* Add an encryption key to bind to this request.
*/
void addBindEncryptKey(Object encryptKey);
/**
* Add a bind value to this request.
*/
@@ -0,0 +1,37 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.plugin.SpiBeanType;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Property expression validation request for a given root bean type.
*/
public class SpiExpressionValidation {
private final SpiBeanType<?> desc;
private final LinkedHashSet<String> unknown = new LinkedHashSet<String>();
public SpiExpressionValidation(SpiBeanType<?> desc) {
this.desc = desc;
}
/**
* Validate that the property expression (path) is valid.
*/
public void validate(String propertyName) {
if (!desc.isValidExpression(propertyName)) {
unknown.add(propertyName);
}
}
/**
* Return the set of properties considered as having unknown paths.
*/
public Set<String> getUnknownProperties() {
return unknown;
}
}
@@ -11,6 +11,7 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -22,6 +23,7 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
@@ -94,6 +96,10 @@ public interface SpiQuery<T> extends Query<T> {
}
enum TemporalMode {
/**
* Includes soft deletes rows in the result.
*/
SOFT_DELETED,
/**
* Query runs against draft tables.
*/
@@ -181,6 +187,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
boolean isAsDraft();
/**
* Return true if this query includes soft deleted rows.
*/
boolean isIncludeSoftDeletes();
/**
* Return the asOf Timestamp which the query should run as.
*/
@@ -196,6 +207,10 @@ public interface SpiQuery<T> extends Query<T> {
*/
List<String> getAsOfTableAlias();
void addSoftDeletePredicate(String softDeletePredicate);
List<String> getSoftDeletePredicates();
/**
* Return a listener that wants to be notified when the bean collection is
* first used.
@@ -694,4 +709,10 @@ public interface SpiQuery<T> extends Query<T> {
* Return root table alias set by {@link #alias(String)} command.
*/
String getAlias();
/**
* Validate the query returning the set of properties with unknown paths.
*/
Set<String> validate(SpiBeanType<T> desc);
}
@@ -908,8 +908,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
Class<T> beanType = (Class<T>) list.get(0).getClass();
BeanDescriptor<T> beanDescriptor = getBeanDescriptor(beanType);
if (beanDescriptor == null) {
String m = "BeanDescriptor not found, is [" + beanType + "] an entity bean?";
throw new PersistenceException(m);
throw new PersistenceException("BeanDescriptor not found, is [" + beanType + "] an entity bean?");
}
beanDescriptor.sort(list, sortByClause);
}
@@ -933,6 +932,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, deployQuery);
}
@Override
public <T> Set<String> validateQuery(Query<T> query) {
BeanDescriptor<T> beanDescriptor = getBeanDescriptor(query.getBeanType());
if (beanDescriptor == null) {
throw new PersistenceException("BeanDescriptor not found, is [" + query.getBeanType() + "] an entity bean?");
}
return ((SpiQuery<T>)query).validate(beanDescriptor);
}
public <T> Filter<T> filter(Class<T> beanType) {
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
@@ -1659,6 +1668,41 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return (liveBeans.size() == 1) ? liveBeans.get(0) : null;
}
@Override
public <T> List<T> draftRestore(Query<T> query, Transaction transaction) {
TransWrapper wrap = initTransIfRequired(transaction);
try {
SpiTransaction trans = wrap.transaction;
List<T> beans = persister.draftRestore(query, trans);
wrap.commitIfCreated();
return beans;
} catch (RuntimeException e) {
wrap.rollbackIfCreated();
throw e;
}
}
@Override
public <T> T draftRestore(Class<T> beanType, Object id, Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> beans = draftRestore(query, transaction);
return (beans.size() == 1) ? beans.get(0) : null;
}
@Override
public <T> T draftRestore(Class<T> beanType, Object id) {
return draftRestore(beanType, id, null);
}
@Override
public <T> List<T> draftRestore(Query<T> query) {
return draftRestore(query, null);
}
private EntityBean checkEntityBean(Object bean) {
if (bean == null) {
throw new IllegalArgumentException(Message.msg("bean.isnull"));
@@ -1832,16 +1876,35 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Delete the bean.
*/
public void delete(Object bean) {
delete(bean, null);
public boolean delete(Object bean) {
return delete(bean, null);
}
/**
* Delete the bean with the explicit transaction.
*/
public void delete(Object bean, Transaction t) {
persister.delete(checkEntityBean(bean), t);
public boolean delete(Object bean, Transaction t) throws OptimisticLockException {
return persister.delete(checkEntityBean(bean), t, false);
}
@Override
public boolean deletePermanent(Object bean) throws OptimisticLockException {
return deletePermanent(bean, null);
}
@Override
public boolean deletePermanent(Object bean, Transaction t) throws OptimisticLockException {
return persister.delete(checkEntityBean(bean), t, true);
}
@Override
public int deleteAllPermanent(Collection<?> beans) {
return deleteAllInternal(beans.iterator(), null, true);
}
@Override
public int deleteAllPermanent(Collection<?> beans, Transaction t) {
return deleteAllInternal(beans.iterator(), t, true);
}
/**
@@ -1849,7 +1912,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public int deleteAll(Collection<?> beans) {
return deleteAllInternal(beans.iterator(), null);
return deleteAllInternal(beans.iterator(), null, false);
}
/**
@@ -1857,13 +1920,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public int deleteAll(Collection<?> beans, Transaction t) {
return deleteAllInternal(beans.iterator(), t);
return deleteAllInternal(beans.iterator(), t, false);
}
/**
* Delete all the beans in the iterator with an explicit transaction.
*/
private int deleteAllInternal(Iterator<?> it, Transaction t) {
private int deleteAllInternal(Iterator<?> it, Transaction t, boolean permanent) {
TransWrapper wrap = initTransIfRequired(t);
@@ -1873,7 +1936,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
int deleteCount = 0;
while (it.hasNext()) {
EntityBean bean = checkEntityBean(it.next());
persister.delete(bean, trans);
persister.delete(bean, trans, permanent);
deleteCount++;
}
@@ -12,7 +12,7 @@ import com.avaje.ebeaninternal.server.persist.PersistExecute;
public abstract class PersistRequest extends BeanRequest implements BatchPostExecute {
public enum Type {
INSERT, UPDATE, DELETE, UPDATESQL, CALLABLESQL
INSERT, UPDATE, DELETE, SOFT_DELETE, DELETE_PERMANENT, UPDATESQL, CALLABLESQL
}
protected boolean persistCascade;
@@ -256,6 +256,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanDescriptor.cacheHandleUpdate(idValue, this);
break;
case DELETE:
case SOFT_DELETE:
// Bean deleted from cache early via postDelete()
break;
default:
@@ -421,6 +422,20 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return beanDescriptor.getId(entityBean);
}
/**
* Create and return a new reference bean matching this beans Id value.
*/
public T createReference() {
return beanDescriptor.createReference(Boolean.FALSE, getBeanId());
}
/**
* Return true if the bean type is a Draftable.
*/
public boolean isDraftable() {
return beanDescriptor.isDraftable();
}
/**
* Return the parent bean for cascading save with unidirectional relationship.
*/
@@ -465,15 +480,27 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
persistExecute.executeUpdateBean(this);
return -1;
case DELETE:
persistExecute.executeDeleteBean(this);
case SOFT_DELETE:
prepareForSoftDelete();
persistExecute.executeUpdateBean(this);
return -1;
case DELETE:
return persistExecute.executeDeleteBean(this);
default:
throw new RuntimeException("Invalid type " + type);
}
}
/**
* Soft delete is executed as update so we want to set deleted=true property.
*/
private void prepareForSoftDelete() {
beanDescriptor.setSoftDeleteValue(entityBean);
}
@Override
public int executeOrQueue() {
@@ -514,12 +541,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Check for optimistic concurrency exception.
*/
public final void checkRowCount(int rowCount) {
if (rowCount != 1) {
if (ConcurrencyMode.VERSION == concurrencyMode && rowCount != 1) {
String m = Message.msg("persist.conc2", "" + rowCount);
throw new OptimisticLockException(m, null, bean);
}
switch (type) {
case DELETE:
case SOFT_DELETE:
postDelete();
break;
case UPDATE:
@@ -588,6 +616,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controller.postUpdate(this);
break;
case DELETE:
case SOFT_DELETE:
controller.postDelete(this);
break;
default:
@@ -609,6 +638,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
case DELETE:
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]" + draft);
break;
case SOFT_DELETE:
transaction.logSummary("SoftDelete [" + name + "] [" + idValue + "]" + draft);
break;
default:
break;
}
@@ -678,6 +710,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
for (int i = 0; i < len; i++) {
intercept.setLoadedProperty(i);
}
beanDescriptor.setEmbeddedOwner(entityBean);
}
public boolean isReference() {
@@ -787,4 +820,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public String getUpdateTable() {
return publish ? beanDescriptor.getBaseTable() : beanDescriptor.getDraftTable();
}
/**
* Return true if this is a soft delete request.
*/
public boolean isSoftDelete() {
return Type.SOFT_DELETE == type;
}
}
@@ -66,7 +66,7 @@ public interface Persister {
/**
* Delete the bean.
*/
void delete(EntityBean entityBean, Transaction t);
boolean delete(EntityBean entityBean, Transaction t, boolean permanent);
/**
* Delete multiple beans given a collection of Id values.
@@ -93,4 +93,9 @@ public interface Persister {
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Restore the draft beans back to the matching live beans.
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
}
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.Query;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
@@ -148,6 +149,9 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final String baseTableVersionsBetween;
private final boolean historySupport;
private final BeanProperty softDeleteProperty;
private final boolean softDelete;
private final String draftTable;
/**
@@ -315,8 +319,9 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private String idBinderIdSql;
private String deleteByIdSql;
private String deleteByIdInSql;
private String softDeleteByIdSql;
private String softDeleteByIdInSql;
private final String name;
@@ -394,6 +399,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
// helper object used to derive lists of properties
DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy);
this.softDeleteProperty = listHelper.getSoftDeleteProperty();
this.softDelete = (softDeleteProperty != null);
this.idProperty = listHelper.getId();
this.versionProperty = listHelper.getVersionProperty();
this.draftDirty = listHelper.getDraftDirty();
@@ -620,6 +627,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql;
deleteByIdInSql = "delete from " + baseTable + " where " + idBinderInLHSSqlNoAlias + " ";
if (softDelete) {
softDeleteByIdSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idEqualsSql;
softDeleteByIdInSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idBinderInLHSSqlNoAlias + " ";
} else {
softDeleteByIdSql = null;
softDeleteByIdInSql = null;
}
if (!isEmbedded()) {
// parse every named update up front into sql dml
for (DeployNamedUpdate namedUpdate : namedUpdates.values()) {
@@ -668,6 +683,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
case INSERT:
return changeLogFilter.includeInsert(request) ? insertBeanChange(request): null;
case UPDATE:
case SOFT_DELETE:
return changeLogFilter.includeUpdate(request) ? updateBeanChange(request): null;
case DELETE:
return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) :null;
@@ -709,11 +725,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
cacheHelp.initialise();
}
public SqlUpdate deleteById(Object id, List<Object> idList) {
public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) {
if (id != null) {
return deleteById(id);
return deleteById(id, softDelete);
} else {
return deleteByIdList(idList);
return deleteByIdList(idList, softDelete);
}
}
@@ -728,9 +744,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
* Return SQL that can be used to delete a list of Id's without any optimistic
* concurrency checking.
*/
private SqlUpdate deleteByIdList(List<Object> idList) {
private SqlUpdate deleteByIdList(List<Object> idList, boolean softDelete) {
StringBuilder sb = new StringBuilder(deleteByIdInSql);
String baseSql = softDelete ? softDeleteByIdInSql : deleteByIdInSql;
StringBuilder sb = new StringBuilder(baseSql);
String inClause = idBinder.getIdInValueExprDelete(idList.size());
sb.append(inClause);
@@ -745,9 +762,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
* Return SQL that can be used to delete by Id without any optimistic
* concurrency checking.
*/
private SqlUpdate deleteById(Object id) {
private SqlUpdate deleteById(Object id, boolean softDelete) {
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByIdSql);
String baseSql = softDelete ? softDeleteByIdSql : deleteByIdSql;
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
Object[] bindValues = idBinder.getBindValues(id);
for (int i = 0; i < bindValues.length; i++) {
@@ -837,6 +855,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return draftHelp.publish(draftBean, liveBean);
}
/**
* Reset properties on the draft bean based on @DraftDirty and @DraftReset.
*/
public boolean draftReset(T draftBean) {
return draftHelp.draftReset(draftBean);
}
/**
* Return the draft dirty boolean property or null if there is not one assigned to this bean type.
*/
@@ -1167,6 +1192,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return deleteRecurseSkippable;
}
/**
* Return true if delete can use a single SQL statement.
*
* This implies cascade delete does not continue depth wise and that this is no
* associated L2 bean caching.
*/
public boolean isDeleteByStatement() {
return deleteRecurseSkippable && !isBeanCaching();
}
/**
* Find a property annotated with @WhenCreated or @CreatedTimestamp.
*/
@@ -1594,6 +1629,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return new ElComparatorProperty<T>(elGetValue, sortProp.isAscending(), nullsHigh);
}
@Override
public boolean isValidExpression(String propertyName) {
try {
return (getElGetValue(propertyName) != null);
} catch (PersistenceException e) {
return false;
}
}
/**
* Get an Expression language Value object.
*/
@@ -1915,6 +1959,22 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return readAuditing;
}
public boolean isSoftDelete() {
return softDelete;
}
public void setSoftDeleteValue(EntityBean bean) {
softDeleteProperty.setSoftDeleteValue(bean);
}
public String getSoftDeleteDbSet() {
return softDeleteProperty.getSoftDeleteDbSet();
}
public String getSoftDeletePredicate(String tableAlias) {
return softDeleteProperty.getSoftDeleteDbPredicate(tableAlias);
}
/**
* Return true if this entity type is draftable.
*/
@@ -1946,6 +2006,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
* Optimise the draft query fetching any draftable element relationships.
*/
public void draftQueryOptimise(Query<T> query) {
// use per query PersistenceContext to ensure fresh beans loaded
query.setPersistenceContextScope(PersistenceContextScope.QUERY);
draftHelp.draftQueryOptimise(query);
}
@@ -2041,6 +2103,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return propertiesEmbedded;
}
/**
* Set the embedded owner on any embedded bean properties.
*/
public void setEmbeddedOwner(EntityBean bean) {
for (int i = 0; i < propertiesEmbedded.length; i++) {
propertiesEmbedded[i].setEmbeddedOwner(bean);
}
}
public BeanProperty getIdProperty() {
return idProperty;
}
@@ -3,6 +3,9 @@ package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.Query;
import com.avaje.ebean.bean.EntityBean;
import java.util.ArrayList;
import java.util.List;
/**
* Helper for BeanDescriptor that manages draft entity beans.
*
@@ -12,16 +15,60 @@ public final class BeanDescriptorDraftHelp<T> {
private final BeanDescriptor<T> desc;
private final BeanProperty draftDirty;
private final BeanProperty[] resetProperties;
public BeanDescriptorDraftHelp(BeanDescriptor<T> desc) {
this.desc = desc;
this.draftDirty = desc.getDraftDirty();
this.resetProperties = resetProperties();
}
/**
*
* Return the properties that are reset on draft beans after publish.
*/
public void initialise() {
private BeanProperty[] resetProperties() {
List<BeanProperty> list = new ArrayList<BeanProperty>();
BeanProperty[] props = desc.propertiesNonMany();
for (BeanProperty prop : props) {
if (prop.isDraftReset()) {
list.add(prop);
}
}
return list.toArray(new BeanProperty[list.size()]);
}
/**
* Set the value of all the 'reset properties' to null on the draft bean.
*/
public boolean draftReset(T draftBean) {
EntityBean draftEntityBean = (EntityBean)draftBean;
if (draftDirty != null) {
// set @DraftDirty property to false
draftDirty.setValueIntercept(draftEntityBean, false);
}
// set to null on all @DraftReset properties
for (BeanProperty resetProperty : resetProperties) {
resetProperty.setValueIntercept(draftEntityBean, null);
}
// return true if the bean is dirty (and should be persisted)
return draftEntityBean._ebean_getIntercept().isDirty();
}
/**
* Transfer the values from the draftBean to the liveBean.
* <p>
* This will recursive transfer values to all @DraftableElement properties.
* </p>
*/
public T publish(T draftBean, T liveBean) {
if (liveBean == null) {
@@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypeBoolean;
import com.avaje.ebeaninternal.util.ValueUtil;
import com.fasterxml.jackson.core.JsonToken;
import org.slf4j.Logger;
@@ -227,6 +228,14 @@ public class BeanProperty implements ElPropertyValue {
final boolean draftDirty;
final boolean draftReset;
final boolean softDelete;
final String softDeleteDbSet;
final String softDeleteDbPredicate;
final boolean indexed;
final String indexName;
@@ -255,7 +264,7 @@ public class BeanProperty implements ElPropertyValue {
this.excludedFromHistory = deploy.isExcludedFromHistory();
this.draftDirty = deploy.isDraftDirty();
this.draftOnly = deploy.isDraftOnly();
this.draftReset = deploy.isDraftReset();
this.secondaryTable = deploy.isSecondaryTable();
if (secondaryTable) {
this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin());
@@ -299,6 +308,16 @@ public class BeanProperty implements ElPropertyValue {
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn);
this.softDelete = deploy.isSoftDelete();
if (softDelete) {
ScalarTypeBoolean.BooleanBase boolType = (ScalarTypeBoolean.BooleanBase)scalarType;
this.softDeleteDbSet = dbColumn+"="+boolType.getDbTrueLiteral();
this.softDeleteDbPredicate = dbColumn+"="+boolType.getDbFalseLiteral();
} else {
this.softDeleteDbSet = null;
this.softDeleteDbPredicate = null;
}
this.jsonSerialize = deploy.isJsonSerialize();
this.jsonDeserialize = deploy.isJsonDeserialize();
}
@@ -341,6 +360,10 @@ public class BeanProperty implements ElPropertyValue {
this.excludedFromHistory = source.excludedFromHistory;
this.draftDirty = source.draftDirty;
this.draftOnly = source.draftOnly;
this.draftReset = source.draftReset;
this.softDelete = source.softDelete;
this.softDeleteDbSet = source.softDeleteDbSet;
this.softDeleteDbPredicate = source.softDeleteDbPredicate;
this.fetchEager = source.fetchEager;
this.unidirectionalShadow = source.unidirectionalShadow;
this.discriminator = source.discriminator;
@@ -612,6 +635,29 @@ public class BeanProperty implements ElPropertyValue {
}
}
/**
* Return the DB literal expression to set the deleted state to true.
*/
public String getSoftDeleteDbSet() {
return softDeleteDbSet;
}
/**
* Return the DB literal predicate used to filter out soft deleted rows from a query.
*/
public String getSoftDeleteDbPredicate(String tableAlias) {
return tableAlias+"."+softDeleteDbPredicate;
}
/**
* Set the soft delete property value on the bean without invoking lazy loading.
*/
public void setSoftDeleteValue(EntityBean bean) {
// assumes boolean deleted true being set which is ok limitation for now
setValue(bean, true);
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
}
/**
* Set the value of the property without interception or
* PropertyChangeSupport.
@@ -1045,6 +1091,20 @@ public class BeanProperty implements ElPropertyValue {
return draftDirty;
}
/**
* Return true if this property is reset/cleared on publish (on the draft bean).
*/
public boolean isDraftReset() {
return draftReset;
}
/**
* Return true if this property is the soft delete property.
*/
public boolean isSoftDelete() {
return softDelete;
}
/**
* Return true if this property should be included in an Insert.
*/
@@ -800,7 +800,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public IntersectionRow buildManyDeleteChildren(EntityBean parentBean, ArrayList<Object> excludeDetailIds) {
IntersectionRow row = new IntersectionRow(tableJoin.getTable());
IntersectionRow row = new IntersectionRow(tableJoin.getTable(), targetDescriptor);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
row.setExcludeIds(excludeDetailIds, getTargetDescriptor());
}
@@ -578,17 +578,30 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
public void setValue(EntityBean bean, Object value) {
super.setValue(bean, value);
if (embedded && value instanceof EntityBean) {
EntityBean embedded = (EntityBean) value;
embedded._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
setEmbeddedOwner(bean, value);
}
}
/**
* Set the owner on the embedded bean property.
*/
public void setEmbeddedOwner(EntityBean owner) {
Object emb = getValue(owner);
if (emb != null) {
setEmbeddedOwner(owner, emb);
}
}
private void setEmbeddedOwner(EntityBean bean, Object value) {
((EntityBean)value)._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
}
@Override
public void setValueIntercept(EntityBean bean, Object value) {
super.setValueIntercept(bean, value);
if (embedded && value instanceof EntityBean) {
EntityBean embedded = (EntityBean) value;
embedded._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
setEmbeddedOwner(bean, value);
}
}
@@ -15,13 +15,21 @@ public class IntersectionRow {
private final String tableName;
private final BeanDescriptor<?> targetDescriptor;
private final LinkedHashMap<String, Object> values = new LinkedHashMap<String, Object>();
private ArrayList<Object> excludeIds;
private BeanDescriptor<?> excludeDescriptor;
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
this.tableName = tableName;
this.targetDescriptor = targetDescriptor;
}
public IntersectionRow(String tableName) {
this.tableName = tableName;
this.targetDescriptor = null;
}
/**
@@ -64,22 +72,20 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDelete(EbeanServer server) {
public SqlUpdate createDelete(EbeanServer server, boolean softDelete) {
BindParams bindParams = new BindParams();
StringBuilder sb = new StringBuilder();
sb.append("delete from ").append(tableName).append(" where ");
int count = 0;
for (Map.Entry<String, Object> entry : values.entrySet()) {
if (count++ > 0) {
sb.append(" and ");
}
sb.append(entry.getKey());
sb.append(" = ?");
bindParams.setParameter(count, entry.getValue());
if (softDelete) {
sb.append("update ").append(tableName).append(" set ");
sb.append(targetDescriptor.getSoftDeleteDbSet());
} else {
sb.append("delete from ").append(tableName);
}
sb.append(" where ");
int count = setBindParams(bindParams, sb);
if (excludeIds != null) {
IdInExpression idIn = new IdInExpression(excludeIds);
@@ -108,6 +114,13 @@ public class IntersectionRow {
StringBuilder sb = new StringBuilder();
sb.append("delete from ").append(tableName).append(" where ");
setBindParams(bindParams, sb);
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
private int setBindParams(BindParams bindParams, StringBuilder sb) {
int count = 0;
for (Map.Entry<String, Object> entry : values.entrySet()) {
if (count++ > 0) {
@@ -120,6 +133,6 @@ public class IntersectionRow {
bindParams.setParameter(count, entry.getValue());
}
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
return count;
}
}
@@ -1,9 +1,12 @@
package com.avaje.ebeaninternal.server.deploy.meta;
import com.avaje.ebean.annotation.CreatedTimestamp;
import com.avaje.ebean.annotation.SoftDelete;
import com.avaje.ebean.annotation.UpdatedTimestamp;
import com.avaje.ebean.annotation.WhenCreated;
import com.avaje.ebean.annotation.WhenModified;
import com.avaje.ebean.annotation.WhoCreated;
import com.avaje.ebean.annotation.WhoModified;
import com.avaje.ebean.config.ScalarTypeConverter;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
@@ -185,6 +188,9 @@ public class DeployBeanProperty {
private boolean draftOnly;
private boolean draftDirty;
private boolean draftReset;
private boolean softDelete;
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
this.desc = desc;
@@ -214,16 +220,25 @@ public class DeployBeanProperty {
return ID_ORDER;
} else if (undirectionalShadow) {
return UNIDIRECTIONAL_ORDER;
} else if (field.getAnnotation(WhenCreated.class) != null || field.getAnnotation(CreatedTimestamp.class) != null) {
return AUDITCOLUMN_ORDER;
} else if (field.getAnnotation(WhenModified.class) != null || field.getAnnotation(UpdatedTimestamp.class) != null) {
} else if (isAuditProperty()) {
return AUDITCOLUMN_ORDER;
} else if (field.getAnnotation(Version.class) != null) {
return VERSIONCOLUMN_ORDER;
} else if (field.getAnnotation(SoftDelete.class) != null) {
return VERSIONCOLUMN_ORDER;
}
return 0;
}
private boolean isAuditProperty() {
return (field.getAnnotation(WhenCreated.class) != null
|| field.getAnnotation(WhenModified.class) != null
|| field.getAnnotation(WhoModified.class) != null
|| field.getAnnotation(WhoCreated.class) != null
|| field.getAnnotation(UpdatedTimestamp.class) != null
|| field.getAnnotation(CreatedTimestamp.class) != null);
}
public String getFullBeanName() {
return desc.getFullName() + "." + name;
}
@@ -854,4 +869,21 @@ public class DeployBeanProperty {
public boolean isDraftDirty() {
return draftDirty;
}
public void setDraftReset() {
this.draftReset = true;
}
public boolean isDraftReset() {
return draftReset;
}
public void setSoftDelete() {
this.softDelete = true;
}
public boolean isSoftDelete() {
return softDelete;
}
}
@@ -293,6 +293,16 @@ public class DeployBeanPropertyLists {
return draftDirty;
}
public BeanProperty getSoftDeleteProperty() {
for (BeanProperty prop: nonManys) {
if (prop.isSoftDelete()) {
return prop;
}
}
return null;
}
/**
* Mode used to determine which BeanPropertyAssoc to include.
*/
@@ -152,10 +152,16 @@ public class AnnotationFields extends AnnotationParser {
if (get(prop, DraftOnly.class) != null) {
prop.setDraftOnly();
}
if (get(prop, DraftDirty.class) != null) {
prop.setDraftDirty();
}
if (get(prop, DraftReset.class) != null) {
prop.setDraftReset();
}
SoftDelete softDelete = get(prop, SoftDelete.class);
if (softDelete != null) {
prop.setSoftDelete();
}
DbJson dbJson = get(prop, DbJson.class);
if (dbJson != null) {
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -42,6 +43,11 @@ public abstract class AbstractExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(getPropertyName());
}
protected ElPropertyValue getElProp(SpiExpressionRequest request) {
String propertyName = getPropertyName();
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
@@ -36,6 +37,13 @@ class AllEqualsExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
for (String propName: propMap.keySet()) {
validation.validate(propName);
}
}
public void addBindValues(SpiExpressionRequest request) {
if (propMap.isEmpty()) {
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
@@ -44,6 +45,12 @@ class BetweenPropertyExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(lowProperty);
validation.validate(highProperty);
}
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(value);
}
@@ -22,7 +22,7 @@ class CaseInsensitiveEqualExpression extends AbstractExpression {
if (prop != null && prop.isDbEncrypted()) {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindValue(encryptKey);
request.addBindEncryptKey(encryptKey);
}
request.addBindValue(value);
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -121,6 +122,13 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
return this;
}
@Override
public void validate(SpiExpressionValidation validation) {
for (int i = 0; i < list.size(); i++) {
list.get(i).validate(validation);
}
}
/**
* Adds bind values to the request.
*/
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
@@ -85,4 +86,9 @@ public class ExistsExpression implements SpiExpression {
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
// Nothing to do for exists expression
}
@Override
public void validate(SpiExpressionValidation validation) {
// Nothing to do for exists expression
}
}
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
@@ -28,6 +29,11 @@ class IdExpression implements SpiExpression {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
public void addBindValues(SpiExpressionRequest request) {
// 'flatten' EmbeddedId and multiple Id cases
@@ -7,6 +7,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
@@ -27,6 +28,11 @@ public class IdInExpression implements SpiExpression {
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
public void addBindValues(SpiExpressionRequest request) {
// Bind the Id values including EmbeddedId and multiple Id
@@ -78,7 +78,7 @@ class JsonPathExpression extends AbstractExpression {
public void addSql(SpiExpressionRequest request) {
// Use DB specific expression handling (Postgres and Oracle supported)
request.getJsonHander().addSql(request, propName, path, operator, value);
request.getJsonHandler().addSql(request, propName, path, operator, value);
}
@Override
@@ -13,6 +13,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionList;
@@ -81,6 +82,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
}
@Override
public void validate(SpiExpressionValidation validation) {
exprList.validate(validation);
}
@Override
public Junction<T> add(Expression item) {
SpiExpression i = (SpiExpression) item;
@@ -228,6 +234,16 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
return exprList.asOf(asOf);
}
@Override
public Query<T> asDraft() {
return exprList.asDraft();
}
@Override
public Query<T> includeSoftDeletes() {
return exprList.includeSoftDeletes();
}
@Override
public List<Version<T>> findVersions() {
return exprList.findVersions();
@@ -29,7 +29,7 @@ class LikeExpression extends AbstractExpression {
if (prop != null && prop.isDbEncrypted()) {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindValue(encryptKey);
request.addBindEncryptKey(encryptKey);
}
String bindValue = getValue(val, caseInsensitive, type);
@@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -53,6 +54,12 @@ abstract class LogicExpression implements SpiExpression {
expTwo.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
expOne.validate(validation);
expTwo.validate(validation);
}
public void addBindValues(SpiExpressionRequest request) {
expOne.addBindValues(request);
expTwo.addBindValues(request);
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -19,6 +20,11 @@ class NoopExpression implements SpiExpression {
// nothing to do
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
@Override
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
builder.add(NoopExpression.class);
@@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
final class NotExpression implements SpiExpression {
@@ -24,6 +25,11 @@ final class NotExpression implements SpiExpression {
exp.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
exp.validate(validation);
}
public void addBindValues(SpiExpressionRequest request) {
exp.addBindValues(request);
}
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
class RawExpression implements SpiExpression {
@@ -24,6 +25,11 @@ class RawExpression implements SpiExpression {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always ignored
}
public void addBindValues(SpiExpressionRequest request) {
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -40,7 +40,7 @@ public class SimpleExpression extends AbstractExpression {
if (prop.isDbEncrypted()) {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindValue(encryptKey);
request.addBindEncryptKey(encryptKey);
}
//else if (prop.isLocalEncrypted()) {
// not supporting this for equals (but probably could)
@@ -45,27 +45,12 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
protected void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
// propagate the readOnly state
if (parent.isReadOnly() != null) {
query.setReadOnly(parent.isReadOnly());
}
// propagate the asOf and lazy loading mode
query.setDisableLazyLoading(parent.isDisableLazyLoading());
query.asOf(parent.getAsOf());
parent.propagateQueryState(query);
query.setParentNode(objectGraphNode);
query.setLazyLoadProperty(lazyLoadProperty);
if (parent.isAsDraft()) {
query.asDraft();
}
if (parent.isDisableReadAudit()) {
query.setDisableReadAuditing();
}
if (queryProps != null) {
queryProps.configureBeanQuery(query);
}
if (parent.isUseAutoTune()) {
query.setAutoTune(true);
}
}
protected void register(EntityBeanIntercept ebi) {
@@ -43,6 +43,7 @@ public class DLoadContext implements LoadContext {
private final int defaultBatchSize;
private final boolean disableLazyLoading;
private final boolean disableReadAudit;
private final boolean includeSoftDeletes;
/**
* The path relative to the root of the object graph.
@@ -67,6 +68,7 @@ public class DLoadContext implements LoadContext {
SpiQuery<?> query = request.getQuery();
this.asOf = query.getAsOf();
this.asDraft = query.isAsDraft();
this.includeSoftDeletes = query.isIncludeSoftDeletes();
this.readOnly = query.isReadOnly();
this.disableReadAudit = query.isDisableReadAudit();
this.disableLazyLoading = query.isDisableLazyLoading();
@@ -201,10 +203,6 @@ public class DLoadContext implements LoadContext {
return new ObjectGraphNode(origin, path);
}
public boolean isUseAutoTune() {
return useProfiling;
}
protected String getFullPath(String path) {
if (relativePath == null) {
return path;
@@ -225,34 +223,6 @@ public class DLoadContext implements LoadContext {
return readOnly;
}
/**
* Return the 'as of' timestamp that should propagate to secondary queries.
*/
protected Timestamp getAsOf() {
return asOf;
}
/**
* Return true if the root query is a 'asDraft' query that should propagate to secondary queries.
*/
protected boolean isAsDraft() {
return asDraft;
}
/**
* Return true if disable read auditing should propagate to secondary queries.
*/
protected boolean isDisableReadAudit() {
return disableReadAudit;
}
/**
* Return true if disable lazy loading should propagate to secondary queries.
*/
protected boolean isDisableLazyLoading() {
return disableLazyLoading;
}
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -335,4 +305,26 @@ public class DLoadContext implements LoadContext {
return desc.getBeanPropertyFromPath(path);
}
/**
* Propagate the original query settings (draft, asOf etc) to the secondary queries.
*/
public void propagateQueryState(SpiQuery<?> query) {
if (readOnly != null) {
query.setReadOnly(readOnly);
}
query.setDisableLazyLoading(disableLazyLoading);
query.asOf(asOf);
if (asDraft) {
query.asDraft();
}
if (includeSoftDeletes) {
query.includeSoftDeletes();
}
if (disableReadAudit) {
query.setDisableReadAuditing();
}
if (useProfiling) {
query.setAutoTune(true);
}
}
}
@@ -56,27 +56,11 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
public void configureQuery(SpiQuery<?> query) {
// propagate the readOnly state
if (parent.isReadOnly() != null) {
query.setReadOnly(parent.isReadOnly());
}
// propagate the asOf and lazy loading mode
query.setDisableLazyLoading(parent.isDisableLazyLoading());
query.asOf(parent.getAsOf());
parent.propagateQueryState(query);
query.setParentNode(objectGraphNode);
if (parent.isAsDraft()) {
query.asDraft();
}
if (parent.isDisableReadAudit()) {
query.setDisableReadAuditing();
}
if (queryProps != null) {
queryProps.configureBeanQuery(query);
}
if (parent.isUseAutoTune()) {
query.setAutoTune(true);
}
}
public BeanPropertyAssocMany<?> getBeanProperty() {
@@ -138,6 +138,7 @@ public class BatchedBeanHolder {
return inserts.size();
case UPDATE:
case SOFT_DELETE:
if (updates == null) {
updates = new ArrayList<PersistRequest>();
}
@@ -22,6 +22,6 @@ public interface BeanPersister {
/**
* execute the delete bean request.
*/
void delete(PersistRequestBean<?> request) throws PersistenceException;
int delete(PersistRequestBean<?> request) throws PersistenceException;
}
@@ -73,15 +73,17 @@ public final class DefaultPersistExecute implements PersistExecute {
/**
* execute the bean delete request.
*/
public <T> void executeDeleteBean(PersistRequestBean<T> request) {
public <T> int executeDeleteBean(PersistRequestBean<T> request) {
BeanManager<T> mgr = request.getBeanManager();
BeanPersister persister = mgr.getBeanPersister();
BeanPersistController controller = request.getBeanController();
if (controller == null || controller.preDelete(request)) {
persister.delete(request);
return persister.delete(request);
}
// delete handled by the BeanController so return 0
return 0;
}
/**
@@ -127,52 +127,82 @@ public final class DefaultPersister implements Persister {
return executeOrQueue(new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute));
}
/**
* Restore draft beans to match live beans given the query.
*/
@Override
public <T> List<T> publish(Query<T> query, Transaction transaction) {
query.asDraft();
public <T> List<T> draftRestore(Query<T> query, Transaction transaction) {
Class<T> beanType = query.getBeanType();
BeanDescriptor<T> desc = server.getBeanDescriptor(beanType);
desc.draftQueryOptimise(query);
List<T> draftBeans = server.findList(query, transaction);
DraftHandler<T> draftHandler = new DraftHandler<T>(desc, transaction);
List<T> liveBeans = draftHandler.fetchSourceBeans(query, false);
PUB.debug("draftRestore [{}] count[{}]", desc.getName(), liveBeans.size());
if (liveBeans.isEmpty()) {
return Collections.emptyList();
}
draftHandler.fetchDestinationBeans(liveBeans, true);
BeanManager<T> mgr = beanDescriptorManager.getBeanManager(beanType);
for (T liveBean: liveBeans) {
T draftBean = draftHandler.publishToDestinationBean(liveBean);
// reset @DraftDirty and @DraftReset properties
draftHandler.resetDraft(draftBean);
PUB.trace("draftRestore bean [{}] id[{}]", desc.getName(), draftHandler.getId());
update(createRequest(draftBean, transaction, null, mgr, Type.UPDATE, true, false));
}
PUB.debug("draftRestore - complete for [{}]", desc.getName());
return draftHandler.getDrafts();
}
/**
* Helper method to return the list of Id values for the list of beans.
*/
private <T> List<Object> getBeanIds(BeanDescriptor<T> desc, List<T> beans) {
List<Object> idList = new ArrayList<Object>();
for (T liveBean: beans) {
idList.add(desc.getBeanId(liveBean));
}
return idList;
}
/**
* Publish from draft to live given the query.
*/
@Override
public <T> List<T> publish(Query<T> query, Transaction transaction) {
Class<T> beanType = query.getBeanType();
BeanDescriptor<T> desc = server.getBeanDescriptor(beanType);
DraftHandler<T> draftHandler = new DraftHandler<T>(desc, transaction);
List<T> draftBeans = draftHandler.fetchSourceBeans(query, true);
PUB.debug("publish [{}] count[{}]", desc.getName(), draftBeans.size());
if (draftBeans.isEmpty()) {
return Collections.emptyList();
}
// get the list of Id's
List<Object> idList = new ArrayList<Object>();
for (T draftBean: draftBeans) {
idList.add(desc.getBeanId(draftBean));
}
// fetch existing live beans to update (or insert if missing)
Query<T> liveBeansQuery = server.find(beanType).where().idIn(idList).query();
desc.draftQueryOptimise(liveBeansQuery);
Map<?, T> liveBeans = liveBeansQuery.findMap();
List<T> livePublish = new ArrayList<T>(idList.size());
draftHandler.fetchDestinationBeans(draftBeans, false);
BeanManager<T> mgr = beanDescriptorManager.getBeanManager(beanType);
// collect a list of draft beans that have had their
// dirty status set back to false by the publish
List<T> draftUpdates = new ArrayList<T>();
BeanProperty draftDirty = desc.getDraftDirty();
List<T> livePublish = new ArrayList<T>(draftBeans.size());
for (T draftBean: draftBeans) {
Object draftID = desc.getBeanId(draftBean);
T existingLiveBean = liveBeans.get(draftID);
T liveBean = desc.publish(draftBean, existingLiveBean);
T liveBean = draftHandler.publishToDestinationBean(draftBean);
livePublish.add(liveBean);
// reset @DraftDirty and @DraftReset properties
draftHandler.resetDraft(draftBean);
Type persistType = (existingLiveBean == null) ? Type.INSERT : Type.UPDATE;
PUB.trace("publish bean [{}] id[{}] type[{}]", desc.getName(), draftID, persistType);
Type persistType = draftHandler.isInsert() ? Type.INSERT : Type.UPDATE;
PUB.trace("publish bean [{}] id[{}] type[{}]", desc.getName(), draftHandler.getId(), persistType);
PersistRequestBean<T> request = createRequest(liveBean, transaction, null, mgr, persistType, true, true);
if (persistType == Type.INSERT) {
@@ -180,43 +210,134 @@ public final class DefaultPersister implements Persister {
} else {
update(request);
}
unsetDraftDirtyProperty(draftUpdates, draftDirty, draftBean);
}
if (!draftUpdates.isEmpty()) {
// update the dirty status on the drafts that have been published
PUB.debug("publish - update dirty status on [{}] drafts", draftUpdates.size());
for (T draftUpdate : draftUpdates) {
update(createRequest(draftUpdate, transaction, null, mgr, Type.UPDATE, false, false));
}
}
draftHandler.updateDrafts(transaction, mgr);
PUB.debug("publish - complete for [{}]", desc.getName());
return livePublish;
}
/**
* Set the draft dirty state to false and add to the draftUpdates list.
* Helper to handle draft beans (properties reset etc).
*/
private <T> void unsetDraftDirtyProperty(List<T> draftUpdates, BeanProperty draftDirty, T draftBean) {
class DraftHandler<T> {
if (draftDirty != null) {
EntityBean draftEntityBean = (EntityBean)draftBean;
draftDirty.setValueIntercept(draftEntityBean, false);
if (draftEntityBean._ebean_getIntercept().isDirty()) {
final BeanDescriptor<T> desc;
final Transaction transaction;
final BeanProperty draftDirty;
final List<T> draftUpdates = new ArrayList<T>();
/**
* Id value of the last published bean.
*/
Object id;
/**
* True if the last published bean is new/insert.
*/
boolean insert;
/**
* The destination beans to publish/restore to mapped by id.
*/
Map<?, T> destBeans;
DraftHandler(BeanDescriptor<T> desc, Transaction transaction) {
this.desc = desc;
this.transaction = transaction;
this.draftDirty = desc.getDraftDirty();
}
/**
* Return the list of draft beans with changes (to be persisted).
*/
List<T> getDrafts() {
return draftUpdates;
}
/**
* Set the draft dirty state to false and reset any dirtyReset properties.
*/
void resetDraft(T draftBean) {
if (desc.draftReset(draftBean)) {
// draft bean is dirty so collect it for persisting later
draftUpdates.add(draftBean);
}
}
/**
* Save all the draft beans (with various properties reset etc).
*/
void updateDrafts(Transaction transaction, BeanManager<T> mgr) {
if (!draftUpdates.isEmpty()) {
// update the dirty status on the drafts that have been published
PUB.debug("publish - update dirty status on [{}] drafts", draftUpdates.size());
for (T draftUpdate : draftUpdates) {
update(createRequest(draftUpdate, transaction, null, mgr, Type.UPDATE, false, false));
}
}
}
/**
* Fetch the source beans based on the query.
*/
List<T> fetchSourceBeans(Query<T> query, boolean asDraft) {
desc.draftQueryOptimise(query);
if (asDraft) {
query.asDraft();
}
return server.findList(query, transaction);
}
/**
* Fetch the destination beans that will be published to.
*/
void fetchDestinationBeans(List<T> sourceBeans, boolean asDraft) {
List<Object> ids = getBeanIds(desc, sourceBeans);
Query<T> destQuery = server.find(desc.getBeanType()).where().idIn(ids).query();
if (asDraft) {
destQuery.asDraft();
}
desc.draftQueryOptimise(destQuery);
this.destBeans = server.findMap(destQuery, transaction);
}
/**
* Publish/restore the values from the sourceBean to the matching destination bean.
*/
T publishToDestinationBean(T sourceBean) {
id = desc.getBeanId(sourceBean);
T destBean = destBeans.get(id);
insert = (destBean == null);
// apply changes from liveBean to draftBean
return desc.publish(sourceBean, destBean);
}
/**
* Return true if the last publish resulted in an new bean to insert.
*/
boolean isInsert() {
return insert;
}
/**
* Return the Id value of the last published/restored bean.
*/
Object getId() {
return id;
}
}
/**
* Recursively delete the bean. This calls back to the EbeanServer.
*/
private void deleteRecurse(Object detailBean, Transaction t) {
// NB: a new PersistRequest is made
server.delete(detailBean, t);
private void deleteRecurse(EntityBean detailBean, Transaction t, boolean softDelete) {
Type deleteType = softDelete ? Type.SOFT_DELETE : Type.DELETE_PERMANENT;
deleteRequest(createRequest(detailBean, t, deleteType));
}
/**
@@ -375,13 +496,26 @@ public final class DefaultPersister implements Persister {
/**
* Delete the bean with the explicit transaction.
* Return false if the delete is executed without OCC and 0 rows were deleted.
*/
public void delete(EntityBean bean, Transaction t) {
public boolean delete(EntityBean bean, Transaction t, boolean permanent) {
deleteRequest(createRequest(bean, t, PersistRequest.Type.DELETE));
Type deleteType = permanent ? Type.DELETE_PERMANENT : Type.DELETE;
PersistRequestBean<EntityBean> request = createRequest(bean, t, deleteType);
boolean deleted = deleteRequest(request);
if (request.isDraftable() && request.getType() == Type.DELETE) {
// we have just deleting a draft bean so now we need to delete the
// associated 'live' bean. This is effectively an 'automatic publish'.
deleteRequest(createPublishRequest(request.createReference(), t, Type.DELETE_PERMANENT, true));
}
return deleted;
}
private void deleteRequest(PersistRequestBean<?> req) {
/**
* Execute the delete request returning true if a delete occurred.
*/
private boolean deleteRequest(PersistRequestBean<?> req) {
if (req.isRegisteredForDeleteBean()) {
// skip deleting bean. Used where cascade is on
@@ -389,25 +523,27 @@ public final class DefaultPersister implements Persister {
if (logger.isDebugEnabled()) {
logger.debug("skipping delete on alreadyRegistered " + req.getBean());
}
return;
return false;
}
try {
req.initTransIfRequiredWithBatchCascade();
delete(req);
boolean deleted = delete(req);
req.commitTransIfRequired();
req.flushBatchOnCascade();
return deleted;
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
throw ex;
}
}
private void deleteList(List<?> beanList, Transaction t) {
private void deleteList(List<?> beanList, Transaction t, boolean softDelete) {
for (int i = 0; i < beanList.size(); i++) {
EntityBean bean = (EntityBean) beanList.get(i);
delete(bean, t);
deleteRecurse((EntityBean) beanList.get(i), t, softDelete);
//delete((EntityBean) beanList.get(i), t);
}
}
@@ -428,7 +564,7 @@ public final class DefaultPersister implements Persister {
idList.add(descriptor.convertId(id));
}
delete(descriptor, null, idList, transaction);
delete(descriptor, null, idList, transaction, descriptor.isSoftDelete());
}
/**
@@ -440,13 +576,13 @@ public final class DefaultPersister implements Persister {
// convert to appropriate type if required
id = descriptor.convertId(id);
return delete(descriptor, id, null, transaction);
return delete(descriptor, id, null, transaction, descriptor.isSoftDelete());
}
/**
* Delete by Id or a List of Id's.
*/
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction) {
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction, boolean softDelete) {
SpiTransaction t = (SpiTransaction) transaction;
if (t.isPersistCascade()) {
@@ -455,14 +591,14 @@ public final class DefaultPersister implements Persister {
// We actually need to execute a query to get the foreign key values
// as they are required for the delete cascade. Query back just the
// Id and the appropriate foreign key values
Query<?> q = deleteRequiresQuery(descriptor, propImportDelete);
Query<?> q = deleteRequiresQuery(descriptor, propImportDelete, softDelete);
if (idList != null) {
q.where().idIn(idList);
if (t.isLogSummary()) {
t.logSummary("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values");
}
List<?> beanList = server.findList(q, t);
deleteList(beanList, t);
deleteList(beanList, t, softDelete);
return beanList.size();
} else {
@@ -474,7 +610,7 @@ public final class DefaultPersister implements Persister {
if (bean == null) {
return 0;
} else {
delete(bean, t);
deleteRecurse(bean, t, softDelete);
return 1;
}
}
@@ -486,12 +622,15 @@ public final class DefaultPersister implements Persister {
BeanPropertyAssocOne<?>[] expOnes = descriptor.propertiesOneExportedDelete();
for (int i = 0; i < expOnes.length; i++) {
BeanDescriptor<?> targetDesc = expOnes[i].getTargetDescriptor();
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
List<Object> childIds = expOnes[i].findIdsByParentId(id, idList, t);
deleteChildrenById(t, targetDesc, childIds);
// only cascade soft deletes when supported by target
if (!softDelete || targetDesc.isSoftDelete()) {
if (!softDelete && targetDesc.isDeleteByStatement()) {
SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
List<Object> childIds = expOnes[i].findIdsByParentId(id, idList, t);
deleteChildrenById(t, targetDesc, childIds, softDelete);
}
}
}
@@ -499,32 +638,37 @@ public final class DefaultPersister implements Persister {
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyDelete();
for (int i = 0; i < manys.length; i++) {
BeanDescriptor<?> targetDesc = manys[i].getTargetDescriptor();
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
// we can just delete children with a single statement
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
// we need to fetch the Id's to delete (recurse or notify L2 cache)
List<Object> childIds = manys[i].findIdsByParentId(id, idList, t, null);
if (!childIds.isEmpty()) {
delete(targetDesc, null, childIds, t);
// only cascade soft deletes when supported by target
if (!softDelete || targetDesc.isSoftDelete()) {
if (!softDelete && targetDesc.isDeleteByStatement()) {
// we can just delete children with a single statement
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
// we need to fetch the Id's to delete (recurse or notify L2 cache)
List<Object> childIds = manys[i].findIdsByParentId(id, idList, t, null);
if (!childIds.isEmpty()) {
delete(targetDesc, null, childIds, t, softDelete);
}
}
}
}
}
// ManyToMany's ... delete from intersection table
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyToMany();
for (int i = 0; i < manys.length; i++) {
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
if (t.isLogSummary()) {
t.logSummary("-- Deleting intersection table entries: " + manys[i].getFullBeanName());
if (!softDelete) {
// ManyToMany's ... delete from intersection table
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyToMany();
for (int i = 0; i < manys.length; i++) {
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
if (t.isLogSummary()) {
t.logSummary("-- Deleting intersection table entries: " + manys[i].getFullBeanName());
}
executeSqlUpdate(sqlDelete, t);
}
executeSqlUpdate(sqlDelete, t);
}
// delete the bean(s)
SqlUpdate deleteById = descriptor.deleteById(id, idList);
SqlUpdate deleteById = descriptor.deleteById(id, idList, softDelete);
if (t.isLogSummary()) {
if (idList != null) {
t.logSummary("-- Deleting " + descriptor.getName() + " Ids: " + idList);
@@ -558,7 +702,7 @@ public final class DefaultPersister implements Persister {
* We need to create and execute a query to get the foreign key values as
* the delete cascades to them (foreign keys).
*/
private Query<?> deleteRequiresQuery(BeanDescriptor<?> desc, BeanPropertyAssocOne<?>[] propImportDelete) {
private Query<?> deleteRequiresQuery(BeanDescriptor<?> desc, BeanPropertyAssocOne<?>[] propImportDelete, boolean softDelete) {
Query<?> q = server.createQuery(desc.getBeanType());
StringBuilder sb = new StringBuilder(30);
@@ -567,6 +711,10 @@ public final class DefaultPersister implements Persister {
}
q.setAutoTune(false);
q.select(sb.toString());
if (!softDelete) {
// hard delete so we want this query to include logically deleted rows (if any)
q.includeSoftDeletes();
}
return q;
}
@@ -576,7 +724,7 @@ public final class DefaultPersister implements Persister {
* Note that preDelete fires before the deletion of children.
* </p>
*/
private void delete(PersistRequestBean<?> request) {
private boolean delete(PersistRequestBean<?> request) {
DeleteUnloadedForeignKeys unloadedForeignKeys = null;
@@ -595,7 +743,7 @@ public final class DefaultPersister implements Persister {
}
}
request.executeOrQueue();
int count = request.executeOrQueue();
if (request.isPersistCascade()) {
deleteAssocOne(request);
@@ -605,6 +753,8 @@ public final class DefaultPersister implements Persister {
}
}
// return true if using JDBC batch (as we can't tell until the batch is flushed)
return count != 0;
}
/**
@@ -785,7 +935,7 @@ public final class DefaultPersister implements Persister {
EntityBean eb = (EntityBean) removedBean;
if (eb._ebean_getIntercept().isLoaded()) {
// only delete if the bean was loaded meaning that it is known to exist in the DB
deleteRequest(createRequest(removedBean, t, PersistRequest.Type.DELETE, saveMany.isPublish()));
deleteRequest(createPublishRequest(removedBean, t, PersistRequest.Type.DELETE, saveMany.isPublish()));
}
}
}
@@ -892,7 +1042,7 @@ public final class DefaultPersister implements Persister {
}
}
// deleting missing children - children not in our collected detailIds
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds);
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds, false);
}
t.depth(-1);
@@ -1036,7 +1186,7 @@ public final class DefaultPersister implements Persister {
// the object from the 'other' side of the ManyToMany
// build a intersection row for 'delete'
IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete, saveManyPropRequest.isPublish());
SqlUpdate sqlDelete = intRow.createDelete(server);
SqlUpdate sqlDelete = intRow.createDelete(server, false);
executeSqlUpdate(sqlDelete, t);
}
}
@@ -1067,6 +1217,7 @@ public final class DefaultPersister implements Persister {
BeanDescriptor<?> desc = request.getBeanDescriptor();
EntityBean parentBean = request.getEntityBean();
boolean softDelete = request.isSoftDelete();
BeanPropertyAssocOne<?>[] expOnes = desc.propertiesOneExportedDelete();
if (expOnes.length > 0) {
@@ -1074,16 +1225,19 @@ public final class DefaultPersister implements Persister {
DeleteUnloadedForeignKeys unloaded = null;
for (int i = 0; i < expOnes.length; i++) {
BeanPropertyAssocOne<?> prop = expOnes[i];
if (request.isLoadedProperty(prop)) {
Object detailBean = prop.getValue(parentBean);
if (detailBean != null) {
deleteRecurse(detailBean, t);
// for soft delete check cascade type also supports soft delete
if (!softDelete || prop.getTargetDescriptor().isSoftDelete()) {
if (request.isLoadedProperty(prop)) {
Object detailBean = prop.getValue(parentBean);
if (detailBean != null) {
deleteRecurse((EntityBean)detailBean, t, softDelete);
}
} else {
if (unloaded == null) {
unloaded = new DeleteUnloadedForeignKeys(server, request);
}
unloaded.add(prop);
}
} else {
if (unloaded == null) {
unloaded = new DeleteUnloadedForeignKeys(server, request);
}
unloaded.add(prop);
}
}
if (unloaded != null) {
@@ -1096,30 +1250,34 @@ public final class DefaultPersister implements Persister {
BeanPropertyAssocMany<?>[] manys = desc.propertiesManyDelete();
for (int i = 0; i < manys.length; i++) {
if (manys[i].isManyToMany()) {
// delete associated rows from intersection table
deleteAssocManyIntersection(parentBean, manys[i], t, request.isPublish());
if (!softDelete) {
// delete associated rows from intersection table (but not during soft delete)
deleteAssocManyIntersection(parentBean, manys[i], t, request.isPublish());
}
} else {
if (ModifyListenMode.REMOVALS.equals(manys[i].getModifyListenMode())) {
// PrivateOwned ...
Object details = manys[i].getValue(parentBean);
if (details instanceof BeanCollection<?>) {
Set<?> modifyRemovals = ((BeanCollection<?>) details).getModifyRemovals();
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
// if soft delete then check target also supports soft delete
if (!softDelete || manys[i].getTargetDescriptor().isSoftDelete()) {
Object details = manys[i].getValue(parentBean);
if (details instanceof BeanCollection<?>) {
Set<?> modifyRemovals = ((BeanCollection<?>) details).getModifyRemovals();
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
// delete the orphans that have been removed from the collection
for (Object detail : modifyRemovals) {
EntityBean detailBean = (EntityBean) detail;
if (manys[i].hasId(detailBean)) {
deleteRecurse(detailBean, t);
// delete the orphans that have been removed from the collection
for (Object detail : modifyRemovals) {
EntityBean detailBean = (EntityBean) detail;
if (manys[i].hasId(detailBean)) {
deleteRecurse(detailBean, t, softDelete);
}
}
}
}
}
}
deleteManyDetails(t, desc, parentBean, manys[i], null);
deleteManyDetails(t, desc, parentBean, manys[i], null, softDelete);
}
}
@@ -1136,23 +1294,25 @@ public final class DefaultPersister implements Persister {
* </p>
*/
private void deleteManyDetails(SpiTransaction t, BeanDescriptor<?> desc, EntityBean parentBean,
BeanPropertyAssocMany<?> many, ArrayList<Object> excludeDetailIds) {
BeanPropertyAssocMany<?> many, ArrayList<Object> excludeDetailIds, boolean softDelete) {
if (many.getCascadeInfo().isDelete()) {
// cascade delete the beans in the collection
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
// Just delete all the children with one statement
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
SqlUpdate sqlDelete = intRow.createDelete(server);
executeSqlUpdate(sqlDelete, t);
if (!softDelete || targetDesc.isSoftDelete()) {
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
// Just delete all the children with one statement
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
SqlUpdate sqlDelete = intRow.createDelete(server, softDelete);
executeSqlUpdate(sqlDelete, t);
} else {
// Delete recurse using the Id values of the children
Object parentId = desc.getId(parentBean);
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
if (!idsByParentId.isEmpty()) {
deleteChildrenById(t, targetDesc, idsByParentId);
} else {
// Delete recurse using the Id values of the children
Object parentId = desc.getId(parentBean);
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
if (!idsByParentId.isEmpty()) {
deleteChildrenById(t, targetDesc, idsByParentId, softDelete);
}
}
}
}
@@ -1163,7 +1323,7 @@ public final class DefaultPersister implements Persister {
* <p>
* Will use delete by object if the child entity has manyToMany relationships.
*/
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds) {
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds, boolean softDelete) {
if (targetDesc.propertiesManyToMany().length > 0) {
// convert into a list of reference objects and perform delete by object
@@ -1171,11 +1331,11 @@ public final class DefaultPersister implements Persister {
for (Object id : childIds) {
refList.add(targetDesc.createReference(null, id));
}
deleteList(refList, t);
deleteList(refList, t, softDelete);
} else {
// perform delete by statement if possible
delete(targetDesc, null, childIds, t);
delete(targetDesc, null, childIds, t, softDelete);
}
}
@@ -1236,9 +1396,7 @@ public final class DefaultPersister implements Persister {
*/
private void deleteAssocOne(PersistRequestBean<?> request) {
BeanDescriptor<?> desc = request.getBeanDescriptor();
BeanPropertyAssocOne<?>[] ones = desc.propertiesOneImportedDelete();
BeanPropertyAssocOne<?>[] ones = request.getBeanDescriptor().propertiesOneImportedDelete();
for (int i = 0; i < ones.length; i++) {
BeanPropertyAssocOne<?> prop = ones[i];
if (request.isLoadedProperty(prop)) {
@@ -1246,7 +1404,7 @@ public final class DefaultPersister implements Persister {
if (detailBean != null) {
EntityBean detail = (EntityBean) detailBean;
if (prop.hasId(detail)) {
deleteRecurse(detail, request.getTransaction());
deleteRecurse(detail, request.getTransaction(), request.isSoftDelete());
}
}
}
@@ -1287,18 +1445,25 @@ public final class DefaultPersister implements Persister {
* perform an insert, update or delete.
*/
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, PersistRequest.Type type) {
return createRequest(bean, t, type, false);
return createRequestInternal(bean, t, type, false, false);
}
/**
* Create the Persist Request Object additionally specifying the publish status.
*/
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, PersistRequest.Type type, boolean publish) {
private <T> PersistRequestBean<T> createPublishRequest(T bean, Transaction t, PersistRequest.Type type, boolean publish) {
return createRequestInternal(bean, t, type, false, publish);
}
/**
* Create the Persist Request Object additionally specifying the publish status.
*/
private <T> PersistRequestBean<T> createRequestInternal(T bean, Transaction t, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
BeanManager<T> mgr = getBeanManager(bean);
if (mgr == null) {
throw new PersistenceException(errNotRegistered(bean.getClass()));
}
return createRequest(bean, t, null, mgr, type, false, publish);
return createRequest(bean, t, null, mgr, type, saveRecurse, publish);
}
/**
@@ -1332,6 +1497,13 @@ public final class DefaultPersister implements Persister {
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, BeanManager<?> mgr,
PersistRequest.Type type, boolean saveRecurse, boolean publish) {
if (type == Type.DELETE_PERMANENT) {
type = Type.DELETE;
} else if (type == Type.DELETE && mgr.getBeanDescriptor().isSoftDelete()) {
// automatically convert to soft delete for types that support it
type = Type.SOFT_DELETE;
}
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse, publish);
}
@@ -33,7 +33,7 @@ public interface PersistExecute {
/**
* Execute a Bean (or MapBean) delete.
*/
<T> void executeDeleteBean(PersistRequestBean<T> request);
<T> int executeDeleteBean(PersistRequestBean<T> request);
/**
* Execute a Update.
@@ -46,9 +46,10 @@ public class DeleteHandler extends DmlHandler {
* Execute the delete non-batch.
*/
@Override
public void execute() throws SQLException, OptimisticLockException {
public int execute() throws SQLException, OptimisticLockException {
int rowCount = dataBind.executeUpdate();
checkRowCount(rowCount);
return rowCount;
}
@Override
@@ -35,10 +35,10 @@ public final class DmlBeanPersister implements BeanPersister {
/**
* execute the bean delete request.
*/
public void delete(PersistRequestBean<?> request) {
public int delete(PersistRequestBean<?> request) {
DeleteHandler delete = new DeleteHandler(request, deleteMeta);
execute(request, delete);
return execute(request, delete);
}
/**
@@ -62,15 +62,17 @@ public final class DmlBeanPersister implements BeanPersister {
/**
* execute request taking batching into account.
*/
private void execute(PersistRequestBean<?> request, PersistHandler handler) {
private int execute(PersistRequestBean<?> request, PersistHandler handler) {
boolean batched = request.isBatched();
try {
handler.bind();
if (batched) {
handler.addBatch();
return -1;
} else {
handler.execute();
return handler.execute();
}
} catch (SQLException e) {
@@ -75,7 +75,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
* Execute now for non-batch execution.
*/
@Override
public abstract void execute() throws SQLException;
public abstract int execute() throws SQLException;
/**
* Check the rowCount.
@@ -125,8 +125,8 @@ public class InsertHandler extends DmlHandler {
* getGeneratedKeys if required.
*/
@Override
public void execute() throws SQLException, OptimisticLockException {
int rc = dataBind.executeUpdate();
public int execute() throws SQLException, OptimisticLockException {
int rowCount = dataBind.executeUpdate();
if (useGeneratedKeys) {
// get the auto-increment value back and set into the bean
getGeneratedKeys();
@@ -136,8 +136,9 @@ public class InsertHandler extends DmlHandler {
fetchGeneratedKeyUsingSelect();
}
checkRowCount(rc);
checkRowCount(rowCount);
executeDerivedRelationships();
return rowCount;
}
protected void executeDerivedRelationships() {
@@ -210,16 +211,14 @@ public class InsertHandler extends DmlHandler {
rset.close();
}
} catch (SQLException ex) {
String msg = "Error closing rset for fetchGeneratedKeyUsingSelect?";
logger.warn(msg, ex);
logger.warn("Error closing ResultSet for fetchGeneratedKeyUsingSelect?", ex);
}
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException ex) {
String msg = "Error closing stmt for fetchGeneratedKeyUsingSelect?";
logger.warn(msg, ex);
logger.warn("Error closing Statement for fetchGeneratedKeyUsingSelect?", ex);
}
}
}
@@ -25,7 +25,7 @@ public interface PersistHandler {
/**
* Execute now for non-batch execution.
*/
void execute() throws SQLException;
int execute() throws SQLException;
/**
* Close resources including underlying preparedStatement.
@@ -67,11 +67,13 @@ public class UpdateHandler extends DmlHandler {
* Execute the update in non-batch.
*/
@Override
public void execute() throws SQLException, OptimisticLockException {
public int execute() throws SQLException, OptimisticLockException {
if (!emptySetClause) {
int rowCount = dataBind.executeUpdate();
checkRowCount(rowCount);
return rowCount;
}
return 0;
}
@Override
@@ -268,6 +268,8 @@ public class CQueryBuilder {
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
if (query.isAsOfQuery()) {
sqlTree.addAsOfTableAlias(query);
} else if (SpiQuery.TemporalMode.CURRENT == query.getTemporalMode()) {
sqlTree.addSoftDeletePredicate(query);
}
SqlLimitResponse res = buildSql(null, request, predicates, sqlTree);
@@ -478,6 +480,23 @@ public class CQueryBuilder {
}
}
if (!query.isIncludeSoftDeletes()) {
List<String> softDeletePredicates = query.getSoftDeletePredicates();
if (softDeletePredicates != null) {
if (!hasWhere) {
sb.append(" where ");
} else {
sb.append("and ");
}
for (int i = 0; i < softDeletePredicates.size(); i++) {
if (i > 0) {
sb.append(" and ");
}
sb.append(softDeletePredicates.get(i));
}
}
}
if (dbOrderBy != null) {
sb.append(" order by ").append(dbOrderBy);
}
@@ -61,7 +61,7 @@ public class CQueryPredicates {
/**
* Bind values from the where expressions.
*/
private ArrayList<Object> filterManyExprBindValues;
private DefaultExpressionRequest filterMany;
/**
* SQL generated from the where expressions.
@@ -71,7 +71,7 @@ public class CQueryPredicates {
/**
* Bind values from the where expressions.
*/
private ArrayList<Object> whereExprBindValues;
private DefaultExpressionRequest where;
/**
* SQL generated from the where expressions.
@@ -86,7 +86,7 @@ public class CQueryPredicates {
/**
* Bind values for having expression.
*/
private ArrayList<Object> havingExprBindValues;
private DefaultExpressionRequest having;
/**
* SQL generated from the having expression.
@@ -129,16 +129,14 @@ public class CQueryPredicates {
public String bind(DataBind dataBind) throws SQLException {
StringBuilder bindLog = new StringBuilder();
if (query.isVersionsBetween() && binder.isBindAsOfWithFromClause()) {
// sql2011 based versions between timestamp syntax
Timestamp start = query.getVersionStart();
Timestamp end = query.getVersionEnd();
bindLog.append("between ").append(start).append(" and ").append(end);
dataBind.append("between ").append(start).append(" and ").append(end);
binder.bindObject(dataBind, start);
binder.bindObject(dataBind, end);
bindLog.append(", ");
dataBind.append(", ");
}
List<String> historyTableAlias = query.getAsOfTableAlias();
@@ -146,51 +144,37 @@ public class CQueryPredicates {
// bind the asOf value for each table alias as part of the from/join clauses
// there is one effective date predicate per table alias
Timestamp asOf = query.getAsOf();
bindLog.append("asOf ").append(asOf);
dataBind.append("asOf ").append(asOf);
for (int i = 0; i < historyTableAlias.size() * binder.getAsOfBindCount(); i++) {
binder.bindObject(dataBind, asOf);
}
bindLog.append(", ");
dataBind.append(", ");
}
if (idValue != null) {
// this is a find by id type query...
request.getBeanDescriptor().bindId(dataBind, idValue);
bindLog.append(idValue);
dataBind.append(idValue);
}
if (bindParams != null) {
// bind named and positioned parameters...
binder.bind(bindParams, dataBind, bindLog);
binder.bind(bindParams, dataBind, dataBind.log());
}
if (whereExprBindValues != null) {
for (int i = 0; i < whereExprBindValues.size(); i++) {
Object bindValue = whereExprBindValues.get(i);
bindValue = binder.bindObject(dataBind, bindValue);
if (i > 0 || idValue != null) {
bindLog.append(",");
}
bindLog.append(bindValue);
}
if (where != null) {
where.bind(dataBind);
}
if (filterManyExprBindValues != null) {
for (int i = 0; i < filterManyExprBindValues.size(); i++) {
Object bindValue = filterManyExprBindValues.get(i);
bindValue = binder.bindObject(dataBind, bindValue);
if (i > 0 || idValue != null) {
bindLog.append(",");
}
bindLog.append(bindValue);
}
if (filterMany != null) {
filterMany.bind(dataBind);
}
if (historyTableAlias != null && !binder.isBindAsOfWithFromClause()) {
// bind the asOf value for each table alias after all the normal predicates
// there is one effective date predicate per table alias
Timestamp asOf = query.getAsOf();
bindLog.append(" asOf ").append(asOf);
dataBind.append(" asOf ").append(asOf);
for (int i = 0; i < historyTableAlias.size() * binder.getAsOfBindCount(); i++) {
binder.bindObject(dataBind, asOf);
}
@@ -198,24 +182,14 @@ public class CQueryPredicates {
if (havingNamedParams != null) {
// bind named parameters in having...
bindLog.append(" havingNamed ");
binder.bind(havingNamedParams.list(), dataBind, bindLog);
binder.bind(havingNamedParams.list(), dataBind, dataBind.log());
}
if (havingExprBindValues != null) {
// bind having expression...
bindLog.append(" having ");
for (int i = 0; i < havingExprBindValues.size(); i++) {
Object bindValue = havingExprBindValues.get(i);
bindValue = binder.bindObject(dataBind, bindValue);
if (i > 0) {
bindLog.append(",");
}
bindLog.append(bindValue);
}
if (having != null) {
having.bind(dataBind);
}
return bindLog.toString();
return dataBind.log().toString();
}
private void buildBindHavingRawSql(boolean buildSql, boolean parseRaw, DeployParser deployParser) {
@@ -302,22 +276,20 @@ public class CQueryPredicates {
SpiExpressionList<?> whereExp = query.getWhereExpressions();
if (whereExp != null) {
DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser, binder);
whereExprBindValues = whereExp.buildBindValues(whereReq);
this.where = new DefaultExpressionRequest(request, deployParser, binder, whereExp);
if (buildSql) {
whereExprSql = whereExp.buildSql(whereReq);
whereExprSql = where.buildSql();
}
}
BeanPropertyAssocMany<?> manyProperty = request.getManyProperty();
if (manyProperty != null) {
OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false);
SpiExpressionList<?> filterMany = chunk.getFilterMany();
if (filterMany != null) {
DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser, binder);
filterManyExprBindValues = filterMany.buildBindValues(filterReq);
SpiExpressionList<?> filterManyExpr = chunk.getFilterMany();
if (filterManyExpr != null) {
this.filterMany = new DefaultExpressionRequest(request, deployParser, binder, filterManyExpr);
if (buildSql) {
filterManyExprSql = filterMany.buildSql(filterReq);
filterManyExprSql = filterMany.buildSql();
}
}
}
@@ -325,10 +297,9 @@ public class CQueryPredicates {
// having expression
SpiExpressionList<?> havingExpr = query.getHavingExpressions();
if (havingExpr != null) {
DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser, binder);
havingExprBindValues = havingExpr.buildBindValues(havingReq);
this.having = new DefaultExpressionRequest(request, deployParser, binder, havingExpr);
if (buildSql) {
havingExprSql = havingExpr.buildSql(havingReq);
havingExprSql = having.buildSql();
}
}
@@ -495,7 +466,7 @@ public class CQueryPredicates {
* Return the bind values for the where expression.
*/
public ArrayList<Object> getWhereExprBindValues() {
return whereExprBindValues;
return where.getBindValues();
}
/**
@@ -89,6 +89,13 @@ public class SqlTree {
rootNode.addAsOfTableAlias(query);
}
/**
* Recurse through the tree adding soft delete predicates as necessary.
*/
public void addSoftDeletePredicate(SpiQuery<?> query) {
rootNode.addSoftDeletePredicate(query);
}
/**
* Build a select expression chain for RawSql.
*/
@@ -39,6 +39,11 @@ public interface SqlTreeNode {
*/
void addAsOfTableAlias(SpiQuery<?> query);
/**
* Recurse through the tree adding soft delete predicates if necessary.
*/
void addSoftDeletePredicate(SpiQuery<?> query);
/**
* Load the appropriate information from the SqlSelectReader.
* <p>
@@ -480,6 +480,16 @@ public class SqlTreeNodeBean implements SqlTreeNode {
ctx.popJoin();
}
public void addSoftDeletePredicate(SpiQuery<?> query) {
if (desc.isSoftDelete()) {
query.addSoftDeletePredicate(desc.getSoftDeletePredicate(baseTableAlias));
}
for (int i = 0; i < children.length; i++) {
children[i].addSoftDeletePredicate(query);
}
}
public void addAsOfTableAlias(SpiQuery<?> query) {
// if history on this bean type add it's alias
// for each alias we add an effect date predicate
@@ -47,6 +47,11 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
// nothing to do here
}
@Override
public void addSoftDeletePredicate(SpiQuery<?> query) {
// nothing to do here
}
/**
* Return true if the extra join is a many join.
* <p>
@@ -44,6 +44,11 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
// do nothing here ...
}
@Override
public void addSoftDeletePredicate(SpiQuery<?> query) {
// do nothing here ...
}
/**
* Append to the FROM clause for this node.
*/
@@ -9,6 +9,7 @@ import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.HashQuery;
@@ -17,6 +18,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -178,6 +180,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private Timestamp versionsStart;
private Timestamp versionsEnd;
private List<String> softDeletePredicates;
private boolean disableReadAudit;
private int bufferFetchSizeHint;
@@ -285,6 +289,19 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return this;
}
@Override
public void addSoftDeletePredicate(String softDeletePredicate) {
if (softDeletePredicates == null) {
softDeletePredicates = new ArrayList<String>();
}
softDeletePredicates.add(softDeletePredicate);
}
@Override
public List<String> getSoftDeletePredicates() {
return softDeletePredicates;
}
/**
* This table alias is for a @History entity involved in the query and as
* such we need to add a 'as of predicate' to the query using this alias.
@@ -315,6 +332,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return this;
}
@Override
public Query<T> includeSoftDeletes() {
this.temporalMode = TemporalMode.SOFT_DELETED;
return this;
}
/**
* Set the BeanDescriptor for the root type of this query.
*/
@@ -433,6 +456,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
// includes joins and we use - delete ... where id in (...)
maxRows = 0;
firstRow = 0;
forUpdate = false;
setSelectId();
}
@@ -657,6 +681,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return TemporalMode.DRAFT == temporalMode;
}
@Override
public boolean isIncludeSoftDeletes() {
return TemporalMode.SOFT_DELETED == temporalMode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
@@ -1413,4 +1442,28 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
}
@Override
public Set<String> validate() {
return server.validateQuery(this);
}
/**
* Validate all the expression properties/paths given the bean descriptor.
*/
public Set<String> validate(SpiBeanType<T> desc) {
SpiExpressionValidation validation = new SpiExpressionValidation(desc);
if (whereExpressions != null) {
whereExpressions.validate(validation);
}
if (havingExpressions != null) {
havingExpressions.validate(validation);
}
if (orderBy != null) {
for (Property property : orderBy.getProperties()) {
validation.validate(property.getProperty());
}
}
return validation.getUnknownProperties();
}
}
@@ -183,6 +183,7 @@ public class BeanPersistIds implements Serializable {
addUpdateId(id);
break;
case DELETE:
case SOFT_DELETE:
addDeleteId(id);
break;
@@ -14,12 +14,31 @@ public class DataBind {
private final PreparedStatement pstmt;
private final StringBuilder bindLog = new StringBuilder();
private int pos;
public DataBind(PreparedStatement pstmt) {
this.pstmt = pstmt;
}
/**
* Append an entry to the bind log.
*/
public StringBuilder append(Object entry) {
return bindLog.append(entry);
}
/**
* Return the bind log.
*/
public StringBuilder log() {
return bindLog;
}
/**
* Close the underlying prepared statement.
*/
public void close() throws SQLException {
pstmt.close();
}
@@ -29,6 +29,16 @@ public class ScalarTypeBoolean {
super(true, Types.BOOLEAN);
}
@Override
public String getDbFalseLiteral() {
return "false";
}
@Override
public String getDbTrueLiteral() {
return "true";
}
public Boolean toBeanType(Object value) {
return BasicTypeConverter.toBoolean(value);
}
@@ -68,6 +78,16 @@ public class ScalarTypeBoolean {
super(true, Types.BIT);
}
@Override
public String getDbFalseLiteral() {
return "0";
}
@Override
public String getDbTrueLiteral() {
return "1";
}
public Boolean toBeanType(Object value) {
return BasicTypeConverter.toBoolean(value);
}
@@ -106,6 +126,16 @@ public class ScalarTypeBoolean {
this.falseValue = falseValue;
}
@Override
public String getDbFalseLiteral() {
return falseValue.toString();
}
@Override
public String getDbTrueLiteral() {
return trueValue.toString();
}
@Override
public int getLength() {
return 1;
@@ -179,6 +209,16 @@ public class ScalarTypeBoolean {
this.falseValue = falseValue;
}
@Override
public String getDbFalseLiteral() {
return "'"+falseValue+"'";
}
@Override
public String getDbTrueLiteral() {
return "'"+trueValue+"'";
}
@Override
public int getLength() {
// typically this will return 1
@@ -245,6 +285,16 @@ public class ScalarTypeBoolean {
super(Boolean.class, jdbcNative, jdbcType);
}
/**
* Return the DB literal value for false.
*/
public abstract String getDbFalseLiteral();
/**
* Return the DB literal value for true.
*/
public abstract String getDbTrueLiteral();
public String formatValue(Boolean t) {
return t.toString();
}
@@ -15,6 +15,7 @@ import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -98,6 +99,13 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
for (int i = 0; i < list.size(); i++) {
list.get(i).validate(validation);
}
}
@Override
public ExpressionList<T> endJunction() {
return parentExprList == null ? this : parentExprList;
@@ -113,6 +121,16 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.asOf(asOf);
}
@Override
public Query<T> asDraft() {
return query.asDraft();
}
@Override
public Query<T> includeSoftDeletes() {
return query.includeSoftDeletes();
}
@Override
public List<Version<T>> findVersions() {
return query.findVersions();
@@ -1,13 +1,16 @@
package com.avaje.ebeaninternal.util;
import java.sql.SQLException;
import java.util.ArrayList;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.DeployParser;
import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.type.DataBind;
public class DefaultExpressionRequest implements SpiExpressionRequest {
@@ -15,7 +18,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
private final BeanDescriptor<?> beanDescriptor;
private final StringBuilder sb = new StringBuilder();
private final StringBuilder sql = new StringBuilder();
private final ArrayList<Object> bindValues = new ArrayList<Object>();
@@ -23,13 +26,20 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
private final Binder binder;
private final SpiExpressionList<?> expressionList;
private int paramIndex;
public DefaultExpressionRequest(SpiOrmQueryRequest<?> queryRequest, DeployParser deployParser, Binder binder) {
private StringBuilder bindLog;
public DefaultExpressionRequest(SpiOrmQueryRequest<?> queryRequest, DeployParser deployParser, Binder binder, SpiExpressionList<?> expressionList) {
this.queryRequest = queryRequest;
this.beanDescriptor = queryRequest.getBeanDescriptor();
this.deployParser = deployParser;
this.binder = binder;
this.expressionList = expressionList;
// immediately build the list of bind values (callback style)
expressionList.buildBindValues(this);
}
public DefaultExpressionRequest(BeanDescriptor<?> beanDescriptor) {
@@ -37,9 +47,30 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
this.queryRequest = null;
this.deployParser = null;
this.binder = null;
this.expressionList = null;
}
public JsonExpressionHandler getJsonHander() {
/**
* Build sql for the underlying expression list.
*/
public String buildSql() {
return expressionList.buildSql(this);
}
/**
* Bind the values from the underlying expression list.
*/
public void bind(DataBind dataBind) throws SQLException {
for (int i = 0; i < bindValues.size(); i++) {
Object bindValue = bindValues.get(i);
binder.bindObject(dataBind, bindValue);
}
if (bindLog != null) {
dataBind.append(bindLog.toString());
}
}
public JsonExpressionHandler getJsonHandler() {
return binder.getJsonExpressionHandler();
}
@@ -54,9 +85,9 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
*/
@Override
public void appendLike() {
sb.append(" ");
sb.append(queryRequest.getDBLikeClause());
sb.append(" ");
sql.append(" ");
sql.append(queryRequest.getDBLikeClause());
sql.append(" ");
}
/**
@@ -74,17 +105,39 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
return queryRequest;
}
public SpiExpressionRequest append(String sql) {
sb.append(sql);
/**
* Append text the underlying sql expression.
*/
public SpiExpressionRequest append(String sqlExpression) {
sql.append(sqlExpression);
return this;
}
public void addBindEncryptKey(Object bindValue) {
bindValues.add(bindValue);
bindLog("****");
}
public void addBindValue(Object bindValue) {
bindValues.add(bindValue);
bindLog(bindValue);
}
private void bindLog(Object val) {
if (bindLog == null) {
bindLog = new StringBuilder();
} else {
bindLog.append(",");
}
bindLog.append(val);
}
public String getBindLog() {
return bindLog == null ? "" : bindLog.toString();
}
public String getSql() {
return sb.toString();
return sql.toString();
}
public ArrayList<Object> getBindValues() {
@@ -26,6 +26,13 @@ public class SpiServerTest {
assertNull(beanType.getPersistListener());
assertNull(beanType.getQueryAdapter());
assertTrue(beanType.isValidExpression("name"));
assertTrue(beanType.isValidExpression("contacts.firstName"));
assertTrue(beanType.isValidExpression("contacts.group.name"));
assertFalse(beanType.isValidExpression("junk"));
assertFalse(beanType.isValidExpression("Name"));
assertFalse(beanType.isValidExpression("contacts.name"));
Customer customer = new Customer();
customer.setId(42);
@@ -292,6 +292,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public <T> Set<String> validateQuery(Query<T> query) {
return null;
}
@Override
public Object nextId(Class<?> beanType) {
return null;
@@ -367,6 +372,26 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public <T> T draftRestore(Class<T> beanType, Object id, Transaction transaction) {
return null;
}
@Override
public <T> List<T> draftRestore(Query<T> query, Transaction transaction) {
return null;
}
@Override
public <T> T draftRestore(Class<T> beanType, Object id) {
return null;
}
@Override
public <T> List<T> draftRestore(Query<T> query) {
return null;
}
@Override
public Transaction createTransaction() {
return null;
@@ -528,8 +553,8 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public void delete(Object bean) throws OptimisticLockException {
public boolean delete(Object bean) throws OptimisticLockException {
return false;
}
@Override
@@ -638,8 +663,28 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public void delete(Object bean, Transaction t) throws OptimisticLockException {
public boolean delete(Object bean, Transaction t) throws OptimisticLockException {
return false;
}
@Override
public boolean deletePermanent(Object bean) throws OptimisticLockException {
return false;
}
@Override
public boolean deletePermanent(Object bean, Transaction transaction) throws OptimisticLockException {
return false;
}
@Override
public int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException {
return 0;
}
@Override
public int deleteAllPermanent(Collection<?> beans, Transaction transaction) throws OptimisticLockException {
return 0;
}
@Override
@@ -3,6 +3,7 @@ package com.avaje.tests.basic.encrypt;
import java.sql.Date;
import java.util.List;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -16,8 +17,24 @@ import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.tests.model.basic.EBasicEncrypt;
import static org.assertj.core.api.Assertions.assertThat;
public class TestEncrypt extends BaseTestCase {
@Test
public void testQueryBind() {
LoggedSqlCollector.start();
Ebean.find(EBasicEncrypt.class)
.where().startsWith("description", "Rob")
.findList();
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("; --bind(****,Rob%)");
}
@Ignore
@Test
public void test() {
@@ -51,6 +51,21 @@ public class TestDeleteByQuery extends BaseTestCase {
assertThat(list).isEmpty();
}
@Test
public void testWithForUpdate() {
EbeanServer server = Ebean.getDefaultServer();
if (server.getName().equals("mysql")) {
// MySql does not the sub query selecting from the delete table
return;
}
server.find(Customer.class)
.where().eq("name","FatsDomino")
.query().setForUpdate(true)
.delete();
}
@Test
public void testCommit() {
@@ -0,0 +1,70 @@
package com.avaje.tests.delete;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.tests.model.basic.Contact;
import com.avaje.tests.model.basic.EBasicVer;
import com.avaje.tests.model.converstation.Group;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class TestDeleteWithoutOptimisticLocking extends BaseTestCase {
@Test
public void testSimpleBeanDelete_missingBean_returnsFalse() {
// delete by by without version loaded ... should not throw OptimisticLockException
Contact ref = Ebean.getReference(Contact.class, 999999);
assertThat(Ebean.delete(ref)).isFalse();
assertThat(Ebean.delete(Contact.class, 999999)).isEqualTo(0);
// same as above but using Model.delete()
Group modelRef = Ebean.getReference(Group.class, 999999);
assertThat(modelRef.delete()).isFalse();
}
@Test
public void testSimpleBeanDelete_existingBean_returnsTrue() {
EBasicVer basic = new EBasicVer();
basic.setName("DelTest");
Ebean.save(basic);
EBasicVer basicRef = Ebean.getReference(EBasicVer.class, basic.getId());
assertThat(Ebean.delete(basicRef)).isTrue();
}
@Test
public void testSimpleBeanDelete_existingBeanWithJdbcBatch_returnsTrue() {
EBasicVer basic = new EBasicVer();
basic.setName("DelTestBatch");
Ebean.save(basic);
EbeanServer server = Ebean.getDefaultServer();
Transaction transaction = server.beginTransaction();
try {
transaction.setBatch(PersistBatch.ALL);
// returns true even though the delete has not occurred yet
assertThat(server.delete(Ebean.getReference(EBasicVer.class, basic.getId()), transaction)).isTrue();
// returns true even though the bean does not exist
assertThat(server.delete(Ebean.getReference(EBasicVer.class, 999999), transaction)).isTrue();
transaction.commit();
assertThat(Ebean.find(EBasicVer.class, basic.getId())).isNull();
} finally {
transaction.end();
}
}
}
@@ -1,46 +1,12 @@
package com.avaje.tests.model;
import com.avaje.ebean.annotation.WhenCreated;
import com.avaje.ebean.annotation.WhenModified;
import com.avaje.ebean.annotation.WhoCreated;
import com.avaje.ebean.annotation.WhoModified;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
import java.sql.Timestamp;
@Entity
public class EWhoProps {
@Id
Long id;
public class EWhoProps extends EWhoPropsSuper {
String name;
@Version
Long version;
@WhenCreated
Timestamp whenCreated;
@WhenModified
Timestamp whenModified;
@WhoCreated
String whoCreated;
@WhoModified
String whoModified;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
@@ -49,43 +15,4 @@ public class EWhoProps {
this.name = name;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Timestamp getWhenCreated() {
return whenCreated;
}
public void setWhenCreated(Timestamp whenCreated) {
this.whenCreated = whenCreated;
}
public Timestamp getWhenModified() {
return whenModified;
}
public void setWhenModified(Timestamp whenModified) {
this.whenModified = whenModified;
}
public String getWhoCreated() {
return whoCreated;
}
public void setWhoCreated(String whoCreated) {
this.whoCreated = whoCreated;
}
public String getWhoModified() {
return whoModified;
}
public void setWhoModified(String whoModified) {
this.whoModified = whoModified;
}
}
@@ -0,0 +1,81 @@
package com.avaje.tests.model;
import com.avaje.ebean.annotation.WhenCreated;
import com.avaje.ebean.annotation.WhenModified;
import com.avaje.ebean.annotation.WhoCreated;
import com.avaje.ebean.annotation.WhoModified;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
import java.sql.Timestamp;
@MappedSuperclass
public class EWhoPropsSuper {
@Id
Long id;
@Version
Long version;
@WhenCreated
Timestamp whenCreated;
@WhenModified
Timestamp whenModified;
@WhoCreated
String whoCreated;
@WhoModified
String whoModified;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Timestamp getWhenCreated() {
return whenCreated;
}
public void setWhenCreated(Timestamp whenCreated) {
this.whenCreated = whenCreated;
}
public Timestamp getWhenModified() {
return whenModified;
}
public void setWhenModified(Timestamp whenModified) {
this.whenModified = whenModified;
}
public String getWhoCreated() {
return whoCreated;
}
public void setWhoCreated(String whoCreated) {
this.whoCreated = whoCreated;
}
public String getWhoModified() {
return whoModified;
}
public void setWhoModified(String whoModified) {
this.whoModified = whoModified;
}
}
@@ -0,0 +1,45 @@
package com.avaje.tests.model.softdelete;
import com.avaje.ebean.annotation.SoftDelete;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
@MappedSuperclass
public class BaseSoftDelete {
@Id
Long id;
@Version
Long version;
@SoftDelete
boolean deleted;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
}
@@ -0,0 +1,72 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Version;
@Entity
public class EBasicNoSDChild {
@Id
Long id;
@Version
Long version;
@ManyToOne(optional = false)
EBasicSoftDelete owner;
String childName;
long amount;
public EBasicNoSDChild(EBasicSoftDelete owner, String childName, long amount) {
this.owner = owner;
this.childName = childName;
this.amount = amount;
}
public EBasicNoSDChild() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public EBasicSoftDelete getOwner() {
return owner;
}
public void setOwner(EBasicSoftDelete owner) {
this.owner = owner;
}
public String getChildName() {
return childName;
}
public void setChildName(String childName) {
this.childName = childName;
}
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;
}
}
@@ -0,0 +1,48 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
@Entity
public class EBasicSDChild extends BaseSoftDelete {
@ManyToOne(optional = false)
EBasicSoftDelete owner;
String childName;
long amount;
public EBasicSDChild(EBasicSoftDelete owner, String childName, long amount) {
this.owner = owner;
this.childName = childName;
this.amount = amount;
}
public EBasicSDChild() {
}
public EBasicSoftDelete getOwner() {
return owner;
}
public void setOwner(EBasicSoftDelete owner) {
this.owner = owner;
}
public String getChildName() {
return childName;
}
public void setChildName(String childName) {
this.childName = childName;
}
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;
}
}
@@ -0,0 +1,61 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class EBasicSoftDelete extends BaseSoftDelete {
String name;
String description;
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
List<EBasicSDChild> children;
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
List<EBasicNoSDChild> nosdChildren;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<EBasicSDChild> getChildren() {
return children;
}
public void setChildren(List<EBasicSDChild> children) {
this.children = children;
}
public void addChild(String childName, long amount) {
getChildren().add(new EBasicSDChild(this, childName, amount));
}
public List<EBasicNoSDChild> getNosdChildren() {
return nosdChildren;
}
public void setNosdChildren(List<EBasicNoSDChild> nosdChildren) {
this.nosdChildren = nosdChildren;
}
public void addNoSoftDeleteChild(String childName, long amount) {
getNosdChildren().add(new EBasicNoSDChild(this, childName, amount));
}
}
@@ -0,0 +1,21 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.Entity;
@Entity
public class ESoftDelDown extends BaseSoftDelete {
String down;
public ESoftDelDown(String up) {
this.down = down;
}
public String getDown() {
return down;
}
public void setDown(String down) {
this.down = down;
}
}
@@ -0,0 +1,64 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class ESoftDelMid extends BaseSoftDelete {
@ManyToOne(optional = false)
ESoftDelTop top;
String mid;
@ManyToOne(cascade = CascadeType.ALL)
ESoftDelUp up;
@OneToMany(cascade = CascadeType.ALL)
List<ESoftDelDown> downs;
public ESoftDelMid(ESoftDelTop top, String mid) {
this.top = top;
this.mid = mid;
}
public ESoftDelTop getTop() {
return top;
}
public void setTop(ESoftDelTop top) {
this.top = top;
}
public ESoftDelUp getUp() {
return up;
}
public void setUp(ESoftDelUp up) {
this.up = up;
}
public String getMid() {
return mid;
}
public void setMid(String mid) {
this.mid = mid;
}
public List<ESoftDelDown> getDowns() {
return downs;
}
public void setDowns(List<ESoftDelDown> downs) {
this.downs = downs;
}
public void addDown(String down) {
getDowns().add(new ESoftDelDown(down));
}
}
@@ -0,0 +1,35 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import java.util.List;
@Entity
public class ESoftDelRole extends BaseSoftDelete {
String roleName;
@ManyToMany(cascade = CascadeType.ALL)
List<ESoftDelUser> users;
public ESoftDelRole(String roleName) {
this.roleName = roleName;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public List<ESoftDelUser> getUsers() {
return users;
}
public void setUsers(List<ESoftDelUser> users) {
this.users = users;
}
}
@@ -0,0 +1,44 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class ESoftDelTop extends BaseSoftDelete {
String top;
@OneToMany(mappedBy = "top", cascade = CascadeType.ALL)
List<ESoftDelMid> mids;
public ESoftDelTop(String top) {
this.top = top;
}
public String getTop() {
return top;
}
public void setTop(String top) {
this.top = top;
}
public List<ESoftDelMid> getMids() {
return mids;
}
public void setMids(List<ESoftDelMid> mids) {
this.mids = mids;
}
public ESoftDelMid addMids(String mid) {
ESoftDelMid bean = new ESoftDelMid(this, mid);
getMids().add(bean);
return bean;
}
}
@@ -0,0 +1,21 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.Entity;
@Entity
public class ESoftDelUp extends BaseSoftDelete {
String up;
public ESoftDelUp(String up) {
this.up = up;
}
public String getUp() {
return up;
}
public void setUp(String up) {
this.up = up;
}
}
@@ -0,0 +1,38 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import java.util.List;
@Entity
public class ESoftDelUser extends BaseSoftDelete {
String userName;
@ManyToMany(cascade = CascadeType.ALL)
List<ESoftDelRole> roles;
public ESoftDelUser(String userName) {
this.userName = userName;
}
public ESoftDelUser() {
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public List<ESoftDelRole> getRoles() {
return roles;
}
public void setRoles(List<ESoftDelRole> roles) {
this.roles = roles;
}
}
@@ -1,7 +1,10 @@
package com.avaje.tests.query.orderby;
import java.util.List;
import java.util.Set;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
@@ -12,9 +15,29 @@ import com.avaje.tests.model.basic.MRole;
import com.avaje.tests.model.basic.MUser;
import com.avaje.tests.model.basic.MUserType;
import static org.assertj.core.api.Assertions.assertThat;
public class TestOrderByWithDistinct extends BaseTestCase {
@Test
public void testOrderByValidation() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.where()
.eq("junk","blah")
.eq("name","jim")
.orderBy("id desc,path.that.does.not.exist,contacts.group.name asc");
Set<String> unknownProperties = query.validate();
assertThat(unknownProperties).isNotEmpty();
assertThat(unknownProperties).hasSize(2);
assertThat(unknownProperties).contains("junk","path.that.does.not.exist");
}
@Test
public void test() {
/*
@@ -0,0 +1,159 @@
package com.avaje.tests.softdelete;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.SqlRow;
import com.avaje.tests.model.softdelete.EBasicSoftDelete;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestSoftDeleteBasic extends BaseTestCase {
@Test
public void test() {
EBasicSoftDelete bean = new EBasicSoftDelete();
bean.setName("one");
Ebean.save(bean);
Ebean.delete(bean);
SqlQuery sqlQuery = Ebean.createSqlQuery("select * from ebasic_soft_delete where id=?");
sqlQuery.setParameter(1, bean.getId());
SqlRow sqlRow = sqlQuery.findUnique();
assertThat(sqlRow).isNotNull();
EBasicSoftDelete findNormal = Ebean.find(EBasicSoftDelete.class)
.setId(bean.getId())
.findUnique();
assertThat(findNormal).isNull();
EBasicSoftDelete findInclude = Ebean.find(EBasicSoftDelete.class)
.setId(bean.getId())
.includeSoftDeletes()
.findUnique();
assertThat(findInclude).isNotNull();
}
@Test
public void testDeleteById() {
EBasicSoftDelete bean = new EBasicSoftDelete();
bean.setName("two");
Ebean.save(bean);
Ebean.delete(EBasicSoftDelete.class, bean.getId());
}
@Test
public void testDeletePartial() {
EBasicSoftDelete bean = new EBasicSoftDelete();
bean.setName("partial");
Ebean.save(bean);
// partially loaded bean without deleted state loaded
EBasicSoftDelete partial = Ebean.find(EBasicSoftDelete.class)
.select("id")
.setId(bean.getId())
.findUnique();
LoggedSqlCollector.start();
Ebean.delete(partial);
// check lazy loading isn't invoked (deleted set to true without invoking lazy loading)
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(2);
assertThat(loggedSql.get(0)).contains("update ebasic_sdchild set deleted=");
assertThat(loggedSql.get(1)).contains("update ebasic_soft_delete set deleted=? where id=?");
}
@Test
public void testCascadeSaveDelete() {
EBasicSoftDelete bean = new EBasicSoftDelete();
bean.setName("cascadeOne");
bean.addChild("child1", 10);
bean.addChild("child2", 20);
bean.addChild("child3", 30);
bean.addNoSoftDeleteChild("nsd1", 101);
bean.addNoSoftDeleteChild("nsd2", 102);
Ebean.save(bean);
LoggedSqlCollector.start();
Ebean.delete(bean);
List<String> loggedSql = LoggedSqlCollector.stop();
// The children without SoftDelete are left as is (so no third statement)
assertThat(loggedSql).hasSize(2);
// first statement is a single bulk update of the children with SoftDelete
assertThat(loggedSql.get(0)).contains("update ebasic_sdchild set deleted=");
assertThat(loggedSql.get(0)).contains("where owner_id = ?");
// second statement is the top level bean
assertThat(loggedSql.get(1)).contains("update ebasic_soft_delete set version=?, deleted=? where id=? and version=?");
}
@Test
public void testFetch() {
EBasicSoftDelete bean = new EBasicSoftDelete();
bean.setName("cascadeOne");
bean.addChild("child1", 10);
bean.addChild("child2", 20);
bean.addChild("child3", 30);
bean.addNoSoftDeleteChild("nsd1", 101);
bean.addNoSoftDeleteChild("nsd2", 102);
Ebean.save(bean);
Ebean.delete(bean.getChildren().get(1));
LoggedSqlCollector.start();
Query<EBasicSoftDelete> query1 =
Ebean.find(EBasicSoftDelete.class)
.fetch("children")
.where().eq("id", bean.getId())
.query();
List<EBasicSoftDelete> fetch1 = query1.findList();
String generatedSql = query1.getGeneratedSql();
// first statement is a single bulk update of the children with SoftDelete
assertThat(generatedSql).contains("t0.deleted=");
assertThat(generatedSql).contains("t1.deleted=");
assertThat(fetch1.get(0).getChildren()).hasSize(2);
assertThat(fetch1.get(0).getNosdChildren()).hasSize(2);
// fetch again using lazy loading
EBasicSoftDelete fetchWithLazy = Ebean.find(EBasicSoftDelete.class, bean.getId());
assertThat(fetchWithLazy.getChildren()).hasSize(2);
// fetch includeSoftDeletes using lazy loading
EBasicSoftDelete fetchAllWithLazy =
Ebean.find(EBasicSoftDelete.class)
.setId(bean.getId())
.where()
.includeSoftDeletes()
.findUnique();
assertThat(fetchAllWithLazy.getChildren()).hasSize(3);
}
}
@@ -0,0 +1,43 @@
package com.avaje.tests.softdelete;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.softdelete.ESoftDelRole;
import com.avaje.tests.model.softdelete.ESoftDelUser;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestSoftDeleteManyToMany extends BaseTestCase {
@Test
public void test() {
ESoftDelRole role1 = new ESoftDelRole("role1");
ESoftDelRole role2 = new ESoftDelRole("role2");
Ebean.save(role1);
Ebean.save(role2);
ESoftDelUser user1 = new ESoftDelUser("user1");
user1.getRoles().add(role1);
user1.getRoles().add(role2);
Ebean.save(user1);
LoggedSqlCollector.start();
Ebean.delete(user1);
List<String> loggedSql = LoggedSqlCollector.stop();
// No Delete from the relationship table
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("update esoft_del_user set version=?, deleted=? where id=? and version=?;");
}
}
@@ -0,0 +1,92 @@
package com.avaje.tests.softdelete;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.tests.model.softdelete.ESoftDelMid;
import com.avaje.tests.model.softdelete.ESoftDelTop;
import com.avaje.tests.model.softdelete.ESoftDelUp;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class TestSoftDeleteTop extends BaseTestCase {
@Test
public void testDeletePermanent() {
ESoftDelUp up1 = new ESoftDelUp("up1");
Ebean.save(up1);
Ebean.delete(up1);
Ebean.deletePermanent(up1);
}
@Test
public void testSoftDeleteJdbcBatch() {
ESoftDelUp up1 = new ESoftDelUp("upBatch1");
Ebean.save(up1);
ESoftDelUp up2 = new ESoftDelUp("upBatch2");
Ebean.save(up2);
Transaction transaction = Ebean.beginTransaction();
try {
transaction.setBatch(PersistBatch.ALL);
Ebean.delete(up1);
Ebean.delete(up2);
transaction.commit();
} finally {
transaction.end();
}
List<ESoftDelUp> list = new ArrayList<ESoftDelUp>();
list.add(up1);
list.add(up2);
Ebean.deleteAllPermanent(list);
}
@Test
public void testSoftDeleteAll() {
ESoftDelUp up1 = new ESoftDelUp("upBatchX");
ESoftDelUp up2 = new ESoftDelUp("upBatchY");
List<ESoftDelUp> list = new ArrayList<ESoftDelUp>();
list.add(up1);
list.add(up2);
// by default uses JDBC for the 'all' methods
Ebean.saveAll(list);
Ebean.deleteAll(list);
Ebean.deleteAllPermanent(list);
}
@Test
public void test() {
ESoftDelUp up1 = new ESoftDelUp("up1");
ESoftDelTop top1 = new ESoftDelTop("top1");
ESoftDelMid mid1 = top1.addMids("mid1");
mid1.addDown("down1");
mid1.addDown("down2");
mid1.setUp(up1);
ESoftDelMid mid2 = top1.addMids("mid2");
mid2.addDown("down3");
mid2.addDown("down4");
Ebean.save(top1);
Ebean.delete(top1);
Ebean.deletePermanent(top1);
}
}
@@ -10,6 +10,8 @@ import com.avaje.tests.model.embedded.EEmbDatePeriod;
import com.avaje.tests.model.embedded.EEmbInner;
import com.avaje.tests.model.embedded.EEmbOuter;
import static org.assertj.core.api.Assertions.assertThat;
public class TestEmbeddedRefreshUpdate extends BaseTestCase {
@Test
@@ -39,4 +41,24 @@ public class TestEmbeddedRefreshUpdate extends BaseTestCase {
Ebean.find(EEmbInner.class).fetch("outer").orderBy("outer.datePeriod.date1").findList();
}
@Test
public void test2() {
EEmbOuter outer = new EEmbOuter();
outer.setNomeOuter("test");
EEmbDatePeriod embeddedBean = new EEmbDatePeriod();
embeddedBean.setDate1(new Date());
outer.setDatePeriod(embeddedBean);
Ebean.save(outer);
assertThat(outer.getUpdateCount()).isEqualTo(1);
Date d = new Date();
d.setTime(1L);
outer.getDatePeriod().setDate1(d);
Ebean.save(outer);
assertThat(outer.getUpdateCount()).isEqualTo(2);
}
}