mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7818e7f23 | ||
|
|
8318bbc4ca | ||
|
|
337646a6ce | ||
|
|
fa6ee93e5b | ||
|
|
0fe691112f | ||
|
|
6946ff07e0 | ||
|
|
7c3d052da0 | ||
|
|
aea7a7fd65 | ||
|
|
f39a684c8e | ||
|
|
3cb5854786 | ||
|
|
e8192723cc | ||
|
|
3787748eae | ||
|
|
0ad52cf6a7 | ||
|
|
51bee836d4 | ||
|
|
9193b6b971 | ||
|
|
e48b9fab0f | ||
|
|
a14ca24b33 | ||
|
|
761f43d6a1 | ||
|
|
737f9a893e | ||
|
|
579812d717 | ||
|
|
fae39fe650 | ||
|
|
debd8ad617 | ||
|
|
6450ba219c | ||
|
|
a578eda2b1 | ||
|
|
a2ee0bfdbe | ||
|
|
872f0e21cb | ||
|
|
8620dbf337 | ||
|
|
4f51a3b130 | ||
|
|
5f194f066a |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user