mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5cfce3f44 | ||
|
|
1c6363e85c | ||
|
|
b8bcbd8af9 | ||
|
|
d70768277b | ||
|
|
b39b4a2e8c | ||
|
|
93b2f4035c | ||
|
|
31ee0c4582 | ||
|
|
6c2dad4851 | ||
|
|
ed190ede03 | ||
|
|
f252b80c2d | ||
|
|
2859ea657e | ||
|
|
664b1cbd75 | ||
|
|
8bc283fef4 | ||
|
|
7d85398139 | ||
|
|
f42be3d65a | ||
|
|
ff8e5af4e5 | ||
|
|
526d67eeb4 | ||
|
|
374fc8d702 | ||
|
|
9c6c59afab | ||
|
|
c3eae0d3a5 | ||
|
|
25ec73364e |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.13.1</version>
|
||||
<version>6.13.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
@@ -1091,14 +1092,17 @@ public interface EbeanServer {
|
||||
<T> Map<?, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning at most one entity bean. This will throw a
|
||||
* PersistenceException if the query finds more than one result.
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* This will throw a NonUniqueResultException if the query finds more than one result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findUnique()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param <T>
|
||||
* the type of entity bean to fetch.
|
||||
* @param query
|
||||
@@ -1106,6 +1110,7 @@ public interface EbeanServer {
|
||||
* @param transaction
|
||||
* the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
*
|
||||
* @see Query#findUnique()
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -232,7 +232,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
List<String> includedColumns = new ArrayList<String>(columns.size());
|
||||
|
||||
for (MColumn column : columns) {
|
||||
if (!column.isHistoryExclude()) {
|
||||
if (column.isIncludeInHistory()) {
|
||||
includedColumns.add(column.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ public class PlatformDdl {
|
||||
|
||||
protected String identitySuffix = " auto_increment";
|
||||
|
||||
protected String alterTableIfExists = "";
|
||||
|
||||
protected String dropConstraintIfExists = "drop constraint if exists";
|
||||
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
@@ -150,7 +152,7 @@ public class PlatformDdl {
|
||||
* Return the drop foreign key clause.
|
||||
*/
|
||||
public String alterTableDropForeignKey(String tableName, String fkName) {
|
||||
return "alter table " + tableName + " " + dropConstraintIfExists + " " + fkName;
|
||||
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + fkName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ public class PostgresDdl extends PlatformDdl {
|
||||
this.historyDdl = new PostgresHistoryDdl();
|
||||
this.dropTableCascade = " cascade";
|
||||
this.columnSetType = "type ";
|
||||
this.alterTableIfExists = "if exists ";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -213,6 +213,13 @@ public class MColumn {
|
||||
return draftOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this column should be included in History DB triggers etc.
|
||||
*/
|
||||
public boolean isIncludeInHistory() {
|
||||
return !draftOnly && !historyExclude;
|
||||
}
|
||||
|
||||
public Column createColumn() {
|
||||
|
||||
Column c = new Column();
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The context used during DDL generation.
|
||||
@@ -134,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;
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public class ModelBuildIntersectionTable {
|
||||
buildFkConstraints();
|
||||
|
||||
if (manyProp.getTargetDescriptor().isDraftable()) {
|
||||
ctx.createDraft(intersectionTable);
|
||||
ctx.createDraft(intersectionTable, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
private void addDraftTable() {
|
||||
if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) {
|
||||
// create a 'Draft' table which looks very similar (change PK, FK etc)
|
||||
ctx.createDraft(table);
|
||||
ctx.createDraft(table, !beanDescriptor.isDraftableElement());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.avaje.ebeaninternal.server.cluster.ClusterManager;
|
||||
import com.avaje.ebeaninternal.server.lib.ShutdownManager;
|
||||
import com.avaje.ebeaninternal.server.lib.sql.DataSourceAlert;
|
||||
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
|
||||
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePoolListener;
|
||||
import com.avaje.ebeaninternal.server.lib.sql.SimpleDataSourceAlert;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -311,7 +312,17 @@ public class DefaultContainer implements SpiContainer {
|
||||
}
|
||||
|
||||
DataSourceAlert notify = new SimpleDataSourceAlert();
|
||||
return new DataSourcePool(notify, config.getName(), dsConfig);
|
||||
DataSourcePoolListener listener = createListener(config, dsConfig);
|
||||
|
||||
return new DataSourcePool(notify, config.getName(), dsConfig, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a DataSourcePoolListener if it has been specified.
|
||||
*/
|
||||
private DataSourcePoolListener createListener(ServerConfig config, DataSourceConfig dsConfig) {
|
||||
String poolListener = dsConfig.getPoolListener();
|
||||
return poolListener != null ? (DataSourcePoolListener) config.getClassLoadConfig().newInstance(poolListener) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,6 +70,7 @@ import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.ArrayList;
|
||||
@@ -1218,13 +1219,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
// a query that is expected to return either 0 or 1 rows
|
||||
List<T> list = findList(query, t);
|
||||
return extractUnique(list);
|
||||
}
|
||||
|
||||
if (list.size() == 0) {
|
||||
private <T> T extractUnique(List<T> list) {
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
|
||||
|
||||
} else if (list.size() > 1) {
|
||||
throw new PersistenceException("Unique expecting 0 or 1 rows but got [" + list.size() + "]");
|
||||
|
||||
throw new NonUniqueResultException("Unique expecting 0 or 1 results but got [" + list.size() + "]");
|
||||
|
||||
} else {
|
||||
return list.get(0);
|
||||
}
|
||||
@@ -1449,17 +1453,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
// no findId() method for SqlQuery...
|
||||
// a query that is expected to return either 0 or 1 rows
|
||||
List<SqlRow> list = findList(query, t);
|
||||
|
||||
if (list.size() == 0) {
|
||||
return null;
|
||||
|
||||
} else if (list.size() > 1) {
|
||||
String m = "Unique expecting 0 or 1 rows but got [" + list.size() + "]";
|
||||
throw new PersistenceException(m);
|
||||
|
||||
} else {
|
||||
return list.get(0);
|
||||
}
|
||||
return extractUnique(list);
|
||||
}
|
||||
|
||||
public SqlFutureList findFutureList(SqlQuery query, Transaction t) {
|
||||
|
||||
@@ -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.isDraftInstance(entityBean)) {
|
||||
throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'.
|
||||
* Save or Update is not allowed to execute using 'live' beans - must use publish().
|
||||
*/
|
||||
public void checkDraft() {
|
||||
if (beanDescriptor.isDraftable() && !beanDescriptor.isDraftInstance(entityBean)) {
|
||||
throw new PersistenceException("Save or update is not allowed on a 'live' bean - only draft beans");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent bean for cascading save with unidirectional relationship.
|
||||
*/
|
||||
@@ -711,6 +754,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
intercept.setLoadedProperty(i);
|
||||
}
|
||||
beanDescriptor.setEmbeddedOwner(entityBean);
|
||||
beanDescriptor.setDraft(entityBean);
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
@@ -827,4 +871,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;
|
||||
@@ -163,6 +164,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
|
||||
private final boolean draftableElement;
|
||||
|
||||
private final BeanProperty draft;
|
||||
|
||||
private final BeanProperty draftDirty;
|
||||
|
||||
/**
|
||||
@@ -403,6 +406,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 +510,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ServerConfig.
|
||||
*/
|
||||
public ServerConfig getServerConfig() {
|
||||
return owner.getServerConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server. Primarily so that the Many's can lazy load.
|
||||
*/
|
||||
@@ -1989,6 +2000,27 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return draftableElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the draft to true for this entity bean instance.
|
||||
* This bean is being loaded via asDraft() query.
|
||||
*/
|
||||
public void setDraft(EntityBean entityBean) {
|
||||
if (draft != null) {
|
||||
draft.setValue(entityBean, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bean is considered a 'draft' instance.
|
||||
*/
|
||||
public boolean isDraftInstance(EntityBean entityBean) {
|
||||
if (draft != null) {
|
||||
return Boolean.TRUE == draft.getValue(entityBean);
|
||||
}
|
||||
// no draft property - so just ignore the check / return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a @DraftDirty property set it's value on the bean.
|
||||
*/
|
||||
|
||||
@@ -125,6 +125,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private final String serverName;
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private Map<Class<?>, DeployBeanInfo<?>> deplyInfoMap = new HashMap<Class<?>, DeployBeanInfo<?>>();
|
||||
|
||||
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<Class<?>, BeanTable>();
|
||||
@@ -178,8 +180,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
*/
|
||||
public BeanDescriptorManager(InternalConfiguration config) {
|
||||
|
||||
ServerConfig serverConfig = config.getServerConfig();
|
||||
|
||||
this.serverConfig = config.getServerConfig();
|
||||
this.serverName = InternString.intern(serverConfig.getName());
|
||||
this.cacheManager = config.getCacheManager();
|
||||
this.xmlConfig = config.getXmlConfig();
|
||||
@@ -240,6 +241,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerConfig getServerConfig() {
|
||||
return serverConfig;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType) {
|
||||
return (BeanDescriptor<T>) descMap.get(entityType.getName());
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.EncryptKey;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
|
||||
|
||||
/**
|
||||
@@ -12,26 +13,31 @@ import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
|
||||
*/
|
||||
public interface BeanDescriptorMap {
|
||||
|
||||
/**
|
||||
* Return the name of the server/database.
|
||||
*/
|
||||
String getServerName();
|
||||
/**
|
||||
* Return the name of the server/database.
|
||||
*/
|
||||
String getServerName();
|
||||
|
||||
/**
|
||||
* Return the Cache Manager.
|
||||
*/
|
||||
ServerCacheManager getCacheManager();
|
||||
/**
|
||||
* Return the ServerConfig.
|
||||
*/
|
||||
ServerConfig getServerConfig();
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor for a given class.
|
||||
*/
|
||||
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType);
|
||||
/**
|
||||
* Return the Cache Manager.
|
||||
*/
|
||||
ServerCacheManager getCacheManager();
|
||||
|
||||
/**
|
||||
* Return the Encrypt key given the table and column name.
|
||||
*/
|
||||
EncryptKey getEncryptKey(String tableName, String columnName);
|
||||
|
||||
IdBinder createIdBinder(BeanProperty id);
|
||||
/**
|
||||
* Return the BeanDescriptor for a given class.
|
||||
*/
|
||||
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType);
|
||||
|
||||
/**
|
||||
* Return the Encrypt key given the table and column name.
|
||||
*/
|
||||
EncryptKey getEncryptKey(String tableName, String columnName);
|
||||
|
||||
IdBinder createIdBinder(BeanProperty id);
|
||||
|
||||
}
|
||||
|
||||
@@ -224,6 +224,8 @@ public class BeanProperty implements ElPropertyValue {
|
||||
|
||||
final boolean jsonDeserialize;
|
||||
|
||||
final boolean draft;
|
||||
|
||||
final boolean draftOnly;
|
||||
|
||||
final boolean draftDirty;
|
||||
@@ -262,6 +264,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();
|
||||
@@ -312,7 +315,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 +361,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;
|
||||
@@ -646,7 +650,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1083,6 +1088,14 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return draftOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is a boolean flag on a draftable bean
|
||||
* indicating if the instance is a draft or live bean.
|
||||
*/
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is a boolean flag only on the draft table
|
||||
* indicating that when the draft is different from the published row.
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@ public class BeanPropertyAssocManyJsonHelp {
|
||||
*/
|
||||
public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
|
||||
this.many = many;
|
||||
this.jsonTransient = !ClassUtil.isJacksonObjectMapperPresent() ? null : new BeanPropertyAssocManyJsonTransient();
|
||||
boolean objectMapperPresent = many.getBeanDescriptor().getServerConfig().getClassLoadConfig().isJacksonObjectMapperPresent();
|
||||
this.jsonTransient = !objectMapperPresent ? null : new BeanPropertyAssocManyJsonTransient();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+17
-4
@@ -3,7 +3,9 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.avaje.ebean.config.ClassLoadConfig;
|
||||
import com.avaje.ebean.config.CurrentUserProvider;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
|
||||
/**
|
||||
@@ -13,9 +15,9 @@ public class GeneratedPropertyFactory {
|
||||
|
||||
private final CounterFactory counterFactory = new CounterFactory();
|
||||
|
||||
private final InsertTimestampFactory insertFactory = new InsertTimestampFactory();
|
||||
private final InsertTimestampFactory insertFactory;
|
||||
|
||||
private final UpdateTimestampFactory updateFactory = new UpdateTimestampFactory();
|
||||
private final UpdateTimestampFactory updateFactory;
|
||||
|
||||
private final HashSet<String> numberTypes = new HashSet<String>();
|
||||
|
||||
@@ -23,8 +25,15 @@ public class GeneratedPropertyFactory {
|
||||
|
||||
private final GeneratedWhoCreated generatedWhoCreated;
|
||||
|
||||
public GeneratedPropertyFactory(CurrentUserProvider currentUserProvider) {
|
||||
private final ClassLoadConfig classLoadConfig;
|
||||
|
||||
public GeneratedPropertyFactory(ServerConfig serverConfig) {
|
||||
|
||||
this.classLoadConfig = serverConfig.getClassLoadConfig();
|
||||
this.insertFactory = new InsertTimestampFactory(classLoadConfig);
|
||||
this.updateFactory = new UpdateTimestampFactory(classLoadConfig);
|
||||
|
||||
CurrentUserProvider currentUserProvider = serverConfig.getCurrentUserProvider();
|
||||
if (currentUserProvider != null) {
|
||||
generatedWhoCreated = new GeneratedWhoCreated(currentUserProvider);
|
||||
generatedWhoModified = new GeneratedWhoModified(currentUserProvider);
|
||||
@@ -44,7 +53,11 @@ public class GeneratedPropertyFactory {
|
||||
numberTypes.add(BigDecimal.class.getName());
|
||||
}
|
||||
|
||||
private boolean isNumberType(String typeClassName) {
|
||||
public ClassLoadConfig getClassLoadConfig() {
|
||||
return classLoadConfig;
|
||||
}
|
||||
|
||||
private boolean isNumberType(String typeClassName) {
|
||||
return numberTypes.contains(typeClassName);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -9,7 +9,7 @@ import java.util.Map;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.avaje.ebean.config.ClassLoadConfig;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
|
||||
/**
|
||||
@@ -21,25 +21,25 @@ public class InsertTimestampFactory {
|
||||
|
||||
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
|
||||
|
||||
public InsertTimestampFactory() {
|
||||
public InsertTimestampFactory(ClassLoadConfig classLoadConfig) {
|
||||
map.put(Timestamp.class, new GeneratedInsertTimestamp());
|
||||
map.put(java.util.Date.class, new GeneratedInsertDate());
|
||||
map.put(Long.class, longTime);
|
||||
map.put(long.class, longTime);
|
||||
|
||||
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
|
||||
if (classLoadConfig.isJavaTimePresent()) {
|
||||
map.put(LocalDateTime.class, new GeneratedInsertJavaTime.LocalDT());
|
||||
map.put(OffsetDateTime.class, new GeneratedInsertJavaTime.OffsetDT());
|
||||
map.put(ZonedDateTime.class, new GeneratedInsertJavaTime.ZonedDT());
|
||||
}
|
||||
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
|
||||
if (classLoadConfig.isJodaTimePresent()) {
|
||||
map.put(org.joda.time.LocalDateTime.class, new GeneratedInsertJodaTime.LocalDT());
|
||||
map.put(org.joda.time.DateTime.class, new GeneratedInsertJodaTime.DateTimeDT());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setInsertTimestamp(DeployBeanProperty property) {
|
||||
public void setInsertTimestamp(DeployBeanProperty property) {
|
||||
|
||||
property.setGeneratedProperty(createInsertTimestamp(property));
|
||||
}
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ import java.util.Map;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.avaje.ebean.config.ClassLoadConfig;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
|
||||
/**
|
||||
@@ -21,18 +21,18 @@ public class UpdateTimestampFactory {
|
||||
|
||||
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
|
||||
|
||||
public UpdateTimestampFactory() {
|
||||
public UpdateTimestampFactory(ClassLoadConfig classLoadConfig) {
|
||||
map.put(Timestamp.class, new GeneratedUpdateTimestamp());
|
||||
map.put(java.util.Date.class, new GeneratedUpdateDate());
|
||||
map.put(Long.class, longTime);
|
||||
map.put(long.class, longTime);
|
||||
|
||||
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
|
||||
if (classLoadConfig.isJavaTimePresent()) {
|
||||
map.put(LocalDateTime.class, new GeneratedUpdateJavaTime.LocalDT());
|
||||
map.put(OffsetDateTime.class, new GeneratedUpdateJavaTime.OffsetDT());
|
||||
map.put(ZonedDateTime.class, new GeneratedUpdateJavaTime.ZonedDT());
|
||||
}
|
||||
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
|
||||
if (classLoadConfig.isJodaTimePresent()) {
|
||||
map.put(org.joda.time.LocalDateTime.class, new GeneratedUpdateJodaTime.LocalDT());
|
||||
map.put(org.joda.time.DateTime.class, new GeneratedUpdateJodaTime.DateTimeDT());
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ public class DeployBeanProperty {
|
||||
|
||||
private boolean excludedFromHistory;
|
||||
|
||||
private boolean draft;
|
||||
private boolean draftOnly;
|
||||
private boolean draftDirty;
|
||||
private boolean draftReset;
|
||||
@@ -657,6 +658,7 @@ public class DeployBeanProperty {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is based on a secondary table.
|
||||
*/
|
||||
@@ -853,6 +855,15 @@ public class DeployBeanProperty {
|
||||
this.excludedFromHistory = true;
|
||||
}
|
||||
|
||||
public void setDraft() {
|
||||
this.draft = true;
|
||||
this.isTransient = true;
|
||||
}
|
||||
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
public void setDraftOnly() {
|
||||
this.draftOnly = true;
|
||||
}
|
||||
@@ -880,6 +891,7 @@ public class DeployBeanProperty {
|
||||
|
||||
public void setSoftDelete() {
|
||||
this.softDelete = true;
|
||||
this.nullable = false;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -162,13 +162,13 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
this.typeMap = new ConcurrentHashMap<Class<?>, ScalarType<?>>();
|
||||
this.nativeMap = new ConcurrentHashMap<Integer, ScalarType<?>>();
|
||||
|
||||
this.objectMapperPresent = ClassUtil.isJacksonObjectMapperPresent();
|
||||
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
|
||||
|
||||
this.extraTypeFactory = new DefaultTypeFactory(config);
|
||||
|
||||
initialiseStandard(jsonDateTime, config);
|
||||
initialiseJavaTimeTypes(jsonDateTime, config);
|
||||
initialiseJodaTypes(jsonDateTime);
|
||||
initialiseJodaTypes(jsonDateTime, config);
|
||||
initialiseJacksonTypes(config);
|
||||
|
||||
if (isPostgres(config.getDatabasePlatform())) {
|
||||
@@ -738,7 +738,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
*/
|
||||
protected void initialiseJacksonTypes(ServerConfig config) {
|
||||
|
||||
if (ClassUtil.isPresent("com.fasterxml.jackson.databind.ObjectMapper", this.getClass())) {
|
||||
if (config.getClassLoadConfig().isJacksonObjectMapperPresent()) {
|
||||
|
||||
logger.trace("Registering JsonNode type support");
|
||||
|
||||
@@ -765,7 +765,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
}
|
||||
|
||||
protected void initialiseJavaTimeTypes(JsonConfig.DateTime mode, ServerConfig config) {
|
||||
if (ClassUtil.isPresent("java.time.LocalDate", this.getClass())) {
|
||||
if (config.getClassLoadConfig().isJavaTimePresent()) {
|
||||
logger.debug("Registering java.time data types");
|
||||
typeMap.put(java.time.LocalDate.class, new ScalarTypeLocalDate());
|
||||
typeMap.put(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(mode));
|
||||
@@ -796,10 +796,10 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
* Detect if Joda classes are in the classpath and if so register the Joda
|
||||
* data types.
|
||||
*/
|
||||
protected void initialiseJodaTypes(JsonConfig.DateTime mode) {
|
||||
protected void initialiseJodaTypes(JsonConfig.DateTime mode, ServerConfig config) {
|
||||
|
||||
// detect if Joda classes are in the classpath
|
||||
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
|
||||
if (config.getClassLoadConfig().isJodaTimePresent()) {
|
||||
// Joda classes are in the classpath so register the types
|
||||
logger.debug("Registering Joda data types");
|
||||
typeMap.put(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(mode));
|
||||
|
||||
@@ -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);
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import com.avaje.ebean.config.ClassLoadConfig;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -11,7 +12,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
public class InsertTimestampFactoryTest {
|
||||
|
||||
InsertTimestampFactory factory = new InsertTimestampFactory();
|
||||
InsertTimestampFactory factory = new InsertTimestampFactory(new ClassLoadConfig());
|
||||
|
||||
@Test
|
||||
public void test_createdTimestamp_LocalDateTime() {
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import com.avaje.ebean.config.ClassLoadConfig;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -12,7 +13,7 @@ import static org.junit.Assert.*;
|
||||
public class UpdateTimestampFactoryTest {
|
||||
|
||||
|
||||
UpdateTimestampFactory factory = new UpdateTimestampFactory();
|
||||
UpdateTimestampFactory factory = new UpdateTimestampFactory(new ClassLoadConfig());
|
||||
|
||||
@Test
|
||||
public void test_createdTimestamp_LocalDateTime() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user