mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
56
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3400928798 | ||
|
|
4e665fd66f | ||
|
|
09cd2c12e2 | ||
|
|
e3e45929f0 | ||
|
|
2caeafaaa2 | ||
|
|
5cd21de174 | ||
|
|
78df595861 | ||
|
|
3c89a0537f | ||
|
|
282463c2e8 | ||
|
|
03bb7a4aaf | ||
|
|
e4fa031cd3 | ||
|
|
00d227c598 | ||
|
|
fb86cb91bc | ||
|
|
f11ee20b73 | ||
|
|
3665c5bfaf | ||
|
|
638446680c | ||
|
|
bd9bfa25be | ||
|
|
753d17a8d2 | ||
|
|
f5cfce3f44 | ||
|
|
1c6363e85c | ||
|
|
b8bcbd8af9 | ||
|
|
d70768277b | ||
|
|
b39b4a2e8c | ||
|
|
93b2f4035c | ||
|
|
31ee0c4582 | ||
|
|
6c2dad4851 | ||
|
|
ed190ede03 | ||
|
|
f252b80c2d | ||
|
|
2859ea657e | ||
|
|
664b1cbd75 | ||
|
|
8bc283fef4 | ||
|
|
7d85398139 | ||
|
|
f42be3d65a | ||
|
|
ff8e5af4e5 | ||
|
|
526d67eeb4 | ||
|
|
374fc8d702 | ||
|
|
9c6c59afab | ||
|
|
c3eae0d3a5 | ||
|
|
25ec73364e | ||
|
|
c7818e7f23 | ||
|
|
8318bbc4ca | ||
|
|
337646a6ce | ||
|
|
fa6ee93e5b | ||
|
|
0fe691112f | ||
|
|
6946ff07e0 | ||
|
|
7c3d052da0 | ||
|
|
aea7a7fd65 | ||
|
|
f39a684c8e | ||
|
|
3cb5854786 | ||
|
|
e8192723cc | ||
|
|
3787748eae | ||
|
|
0ad52cf6a7 | ||
|
|
51bee836d4 | ||
|
|
9193b6b971 | ||
|
|
e48b9fab0f | ||
|
|
a14ca24b33 |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.12.1</version>
|
||||
<version>6.13.5</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
|
||||
@@ -46,6 +46,7 @@ class DRawSqlParser {
|
||||
|
||||
private DRawSqlParser(String sqlString) {
|
||||
sqlString = sqlString.trim();
|
||||
sqlString = sqlString.replace('\n',' ');
|
||||
this.sql = sqlString;
|
||||
this.hasPlaceHolders = findAndRemovePlaceHolders();
|
||||
this.textParser = new SimpleTextParser(sqlString);
|
||||
@@ -120,8 +121,7 @@ class DRawSqlParser {
|
||||
// trim of distinct keyword
|
||||
String distinct = preWhereExprSql.substring(0, 9);
|
||||
if (!distinct.equalsIgnoreCase("distinct ")) {
|
||||
throw new RuntimeException("Expecting [" + preWhereExprSql
|
||||
+ "] to start with \"select distinct\"");
|
||||
throw new RuntimeException("Expecting [" + preWhereExprSql + "] to start with \"select distinct\"");
|
||||
}
|
||||
preWhereExprSql = preWhereExprSql.substring(9);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -1019,7 +1020,7 @@ public interface EbeanServer {
|
||||
SqlFutureList findFutureList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -1041,6 +1042,38 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
@@ -1091,14 +1124,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 +1142,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 +1310,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 +1529,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 +1581,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
|
||||
@@ -227,7 +245,7 @@ public interface ExpressionList<T> extends Serializable {
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -247,6 +265,38 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
|
||||
@@ -17,6 +17,33 @@ import java.util.concurrent.Future;
|
||||
* limit the result set.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <h4>Example: typical use including total row count</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // We want to find the first 50 new orders
|
||||
* // ... so we don't really need setFirstRow(0)
|
||||
*
|
||||
* PagedList<Order> pagedList
|
||||
* = ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .setFirstRow(0)
|
||||
* .setMaxRows(50)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // Optional: initiate the loading of the total
|
||||
* // row count in a background thread
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* // fetch and return the list in the foreground thread
|
||||
* List<Order> orders = pagedList.getList();
|
||||
*
|
||||
* // get the total row count (from the future)
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h4>Example: typical use including total row count</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -156,11 +183,15 @@ public interface PagedList<T> {
|
||||
|
||||
/**
|
||||
* Return the index position of this page. Zero based.
|
||||
* <p>
|
||||
* Note that if firstRows/maxRows is used rather than pageIndex/pageSize then
|
||||
* this always returns 0.
|
||||
* </p>
|
||||
*/
|
||||
int getPageIndex();
|
||||
|
||||
/**
|
||||
* Return the page size used for this query.
|
||||
* Return the page size used for this query. This is the same value as maxRows used by the query.
|
||||
*/
|
||||
int getPageSize();
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -814,7 +822,7 @@ public interface Query<T> extends Serializable {
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -859,6 +867,36 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Set a named bind parameter. Named parameters have a colon to prefix the name.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* A database table or column comment.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface DbComment {
|
||||
|
||||
/**
|
||||
* The database table or column comment.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
import com.avaje.ebean.dbmigration.migration.ForeignKey;
|
||||
import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
@@ -123,7 +124,11 @@ public class BaseTableDdl implements TableDdl {
|
||||
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
|
||||
}
|
||||
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
apply.newLine().append(")");
|
||||
addTableCommentInline(apply, createTable);
|
||||
apply.endOfStatement();
|
||||
|
||||
addComments(apply, createTable);
|
||||
|
||||
writeUniqueOneToOneConstraints(writer, createTable);
|
||||
|
||||
@@ -150,6 +155,37 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table and column comments (separate from the create table statement).
|
||||
*/
|
||||
private void addComments(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
if (!platformDdl.isInlineComments()) {
|
||||
String tableComment = createTable.getComment();
|
||||
if (!StringHelper.isNull(tableComment)) {
|
||||
platformDdl.addTableComment(apply, createTable.getName(), tableComment);
|
||||
}
|
||||
|
||||
List<Column> columns = createTable.getColumn();
|
||||
for (Column column : columns) {
|
||||
if (!StringHelper.isNull(column.getComment())) {
|
||||
platformDdl.addColumnComment(apply, createTable.getName(), column.getName(), column.getComment());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the table comment inline with the create table statement.
|
||||
*/
|
||||
private void addTableCommentInline(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
if (platformDdl.isInlineComments()) {
|
||||
String tableComment = createTable.getComment();
|
||||
if (!StringHelper.isNull(tableComment)) {
|
||||
platformDdl.inlineTableComment(apply, tableComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException {
|
||||
platformDdl.writeTableColumns(apply, columns, useIdentity);
|
||||
}
|
||||
|
||||
+5
-3
@@ -160,8 +160,10 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
Collection<MColumn> cols = table.getColumns().values();
|
||||
for (MColumn column : cols) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
if (!column.isDraftOnly()) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
}
|
||||
}
|
||||
writeColumnDefinition(apply, sysPeriodStart, sysPeriodType);
|
||||
apply.append(",").newLine();
|
||||
@@ -232,7 +234,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MS SQL Server platform specific DDL.
|
||||
*/
|
||||
@@ -15,6 +18,7 @@ public class MsSqlServerDdl extends PlatformDdl {
|
||||
this.foreignKeyRestrict = "";
|
||||
this.inlineUniqueOneToOne = false;
|
||||
this.columnSetDefault = "add default";
|
||||
this.dropConstraintIfExists = "drop constraint";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,4 +87,20 @@ public class MsSqlServerDdl extends PlatformDdl {
|
||||
// can't alter itself - done in alterColumnBaseAttributes()
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table comment as a separate statement (from the create table statement).
|
||||
*/
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
|
||||
// do nothing for MS SQL Server (cause it requires stored procedures etc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column comment as a separate statement.
|
||||
*/
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
|
||||
// do nothing for MS SQL Server (cause it requires stored procedures etc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.Column;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MySql specific DDL.
|
||||
@@ -14,6 +19,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
this.alterColumn = "modify";
|
||||
this.dropUniqueConstraint = "drop index";
|
||||
this.historyDdl = new MySqlHistoryDdl();
|
||||
this.inlineComments = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,4 +70,26 @@ public class MySqlDdl extends PlatformDdl {
|
||||
// use modify
|
||||
return "alter table " + tableName + " modify " + columnName + " " + type + notnullClause;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeColumnDefinition(DdlBuffer buffer, Column column, boolean useIdentity) throws IOException {
|
||||
super.writeColumnDefinition(buffer, column, useIdentity);
|
||||
String comment = column.getComment();
|
||||
if (!StringHelper.isNull(comment)) {
|
||||
// in mysql 5.5 column comment save in information_schema.COLUMNS.COLUMN_COMMENT(VARCHAR 1024)
|
||||
if (comment.length() > 500) {
|
||||
comment = comment.substring(0, 500);
|
||||
}
|
||||
buffer.append(String.format(" comment '%s'", comment));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void inlineTableComment(DdlBuffer apply, String tableComment) throws IOException {
|
||||
if (tableComment.length() > 1000) {
|
||||
tableComment = tableComment.substring(0, 1000);
|
||||
}
|
||||
apply.append(" comment='").append(tableComment).append("'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ public class PlatformDdl {
|
||||
*/
|
||||
private final DbIdentity dbIdentity;
|
||||
|
||||
/**
|
||||
* Set to true if table and column comments are included inline with the create statements.
|
||||
*/
|
||||
protected boolean inlineComments;
|
||||
|
||||
/**
|
||||
* Default assumes if exists is supported.
|
||||
*/
|
||||
@@ -53,6 +58,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 ";
|
||||
@@ -114,6 +121,13 @@ public class PlatformDdl {
|
||||
return columnDefn + identitySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table and column comments are included inline.
|
||||
*/
|
||||
public boolean isInlineComments() {
|
||||
return inlineComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the table columns converting to platform types as necessary.
|
||||
*/
|
||||
@@ -150,7 +164,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,4 +398,27 @@ public class PlatformDdl {
|
||||
protected boolean isTrue(Boolean value) {
|
||||
return Boolean.TRUE.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an inline table comment to the create table statement.
|
||||
*/
|
||||
public void inlineTableComment(DdlBuffer apply, String tableComment) throws IOException {
|
||||
// do nothing by default (MySql only)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table comment as a separate statement (from the create table statement).
|
||||
*/
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
|
||||
apply.append(String.format("comment on table %s is '%s'", tableName, tableComment)).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column comment as a separate statement.
|
||||
*/
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
|
||||
apply.append(String.format("comment on column %s.%s is '%s'", table, column, comment)).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public class PostgresDdl extends PlatformDdl {
|
||||
this.historyDdl = new PostgresHistoryDdl();
|
||||
this.dropTableCascade = " cascade";
|
||||
this.columnSetType = "type ";
|
||||
this.alterTableIfExists = "if exists ";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ public class MColumn {
|
||||
private String references;
|
||||
private String foreignKeyName;
|
||||
private String foreignKeyIndex;
|
||||
private String comment;
|
||||
|
||||
private boolean historyExclude;
|
||||
private boolean notnull;
|
||||
@@ -43,6 +44,7 @@ public class MColumn {
|
||||
this.checkConstraint = column.getCheckConstraint();
|
||||
this.checkConstraintName = column.getCheckConstraintName();
|
||||
this.defaultValue = column.getDefaultValue();
|
||||
this.comment = column.getComment();
|
||||
this.references = column.getReferences();
|
||||
this.foreignKeyName = column.getForeignKeyName();
|
||||
this.foreignKeyIndex = column.getForeignKeyIndex();
|
||||
@@ -76,6 +78,7 @@ public class MColumn {
|
||||
copy.checkConstraintName = checkConstraintName;
|
||||
copy.defaultValue = defaultValue;
|
||||
copy.references = references;
|
||||
copy.comment = comment;
|
||||
copy.foreignKeyName = foreignKeyName;
|
||||
copy.foreignKeyIndex = foreignKeyIndex;
|
||||
copy.historyExclude = historyExclude;
|
||||
@@ -198,6 +201,19 @@ public class MColumn {
|
||||
return uniqueOneToOne;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column comment.
|
||||
*/
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column comment.
|
||||
*/
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the draftOnly status for this column.
|
||||
@@ -213,6 +229,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();
|
||||
@@ -230,6 +253,7 @@ public class MColumn {
|
||||
c.setForeignKeyName(foreignKeyName);
|
||||
c.setForeignKeyIndex(foreignKeyIndex);
|
||||
c.setDefaultValue(defaultValue);
|
||||
c.setComment(comment);
|
||||
c.setUnique(unique);
|
||||
c.setUniqueOneToOne(uniqueOneToOne);
|
||||
|
||||
|
||||
@@ -325,6 +325,10 @@ public class MTable {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getTablespace() {
|
||||
return tablespace;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
|
||||
}
|
||||
|
||||
MTable table = new MTable(descriptor.getBaseTable());
|
||||
table.setComment(descriptor.getDbComment());
|
||||
if (descriptor.isHistorySupport()) {
|
||||
table.setWithHistory(true);
|
||||
BeanProperty whenCreated = descriptor.findWhenCreatedProperty();
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public class ModelBuildIntersectionTable {
|
||||
buildFkConstraints();
|
||||
|
||||
if (manyProp.getTargetDescriptor().isDraftable()) {
|
||||
ctx.createDraft(intersectionTable);
|
||||
ctx.createDraft(intersectionTable, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
}
|
||||
|
||||
MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p));
|
||||
col.setComment(p.getDbComment());
|
||||
col.setDraftOnly(p.isDraftOnly());
|
||||
|
||||
if (p.isId()) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -401,8 +401,10 @@ public class DefaultBeanLoader {
|
||||
ebi.setPersistenceContext(pc);
|
||||
}
|
||||
|
||||
boolean draft = desc.isDraftInstance(bean);
|
||||
|
||||
if (embeddedOwnerIndex == -1) {
|
||||
if (SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
|
||||
if (!draft && SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
|
||||
// lazy loading and the bean cache is active
|
||||
if (desc.cacheBeanLoad(bean, ebi, id)) {
|
||||
return;
|
||||
@@ -415,10 +417,12 @@ public class DefaultBeanLoader {
|
||||
|
||||
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(desc.getBeanType());
|
||||
query.setLazyLoadProperty(ebi.getLazyLoadProperty());
|
||||
if (draft) {
|
||||
query.asDraft();
|
||||
}
|
||||
|
||||
if (embeddedOwnerIndex > -1) {
|
||||
String embeddedBeanPropertyName = ebi.getProperty(embeddedOwnerIndex);
|
||||
query.select("id," + embeddedBeanPropertyName);
|
||||
query.select("id," + ebi.getProperty(embeddedOwnerIndex));
|
||||
}
|
||||
|
||||
// don't collect AutoTune usage profiling information
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1380,6 +1384,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return new LimitOffsetPagedList<T>(this, (SpiQuery<T>)query, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction) {
|
||||
|
||||
SpiQuery<T> spiQuery = (SpiQuery<T>)query;
|
||||
int maxRows = spiQuery.getMaxRows();
|
||||
if (maxRows == 0) {
|
||||
throw new PersistenceException("maxRows must be specified for findPagedList() query");
|
||||
}
|
||||
|
||||
return new LimitOffsetPagedList<T>(this, spiQuery);
|
||||
}
|
||||
|
||||
public <T> void findEach(Query<T> query, QueryEachConsumer<T> consumer, Transaction t) {
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
|
||||
@@ -1449,17 +1465,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 +1889,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 +1918,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 +1926,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 +1942,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.isLiveInstance(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.isLiveInstance(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,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
intercept.setLoadedProperty(i);
|
||||
}
|
||||
beanDescriptor.setEmbeddedOwner(entityBean);
|
||||
if (!publish) {
|
||||
beanDescriptor.setDraft(entityBean);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
@@ -801,4 +866,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,8 +150,16 @@ 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;
|
||||
|
||||
/**
|
||||
* DB table comment.
|
||||
*/
|
||||
private final String dbComment;
|
||||
|
||||
/**
|
||||
* Set to true if read auditing is on for this bean type.
|
||||
*/
|
||||
@@ -160,6 +169,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
|
||||
private final boolean draftableElement;
|
||||
|
||||
private final BeanProperty draft;
|
||||
|
||||
private final BeanProperty draftDirty;
|
||||
|
||||
/**
|
||||
@@ -316,8 +327,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;
|
||||
|
||||
@@ -390,13 +402,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
this.baseTable = InternString.intern(deploy.getBaseTable());
|
||||
this.baseTableAsOf = deploy.getBaseTableAsOf();
|
||||
this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween();
|
||||
this.dbComment = deploy.getDbComment();
|
||||
this.autoTunable = EntityType.ORM.equals(entityType) && (beanFinder == null);
|
||||
|
||||
// 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 +516,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 +644,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 +700,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 +742,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 +761,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 +779,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 +1209,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.
|
||||
*/
|
||||
@@ -1898,6 +1942,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return EntityType.SQL.equals(entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB comment for the base table.
|
||||
*/
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base table. Only properties mapped to the base table are by
|
||||
* default persisted.
|
||||
@@ -1932,6 +1983,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 +2013,38 @@ 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 (not 'live').
|
||||
*/
|
||||
public boolean isDraftInstance(EntityBean entityBean) {
|
||||
if (draft != null) {
|
||||
return Boolean.TRUE == draft.getValue(entityBean);
|
||||
}
|
||||
// no draft property - so return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bean is draftable and considered a 'live' instance.
|
||||
*/
|
||||
public boolean isLiveInstance(EntityBean entityBean) {
|
||||
if (draft != null) {
|
||||
return Boolean.FALSE == draft.getValue(entityBean);
|
||||
}
|
||||
// no draft property - so return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -210,6 +211,11 @@ public class BeanProperty implements ElPropertyValue {
|
||||
*/
|
||||
final String dbColumnDefn;
|
||||
|
||||
/**
|
||||
* Database DDL column comment.
|
||||
*/
|
||||
final String dbComment;
|
||||
|
||||
/**
|
||||
* DB Constraint (typically check constraint on enum)
|
||||
*/
|
||||
@@ -223,12 +229,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 +269,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());
|
||||
@@ -289,6 +303,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.setter = deploy.getSetter();
|
||||
|
||||
this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null);
|
||||
this.dbComment = deploy.getDbComment();
|
||||
this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin());
|
||||
this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect());
|
||||
this.formula = sqlFormulaSelect != null;
|
||||
@@ -302,6 +317,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 +367,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;
|
||||
@@ -354,6 +383,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.secondaryTableJoin = source.secondaryTableJoin;
|
||||
this.secondaryTableJoinPrefix = source.secondaryTableJoinPrefix;
|
||||
|
||||
this.dbComment = source.dbComment;
|
||||
this.dbBind = source.getDbBind();
|
||||
this.dbEncrypted = source.isDbEncrypted();
|
||||
this.dbEncryptedType = source.getDbEncryptedType();
|
||||
@@ -616,6 +646,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.
|
||||
@@ -952,6 +1006,13 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the comment for the associated DB column.
|
||||
*/
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database jdbc data type this is mapped to.
|
||||
*/
|
||||
@@ -1041,6 +1102,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 +1125,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());
|
||||
}
|
||||
|
||||
+2
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -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));
|
||||
}
|
||||
|
||||
+4
-4
@@ -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());
|
||||
}
|
||||
|
||||
@@ -165,6 +165,9 @@ public class DeployBeanDescriptor<T> {
|
||||
|
||||
private ChangeLogFilter changeLogFilter;
|
||||
|
||||
private String dbComment;
|
||||
|
||||
|
||||
/**
|
||||
* Construct the BeanDescriptor.
|
||||
*/
|
||||
@@ -207,6 +210,14 @@ public class DeployBeanDescriptor<T> {
|
||||
return readAuditing;
|
||||
}
|
||||
|
||||
public void setDbComment(String dbComment) {
|
||||
this.dbComment = dbComment;
|
||||
}
|
||||
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
|
||||
public void setDraftable() {
|
||||
draftable = true;
|
||||
}
|
||||
|
||||
@@ -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,15 @@ public class DeployBeanProperty {
|
||||
|
||||
private boolean excludedFromHistory;
|
||||
|
||||
private boolean draft;
|
||||
private boolean draftOnly;
|
||||
private boolean draftDirty;
|
||||
private boolean draftReset;
|
||||
|
||||
private boolean softDelete;
|
||||
|
||||
private String dbComment;
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
@@ -215,16 +223,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 +660,7 @@ public class DeployBeanProperty {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is based on a secondary table.
|
||||
*/
|
||||
@@ -839,6 +857,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 +890,21 @@ public class DeployBeanProperty {
|
||||
public boolean isDraftReset() {
|
||||
return draftReset;
|
||||
}
|
||||
|
||||
public void setSoftDelete() {
|
||||
this.softDelete = true;
|
||||
this.nullable = false;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
public void setDbComment(String dbComment) {
|
||||
this.dbComment = dbComment;
|
||||
}
|
||||
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.parse;
|
||||
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.NamedQueries;
|
||||
@@ -9,6 +11,7 @@ import javax.persistence.UniqueConstraint;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
import com.avaje.ebean.annotation.DbComment;
|
||||
import com.avaje.ebean.annotation.Draftable;
|
||||
import com.avaje.ebean.annotation.DraftableElement;
|
||||
import com.avaje.ebean.annotation.EntityConcurrencyMode;
|
||||
@@ -25,22 +28,60 @@ import com.avaje.ebeaninternal.server.deploy.CompoundUniqueConstraint;
|
||||
import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
|
||||
import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Read the class level deployment annotations.
|
||||
*/
|
||||
public class AnnotationClass extends AnnotationParser {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnnotationClass.class);
|
||||
|
||||
private final String asOfViewSuffix;
|
||||
|
||||
private final String versionsBetweenSuffix;
|
||||
|
||||
/**
|
||||
* Create for normal early parse of class level annotations.
|
||||
*/
|
||||
public AnnotationClass(DeployBeanInfo<?> info, boolean validationAnnotations, String asOfViewSuffix, String versionsBetweenSuffix) {
|
||||
super(info, validationAnnotations);
|
||||
this.asOfViewSuffix = asOfViewSuffix;
|
||||
this.versionsBetweenSuffix = versionsBetweenSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create to parse AttributeOverride annotations which is run last
|
||||
* after all the properties/fields have been parsed fully.
|
||||
*/
|
||||
public AnnotationClass(DeployBeanInfo<?> info) {
|
||||
super(info, false);
|
||||
this.asOfViewSuffix = null;
|
||||
this.versionsBetweenSuffix = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse any AttributeOverride set on the class.
|
||||
*/
|
||||
public void parseAttributeOverride() {
|
||||
|
||||
Class<?> cls = descriptor.getBeanType();
|
||||
AttributeOverride override = cls.getAnnotation(AttributeOverride.class);
|
||||
if (override != null) {
|
||||
String propertyName = override.name();
|
||||
Column column = override.column();
|
||||
if (column != null) {
|
||||
DeployBeanProperty beanProperty = descriptor.getBeanProperty(propertyName);
|
||||
if (beanProperty == null) {
|
||||
logger.error("AttributeOverride property [" + propertyName + "] not found on " + descriptor.getFullName());
|
||||
} else {
|
||||
readColumn(column, beanProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the class level deployment annotations.
|
||||
*/
|
||||
@@ -120,6 +161,11 @@ public class AnnotationClass extends AnnotationParser {
|
||||
descriptor.setHistorySupport();
|
||||
}
|
||||
|
||||
DbComment comment = cls.getAnnotation(DbComment.class);
|
||||
if (comment != null) {
|
||||
descriptor.setDbComment(comment.value());
|
||||
}
|
||||
|
||||
UpdateMode updateMode = cls.getAnnotation(UpdateMode.class);
|
||||
if (updateMode != null) {
|
||||
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
|
||||
|
||||
@@ -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,7 +161,15 @@ public class AnnotationFields extends AnnotationParser {
|
||||
if (get(prop, DraftReset.class) != null) {
|
||||
prop.setDraftReset();
|
||||
}
|
||||
SoftDelete softDelete = get(prop, SoftDelete.class);
|
||||
if (softDelete != null) {
|
||||
prop.setSoftDelete();
|
||||
}
|
||||
|
||||
DbComment comment = get(prop, DbComment.class);
|
||||
if (comment != null) {
|
||||
prop.setDbComment(comment.value());
|
||||
}
|
||||
DbJson dbJson = get(prop, DbJson.class);
|
||||
if (dbJson != null) {
|
||||
util.setDbJsonType(prop, dbJson);
|
||||
@@ -449,32 +460,5 @@ public class AnnotationFields extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void readColumn(Column columnAnn, DeployBeanProperty prop) {
|
||||
|
||||
if (!isEmpty(columnAnn.name())) {
|
||||
String dbColumn = databasePlatform.convertQuotedIdentifiers(columnAnn.name());
|
||||
prop.setDbColumn(dbColumn);
|
||||
}
|
||||
|
||||
prop.setDbInsertable(columnAnn.insertable());
|
||||
prop.setDbUpdateable(columnAnn.updatable());
|
||||
prop.setNullable(columnAnn.nullable());
|
||||
prop.setUnique(columnAnn.unique());
|
||||
if (columnAnn.precision() > 0) {
|
||||
prop.setDbLength(columnAnn.precision());
|
||||
} else if (columnAnn.length() != 255) {
|
||||
// set default 255 on DbTypeMap
|
||||
prop.setDbLength(columnAnn.length());
|
||||
}
|
||||
prop.setDbScale(columnAnn.scale());
|
||||
prop.setDbColumnDefn(columnAnn.columnDefinition());
|
||||
|
||||
String baseTable = descriptor.getBaseTable();
|
||||
String tableName = columnAnn.table();
|
||||
if (!"".equals(tableName) && !tableName.equalsIgnoreCase(baseTable)) {
|
||||
// its on a secondary table...
|
||||
prop.setSecondaryTable(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import java.util.HashMap;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
|
||||
|
||||
/**
|
||||
@@ -66,4 +68,31 @@ public abstract class AnnotationParser extends AnnotationBase {
|
||||
|
||||
}
|
||||
|
||||
protected void readColumn(Column columnAnn, DeployBeanProperty prop) {
|
||||
|
||||
if (!isEmpty(columnAnn.name())) {
|
||||
String dbColumn = databasePlatform.convertQuotedIdentifiers(columnAnn.name());
|
||||
prop.setDbColumn(dbColumn);
|
||||
}
|
||||
|
||||
prop.setDbInsertable(columnAnn.insertable());
|
||||
prop.setDbUpdateable(columnAnn.updatable());
|
||||
prop.setNullable(columnAnn.nullable());
|
||||
prop.setUnique(columnAnn.unique());
|
||||
if (columnAnn.precision() > 0) {
|
||||
prop.setDbLength(columnAnn.precision());
|
||||
} else if (columnAnn.length() != 255) {
|
||||
// set default 255 on DbTypeMap
|
||||
prop.setDbLength(columnAnn.length());
|
||||
}
|
||||
prop.setDbScale(columnAnn.scale());
|
||||
prop.setDbColumnDefn(columnAnn.columnDefinition());
|
||||
|
||||
String baseTable = descriptor.getBaseTable();
|
||||
String tableName = columnAnn.table();
|
||||
if (!"".equals(tableName) && !tableName.equalsIgnoreCase(baseTable)) {
|
||||
// its on a secondary table...
|
||||
prop.setSecondaryTable(tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,6 +79,8 @@ public class ReadAnnotations {
|
||||
// dependent on field level annotations
|
||||
new AnnotationSql(info, javaxValidationAnnotations).parse();
|
||||
|
||||
new AnnotationClass(info).parseAttributeOverride();
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
throw new RuntimeException("Error reading annotations for " + info, e);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -304,6 +314,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
return exprList.findPagedList(pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<T> findPagedList() {
|
||||
return exprList.findPagedList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findRowCount() {
|
||||
return exprList.findRowCount();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
|
||||
private final SpiQuery<T> query;
|
||||
|
||||
private final int pageSize;
|
||||
private final int firstRow;
|
||||
|
||||
private final int maxRows;
|
||||
|
||||
private final int pageIndex;
|
||||
|
||||
@@ -33,11 +35,29 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
|
||||
private List<T> list;
|
||||
|
||||
/**
|
||||
* Construct with pageIndex/pageSize.
|
||||
*/
|
||||
public LimitOffsetPagedList(EbeanServer server, SpiQuery<T> query, int pageIndex, int pageSize) {
|
||||
this.server = server;
|
||||
this.query = query;
|
||||
this.pageSize = pageSize;
|
||||
this.maxRows = pageSize;
|
||||
this.firstRow = pageIndex * pageSize;
|
||||
this.pageIndex = pageIndex;
|
||||
|
||||
query.setFirstRow(firstRow);
|
||||
query.setMaxRows(pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with firstRow/maxRows.
|
||||
*/
|
||||
public LimitOffsetPagedList(EbeanServer server, SpiQuery<T> query) {
|
||||
this.server = server;
|
||||
this.query = query;
|
||||
this.maxRows = query.getMaxRows();
|
||||
this.firstRow = query.getFirstRow();
|
||||
this.pageIndex = 0;
|
||||
}
|
||||
|
||||
public void loadRowCount() {
|
||||
@@ -56,8 +76,6 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
public List<T> getList() {
|
||||
synchronized (monitor) {
|
||||
if (list == null) {
|
||||
query.setFirstRow(pageIndex * pageSize);
|
||||
query.setMaxRows(pageSize);
|
||||
list = server.findList(query, null);
|
||||
}
|
||||
return list;
|
||||
@@ -70,7 +88,7 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
if (rowCount == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return ((rowCount - 1) / pageSize) + 1;
|
||||
return ((rowCount - 1) / maxRows) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +112,11 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return pageIndex < (getTotalPageCount() - 1);
|
||||
return (firstRow + maxRows) < getTotalRowCount();
|
||||
}
|
||||
|
||||
public boolean hasPrev() {
|
||||
return pageIndex > 0;
|
||||
return firstRow > 0;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
@@ -106,13 +124,13 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
return maxRows;
|
||||
}
|
||||
|
||||
public String getDisplayXtoYofZ(String to, String of) {
|
||||
|
||||
int first = pageIndex * pageSize + 1;
|
||||
int last = first + getList().size() - 1;
|
||||
int first = firstRow + 1;
|
||||
int last = firstRow + getList().size();
|
||||
int total = getTotalRowCount();
|
||||
|
||||
return first + to + last + of + total;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1068,6 +1094,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return server.findPagedList(this, null, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<T> findPagedList() {
|
||||
return server.findPagedList(this, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an ordered bind parameter according to its position. Note that the position starts at 1 to
|
||||
* be consistent with JDBC PreparedStatement. You need to set a parameter value for each ? you
|
||||
|
||||
@@ -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())) {
|
||||
@@ -553,7 +553,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
maxValueLen = Math.max(maxValueLen, value.length());
|
||||
|
||||
Object enumValue = Enum.valueOf(enumType, name.trim());
|
||||
beanDbMap.add(enumValue, value.trim());
|
||||
beanDbMap.add(enumValue, value);
|
||||
}
|
||||
|
||||
if (dbColumnLength == 0 && !integerType) {
|
||||
@@ -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();
|
||||
@@ -191,6 +201,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return query.findPagedList(pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<T> findPagedList() {
|
||||
return query.findPagedList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findRowCount() {
|
||||
return query.findRowCount();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.RawSql.Sql;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class TestRawSqlBuilder extends BaseTestCase {
|
||||
|
||||
@@ -17,8 +19,27 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id", sql.getPreFrom());
|
||||
assertEquals("from t_cust", sql.getPreWhere());
|
||||
assertEquals("", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNewLineCharacters() {
|
||||
|
||||
RawSqlBuilder r = RawSqlBuilder.parse("select\n id from\n o_customer");
|
||||
Sql sql = r.getSql();
|
||||
|
||||
assertEquals("id", sql.getPreFrom());
|
||||
assertEquals("from o_customer", sql.getPreWhere());
|
||||
assertEquals("", sql.getPreHaving());
|
||||
assertNull(sql.getOrderBy());
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql = r.create();
|
||||
|
||||
Ebean.find(Customer.class)
|
||||
.setRawSql(rawSql)
|
||||
.findList();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -29,7 +50,7 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id", sql.getPreFrom());
|
||||
assertEquals("from t_cust where id > ?", sql.getPreWhere());
|
||||
assertEquals("", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,7 +108,7 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id, sum(x)", sql.getPreFrom());
|
||||
assertEquals("from t_cust", sql.getPreWhere());
|
||||
assertEquals("group by id having sum(x) > ?", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
assertEquals("order by", sql.getOrderByPrefix());
|
||||
|
||||
// no order by
|
||||
@@ -97,7 +118,7 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id, sum(x)", sql.getPreFrom());
|
||||
assertEquals("from t_cust where id > ?", sql.getPreWhere());
|
||||
assertEquals("group by id having sum(x) > ?", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
assertEquals("order by", sql.getOrderByPrefix());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.avaje.ebean.server.type;
|
||||
|
||||
import com.avaje.ebean.annotation.EnumValue;
|
||||
|
||||
/**
|
||||
* Enum test when DB CHAR column used with spaces.
|
||||
*/
|
||||
public enum MyDayOfWeek {
|
||||
|
||||
@EnumValue("MONDAY ") MONDAY,
|
||||
@EnumValue("TUESDAY ") TUESDAY,
|
||||
@EnumValue("WEDNESDAY") WEDNESDAY,
|
||||
@EnumValue("THURSDAY ") THURSDAY,
|
||||
@EnumValue("FRIDAY ") FRIDAY,
|
||||
@EnumValue("SATURDAY ") SATURDAY,
|
||||
@EnumValue("SUNDAY ") SUNDAY
|
||||
}
|
||||
@@ -1,34 +1,55 @@
|
||||
package com.avaje.ebean.server.type;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.H2Platform;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.type.CtCompoundType;
|
||||
import com.avaje.ebeaninternal.server.type.DefaultTypeManager;
|
||||
import com.avaje.ebeaninternal.server.type.RsetDataReader;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarDataReader;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
|
||||
import com.avaje.tests.model.ivo.CMoney;
|
||||
import com.avaje.tests.model.ivo.ExhangeCMoneyRate;
|
||||
import com.avaje.tests.model.ivo.Money;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestTypeManager extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testEnumWithChar() throws SQLException {
|
||||
|
||||
DefaultTypeManager typeManager = createTypeManager();
|
||||
|
||||
ScalarType<?> dayOfWeekType = typeManager.createEnumScalarType(MyDayOfWeek.class);
|
||||
|
||||
Object val = dayOfWeekType.read(new DummyDataReader("MONDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.MONDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("TUESDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.TUESDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("WEDNESDAY"));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.WEDNESDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("THURSDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.THURSDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("FRIDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.FRIDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ServerConfig serverConfig = new ServerConfig();
|
||||
serverConfig.setDatabasePlatform(new H2Platform());
|
||||
|
||||
BootupClasses bootupClasses = new BootupClasses();
|
||||
|
||||
DefaultTypeManager typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
|
||||
DefaultTypeManager typeManager = createTypeManager();
|
||||
|
||||
CheckImmutableResponse checkImmutable = typeManager.checkImmutable(Money.class);
|
||||
Assert.assertTrue(checkImmutable.isImmutable());
|
||||
@@ -50,4 +71,31 @@ public class TestTypeManager extends BaseTestCase {
|
||||
|
||||
}
|
||||
|
||||
private DefaultTypeManager createTypeManager() {
|
||||
|
||||
ServerConfig serverConfig = new ServerConfig();
|
||||
serverConfig.setDatabasePlatform(new H2Platform());
|
||||
|
||||
BootupClasses bootupClasses = new BootupClasses();
|
||||
|
||||
return new DefaultTypeManager(serverConfig, bootupClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test double DataReader implementation.
|
||||
*/
|
||||
private static class DummyDataReader extends RsetDataReader {
|
||||
|
||||
String val;
|
||||
|
||||
public DummyDataReader(String val) {
|
||||
super(null);
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString() throws SQLException {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,6 +512,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Set<T> findSet(Query<T> query, Transaction transaction) {
|
||||
return null;
|
||||
@@ -667,6 +672,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;
|
||||
|
||||
+2
-1
@@ -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() {
|
||||
|
||||
+2
-1
@@ -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() {
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.avaje.tests.draftable;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.tests.model.draftable.Doc;
|
||||
import com.avaje.tests.model.draftable.Link;
|
||||
import org.assertj.core.api.StrictAssertions;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DocLinkTest {
|
||||
|
||||
@Test
|
||||
public void testUpdate_whenNotPublished() {
|
||||
|
||||
Link link1 = new Link("update");
|
||||
StrictAssertions.assertThat(link1.isDraft()).isFalse();
|
||||
|
||||
link1.save();
|
||||
StrictAssertions.assertThat(link1.isDraft()).isTrue();
|
||||
|
||||
// perform stateless update
|
||||
Link linkUpdate = new Link();
|
||||
linkUpdate.setId(link1.getId());
|
||||
linkUpdate.setComment("stateless update");
|
||||
linkUpdate.setDraft(true);
|
||||
linkUpdate.update();
|
||||
|
||||
// invoke lazy loading on the updated bean
|
||||
// automatically set asDraft() on lazy loading query
|
||||
linkUpdate.getLocation();
|
||||
|
||||
Ebean.deletePermanent(linkUpdate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete_whenNotPublished() {
|
||||
|
||||
Link link1 = new Link("Ld1");
|
||||
StrictAssertions.assertThat(link1.isDraft()).isFalse();
|
||||
|
||||
link1.save();
|
||||
StrictAssertions.assertThat(link1.isDraft()).isTrue();
|
||||
|
||||
link1.setComment("some change");
|
||||
link1.save();
|
||||
|
||||
Ebean.delete(link1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeletePermanent_whenPublished2() {
|
||||
|
||||
Link link1 = new Link("Ld2");
|
||||
link1.save();
|
||||
Ebean.getDefaultServer().publish(Link.class, link1.getId());
|
||||
|
||||
Link link = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
Ebean.deletePermanent(link);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteLivePermanent_throwsException() {
|
||||
|
||||
Link link1 = new Link("Ld2");
|
||||
link1.save();
|
||||
|
||||
Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());
|
||||
|
||||
try {
|
||||
Ebean.deletePermanent(live);
|
||||
assertTrue("never get here",false);
|
||||
|
||||
} catch (PersistenceException e) {
|
||||
// assert nice message when trying to delete live bean
|
||||
assertThat(e.getMessage().contains("Explicit Delete is not allowed on a 'live' bean - only draft beans"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete_whenPublished() {
|
||||
|
||||
Link link1 = new Link("Ld2");
|
||||
link1.save();
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
server.publish(Link.class, link1.getId());
|
||||
|
||||
link1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
StrictAssertions.assertThat(link1.isDraft()).isTrue();
|
||||
|
||||
// this is a soft delete (no automatic publish here, only updates draft)
|
||||
link1.delete();
|
||||
|
||||
Link live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
|
||||
assertThat(live).isNotNull();
|
||||
StrictAssertions.assertThat(live.isDraft()).isFalse();
|
||||
StrictAssertions.assertThat(live.isDeleted()).isFalse(); // soft delete state not published yet
|
||||
|
||||
// this is a permanent delete (effectively has automatic publish)
|
||||
server.deletePermanent(link1);
|
||||
|
||||
live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
|
||||
assertThat(live).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLive_throwsException() {
|
||||
|
||||
Link link1 = new Link("forUpdateLive");
|
||||
link1.save();
|
||||
|
||||
Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());
|
||||
|
||||
live.setComment("foo");
|
||||
// Expect a nice
|
||||
try {
|
||||
live.save();
|
||||
assertTrue("Never get here",false);
|
||||
|
||||
} catch (PersistenceException e) {
|
||||
// we want to assert the message is nice and meaningful (and not a optimistic locking exception etc)
|
||||
assertThat(e.getMessage()).contains("Save or update is not allowed on a 'live' bean - only draft beans");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirtyState() {
|
||||
|
||||
Timestamp when = new Timestamp(System.currentTimeMillis());
|
||||
String comment = "Really interesting";
|
||||
|
||||
Link link1 = new Link("Ls1");
|
||||
link1.setComment(comment);
|
||||
link1.setWhenPublish(when);
|
||||
link1.save();
|
||||
|
||||
Link draft1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
StrictAssertions.assertThat(draft1.isDirty()).isTrue();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
Link linkLive = server.publish(Link.class, link1.getId(), null);
|
||||
StrictAssertions.assertThat(linkLive.getComment()).isEqualTo(comment);
|
||||
StrictAssertions.assertThat(linkLive.getWhenPublish()).isEqualTo(when);
|
||||
|
||||
Link draft1b = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
StrictAssertions.assertThat(draft1b.isDirty()).isFalse();
|
||||
StrictAssertions.assertThat(draft1b.getComment()).isNull();
|
||||
StrictAssertions.assertThat(draft1b.getWhenPublish()).isNull();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSave() {
|
||||
|
||||
Link link1 = new Link("LinkOne");
|
||||
link1.save();
|
||||
|
||||
Link link2 = new Link("LinkTwo");
|
||||
link2.save();
|
||||
|
||||
Link link3 = new Link("LinkThree");
|
||||
link3.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
server.publish(Link.class, link1.getId(), null);
|
||||
server.publish(Link.class, link2.getId(), null);
|
||||
server.publish(Link.class, link3.getId(), null);
|
||||
|
||||
Doc doc1 = new Doc("DocOne");
|
||||
doc1.getLinks().add(link1);
|
||||
doc1.getLinks().add(link2);
|
||||
doc1.save();
|
||||
|
||||
Doc draftDoc1 = server.find(Doc.class)
|
||||
.setId(doc1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
assertThat(draftDoc1.getLinks()).hasSize(2);
|
||||
|
||||
Doc liveDoc1 = server.publish(Doc.class, doc1.getId(), null);
|
||||
|
||||
assertThat(liveDoc1.getLinks()).hasSize(2);
|
||||
assertThat(liveDoc1.getLinks()).extracting("id").contains(link1.getId(), link2.getId());
|
||||
|
||||
|
||||
draftDoc1.getLinks().remove(0);
|
||||
draftDoc1.getLinks().add(link3);
|
||||
|
||||
draftDoc1.save();
|
||||
|
||||
// publish with insert and delete of Links M2M relationship
|
||||
Doc liveDoc2 = server.publish(Doc.class, doc1.getId(), null);
|
||||
assertThat(liveDoc2.getLinks()).hasSize(2);
|
||||
assertThat(liveDoc2.getLinks()).extracting("id").contains(link2.getId(), link3.getId());
|
||||
|
||||
// delete the draft and live beans (with associated children)
|
||||
draftDoc1.delete();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDraftRestore() {
|
||||
|
||||
Link link1 = new Link("Ldr1");
|
||||
link1.setLocation("firstLocation");
|
||||
link1.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
Link live = server.publish(Link.class, link1.getId(), null);
|
||||
StrictAssertions.assertThat(live.isDraft()).isFalse();
|
||||
|
||||
Link draftLink = Ebean.find(Link.class)
|
||||
.setId(link1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
draftLink.setLocation("secondLocation");
|
||||
draftLink.save();
|
||||
|
||||
server.draftRestore(Link.class, link1.getId(), null);
|
||||
|
||||
draftLink = Ebean.find(Link.class)
|
||||
.setId(link1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
StrictAssertions.assertThat(draftLink.getLocation()).isEqualTo("firstLocation");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDraftRestoreViaQuery() {
|
||||
|
||||
Link link1 = new Link("Ldr1");
|
||||
link1.setLocation("firstLocation");
|
||||
link1.setComment("Banana");
|
||||
link1.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
server.publish(Link.class, link1.getId(), null);
|
||||
|
||||
Link draftLink = Ebean.find(Link.class)
|
||||
.setId(link1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
draftLink.setLocation("secondLocation");
|
||||
draftLink.setComment("A good change");
|
||||
draftLink.save();
|
||||
|
||||
Query<Link> query = server.find(Link.class).where().eq("id", link1.getId()).query();
|
||||
List<Link> links = server.draftRestore(query);
|
||||
|
||||
assertThat(links).hasSize(1);
|
||||
StrictAssertions.assertThat(links.get(0).getLocation()).isEqualTo("firstLocation");
|
||||
StrictAssertions.assertThat(links.get(0).isDirty()).isEqualTo(false);
|
||||
StrictAssertions.assertThat(links.get(0).getComment()).isNull();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.avaje.tests.draftable;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.tests.model.draftable.Link;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class LinkQueryPublishTest {
|
||||
|
||||
@Test
|
||||
public void testPublishViaQuery() {
|
||||
|
||||
Link link1 = new Link("L1");
|
||||
link1.save();
|
||||
|
||||
Link link2 = new Link("L2");
|
||||
link2.save();
|
||||
|
||||
Link link3 = new Link("L3");
|
||||
link3.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
List<Object> ids = new ArrayList<Object>();
|
||||
ids.add(link1.getId());
|
||||
ids.add(link2.getId());
|
||||
ids.add(link3.getId());
|
||||
|
||||
Query<Link> pubQuery = server.find(Link.class)
|
||||
.where().idIn(ids)
|
||||
.order().asc("id");
|
||||
|
||||
|
||||
List<Link> pubList = server.publish(pubQuery);
|
||||
|
||||
assertThat(pubList).hasSize(3);
|
||||
assertThat(pubList).extracting("id").contains(link1.getId(), link2.getId(), link3.getId());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.avaje.tests.draftable;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.tests.model.draftable.Document;
|
||||
import com.avaje.tests.model.draftable.DocumentMedia;
|
||||
import com.avaje.tests.model.draftable.Organisation;
|
||||
import org.assertj.core.api.StrictAssertions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class OrganisationTest {
|
||||
|
||||
@Test
|
||||
public void testSave() {
|
||||
|
||||
Organisation org = new Organisation("OrgOne");
|
||||
org.save();
|
||||
|
||||
assertNotNull(org.getId());
|
||||
|
||||
Document doc = new Document();
|
||||
doc.setTitle("NewTitle");
|
||||
doc.setOrganisation(org);
|
||||
doc.setBody("Hello");
|
||||
|
||||
doc.save();
|
||||
|
||||
doc.setBody("Change content");
|
||||
doc.save();
|
||||
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
Document draftDoc = server.find(Document.class)
|
||||
.asDraft()
|
||||
.setId(doc.getId())
|
||||
.findUnique();
|
||||
|
||||
assertNotNull(draftDoc);
|
||||
|
||||
Document liveDoc = server.find(Document.class)
|
||||
.setId(doc.getId())
|
||||
.findUnique();
|
||||
assertNull(liveDoc);
|
||||
|
||||
|
||||
server.publish(Document.class, doc.getId(), null);
|
||||
|
||||
doc.setTitle("Mod1");
|
||||
doc.save();
|
||||
|
||||
server.publish(Document.class, doc.getId(), null);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testSaveWithCascade() {
|
||||
|
||||
Organisation org = new Organisation("Org2");
|
||||
org.save();
|
||||
|
||||
Document doc = new Document();
|
||||
doc.setTitle("Title1");
|
||||
doc.setOrganisation(org);
|
||||
doc.setBody("Body1");
|
||||
|
||||
doc.getMedia().add(createMedia("media1"));
|
||||
doc.getMedia().add(createMedia("media2"));
|
||||
doc.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
|
||||
server.publish(Document.class, doc.getId(), null);
|
||||
|
||||
Document fetchDoc = Ebean.find(Document.class).setId(doc.getId()).asDraft().findUnique();
|
||||
List<DocumentMedia> media = fetchDoc.getMedia();
|
||||
|
||||
assertThat(media.size()).isEqualTo(2);
|
||||
|
||||
// // delete one of the 'child' @DraftElement rows ...
|
||||
// SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from document_media_draft where id = ?");
|
||||
// sqlUpdate.setParameter(1, doc.getMedia().get(0).getId());
|
||||
// sqlUpdate.execute();
|
||||
|
||||
doc.getMedia().get(1).setDescription("mod");
|
||||
doc.getMedia().add(createMedia("media3"));
|
||||
doc.getMedia().remove(0);
|
||||
doc.setBody("Body2");
|
||||
doc.save();
|
||||
|
||||
// publish will perform an insert, update and delete on child DocumentMedia
|
||||
// during the publish below with media1 being deleted
|
||||
Document liveBean = server.publish(Document.class, doc.getId(), null);
|
||||
StrictAssertions.assertThat(liveBean.getBody()).isEqualTo("Body2");
|
||||
StrictAssertions.assertThat(liveBean.getMedia().size()).isEqualTo(2);
|
||||
assertThat(liveBean.getMedia()).extracting("name").contains("media2","media3");
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DocumentMedia createMedia(String name) {
|
||||
DocumentMedia media = new DocumentMedia();
|
||||
media.setName(name);
|
||||
return media;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.avaje.tests.model.basic;
|
||||
|
||||
import com.avaje.ebean.annotation.ChangeLog;
|
||||
import com.avaje.ebean.annotation.ChangeLogInsertMode;
|
||||
import com.avaje.ebean.annotation.DbComment;
|
||||
import com.avaje.ebean.annotation.DbEnumValue;
|
||||
import com.avaje.ebean.annotation.JsonIgnore;
|
||||
import com.avaje.ebean.annotation.Where;
|
||||
@@ -26,6 +27,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
@ChangeLog(inserts = ChangeLogInsertMode.EXCLUDE, updatesThatInclude = {"name","status"})
|
||||
@Entity
|
||||
@Table(name = "o_customer")
|
||||
@DbComment("Holds external customers")
|
||||
public class Customer extends BasicDomain {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -60,15 +62,18 @@ public class Customer extends BasicDomain {
|
||||
@Transient
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
@DbComment("status of the customer")
|
||||
Status status;
|
||||
|
||||
@NotNull
|
||||
@Size(max = 40)
|
||||
String name;
|
||||
|
||||
@DbComment("Short notes regarding the customer")
|
||||
@Size(max = 100)
|
||||
String smallnote;
|
||||
|
||||
@DbComment("Join date of the customer")
|
||||
@NotNull(groups = { ValidationGroupSomething.class })
|
||||
Date anniversary;
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.avaje.tests.model.basic.first;
|
||||
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@AttributeOverride(name="name", column=@Column(name = "mod_name"))
|
||||
@Entity
|
||||
@Table(name = "f_second")
|
||||
public class Second extends SuperSecond {
|
||||
|
||||
@@ -8,6 +8,8 @@ import javax.persistence.OneToOne;
|
||||
@MappedSuperclass
|
||||
public abstract class SuperSecond {
|
||||
|
||||
protected String name;
|
||||
|
||||
@OneToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
|
||||
@JoinColumn(name = "first")
|
||||
protected First first;
|
||||
@@ -20,4 +22,11 @@ public abstract class SuperSecond {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ public class TestFirstSecond extends BaseTestCase {
|
||||
Ebean.save(first);
|
||||
|
||||
Second second = new Second();
|
||||
second.setTitle("John");
|
||||
second.setName("Jim");
|
||||
second.setTitle("Sir");
|
||||
second.setFirst(first);
|
||||
Ebean.save(second);
|
||||
|
||||
@@ -23,7 +24,5 @@ public class TestFirstSecond extends BaseTestCase {
|
||||
second.getFirst().setName("changed");
|
||||
Ebean.save(second);
|
||||
Ebean.save(first);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.avaje.tests.model.draftable;
|
||||
|
||||
import com.avaje.ebean.Model;
|
||||
import com.avaje.ebean.annotation.WhenCreated;
|
||||
import com.avaje.ebean.annotation.WhenModified;
|
||||
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.Version;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
@MappedSuperclass
|
||||
public class BaseDomain extends Model {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
@Version
|
||||
Long version;
|
||||
|
||||
@WhenCreated
|
||||
Timestamp whenCreated;
|
||||
|
||||
@WhenModified
|
||||
Timestamp whenModified;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.avaje.tests.model.draftable;
|
||||
|
||||
import com.avaje.ebean.annotation.Draftable;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToMany;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Draftable
|
||||
@Entity
|
||||
public class Doc extends BaseDomain {
|
||||
|
||||
String name;
|
||||
|
||||
@ManyToMany(cascade = CascadeType.ALL)
|
||||
List<Link> links;
|
||||
|
||||
public Doc(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<Link> getLinks() {
|
||||
return links;
|
||||
}
|
||||
|
||||
public void setLinks(List<Link> links) {
|
||||
this.links = links;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.avaje.tests.model.draftable;
|
||||
|
||||
import com.avaje.ebean.annotation.DraftOnly;
|
||||
import com.avaje.ebean.annotation.Draftable;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Draftable
|
||||
@Entity
|
||||
public class Document extends BaseDomain {
|
||||
|
||||
@Column(unique = true)
|
||||
String title;
|
||||
|
||||
String body;
|
||||
|
||||
@DraftOnly
|
||||
Timestamp whenPublish;
|
||||
|
||||
@ManyToOne
|
||||
Organisation organisation;
|
||||
|
||||
/**
|
||||
* Relationship to draftable elements.
|
||||
*/
|
||||
//@PrivateOwned
|
||||
@OneToMany(mappedBy = "document")//, cascade = CascadeType.ALL)
|
||||
List<DocumentMedia> media;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Organisation getOrganisation() {
|
||||
return organisation;
|
||||
}
|
||||
|
||||
public void setOrganisation(Organisation organisation) {
|
||||
this.organisation = organisation;
|
||||
}
|
||||
|
||||
public List<DocumentMedia> getMedia() {
|
||||
return media;
|
||||
}
|
||||
|
||||
public void setMedia(List<DocumentMedia> media) {
|
||||
this.media = media;
|
||||
}
|
||||
|
||||
public Timestamp getWhenPublish() {
|
||||
return whenPublish;
|
||||
}
|
||||
|
||||
public void setWhenPublish(Timestamp whenPublish) {
|
||||
this.whenPublish = whenPublish;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.avaje.tests.model.draftable;
|
||||
|
||||
import com.avaje.ebean.annotation.DraftableElement;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* 'Owned' by @Draftable root.
|
||||
*/
|
||||
@DraftableElement
|
||||
@Entity
|
||||
public class DocumentMedia extends BaseDomain {
|
||||
|
||||
@ManyToOne
|
||||
Document document;
|
||||
|
||||
String name;
|
||||
|
||||
String description;
|
||||
|
||||
public Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
public void setDocument(Document document) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.avaje.tests.model.draftable;
|
||||
|
||||
import com.avaje.ebean.annotation.Draft;
|
||||
import com.avaje.ebean.annotation.DraftDirty;
|
||||
import com.avaje.ebean.annotation.DraftReset;
|
||||
import com.avaje.ebean.annotation.Draftable;
|
||||
import com.avaje.ebean.annotation.History;
|
||||
import com.avaje.ebean.annotation.SoftDelete;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToMany;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@History
|
||||
@Draftable
|
||||
@Entity
|
||||
public class Link extends BaseDomain {
|
||||
|
||||
@SoftDelete
|
||||
boolean deleted;
|
||||
|
||||
String name;
|
||||
|
||||
String location;
|
||||
|
||||
/**
|
||||
* Draft reset to null on publish.
|
||||
*/
|
||||
@DraftReset
|
||||
Timestamp whenPublish;
|
||||
|
||||
/**
|
||||
* Draft reset to null on publish.
|
||||
*/
|
||||
@DraftReset
|
||||
String comment;
|
||||
|
||||
/**
|
||||
* Indicates if the instance is a 'draft' or 'live' bean.
|
||||
*/
|
||||
@Draft
|
||||
boolean draft;
|
||||
|
||||
/**
|
||||
* Indicates if the draft has modifications that have not been published
|
||||
* to 'live'. This is automatically set when a draft is saved.
|
||||
*/
|
||||
@DraftDirty
|
||||
boolean dirty;
|
||||
|
||||
@ManyToMany(mappedBy = "links")
|
||||
List<Doc> docs;
|
||||
|
||||
public Link(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Link() {
|
||||
}
|
||||
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
public void setDraft(boolean draft) {
|
||||
this.draft = draft;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void setDeleted(boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public List<Doc> getDocs() {
|
||||
return docs;
|
||||
}
|
||||
|
||||
public void setDocs(List<Doc> docs) {
|
||||
this.docs = docs;
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
public void setDirty(boolean dirty) {
|
||||
this.dirty = dirty;
|
||||
}
|
||||
|
||||
public Timestamp getWhenPublish() {
|
||||
return whenPublish;
|
||||
}
|
||||
|
||||
public void setWhenPublish(Timestamp whenPublish) {
|
||||
this.whenPublish = whenPublish;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.avaje.tests.model.draftable;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class Organisation extends BaseDomain {
|
||||
|
||||
String name;
|
||||
|
||||
public Organisation(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user