Compare commits

...
Author SHA1 Message Date
Robin Bygrave f5cfce3f44 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.3 2015-12-09 12:46:18 +13:00
Robin Bygrave 1c6363e85c #490 - Invalid SQL for generating ID from sequence on DB2 2015-12-09 12:44:27 +13:00
Robin Bygrave b8bcbd8af9 #489 - @Draftable - insert does not set 'draft' to true ... so subsequent update fails 2015-12-09 11:06:48 +13:00
Robin Bygrave d70768277b #488 - BeanPropertyAssocManyJsonHelp error when Jackson not in classPath 2015-12-09 11:00:21 +13:00
Robin Bygrave b39b4a2e8c #487 - DDL - @SoftDelete deleted column should have NOT NULL constraint in create table DDL 2015-12-09 10:27:04 +13:00
Robin Bygrave 93b2f4035c #485 - @SoftDelete with join to nullable relation (optional @ManyToOne for example) ... will incorrectly filter out row from result when FK value is null. 2015-12-09 10:12:25 +13:00
Robin Bygrave 31ee0c4582 #486 - Postgres DDL - Add "if exists" to alter table [if exists] drop constraint if exists .... 2015-12-09 10:06:03 +13:00
Robin Bygrave 6c2dad4851 [maven-release-plugin] prepare for next development iteration 2015-12-07 22:20:54 +13:00
Robin Bygrave ed190ede03 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.2 2015-12-07 22:20:25 +13:00
Robin Bygrave f252b80c2d #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 3: Refactor with ClassLoadConfig 2015-12-07 22:13:27 +13:00
Robin Bygrave 2859ea657e #484 - JDBC4+ drivers do not need Class.forName() registration - removing that call 2015-12-07 21:02:00 +13:00
Robin Bygrave 664b1cbd75 #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 2 : Use current context classLoader 2015-12-07 17:41:56 +13:00
Robin Bygrave 8bc283fef4 #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 1 : protected methods 2015-12-07 17:37:59 +13:00
Robin Bygrave 7d85398139 #483 - ENH: Draftable - Add FK from live table back to draft table (for top level @Draftable tables) 2015-12-07 15:21:22 +13:00
Robin Bygrave f42be3d65a #482 - ENH: @Draftable - Save or update is not allowed on a 'live' bean - only draft beans ... add this check when @Draft property is used 2015-12-07 12:09:10 +13:00
Robin Bygrave ff8e5af4e5 #481 - ENH: @Draftable ... add support for transient @Draft boolean draft; property ... used to distinguish a 'live' instance from a 'draft' instance. 2015-12-07 11:11:22 +13:00
Robin Bygrave 526d67eeb4 No effective change - whitespace 2015-12-07 11:02:32 +13:00
Robin Bygrave 374fc8d702 #480 - @History trigger on @Draftable does not exclude the @DraftOnly and @DraftDirty properties - ERROR: column "dirty" of relation "link_history" does not exist Where: PL/pgSQL function link_history_version() 2015-12-07 09:44:34 +13:00
Robin Bygrave 9c6c59afab #479 - Insert of @Draftable with getGeneratedKeys ... PersistenceException executing insert 2015-12-07 09:21:26 +13:00
Robin Bygrave c3eae0d3a5 #477 - findUnique() should throw NonUniqueResultException and not PersistenceException 2015-12-04 11:12:45 +13:00
Robin Bygrave 25ec73364e [maven-release-plugin] prepare for next development iteration 2015-12-03 21:41:41 +13:00
Robin Bygrave c7818e7f23 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.1 2015-12-03 21:41:20 +13:00
Robin Bygrave 8318bbc4ca #118 - Additional: Add query includeSoftDeletes() asDraft() to ExpressionList (for fluid style when added after predicates) 2015-12-03 17:42:26 +13:00
Robin Bygrave 337646a6ce No effective change - javadoc update 2015-12-03 16:47:19 +13:00
Robin Bygrave fa6ee93e5b Bump pom to 6.13.1-SNAPSHOT 2015-12-03 16:35:43 +13:00
Robin Bygrave 0fe691112f Merge branch 'softDelete1' into softDelete2 2015-12-03 00:33:47 +13:00
Robin Bygrave 6946ff07e0 [maven-release-plugin] prepare for next development iteration 2015-12-02 23:48:15 +13:00
Robin Bygrave 7c3d052da0 [maven-release-plugin] prepare release avaje-ebeanorm-6.12.3 2015-12-02 23:47:49 +13:00
Robin Bygrave aea7a7fd65 #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... 2015-12-02 23:46:26 +13:00
Robin Bygrave f39a684c8e #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... 2015-12-02 21:38:34 +13:00
Robin Bygrave 3cb5854786 #475 - Unique constraint name not unique when using @Draftable 2015-12-02 07:57:27 +13:00
Robin Bygrave e8192723cc [maven-release-plugin] prepare for next development iteration 2015-12-01 23:25:26 +13:00
Robin Bygrave 3787748eae [maven-release-plugin] prepare release avaje-ebeanorm-6.12.2 2015-12-01 23:25:00 +13:00
Robin Bygrave 0ad52cf6a7 #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... - initial 2015-12-01 23:21:11 +13:00
Robin Bygrave 51bee836d4 #472 - DDL column ordering for @WhoCreated / @WhoModified. These columns should appear in the DDL with the @WhenCreated/@WhenModified columns. 2015-12-01 10:19:36 +13:00
Robin Bygrave 9193b6b971 No effective change - remove whitespace 2015-12-01 09:32:09 +13:00
Robin Bygrave e48b9fab0f No effective change - spelling in javadoc 2015-12-01 08:08:14 +13:00
Robin Bygrave a14ca24b33 [maven-release-plugin] prepare for next development iteration 2015-11-30 23:25:13 +13:00
84 changed files with 2045 additions and 599 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.12.1</version>
<version>6.13.3</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
+18
View File
@@ -704,6 +704,10 @@ public final class Ebean {
* 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.
@@ -713,6 +717,13 @@ public final class Ebean {
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);
}
/**
* Delete the bean given its type and id.
*/
@@ -734,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>
+29 -5
View File
@@ -9,6 +9,7 @@ import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
@@ -1091,14 +1092,17 @@ public interface EbeanServer {
<T> Map<?, T> findMap(Query<T> query, Transaction transaction);
/**
* Execute the query returning at most one entity bean. This will throw a
* PersistenceException if the query finds more than one result.
* Execute the query returning at most one entity bean or null (if no matching
* bean is found).
* <p>
* This will throw a NonUniqueResultException if the query finds more than one result.
* </p>
* <p>
* Generally you are able to use {@link Query#findUnique()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
*
* @param <T>
* the type of entity bean to fetch.
* @param query
@@ -1106,6 +1110,7 @@ public interface EbeanServer {
* @param transaction
* the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
*
* @see Query#findUnique()
*/
@@ -1273,6 +1278,26 @@ public interface EbeanServer {
*/
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.
*/
@@ -1472,7 +1497,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
@@ -1524,7 +1549,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;
@@ -3,6 +3,7 @@ package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Collection;
@@ -107,6 +108,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.
@@ -183,8 +194,15 @@ public interface ExpressionList<T> extends Serializable {
<K> Map<K, T> findMap(String keyProperty, Class<K> keyType);
/**
* Execute the query returning a single bean.
*
* Execute the query returning a single bean or null (if no matching
* bean is found).
* <p>
* If more than 1 row is found for this query then a NonUniqueResultException is
* thrown.
* </p>
*
* @throws NonUniqueResultException if more than one result was found
*
* @see Query#findUnique()
*/
@Nullable
+9 -1
View File
@@ -3,6 +3,7 @@ package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;
@@ -302,6 +303,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.
@@ -695,7 +701,7 @@ public interface Query<T> extends Serializable {
* Execute the query returning either a single bean or null (if no matching
* bean is found).
* <p>
* If more than 1 row is found for this query then a PersistenceException is
* If more than 1 row is found for this query then a NonUniqueResultException is
* thrown.
* </p>
* <p>
@@ -731,6 +737,8 @@ public interface Query<T> extends Serializable {
* List<OrderDetail> details = order.getDetails();
* ...
* }</pre>
*
* @throws NonUniqueResultException if more than one result was found
*/
@Nullable
T findUnique();
@@ -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 boolean property on a @Draftable bean that indicates if the bean instance is a 'draft' or 'live' bean.
* The property is transient and has no underlying DB column.
* <p>
* For beans returned from an <code>asDraft()</code> query this property will be set to true.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Draft {
}
@@ -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];
}
@@ -0,0 +1,143 @@
package com.avaje.ebean.config;
/**
* Helper to find classes taking into account the context class loader.
*/
public class ClassLoadConfig {
protected final ClassLoaderContext context;
/**
* Construct with the default classLoader search with context classLoader first.
*/
public ClassLoadConfig() {
this(null);
}
/**
* Specify the classLoader to use for class detection and new instance creation.
*/
public ClassLoadConfig(ClassLoader classLoader) {
this.context = new ClassLoaderContext(classLoader);
}
/**
* Return true if the Java.time types are available and should be supported.
*/
public boolean isJavaTimePresent() {
return isPresent("java.time.LocalDate");
}
/**
* Return true if the Joda types are available and should be supported.
*/
public boolean isJodaTimePresent() {
return isPresent("org.joda.time.LocalDateTime");
}
/**
* Return true if javax validation annotations like Size and NotNull are present.
*/
public boolean isJavaxValidationAnnotationsPresent() {
return isPresent("javax.validation.constraints.NotNull");
}
/**
* Return true if Jackson annotations like JsonIgnore are present.
*/
public boolean isJacksonAnnotationsPresent() {
return isPresent("com.fasterxml.jackson.annotation.JsonIgnore");
}
/**
* Return true if Jackson ObjectMapper is present.
*/
public boolean isJacksonObjectMapperPresent() {
return isPresent("com.fasterxml.jackson.databind.ObjectMapper");
}
/**
* Return a new instance of the class using the default constructor.
*/
public Object newInstance(String className) {
try {
Class<?> cls = forName(className);
return cls.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("Error constructing " + className, e);
}
}
/**
* Return true if the given class is present.
*/
protected boolean isPresent(String className) {
try {
forName(className);
return true;
} catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
/**
* Load a class taking into account a context class loader (if present).
*/
protected Class<?> forName(String name) throws ClassNotFoundException {
return context.forName(name);
}
/**
* Wraps the preferred, caller and context class loaders.
*/
protected class ClassLoaderContext {
/**
* Optional - if set only use this classLoader (no fallback).
*/
protected final ClassLoader preferredLoader;
protected final ClassLoader contextLoader;
protected final ClassLoader callerLoader;
ClassLoaderContext(ClassLoader preferredLoader) {
this.preferredLoader = preferredLoader;
this.callerLoader = ServerConfig.class.getClassLoader();
this.contextLoader = contextLoader();
}
ClassLoader contextLoader() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return (loader != null) ? loader: callerLoader;
}
Class<?> forName(String name) throws ClassNotFoundException {
if (preferredLoader != null) {
// only use the explicitly set classLoader
return classForName(name, preferredLoader);
}
try {
// try the context loader first
return classForName(name, contextLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == contextLoader) {
throw e;
} else {
// fallback to the caller classLoader
return classForName(name, callerLoader);
}
}
}
Class<?> classForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
return Class.forName(name, true, classLoader);
}
}
}
@@ -7,14 +7,20 @@ import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.event.*;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanPostLoad;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BulkTableEventListener;
import com.avaje.ebean.event.ServerConfigStartup;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.util.ClassUtil;
import com.fasterxml.jackson.core.JsonFactory;
import javax.sql.DataSource;
@@ -228,6 +234,11 @@ public class ServerConfig {
*/
private DbMigrationConfig migrationConfig = new DbMigrationConfig();
/**
* The ClassLoadConfig used to detect Joda, Java8, Jackson etc and create plugin instances given a className.
*/
private ClassLoadConfig classLoadConfig = new ClassLoadConfig();
/**
* Set to true if the DataSource uses autoCommit.
* <p>
@@ -2023,6 +2034,22 @@ public class ServerConfig {
this.persistenceContextScope = persistenceContextScope;
}
/**
* Return the ClassLoadConfig which is used to detect Joda, Java8 types etc and also
* create new instances of plugins given a className.
*/
public ClassLoadConfig getClassLoadConfig() {
return classLoadConfig;
}
/**
* Set the ClassLoadConfig which is used to detect Joda, Java8 types etc and also
* create new instances of plugins given a className.
*/
public void setClassLoadConfig(ClassLoadConfig classLoadConfig) {
this.classLoadConfig = classLoadConfig;
}
/**
* Load settings from ebean.properties.
*/
@@ -2062,14 +2089,31 @@ public class ServerConfig {
return properties;
}
/**
* Return the instance to use (can be null) for the given plugin.
*
* @param properties the properties
* @param pluginType the type of plugin
* @param key properties key
* @param instance existing instance
*/
@SuppressWarnings("unchecked")
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key, T instance) {
protected <T> T createInstance(PropertiesWrapper properties, Class<T> pluginType, String key, T instance) {
if (instance != null) {
return instance;
}
String classname = p.get(key, null);
return classname == null ? null : (T) ClassUtil.newInstance(classname);
String classname = properties.get(key, null);
return createInstance(pluginType, classname);
}
/**
* Return the instance to use (can be null) for the given plugin.
* @param pluginType the type of plugin
* @param classname the implementation class as per properties
*/
protected <T> T createInstance(Class<T> pluginType, String classname) {
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
}
/**
@@ -17,7 +17,7 @@ public class DB2SequenceIdGenerator extends SequenceIdGenerator {
*/
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.baseSql = "select nextval for " + seqName;
this.baseSql = "values nextval for " + seqName;
this.unionBaseSql = " union " + baseSql;
}
@@ -232,7 +232,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
List<String> includedColumns = new ArrayList<String>(columns.size());
for (MColumn column : columns) {
if (!column.isHistoryExclude()) {
if (column.isIncludeInHistory()) {
includedColumns.add(column.getName());
}
}
@@ -53,6 +53,8 @@ public class PlatformDdl {
protected String identitySuffix = " auto_increment";
protected String alterTableIfExists = "";
protected String dropConstraintIfExists = "drop constraint if exists";
protected String dropIndexIfExists = "drop index if exists ";
@@ -150,7 +152,7 @@ public class PlatformDdl {
* Return the drop foreign key clause.
*/
public String alterTableDropForeignKey(String tableName, String fkName) {
return "alter table " + tableName + " " + dropConstraintIfExists + " " + fkName;
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + fkName;
}
/**
@@ -13,6 +13,7 @@ public class PostgresDdl extends PlatformDdl {
this.historyDdl = new PostgresHistoryDdl();
this.dropTableCascade = " cascade";
this.columnSetType = "type ";
this.alterTableIfExists = "if exists ";
}
/**
@@ -213,6 +213,13 @@ public class MColumn {
return draftOnly;
}
/**
* Return true if this column should be included in History DB triggers etc.
*/
public boolean isIncludeInHistory() {
return !draftOnly && !historyExclude;
}
public Column createColumn() {
Column c = new Column();
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.type.ScalarType;
import java.util.Collection;
import java.util.List;
/**
* The context used during DDL generation.
@@ -134,13 +135,25 @@ public class ModelBuildContext {
/**
* Create the draft table for a given table.
*/
public void createDraft(MTable table) {
public void createDraft(MTable table, boolean draftable) {
MTable draftTable = table.createDraftTable();
draftTable.setPkName(primaryKeyName(draftTable.getName()));
if (draftable) {
// Add a FK from @Draftable live table back to it's draft table)
List<MColumn> pkCols = table.primaryKeyColumns();
if (pkCols.size() == 1) {
// only doing this for single column PK at this stage
MColumn pk = pkCols.get(0);
pk.setReferences(draftTable.getName() + "." + pk.getName());
pk.setForeignKeyName(foreignKeyConstraintName(table.getName(), pk.getName(), 0));
}
}
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 +165,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);
@@ -44,7 +44,7 @@ public class ModelBuildIntersectionTable {
buildFkConstraints();
if (manyProp.getTargetDescriptor().isDraftable()) {
ctx.createDraft(intersectionTable);
ctx.createDraft(intersectionTable, false);
}
}
@@ -112,7 +112,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
private void addDraftTable() {
if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) {
// create a 'Draft' table which looks very similar (change PK, FK etc)
ctx.createDraft(table);
ctx.createDraft(table, !beanDescriptor.isDraftableElement());
}
}
@@ -9,20 +9,6 @@ import java.util.Arrays;
*/
public class ClassUtil {
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className) {
try {
Class<?> cls = Class.forName(className);
return cls.newInstance();
} catch (Exception e) {
String msg = "Error constructing " + className;
throw new IllegalArgumentException(msg, e);
}
}
/**
* Returns the raw type for the 2nd generic parameter for a subclass.
*/
@@ -1,146 +0,0 @@
package com.avaje.ebeaninternal.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wraps the caller and context class loaders.
* <p>
* Helper for ClassUtil.
* </p>
*
* @author rbygrave
*/
class ClassLoadContext {
private static final Logger logger = LoggerFactory.getLogger(ClassLoadContext.class);
private final ClassLoader callerLoader;
private final ClassLoader contextLoader;
private final boolean preferContext;
private boolean ambiguous;
public static ClassLoadContext of(Class<?> caller, boolean preferContext) {
return new ClassLoadContext(caller, preferContext);
}
/**
* This constructor is package-private to restrict instantiation to
*/
ClassLoadContext(final Class<?> caller, boolean preferContext) {
if (caller == null) {
throw new IllegalArgumentException("caller is null");
}
this.callerLoader = caller.getClassLoader();
this.contextLoader = Thread.currentThread().getContextClassLoader();
this.preferContext = preferContext;
}
public Class<?> forName(String name) throws ClassNotFoundException {
ClassLoader defaultLoader = getDefault(preferContext);
try {
return Class.forName(name, true, defaultLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == defaultLoader) {
throw e;
} else {
return Class.forName(name, true, callerLoader);
}
}
}
/**
* Return the expected class loader to use.
* <p>
* Works on the assumption that the child of the caller or context class
* loader is preferred.
* </p>
*/
public ClassLoader getDefault(boolean preferContext) {
if (contextLoader == null) {
if (logger.isDebugEnabled()) {
logger.debug("No Context ClassLoader, using " + callerLoader.getClass().getName());
}
return callerLoader;
}
if (contextLoader == callerLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Context and Caller ClassLoader's same instance of " + contextLoader.getClass().getName());
}
return callerLoader;
}
if (isChild(contextLoader, callerLoader)) {
if (logger.isDebugEnabled()) {
logger.debug("Caller ClassLoader " + callerLoader.getClass().getName()
+ " child of ContextLoader " + contextLoader.getClass().getName());
}
return callerLoader;
} else if (isChild(callerLoader, contextLoader)) {
if (logger.isDebugEnabled()) {
logger.debug("Context ClassLoader " + contextLoader.getClass().getName()
+ " child of Caller ClassLoader " + callerLoader.getClass().getName());
}
return contextLoader;
} else {
// ambiguous case, perhaps both null
logger.debug("Ambiguous ClassLoader choice preferContext:" + preferContext
+ " Context:" + contextLoader.getClass().getName() + " Caller:" + callerLoader.getClass().getName());
ambiguous = true;
return preferContext ? contextLoader : callerLoader;
}
}
/**
* Return true if the 'default' class loader is ambiguous.
*/
public boolean isAmbiguous() {
return ambiguous;
}
/**
* Return the ClassLoader of the caller.
*/
public ClassLoader getCallerLoader() {
return callerLoader;
}
/**
* Return the Thread Context ClassLoader.
*/
public ClassLoader getContextLoader() {
return contextLoader;
}
/**
* Return the ClassLoader for this class.
*/
public ClassLoader getThisLoader() {
return this.getClass().getClassLoader();
}
/**
* Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
* 'loader1'=='loader2'].
*/
private boolean isChild(final ClassLoader loader1, ClassLoader loader2) {
for (; loader2 != null; loader2 = loader2.getParent()) {
if (loader2 == loader1) {
return true;
}
}
return false;
}
}
@@ -6,79 +6,60 @@ package com.avaje.ebeaninternal.api;
*/
public class ClassUtil {
/**
* Load a class taking into account a context class loader (if present).
*/
public static Class<?> forName(String name, Class<?> caller) throws ClassNotFoundException {
if (caller == null) {
caller = ClassUtil.class;
}
ClassLoadContext ctx = ClassLoadContext.of(caller, true);
return ctx.forName(name);
}
/**
* Return true if javax validation annotations like Size and NotNull are present.
*/
public static boolean isJavaxValidationAnnotationsPresent() {
return isPresent("javax.validation.constraints.NotNull", null);
}
/**
* Return true if Jackson annotations like JsonIgnore are present.
*/
public static boolean isJacksonAnnotationsPresent() {
return isPresent("com.fasterxml.jackson.annotation.JsonIgnore", null);
}
/**
* Return true if Jackson ObjectMapper is present.
*/
public static boolean isJacksonObjectMapperPresent() {
return isPresent("com.fasterxml.jackson.databind.ObjectMapper", null);
}
/**
* Return true if the given class is present.
*/
public static boolean isPresent(String className) {
return isPresent(className, null);
}
/**
* Return true if the given class is present.
*/
public static boolean isPresent(String className, Class<?> caller) {
try {
forName(className, caller);
return true;
} catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className) {
return newInstance(className, null);
}
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className, Class<?> caller) {
try {
Class<?> cls = forName(className, caller);
Class<?> cls = forName(className);
return cls.newInstance();
} catch (Exception e) {
String msg = "Error constructing " + className;
throw new IllegalArgumentException(msg, e);
}
}
/**
* Load a class taking into account a context class loader (if present).
*/
private static Class<?> forName(String name) throws ClassNotFoundException {
return new ClassLoadContext().forName(name);
}
/**
* Helper to wrap the context and caller classLoaders (to use/try both).
*/
static class ClassLoadContext {
private final ClassLoader contextLoader;
private final ClassLoader callerLoader;
ClassLoadContext() {
this.callerLoader = ClassUtil.class.getClassLoader();
this.contextLoader = contextLoader();
}
ClassLoader contextLoader() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return (loader != null) ? loader: callerLoader;
}
public Class<?> forName(String name) throws ClassNotFoundException {
try {
return Class.forName(name, true, contextLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == contextLoader) {
throw e;
} else {
return Class.forName(name, true, callerLoader);
}
}
}
}
}
@@ -96,6 +96,11 @@ public interface SpiQuery<T> extends Query<T> {
}
enum TemporalMode {
/**
* Includes soft deletes rows in the result.
*/
SOFT_DELETED,
/**
* Query runs against draft tables.
*/
@@ -183,6 +188,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.
*/
@@ -198,6 +208,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.
@@ -20,6 +20,7 @@ import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.lib.ShutdownManager;
import com.avaje.ebeaninternal.server.lib.sql.DataSourceAlert;
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePoolListener;
import com.avaje.ebeaninternal.server.lib.sql.SimpleDataSourceAlert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -311,7 +312,17 @@ public class DefaultContainer implements SpiContainer {
}
DataSourceAlert notify = new SimpleDataSourceAlert();
return new DataSourcePool(notify, config.getName(), dsConfig);
DataSourcePoolListener listener = createListener(config, dsConfig);
return new DataSourcePool(notify, config.getName(), dsConfig, listener);
}
/**
* Create and return a DataSourcePoolListener if it has been specified.
*/
private DataSourcePoolListener createListener(ServerConfig config, DataSourceConfig dsConfig) {
String poolListener = dsConfig.getPoolListener();
return poolListener != null ? (DataSourcePoolListener) config.getClassLoadConfig().newInstance(poolListener) : null;
}
/**
@@ -70,6 +70,7 @@ import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
@@ -1218,13 +1219,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// a query that is expected to return either 0 or 1 rows
List<T> list = findList(query, t);
return extractUnique(list);
}
if (list.size() == 0) {
private <T> T extractUnique(List<T> list) {
if (list.isEmpty()) {
return null;
} else if (list.size() > 1) {
throw new PersistenceException("Unique expecting 0 or 1 rows but got [" + list.size() + "]");
throw new NonUniqueResultException("Unique expecting 0 or 1 results but got [" + list.size() + "]");
} else {
return list.get(0);
}
@@ -1449,17 +1453,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// no findId() method for SqlQuery...
// a query that is expected to return either 0 or 1 rows
List<SqlRow> list = findList(query, t);
if (list.size() == 0) {
return null;
} else if (list.size() > 1) {
String m = "Unique expecting 0 or 1 rows but got [" + list.size() + "]";
throw new PersistenceException(m);
} else {
return list.get(0);
}
return extractUnique(list);
}
public SqlFutureList findFutureList(SqlQuery query, Transaction t) {
@@ -1883,9 +1877,28 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Delete the bean with the explicit transaction.
*/
public boolean delete(Object bean, Transaction t) {
return 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);
}
/**
@@ -1893,7 +1906,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);
}
/**
@@ -1901,13 +1914,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);
@@ -1917,7 +1930,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++;
}
@@ -357,6 +357,6 @@ public class InternalConfiguration {
}
public GeneratedPropertyFactory getGeneratedPropertyFactory() {
return new GeneratedPropertyFactory(serverConfig.getCurrentUserProvider());
return new GeneratedPropertyFactory(serverConfig);
}
}
@@ -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;
@@ -21,6 +21,7 @@ import com.avaje.ebeaninternal.server.persist.PersistExecute;
import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -150,12 +151,29 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
this.publish = publish;
if (!publish && beanDescriptor.isDraftable()) {
if (isMarkDraftDirty(publish)) {
beanDescriptor.setDraftDirty(entityBean, true);
}
this.dirty = intercept.isDirty();
}
/**
* Return true if the draftDirty property should be set to true for this request.
*/
private boolean isMarkDraftDirty(boolean publish) {
return !publish && type != Type.DELETE && beanDescriptor.isDraftable();
}
/**
* Set the transaction from prior persist request.
* Only used when hard deleting draft & associated live beans.
*/
public void setTrans(SpiTransaction transaction) {
this.transaction = transaction;
this.createdTransaction = false;
this.persistCascade = transaction.isPersistCascade();
}
/**
* Init the transaction and also check for batch on cascade escalation.
*/
@@ -256,6 +274,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:
@@ -435,6 +454,31 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return beanDescriptor.isDraftable();
}
/**
* Return true if this request is a hard delete of a draftable bean.
* If this is true Ebean is expected to auto-publish and delete the associated live bean.
*/
public boolean isHardDeleteDraft() {
if (type == Type.DELETE && beanDescriptor.isDraftable() && !beanDescriptor.isDraftableElement()) {
// deleting a top level draftable bean
if (!beanDescriptor.isDraftInstance(entityBean)) {
throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans");
}
return true;
}
return false;
}
/**
* Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'.
* Save or Update is not allowed to execute using 'live' beans - must use publish().
*/
public void checkDraft() {
if (beanDescriptor.isDraftable() && !beanDescriptor.isDraftInstance(entityBean)) {
throw new PersistenceException("Save or update is not allowed on a 'live' bean - only draft beans");
}
}
/**
* Return the parent bean for cascading save with unidirectional relationship.
*/
@@ -479,6 +523,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
persistExecute.executeUpdateBean(this);
return -1;
case SOFT_DELETE:
prepareForSoftDelete();
persistExecute.executeUpdateBean(this);
return -1;
case DELETE:
return persistExecute.executeDeleteBean(this);
@@ -487,6 +536,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Soft delete is executed as update so we want to set deleted=true property.
*/
private void prepareForSoftDelete() {
beanDescriptor.setSoftDeleteValue(entityBean);
}
@Override
public int executeOrQueue() {
@@ -533,6 +590,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
switch (type) {
case DELETE:
case SOFT_DELETE:
postDelete();
break;
case UPDATE:
@@ -601,6 +659,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controller.postUpdate(this);
break;
case DELETE:
case SOFT_DELETE:
controller.postDelete(this);
break;
default:
@@ -622,6 +681,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;
}
@@ -692,6 +754,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
intercept.setLoadedProperty(i);
}
beanDescriptor.setEmbeddedOwner(entityBean);
beanDescriptor.setDraft(entityBean);
}
public boolean isReference() {
@@ -801,4 +864,12 @@ 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.
*/
boolean delete(EntityBean entityBean, Transaction t);
boolean delete(EntityBean entityBean, Transaction t, boolean permanent);
/**
* Delete multiple beans given a collection of Id values.
@@ -12,6 +12,7 @@ import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.IdGenerator;
import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.event.BeanFindController;
@@ -149,6 +150,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;
/**
@@ -160,6 +164,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final boolean draftableElement;
private final BeanProperty draft;
private final BeanProperty draftDirty;
/**
@@ -316,8 +322,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;
@@ -395,8 +402,11 @@ 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.draft = listHelper.getDraft();
this.draftDirty = listHelper.getDraftDirty();
this.propMap = listHelper.getPropertyMap();
this.propertiesTransient = listHelper.getTransients();
@@ -500,6 +510,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
}
}
/**
* Return the ServerConfig.
*/
public ServerConfig getServerConfig() {
return owner.getServerConfig();
}
/**
* Set the server. Primarily so that the Many's can lazy load.
*/
@@ -621,6 +638,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()) {
@@ -669,6 +694,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;
@@ -710,11 +736,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);
}
}
@@ -729,9 +755,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);
@@ -746,9 +773,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++) {
@@ -1175,6 +1203,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.
*/
@@ -1932,6 +1970,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 +2000,27 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return draftableElement;
}
/**
* Set the draft to true for this entity bean instance.
* This bean is being loaded via asDraft() query.
*/
public void setDraft(EntityBean entityBean) {
if (draft != null) {
draft.setValue(entityBean, true);
}
}
/**
* Return true if the bean is considered a 'draft' instance.
*/
public boolean isDraftInstance(EntityBean entityBean) {
if (draft != null) {
return Boolean.TRUE == draft.getValue(entityBean);
}
// no draft property - so just ignore the check / return true
return true;
}
/**
* If there is a @DraftDirty property set it's value on the bean.
*/
@@ -125,6 +125,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final String serverName;
private final ServerConfig serverConfig;
private Map<Class<?>, DeployBeanInfo<?>> deplyInfoMap = new HashMap<Class<?>, DeployBeanInfo<?>>();
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<Class<?>, BeanTable>();
@@ -178,8 +180,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
public BeanDescriptorManager(InternalConfiguration config) {
ServerConfig serverConfig = config.getServerConfig();
this.serverConfig = config.getServerConfig();
this.serverName = InternString.intern(serverConfig.getName());
this.cacheManager = config.getCacheManager();
this.xmlConfig = config.getXmlConfig();
@@ -240,6 +241,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
}
@Override
public ServerConfig getServerConfig() {
return serverConfig;
}
@SuppressWarnings("unchecked")
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType) {
return (BeanDescriptor<T>) descMap.get(entityType.getName());
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
/**
@@ -12,26 +13,31 @@ import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
*/
public interface BeanDescriptorMap {
/**
* Return the name of the server/database.
*/
String getServerName();
/**
* Return the name of the server/database.
*/
String getServerName();
/**
* Return the Cache Manager.
*/
ServerCacheManager getCacheManager();
/**
* Return the ServerConfig.
*/
ServerConfig getServerConfig();
/**
* Return the BeanDescriptor for a given class.
*/
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType);
/**
* Return the Cache Manager.
*/
ServerCacheManager getCacheManager();
/**
* Return the Encrypt key given the table and column name.
*/
EncryptKey getEncryptKey(String tableName, String columnName);
IdBinder createIdBinder(BeanProperty id);
/**
* Return the BeanDescriptor for a given class.
*/
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType);
/**
* Return the Encrypt key given the table and column name.
*/
EncryptKey getEncryptKey(String tableName, String columnName);
IdBinder createIdBinder(BeanProperty id);
}
@@ -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;
@@ -223,12 +224,20 @@ public class BeanProperty implements ElPropertyValue {
final boolean jsonDeserialize;
final boolean draft;
final boolean draftOnly;
final boolean draftDirty;
final boolean draftReset;
final boolean softDelete;
final String softDeleteDbSet;
final String softDeleteDbPredicate;
final boolean indexed;
final String indexName;
@@ -255,10 +264,10 @@ public class BeanProperty implements ElPropertyValue {
this.dbInsertable = deploy.isDbInsertable();
this.dbUpdatable = deploy.isDbUpdateable();
this.excludedFromHistory = deploy.isExcludedFromHistory();
this.draft = deploy.isDraft();
this.draftDirty = deploy.isDraftDirty();
this.draftOnly = deploy.isDraftOnly();
this.draftReset = deploy.isDraftReset();
this.secondaryTable = deploy.isSecondaryTable();
if (secondaryTable) {
this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin());
@@ -302,6 +311,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()+")="+boolType.getDbFalseLiteral();
} else {
this.softDeleteDbSet = null;
this.softDeleteDbPredicate = null;
}
this.jsonSerialize = deploy.isJsonSerialize();
this.jsonDeserialize = deploy.isJsonDeserialize();
}
@@ -342,9 +361,13 @@ public class BeanProperty implements ElPropertyValue {
this.formula = false;
this.excludedFromHistory = source.excludedFromHistory;
this.draft = source.draft;
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;
@@ -616,6 +639,30 @@ 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) {
// use coalesce to handle null values from optional relationships
return "coalesce(" + 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.
@@ -1041,6 +1088,14 @@ public class BeanProperty implements ElPropertyValue {
return draftOnly;
}
/**
* Return true if this property is a boolean flag on a draftable bean
* indicating if the instance is a draft or live bean.
*/
public boolean isDraft() {
return draft;
}
/**
* Return true if this property is a boolean flag only on the draft table
* indicating that when the draft is different from the published row.
@@ -1056,6 +1111,13 @@ public class BeanProperty implements ElPropertyValue {
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());
}
@@ -31,7 +31,8 @@ public class BeanPropertyAssocManyJsonHelp {
*/
public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
this.many = many;
this.jsonTransient = !ClassUtil.isJacksonObjectMapperPresent() ? null : new BeanPropertyAssocManyJsonTransient();
boolean objectMapperPresent = many.getBeanDescriptor().getServerConfig().getClassLoadConfig().isJacksonObjectMapperPresent();
this.jsonTransient = !objectMapperPresent ? null : new BeanPropertyAssocManyJsonTransient();
}
/**
@@ -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;
}
}
@@ -3,7 +3,9 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.math.BigDecimal;
import java.util.HashSet;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebean.config.CurrentUserProvider;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
@@ -13,9 +15,9 @@ public class GeneratedPropertyFactory {
private final CounterFactory counterFactory = new CounterFactory();
private final InsertTimestampFactory insertFactory = new InsertTimestampFactory();
private final InsertTimestampFactory insertFactory;
private final UpdateTimestampFactory updateFactory = new UpdateTimestampFactory();
private final UpdateTimestampFactory updateFactory;
private final HashSet<String> numberTypes = new HashSet<String>();
@@ -23,8 +25,15 @@ public class GeneratedPropertyFactory {
private final GeneratedWhoCreated generatedWhoCreated;
public GeneratedPropertyFactory(CurrentUserProvider currentUserProvider) {
private final ClassLoadConfig classLoadConfig;
public GeneratedPropertyFactory(ServerConfig serverConfig) {
this.classLoadConfig = serverConfig.getClassLoadConfig();
this.insertFactory = new InsertTimestampFactory(classLoadConfig);
this.updateFactory = new UpdateTimestampFactory(classLoadConfig);
CurrentUserProvider currentUserProvider = serverConfig.getCurrentUserProvider();
if (currentUserProvider != null) {
generatedWhoCreated = new GeneratedWhoCreated(currentUserProvider);
generatedWhoModified = new GeneratedWhoModified(currentUserProvider);
@@ -44,7 +53,11 @@ public class GeneratedPropertyFactory {
numberTypes.add(BigDecimal.class.getName());
}
private boolean isNumberType(String typeClassName) {
public ClassLoadConfig getClassLoadConfig() {
return classLoadConfig;
}
private boolean isNumberType(String typeClassName) {
return numberTypes.contains(typeClassName);
}
@@ -9,7 +9,7 @@ import java.util.Map;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
@@ -21,25 +21,25 @@ public class InsertTimestampFactory {
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
public InsertTimestampFactory() {
public InsertTimestampFactory(ClassLoadConfig classLoadConfig) {
map.put(Timestamp.class, new GeneratedInsertTimestamp());
map.put(java.util.Date.class, new GeneratedInsertDate());
map.put(Long.class, longTime);
map.put(long.class, longTime);
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
if (classLoadConfig.isJavaTimePresent()) {
map.put(LocalDateTime.class, new GeneratedInsertJavaTime.LocalDT());
map.put(OffsetDateTime.class, new GeneratedInsertJavaTime.OffsetDT());
map.put(ZonedDateTime.class, new GeneratedInsertJavaTime.ZonedDT());
}
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
if (classLoadConfig.isJodaTimePresent()) {
map.put(org.joda.time.LocalDateTime.class, new GeneratedInsertJodaTime.LocalDT());
map.put(org.joda.time.DateTime.class, new GeneratedInsertJodaTime.DateTimeDT());
}
}
public void setInsertTimestamp(DeployBeanProperty property) {
public void setInsertTimestamp(DeployBeanProperty property) {
property.setGeneratedProperty(createInsertTimestamp(property));
}
@@ -9,7 +9,7 @@ import java.util.Map;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
@@ -21,18 +21,18 @@ public class UpdateTimestampFactory {
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
public UpdateTimestampFactory() {
public UpdateTimestampFactory(ClassLoadConfig classLoadConfig) {
map.put(Timestamp.class, new GeneratedUpdateTimestamp());
map.put(java.util.Date.class, new GeneratedUpdateDate());
map.put(Long.class, longTime);
map.put(long.class, longTime);
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
if (classLoadConfig.isJavaTimePresent()) {
map.put(LocalDateTime.class, new GeneratedUpdateJavaTime.LocalDT());
map.put(OffsetDateTime.class, new GeneratedUpdateJavaTime.OffsetDT());
map.put(ZonedDateTime.class, new GeneratedUpdateJavaTime.ZonedDT());
}
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
if (classLoadConfig.isJodaTimePresent()) {
map.put(org.joda.time.LocalDateTime.class, new GeneratedUpdateJodaTime.LocalDT());
map.put(org.joda.time.DateTime.class, new GeneratedUpdateJodaTime.DateTimeDT());
}
@@ -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;
@@ -183,10 +186,13 @@ public class DeployBeanProperty {
private boolean excludedFromHistory;
private boolean draft;
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;
this.propertyType = propertyType;
@@ -215,16 +221,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;
}
@@ -643,6 +658,7 @@ public class DeployBeanProperty {
return false;
}
}
/**
* Return true if this property is based on a secondary table.
*/
@@ -839,6 +855,15 @@ public class DeployBeanProperty {
this.excludedFromHistory = true;
}
public void setDraft() {
this.draft = true;
this.isTransient = true;
}
public boolean isDraft() {
return draft;
}
public void setDraftOnly() {
this.draftOnly = true;
}
@@ -863,4 +888,14 @@ public class DeployBeanProperty {
public boolean isDraftReset() {
return draftReset;
}
public void setSoftDelete() {
this.softDelete = true;
this.nullable = false;
}
public boolean isSoftDelete() {
return softDelete;
}
}
@@ -28,6 +28,8 @@ public class DeployBeanPropertyLists {
private BeanProperty versionProperty;
private BeanProperty draft;
private BeanProperty draftDirty;
private final BeanDescriptor<?> desc;
@@ -136,6 +138,9 @@ public class DeployBeanPropertyLists {
private void allocateToList(BeanProperty prop) {
if (prop.isTransient()) {
transients.add(prop);
if (prop.isDraft()) {
draft = prop;
}
return;
}
if (prop.isId()) {
@@ -293,6 +298,20 @@ public class DeployBeanPropertyLists {
return draftDirty;
}
public BeanProperty getDraft() {
return draft;
}
public BeanProperty getSoftDeleteProperty() {
for (BeanProperty prop: nonManys) {
if (prop.isSoftDelete()) {
return prop;
}
}
return null;
}
/**
* Mode used to determine which BeanPropertyAssoc to include.
*/
@@ -149,6 +149,9 @@ public class AnnotationFields extends AnnotationParser {
util.setLobType(prop);
}
if (get(prop, Draft.class) != null) {
prop.setDraft();
}
if (get(prop, DraftOnly.class) != null) {
prop.setDraftOnly();
}
@@ -158,6 +161,10 @@ public class AnnotationFields extends AnnotationParser {
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) {
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.deploy.parse;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
@@ -37,8 +36,8 @@ public class ReadAnnotations {
this.generatedPropFactory = generatedPropFactory;
this.asOfViewSuffix = asOfViewSuffix;
this.versionsBetweenSuffix = versionsBetweenSuffix;
this.javaxValidationAnnotations = ClassUtil.isJavaxValidationAnnotationsPresent();
this.jacksonAnnotations = ClassUtil.isJacksonAnnotationsPresent();
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
}
/**
@@ -234,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();
@@ -171,10 +171,14 @@ public class DataSourcePool implements DataSource {
private final Runnable heartbeatRunnable = new HeartBeatRunnable();
public DataSourcePool(DataSourceAlert notify, String name, DataSourceConfig params) {
this(notify, name, params, null);
}
public DataSourcePool(DataSourceAlert notify, String name, DataSourceConfig params, DataSourcePoolListener listener) {
this.notify = notify;
this.name = name;
this.poolListener = createPoolListener(params.getPoolListener());
this.poolListener = listener;
this.autoCommit = params.isAutoCommit();
this.transactionIsolation = params.getIsolationLevel();
@@ -238,30 +242,8 @@ public class DataSourcePool implements DataSource {
throw new SQLFeatureNotSupportedException("We do not support java.util.logging");
}
/**
* Create the DataSourcePoolListener if there is one.
*/
private DataSourcePoolListener createPoolListener(String cn) {
if (cn == null) {
return null;
}
try {
return (DataSourcePoolListener) ClassUtil.newInstance(cn, this.getClass());
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
private void initialise() throws SQLException {
// Ensure database driver is loaded
try {
ClassUtil.forName(this.databaseDriver, this.getClass());
} catch (Throwable e) {
throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: "
+ e.getMessage(), e);
}
String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation);
//noinspection StringBufferReplaceableByString
StringBuilder sb = new StringBuilder(70);
@@ -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>();
}
@@ -33,7 +33,6 @@ import com.avaje.ebeaninternal.server.deploy.ManyType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.Collection;
@@ -60,8 +59,6 @@ import java.util.Set;
*/
public final class DefaultPersister implements Persister {
private static final Logger SUM = LoggerFactory.getLogger("org.avaje.ebean.SUM");
private static final Logger PUB = LoggerFactory.getLogger("org.avaje.ebean.PUB");
private static final Logger logger = LoggerFactory.getLogger(DefaultPersister.class);
@@ -337,9 +334,10 @@ public final class DefaultPersister implements Persister {
/**
* 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));
}
/**
@@ -356,6 +354,7 @@ public final class DefaultPersister implements Persister {
PersistRequestBean<?> req = createRequest(entityBean, t, PersistRequest.Type.UPDATE);
req.setDeleteMissingChildren(deleteMissingChildren);
req.checkDraft();
try {
req.initTransIfRequiredWithBatchCascade();
if (req.isReference()) {
@@ -500,23 +499,34 @@ 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 boolean delete(EntityBean bean, Transaction t) {
public boolean delete(EntityBean bean, Transaction t, boolean permanent) {
PersistRequestBean<EntityBean> request = createRequest(bean, t, Type.DELETE);
boolean deleted = deleteRequest(request);
Type deleteType = permanent ? Type.DELETE_PERMANENT : Type.DELETE;
PersistRequestBean<EntityBean> originalRequest = createRequest(bean, t, deleteType);
if (request.isDraftable()) {
// we have just deleting a draft bean so now we need to delete the
// associated 'live' bean. This is effectively an 'automatic publish'.
deleteRequest(createRequest(request.createReference(), t, Type.DELETE, true));
if (originalRequest.isHardDeleteDraft()) {
// a hard delete of a draftable bean so first we need to delete the associated 'live' bean
// due to FK constraint and then after that execute the original delete of the draft bean
return deleteRequest(createPublishRequest(originalRequest.createReference(), t, Type.DELETE_PERMANENT, true), originalRequest);
} else {
// normal delete or soft delete
return deleteRequest(originalRequest);
}
return deleted;
}
/**
* Execute the delete request returning true if a delete occurred.
*/
private boolean deleteRequest(PersistRequestBean<?> req) {
return deleteRequest(req, null);
}
/**
* Execute the delete request support a second delete request for live and draft permanent delete.
* A common transaction is used across both requests.
*/
private boolean deleteRequest(PersistRequestBean<?> req, PersistRequestBean<?> draftReq) {
if (req.isRegisteredForDeleteBean()) {
// skip deleting bean. Used where cascade is on
@@ -530,6 +540,11 @@ public final class DefaultPersister implements Persister {
try {
req.initTransIfRequiredWithBatchCascade();
boolean deleted = delete(req);
if (draftReq != null) {
// delete the 'draft' bean ('live' bean deleted first)
draftReq.setTrans(req.getTransaction());
deleted = delete(draftReq);
}
req.commitTransIfRequired();
req.flushBatchOnCascade();
@@ -541,10 +556,10 @@ public final class DefaultPersister implements Persister {
}
}
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);
}
}
@@ -565,7 +580,7 @@ public final class DefaultPersister implements Persister {
idList.add(descriptor.convertId(id));
}
delete(descriptor, null, idList, transaction);
delete(descriptor, null, idList, transaction, descriptor.isSoftDelete());
}
/**
@@ -577,13 +592,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()) {
@@ -592,14 +607,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 {
@@ -611,7 +626,7 @@ public final class DefaultPersister implements Persister {
if (bean == null) {
return 0;
} else {
delete(bean, t);
deleteRecurse(bean, t, softDelete);
return 1;
}
}
@@ -623,12 +638,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);
}
}
}
@@ -636,32 +654,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);
@@ -695,7 +718,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);
@@ -704,6 +727,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;
}
@@ -924,7 +951,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()));
}
}
}
@@ -1031,7 +1058,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);
@@ -1175,7 +1202,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);
}
}
@@ -1206,6 +1233,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) {
@@ -1213,16 +1241,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) {
@@ -1235,30 +1266,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);
}
}
@@ -1275,23 +1310,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);
}
}
}
}
@@ -1302,7 +1339,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
@@ -1310,11 +1347,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);
}
}
@@ -1375,9 +1412,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)) {
@@ -1385,7 +1420,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());
}
}
}
@@ -1426,18 +1461,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);
}
/**
@@ -1471,6 +1513,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);
}
@@ -84,7 +84,7 @@ public final class InsertMeta {
this.selectLastInsertedId = desc.getSelectLastInsertedId();
}
this.sqlNullId = genSql(true, tableName, false);
this.sqlDraftNullId = desc.isDraftable() ? genSql(false, draftTableName, true) : sqlNullId;
this.sqlDraftNullId = desc.isDraftable() ? genSql(true, draftTableName, true) : sqlNullId;
}
}
@@ -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);
}
@@ -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>
@@ -301,6 +301,9 @@ public class SqlTreeNodeBean implements SqlTreeNode {
if (readId && !temporalVersions) {
createListProxies(localDesc, ctx, localBean);
}
if (temporalMode == SpiQuery.TemporalMode.DRAFT) {
localDesc.setDraft(localBean);
}
localDesc.postLoad(localBean);
EntityBeanIntercept ebi = localBean._ebean_getIntercept();
@@ -480,6 +483,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.
*/
@@ -180,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;
@@ -287,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.
@@ -317,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.
*/
@@ -660,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;
}
@@ -183,6 +183,7 @@ public class BeanPersistIds implements Serializable {
addUpdateId(id);
break;
case DELETE:
case SOFT_DELETE:
addDeleteId(id);
break;
@@ -162,13 +162,13 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
this.typeMap = new ConcurrentHashMap<Class<?>, ScalarType<?>>();
this.nativeMap = new ConcurrentHashMap<Integer, ScalarType<?>>();
this.objectMapperPresent = ClassUtil.isJacksonObjectMapperPresent();
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.extraTypeFactory = new DefaultTypeFactory(config);
initialiseStandard(jsonDateTime, config);
initialiseJavaTimeTypes(jsonDateTime, config);
initialiseJodaTypes(jsonDateTime);
initialiseJodaTypes(jsonDateTime, config);
initialiseJacksonTypes(config);
if (isPostgres(config.getDatabasePlatform())) {
@@ -738,7 +738,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
*/
protected void initialiseJacksonTypes(ServerConfig config) {
if (ClassUtil.isPresent("com.fasterxml.jackson.databind.ObjectMapper", this.getClass())) {
if (config.getClassLoadConfig().isJacksonObjectMapperPresent()) {
logger.trace("Registering JsonNode type support");
@@ -765,7 +765,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
}
protected void initialiseJavaTimeTypes(JsonConfig.DateTime mode, ServerConfig config) {
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
if (config.getClassLoadConfig().isJavaTimePresent()) {
logger.debug("Registering java.time data types");
typeMap.put(java.time.LocalDate.class, new ScalarTypeLocalDate());
typeMap.put(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(mode));
@@ -796,10 +796,10 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
* Detect if Joda classes are in the classpath and if so register the Joda
* data types.
*/
protected void initialiseJodaTypes(JsonConfig.DateTime mode) {
protected void initialiseJodaTypes(JsonConfig.DateTime mode, ServerConfig config) {
// detect if Joda classes are in the classpath
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
if (config.getClassLoadConfig().isJodaTimePresent()) {
// Joda classes are in the classpath so register the types
logger.debug("Registering Joda data types");
typeMap.put(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(mode));
@@ -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();
}
@@ -75,7 +75,7 @@ public class ClassPathSearch implements ClassPathSearchService {
if (classPathReaderCN != null) {
// use a user defined classPathReader
logger.info("Using [" + classPathReaderCN + "] to read the searchable class path");
classPathReader = (ClassPathReader) ClassUtil.newInstance(classPathReaderCN, this.getClass());
classPathReader = (ClassPathReader) ClassUtil.newInstance(classPathReaderCN);
}
Object[] rawClassPaths = classPathReader.readPath(classLoader);
@@ -121,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();
@@ -667,6 +667,26 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
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
public int execute(SqlUpdate updSql, Transaction t) {
return 0;
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import org.junit.Test;
@@ -11,7 +12,7 @@ import static org.junit.Assert.*;
public class InsertTimestampFactoryTest {
InsertTimestampFactory factory = new InsertTimestampFactory();
InsertTimestampFactory factory = new InsertTimestampFactory(new ClassLoadConfig());
@Test
public void test_createdTimestamp_LocalDateTime() {
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import org.junit.Test;
@@ -12,7 +13,7 @@ import static org.junit.Assert.*;
public class UpdateTimestampFactoryTest {
UpdateTimestampFactory factory = new UpdateTimestampFactory();
UpdateTimestampFactory factory = new UpdateTimestampFactory(new ClassLoadConfig());
@Test
public void test_createdTimestamp_LocalDateTime() {
@@ -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,63 @@
package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class ESoftDelMid extends BaseSoftDelete {
@ManyToOne
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;
}
}
@@ -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("coalesce(t0.deleted,");
assertThat(generatedSql).contains("coalesce(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,28 @@
package com.avaje.tests.softdelete;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.softdelete.ESoftDelMid;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class TestSoftDeleteOptionalRelationship extends BaseTestCase {
@Test
public void testFindWhenNullRelationship() {
ESoftDelMid mid1 = new ESoftDelMid(null, "mid1");
Ebean.save(mid1);
ESoftDelMid bean = Ebean.find(ESoftDelMid.class)
.setId(mid1.getId())
.fetch("top")
.findUnique();
assertThat(bean).isNotNull();
assertThat(bean.getTop()).isNull();
}
}
@@ -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);
}
}