Compare commits

..
Author SHA1 Message Date
Robin Bygrave 3400928798 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.5 2015-12-15 15:45:55 +13:00
Robin Bygrave 4e665fd66f #499 - DDL issue for entity bean that is both @Draftable with @History - draft only columns included in history table 2015-12-15 15:44:22 +13:00
Robin Bygrave 09cd2c12e2 Add tests for @Draftable from example application 2015-12-15 15:25:19 +13:00
Robin Bygrave e3e45929f0 #498 - @Draftable - lazy loading after stateless update results in - EntityNotFoundException: Bean not found during lazy load or refresh 2015-12-15 15:15:20 +13:00
Robin Bygrave 2caeafaaa2 #498 - @Draftable - lazy loading after stateless update results in - EntityNotFoundException: Bean not found during lazy load or refresh 2015-12-15 14:38:39 +13:00
Robin Bygrave 5cd21de174 #496 - @EnumValue is being trimmed, may result in unexpected behaviour with CHAR columns for some database systems 2015-12-14 17:21:01 +13:00
Robin Bygrave 78df595861 [maven-release-plugin] prepare for next development iteration 2015-12-11 10:28:21 +13:00
Robin Bygrave 3c89a0537f [maven-release-plugin] prepare release avaje-ebeanorm-6.13.4 2015-12-11 10:28:01 +13:00
Robin Bygrave 282463c2e8 #247 - @AttributeOverride at class level not read - was: @AttributeOverride annotation does not seem to work (Play 2.2.0 - ebean 3.2.2) 2015-12-11 10:23:34 +13:00
Robin Bygrave 03bb7a4aaf #494 - ENH: Support findPagingList() ... that uses firstRow & maxRows (rather than pageIndex, pageSize) 2015-12-11 10:20:53 +13:00
Robin Bygrave e4fa031cd3 #494 - ENH: Support findPagingList() ... that uses firstRow & maxRows (rather than pageIndex, pageSize) 2015-12-10 14:52:27 +13:00
Robin Bygrave 00d227c598 #491 - RawSqlParser doesn't seem to understand new lines directly after the SELECT keyword 2015-12-09 17:24:58 +13:00
Robin Bygrave fb86cb91bc #492 - MS Sql Server DDL - Fix for drop constraint syntax (remove extra IF EXISTS) and ignore table and column comments 2015-12-09 16:59:51 +13:00
Robin Bygrave f11ee20b73 #365 - ENH: Add @DbComment with DDL generation of table and column comments 2015-12-09 16:56:29 +13:00
Robin Bygrave 3665c5bfaf No effective change - adjust test connection properties for sql server 2015-12-09 16:53:26 +13:00
Robin Bygrave 638446680c #365 - ENH: Add @DbComment with DDL generation of table and column comments 2015-12-09 16:52:58 +13:00
Robin Bygrave bd9bfa25be No effective change - rename test property from 'top' as that is ms sql server keyword 2015-12-09 16:49:35 +13:00
Robin Bygrave 753d17a8d2 [maven-release-plugin] prepare for next development iteration 2015-12-09 12:46:40 +13:00
Robin Bygrave f5cfce3f44 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.3 2015-12-09 12:46:18 +13:00
Robin Bygrave 1c6363e85c #490 - Invalid SQL for generating ID from sequence on DB2 2015-12-09 12:44:27 +13:00
Robin Bygrave b8bcbd8af9 #489 - @Draftable - insert does not set 'draft' to true ... so subsequent update fails 2015-12-09 11:06:48 +13:00
Robin Bygrave d70768277b #488 - BeanPropertyAssocManyJsonHelp error when Jackson not in classPath 2015-12-09 11:00:21 +13:00
Robin Bygrave b39b4a2e8c #487 - DDL - @SoftDelete deleted column should have NOT NULL constraint in create table DDL 2015-12-09 10:27:04 +13:00
Robin Bygrave 93b2f4035c #485 - @SoftDelete with join to nullable relation (optional @ManyToOne for example) ... will incorrectly filter out row from result when FK value is null. 2015-12-09 10:12:25 +13:00
Robin Bygrave 31ee0c4582 #486 - Postgres DDL - Add "if exists" to alter table [if exists] drop constraint if exists .... 2015-12-09 10:06:03 +13:00
Robin Bygrave 6c2dad4851 [maven-release-plugin] prepare for next development iteration 2015-12-07 22:20:54 +13:00
Robin Bygrave ed190ede03 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.2 2015-12-07 22:20:25 +13:00
Robin Bygrave f252b80c2d #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 3: Refactor with ClassLoadConfig 2015-12-07 22:13:27 +13:00
Robin Bygrave 2859ea657e #484 - JDBC4+ drivers do not need Class.forName() registration - removing that call 2015-12-07 21:02:00 +13:00
Robin Bygrave 664b1cbd75 #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 2 : Use current context classLoader 2015-12-07 17:41:56 +13:00
Robin Bygrave 8bc283fef4 #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 1 : protected methods 2015-12-07 17:37:59 +13:00
Robin Bygrave 7d85398139 #483 - ENH: Draftable - Add FK from live table back to draft table (for top level @Draftable tables) 2015-12-07 15:21:22 +13:00
Robin Bygrave f42be3d65a #482 - ENH: @Draftable - Save or update is not allowed on a 'live' bean - only draft beans ... add this check when @Draft property is used 2015-12-07 12:09:10 +13:00
Robin Bygrave ff8e5af4e5 #481 - ENH: @Draftable ... add support for transient @Draft boolean draft; property ... used to distinguish a 'live' instance from a 'draft' instance. 2015-12-07 11:11:22 +13:00
Robin Bygrave 526d67eeb4 No effective change - whitespace 2015-12-07 11:02:32 +13:00
Robin Bygrave 374fc8d702 #480 - @History trigger on @Draftable does not exclude the @DraftOnly and @DraftDirty properties - ERROR: column "dirty" of relation "link_history" does not exist Where: PL/pgSQL function link_history_version() 2015-12-07 09:44:34 +13:00
Robin Bygrave 9c6c59afab #479 - Insert of @Draftable with getGeneratedKeys ... PersistenceException executing insert 2015-12-07 09:21:26 +13:00
Robin Bygrave c3eae0d3a5 #477 - findUnique() should throw NonUniqueResultException and not PersistenceException 2015-12-04 11:12:45 +13:00
Robin Bygrave 25ec73364e [maven-release-plugin] prepare for next development iteration 2015-12-03 21:41:41 +13:00
84 changed files with 2135 additions and 419 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.13.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);
}
+41 -4
View File
@@ -9,6 +9,7 @@ import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
@@ -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()
*/
@@ -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;
@@ -193,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
@@ -237,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
@@ -257,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>
+32 -1
View File
@@ -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();
+35 -2
View File
@@ -3,6 +3,7 @@ package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;
@@ -700,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>
@@ -736,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();
@@ -819,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
@@ -864,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.
*
@@ -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,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);
}
@@ -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,11 +135,22 @@ 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;
@@ -44,7 +44,7 @@ public class ModelBuildIntersectionTable {
buildFkConstraints();
if (manyProp.getTargetDescriptor().isDraftable()) {
ctx.createDraft(intersectionTable);
ctx.createDraft(intersectionTable, false);
}
}
@@ -112,7 +112,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
private void addDraftTable() {
if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) {
// create a 'Draft' table which looks very similar (change PK, FK etc)
ctx.createDraft(table);
ctx.createDraft(table, !beanDescriptor.isDraftableElement());
}
}
@@ -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);
}
}
}
}
}
@@ -100,6 +100,7 @@ public interface SpiQuery<T> extends Query<T> {
* Includes soft deletes rows in the result.
*/
SOFT_DELETED,
/**
* Query runs against draft tables.
*/
@@ -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) {
@@ -357,6 +357,6 @@ public class InternalConfiguration {
}
public GeneratedPropertyFactory getGeneratedPropertyFactory() {
return new GeneratedPropertyFactory(serverConfig.getCurrentUserProvider());
return new GeneratedPropertyFactory(serverConfig);
}
}
@@ -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.
*/
@@ -436,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.
*/
@@ -711,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() {
@@ -827,4 +873,5 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public boolean isSoftDelete() {
return Type.SOFT_DELETE == type;
}
}
@@ -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;
@@ -154,6 +155,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final String draftTable;
/**
* DB table comment.
*/
private final String dbComment;
/**
* Set to true if read auditing is on for this bean type.
*/
@@ -163,6 +169,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final boolean draftableElement;
private final BeanProperty draft;
private final BeanProperty draftDirty;
/**
@@ -394,6 +402,7 @@ 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
@@ -403,6 +412,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
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();
@@ -506,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.
*/
@@ -1925,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.
@@ -1989,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);
}
@@ -211,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)
*/
@@ -224,6 +229,8 @@ public class BeanProperty implements ElPropertyValue {
final boolean jsonDeserialize;
final boolean draft;
final boolean draftOnly;
final boolean draftDirty;
@@ -262,6 +269,7 @@ 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();
@@ -295,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;
@@ -312,7 +321,7 @@ public class BeanProperty implements ElPropertyValue {
if (softDelete) {
ScalarTypeBoolean.BooleanBase boolType = (ScalarTypeBoolean.BooleanBase)scalarType;
this.softDeleteDbSet = dbColumn+"="+boolType.getDbTrueLiteral();
this.softDeleteDbPredicate = dbColumn+"="+boolType.getDbFalseLiteral();
this.softDeleteDbPredicate = "."+dbColumn+","+boolType.getDbFalseLiteral()+")="+boolType.getDbFalseLiteral();
} else {
this.softDeleteDbSet = null;
this.softDeleteDbPredicate = null;
@@ -358,6 +367,7 @@ 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;
@@ -373,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();
@@ -646,7 +657,8 @@ public class BeanProperty implements ElPropertyValue {
* Return the DB literal predicate used to filter out soft deleted rows from a query.
*/
public String getSoftDeleteDbPredicate(String tableAlias) {
return tableAlias+"."+softDeleteDbPredicate;
// use coalesce to handle null values from optional relationships
return "coalesce(" + tableAlias + softDeleteDbPredicate;
}
/**
@@ -994,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.
*/
@@ -1083,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.
@@ -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();
}
/**
@@ -3,7 +3,9 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.math.BigDecimal;
import java.util.HashSet;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebean.config.CurrentUserProvider;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
@@ -13,9 +15,9 @@ public class GeneratedPropertyFactory {
private final CounterFactory counterFactory = new CounterFactory();
private final InsertTimestampFactory insertFactory = new InsertTimestampFactory();
private final InsertTimestampFactory insertFactory;
private final UpdateTimestampFactory updateFactory = new UpdateTimestampFactory();
private final UpdateTimestampFactory updateFactory;
private final HashSet<String> numberTypes = new HashSet<String>();
@@ -23,8 +25,15 @@ public class GeneratedPropertyFactory {
private final GeneratedWhoCreated generatedWhoCreated;
public GeneratedPropertyFactory(CurrentUserProvider currentUserProvider) {
private final ClassLoadConfig classLoadConfig;
public GeneratedPropertyFactory(ServerConfig serverConfig) {
this.classLoadConfig = serverConfig.getClassLoadConfig();
this.insertFactory = new InsertTimestampFactory(classLoadConfig);
this.updateFactory = new UpdateTimestampFactory(classLoadConfig);
CurrentUserProvider currentUserProvider = serverConfig.getCurrentUserProvider();
if (currentUserProvider != null) {
generatedWhoCreated = new GeneratedWhoCreated(currentUserProvider);
generatedWhoModified = new GeneratedWhoModified(currentUserProvider);
@@ -44,7 +53,11 @@ public class GeneratedPropertyFactory {
numberTypes.add(BigDecimal.class.getName());
}
private boolean isNumberType(String typeClassName) {
public ClassLoadConfig getClassLoadConfig() {
return classLoadConfig;
}
private boolean isNumberType(String typeClassName) {
return numberTypes.contains(typeClassName);
}
@@ -9,7 +9,7 @@ import java.util.Map;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
@@ -21,25 +21,25 @@ public class InsertTimestampFactory {
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
public InsertTimestampFactory() {
public InsertTimestampFactory(ClassLoadConfig classLoadConfig) {
map.put(Timestamp.class, new GeneratedInsertTimestamp());
map.put(java.util.Date.class, new GeneratedInsertDate());
map.put(Long.class, longTime);
map.put(long.class, longTime);
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
if (classLoadConfig.isJavaTimePresent()) {
map.put(LocalDateTime.class, new GeneratedInsertJavaTime.LocalDT());
map.put(OffsetDateTime.class, new GeneratedInsertJavaTime.OffsetDT());
map.put(ZonedDateTime.class, new GeneratedInsertJavaTime.ZonedDT());
}
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
if (classLoadConfig.isJodaTimePresent()) {
map.put(org.joda.time.LocalDateTime.class, new GeneratedInsertJodaTime.LocalDT());
map.put(org.joda.time.DateTime.class, new GeneratedInsertJodaTime.DateTimeDT());
}
}
public void setInsertTimestamp(DeployBeanProperty property) {
public void setInsertTimestamp(DeployBeanProperty property) {
property.setGeneratedProperty(createInsertTimestamp(property));
}
@@ -9,7 +9,7 @@ import java.util.Map;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
@@ -21,18 +21,18 @@ public class UpdateTimestampFactory {
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
public UpdateTimestampFactory() {
public UpdateTimestampFactory(ClassLoadConfig classLoadConfig) {
map.put(Timestamp.class, new GeneratedUpdateTimestamp());
map.put(java.util.Date.class, new GeneratedUpdateDate());
map.put(Long.class, longTime);
map.put(long.class, longTime);
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
if (classLoadConfig.isJavaTimePresent()) {
map.put(LocalDateTime.class, new GeneratedUpdateJavaTime.LocalDT());
map.put(OffsetDateTime.class, new GeneratedUpdateJavaTime.OffsetDT());
map.put(ZonedDateTime.class, new GeneratedUpdateJavaTime.ZonedDT());
}
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
if (classLoadConfig.isJodaTimePresent()) {
map.put(org.joda.time.LocalDateTime.class, new GeneratedUpdateJodaTime.LocalDT());
map.put(org.joda.time.DateTime.class, new GeneratedUpdateJodaTime.DateTimeDT());
}
@@ -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;
}
@@ -186,12 +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;
@@ -657,6 +660,7 @@ public class DeployBeanProperty {
return false;
}
}
/**
* Return true if this property is based on a secondary table.
*/
@@ -853,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;
}
@@ -880,10 +893,18 @@ public class DeployBeanProperty {
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,10 @@ public class DeployBeanPropertyLists {
return draftDirty;
}
public BeanProperty getDraft() {
return draft;
}
public BeanProperty getSoftDeleteProperty() {
for (BeanProperty prop: nonManys) {
@@ -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();
}
@@ -163,6 +166,10 @@ public class AnnotationFields extends AnnotationParser {
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);
@@ -453,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);
}
@@ -314,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);
@@ -354,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()) {
@@ -501,21 +502,31 @@ public final class DefaultPersister implements Persister {
public boolean delete(EntityBean bean, Transaction t, boolean permanent) {
Type deleteType = permanent ? Type.DELETE_PERMANENT : Type.DELETE;
PersistRequestBean<EntityBean> request = createRequest(bean, t, deleteType);
boolean deleted = deleteRequest(request);
PersistRequestBean<EntityBean> originalRequest = createRequest(bean, t, deleteType);
if (request.isDraftable() && request.getType() == Type.DELETE) {
// we have just deleting a draft bean so now we need to delete the
// associated 'live' bean. This is effectively an 'automatic publish'.
deleteRequest(createPublishRequest(request.createReference(), t, Type.DELETE_PERMANENT, true));
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
@@ -529,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();
@@ -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;
}
}
@@ -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;
@@ -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();
@@ -1094,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
@@ -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));
@@ -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);
@@ -201,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;
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import org.junit.Test;
@@ -11,7 +12,7 @@ import static org.junit.Assert.*;
public class InsertTimestampFactoryTest {
InsertTimestampFactory factory = new InsertTimestampFactory();
InsertTimestampFactory factory = new InsertTimestampFactory(new ClassLoadConfig());
@Test
public void test_createdTimestamp_LocalDateTime() {
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import org.junit.Test;
@@ -12,7 +13,7 @@ import static org.junit.Assert.*;
public class UpdateTimestampFactoryTest {
UpdateTimestampFactory factory = new UpdateTimestampFactory();
UpdateTimestampFactory factory = new UpdateTimestampFactory(new ClassLoadConfig());
@Test
public void test_createdTimestamp_LocalDateTime() {
@@ -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;
}
}
@@ -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;
}
}
@@ -2,7 +2,6 @@ package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.util.List;
@@ -10,7 +9,7 @@ import java.util.List;
@Entity
public class ESoftDelMid extends BaseSoftDelete {
@ManyToOne(optional = false)
@ManyToOne
ESoftDelTop top;
String mid;
@@ -2,28 +2,27 @@ package com.avaje.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class ESoftDelTop extends BaseSoftDelete {
String top;
String name;
@OneToMany(mappedBy = "top", cascade = CascadeType.ALL)
List<ESoftDelMid> mids;
public ESoftDelTop(String top) {
this.top = top;
public ESoftDelTop(String name) {
this.name = name;
}
public String getTop() {
return top;
public String getName() {
return name;
}
public void setTop(String top) {
this.top = top;
public void setName(String top) {
this.name = name;
}
public List<ESoftDelMid> getMids() {
@@ -8,6 +8,7 @@ import com.avaje.tests.model.basic.ResetBasicData;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import javax.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -16,10 +17,126 @@ import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class TestQueryFindPagedList extends BaseTestCase {
@Test(expected = PersistenceException.class)
public void test_noMaxRows() throws ExecutionException, InterruptedException {
Ebean.find(Order.class).findPagedList();
}
@Test
public void test_maxRows_NoCount() throws ExecutionException, InterruptedException {
ResetBasicData.reset();
PagedList<Order> pagedList = Ebean.find(Order.class)
.setMaxRows(4)
.findPagedList();
LoggedSqlCollector.start();
List<Order> orders = pagedList.getList();
assertTrue(!orders.isEmpty());
List<String> loggedSql = LoggedSqlCollector.stop();
assertEquals("Only 1 SQL statement, no count query",1, loggedSql.size());
}
@Test
public void test_maxRows_countInBackground() throws ExecutionException, InterruptedException, TimeoutException {
ResetBasicData.reset();
PagedList<Order> pagedList = Ebean.find(Order.class)
.setMaxRows(3)
.findPagedList();
Future<Integer> rowCount = pagedList.getFutureRowCount();
List<Order> orders = pagedList.getList();
// these are each getting the total row count
int totalRowCount = pagedList.getTotalRowCount();
Integer totalRowCountWithTimeout = rowCount.get(30, TimeUnit.SECONDS);
Integer totalRowCountViaFuture = rowCount.get();
assertTrue(orders.size() < totalRowCount);
assertEquals(Integer.valueOf(totalRowCount), totalRowCountViaFuture);
assertEquals(Integer.valueOf(totalRowCount), totalRowCountWithTimeout);
}
@Test
public void test_maxRows_countInBackground_withLoadRowCount() throws InterruptedException {
ResetBasicData.reset();
// fetch less that total orders (page size 3)
PagedList<Order> pagedList = Ebean.find(Order.class)
.setMaxRows(3)
.findPagedList();
pagedList.loadRowCount();
List<Order> orders = pagedList.getList();
int totalRowCount = pagedList.getTotalRowCount();
assertThat(orders.size()).isLessThan(totalRowCount);
assertTrue(pagedList.hasNext());
assertFalse(pagedList.hasPrev());
// fetch less that total orders (page size 3)
PagedList<Order> pagedList2 = Ebean.find(Order.class)
.setFirstRow(1)
.setMaxRows(3)
.findPagedList();
pagedList2.loadRowCount();
List<Order> orders2 = pagedList2.getList();
int totalRowCount2 = pagedList2.getTotalRowCount();
assertTrue(pagedList2.hasNext());
assertTrue(pagedList2.hasPrev());
assertThat(totalRowCount).isEqualTo(totalRowCount2);
assertThat(orders2.size()).isLessThan(totalRowCount);
PagedList<Order> pagedList3 = Ebean.find(Order.class)
.setFirstRow(2)
.setMaxRows(150)
.findPagedList();
assertFalse(pagedList3.hasNext());
assertTrue(pagedList3.hasPrev());
List<Order> list3 = pagedList3.getList();
String xtoYofZ = pagedList3.getDisplayXtoYofZ(" to ", " of ");
assertThat(xtoYofZ).isEqualTo("3 to "+totalRowCount+" of "+totalRowCount);
assertThat(list3.size()).isEqualTo(totalRowCount - 2);
PagedList<Order> pagedList4 = Ebean.find(Order.class)
.setFirstRow(0)
.setMaxRows(totalRowCount)
.findPagedList();
assertFalse(pagedList4.hasNext());
assertFalse(pagedList4.hasPrev());
assertThat(pagedList4.getDisplayXtoYofZ(" to ", " of ")).isEqualTo("1 to "+totalRowCount+" of "+totalRowCount);
PagedList<Order> pagedList5 = Ebean.find(Order.class)
.setFirstRow(0)
.setMaxRows(totalRowCount - 1)
.findPagedList();
assertTrue(pagedList5.hasNext());
assertFalse(pagedList5.hasPrev());
}
@Test
public void test_noCount() throws ExecutionException, InterruptedException {
@@ -135,8 +135,8 @@ public class TestSoftDeleteBasic extends BaseTestCase {
String generatedSql = query1.getGeneratedSql();
// first statement is a single bulk update of the children with SoftDelete
assertThat(generatedSql).contains("t0.deleted=");
assertThat(generatedSql).contains("t1.deleted=");
assertThat(generatedSql).contains("coalesce(t0.deleted,");
assertThat(generatedSql).contains("coalesce(t1.deleted,");
assertThat(fetch1.get(0).getChildren()).hasSize(2);
assertThat(fetch1.get(0).getNosdChildren()).hasSize(2);
@@ -0,0 +1,28 @@
package com.avaje.tests.softdelete;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.softdelete.ESoftDelMid;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class TestSoftDeleteOptionalRelationship extends BaseTestCase {
@Test
public void testFindWhenNullRelationship() {
ESoftDelMid mid1 = new ESoftDelMid(null, "mid1");
Ebean.save(mid1);
ESoftDelMid bean = Ebean.find(ESoftDelMid.class)
.setId(mid1.getId())
.fetch("top")
.findUnique();
assertThat(bean).isNotNull();
assertThat(bean.getTop()).isNull();
}
}
@@ -4,6 +4,7 @@ create table foo (
col3 varchar(30) not null,
constraint pk_foo primary key (col1)
);
comment on table foo is 'comment';
create sequence foo_seq;
alter table foo add column added_to_foo varchar(20);
@@ -4,5 +4,6 @@ create table foo (
col3 varchar(30) not null,
constraint pk_foo primary key (col1)
);
comment on table foo is 'comment';
create sequence foo_seq;
+1 -1
View File
@@ -117,6 +117,6 @@ datasource.pg.databaseDriver=org.postgresql.Driver
datasource.ms.username=test
datasource.ms.password=test
datasource.ms.databaseUrl=jdbc:sqlserver://192.168.1.69:1433;databaseName=test
datasource.ms.databaseUrl=jdbc:sqlserver://192.168.1.70:1433;databaseName=test
datasource.ms.databaseDriver=com.microsoft.sqlserver.jdbc.SQLServerDriver