diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 8575f4580..357f8aefb 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,9 +1,3 @@ - -GITHUB ISSUES ARE STRICTLY CONTROLLED FOR THIS PROJECT. - -Refer to http://ebean-orm.github.io/support for the policies controlling the use of github issues. -Please post issues to the Ebean group https://groups.google.com/forum/#!forum/ebean first. - ## Expected behavior ## Actual behavior diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 0a2a3b777..72fbfe3ae 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean api @@ -50,7 +50,7 @@ io.ebean ebean-annotation - 7.0 + 7.2 diff --git a/ebean-api/src/main/java/io/ebean/ExtendedServer.java b/ebean-api/src/main/java/io/ebean/ExtendedServer.java index 9a7e62e0c..59b1c678e 100644 --- a/ebean-api/src/main/java/io/ebean/ExtendedServer.java +++ b/ebean-api/src/main/java/io/ebean/ExtendedServer.java @@ -67,7 +67,7 @@ public interface ExtendedServer { * * @return True if the query finds a matching row in the database */ - boolean exists(Query ormQuery, Transaction transaction); + boolean exists(Query ormQuery, Transaction transaction); /** * Return the number of 'top level' or 'root' entities this query should return. diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBean.java b/ebean-api/src/main/java/io/ebean/bean/EntityBean.java index 03d1195c8..ee54991ad 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBean.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBean.java @@ -27,17 +27,6 @@ public interface EntityBean extends Serializable { throw new NotEnhancedException(); } - /** - * Return the enhancement marker value. - *

- * This is the class name of the enhanced class and used to check that all - * entity classes are enhanced (specifically not just a super class). - *

- */ - default String _ebean_getMarker() { - throw new NotEnhancedException(); - } - /** * Create and return a new entity bean instance. */ diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java index 4695c4112..527860200 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -51,6 +51,11 @@ public final class EntityBeanIntercept implements Serializable { */ private static final byte FLAG_ORIG_VALUE_SET = 8; + /** + * Flags indicating if the mutable hash is set. + */ + private static final byte FLAG_MUTABLE_HASH_SET = 16; + private transient final ReentrantLock lock = new ReentrantLock(); private transient NodeUsageCollector nodeUsageCollector; private transient PersistenceContext persistenceContext; @@ -91,6 +96,17 @@ public final class EntityBeanIntercept implements Serializable { private Object ownerId; private int sortOrder; + /** + * Holds information of json loaded jackson beans (e.g. the original json or checksum). + */ + private MutableValueInfo[] mutableInfo; + + /** + * Holds json content determined at point of dirty check. + * Stored here on dirty check such that we only convert to json once. + */ + private MutableValueNext[] mutableNext; + /** * Create a intercept with a given entity. */ @@ -228,6 +244,17 @@ public final class EntityBeanIntercept implements Serializable { * if any embedded beans are either new or dirty (and hence need saving). */ public boolean isDirty() { + if (dirty) { + return true; + } + if (mutableInfo != null) { + for (int i = 0; i < mutableInfo.length; i++) { + if (mutableInfo[i] != null && !mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) { + dirty = true; + break; + } + } + } return dirty; } @@ -368,8 +395,18 @@ public final class EntityBeanIntercept implements Serializable { this.owner._ebean_setEmbeddedLoaded(); this.lazyLoadProperty = -1; this.origValues = null; + // after save, transfer the mutable next values back to mutable info + if (mutableNext != null) { + for (int i = 0; i < mutableNext.length; i++) { + MutableValueNext next = mutableNext[i]; + if (next != null) { + mutableInfo(i, next.info()); + } + } + } + this.mutableNext = null; for (int i = 0; i < flags.length; i++) { - flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET); + flags[i] &= ~(FLAG_CHANGED_PROP | FLAG_ORIG_VALUE_SET); } this.dirty = false; } @@ -442,6 +479,10 @@ public final class EntityBeanIntercept implements Serializable { * Return the original value that was changed via an update. */ public Object getOrigValue(int propertyIndex) { + if ((flags[propertyIndex] & (FLAG_ORIG_VALUE_SET | FLAG_MUTABLE_HASH_SET)) == FLAG_MUTABLE_HASH_SET) { + // mutable hash set, but not ORIG_VALUE + setOriginalValue(propertyIndex, mutableInfo[propertyIndex].get()); + } if (origValues == null) { return null; } @@ -476,7 +517,7 @@ public final class EntityBeanIntercept implements Serializable { * Return the number of properties. */ public int getPropertyLength() { - return owner._ebean_getPropertyNames().length; + return flags.length; } /** @@ -564,7 +605,7 @@ public final class EntityBeanIntercept implements Serializable { private void setOriginalValue(int propertyIndex, Object value) { if (origValues == null) { - origValues = new Object[owner._ebean_getPropertyNames().length]; + origValues = new Object[flags.length]; } if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) { flags[propertyIndex] |= FLAG_ORIG_VALUE_SET; @@ -577,7 +618,7 @@ public final class EntityBeanIntercept implements Serializable { */ private void setOriginalValueForce(int propertyIndex, Object value) { if (origValues == null) { - origValues = new Object[owner._ebean_getPropertyNames().length]; + origValues = new Object[flags.length]; } origValues[propertyIndex] = value; } @@ -638,7 +679,7 @@ public final class EntityBeanIntercept implements Serializable { public void addDirtyPropertyNames(Set props, String prefix) { int len = getPropertyLength(); for (int i = 0; i < len; i++) { - if ((flags[i] & FLAG_CHANGED_PROP) != 0) { + if (isChangedProp(i)) { // the property has been changed on this bean props.add((prefix == null ? getProperty(i) : prefix + getProperty(i))); } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) { @@ -656,7 +697,7 @@ public final class EntityBeanIntercept implements Serializable { String[] names = owner._ebean_getPropertyNames(); int len = getPropertyLength(); for (int i = 0; i < len; i++) { - if ((flags[i] & FLAG_CHANGED_PROP) != 0) { + if (isChangedProp(i)) { if (propertyNames.contains(names[i])) { return true; } @@ -684,7 +725,7 @@ public final class EntityBeanIntercept implements Serializable { public void addDirtyPropertyValues(Map dirtyValues, String prefix) { int len = getPropertyLength(); for (int i = 0; i < len; i++) { - if ((flags[i] & FLAG_CHANGED_PROP) != 0) { + if (isChangedProp(i)) { // the property has been changed on this bean String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i)); Object newVal = owner._ebean_getField(i); @@ -706,7 +747,7 @@ public final class EntityBeanIntercept implements Serializable { public void addDirtyPropertyValues(BeanDiffVisitor visitor) { int len = getPropertyLength(); for (int i = 0; i < len; i++) { - if ((flags[i] & FLAG_CHANGED_PROP) != 0) { + if (isChangedProp(i)) { // the property has been changed on this bean Object newVal = owner._ebean_getField(i); Object oldVal = getOrigValue(i); @@ -741,7 +782,7 @@ public final class EntityBeanIntercept implements Serializable { } int len = getPropertyLength(); for (int i = 0; i < len; i++) { - if ((flags[i] & FLAG_CHANGED_PROP) != 0) { + if ((flags[i] & FLAG_CHANGED_PROP) != 0) { // we do not check against mutablecontent here. sb.append(i).append(','); } else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) { // an embedded property has been changed - recurse @@ -1112,7 +1153,7 @@ public final class EntityBeanIntercept implements Serializable { */ public void setLoadError(int propertyIndex, Exception t) { if (loadErrors == null) { - loadErrors = new Exception[owner._ebean_getPropertyNames().length]; + loadErrors = new Exception[flags.length]; } loadErrors[propertyIndex] = t; flags[propertyIndex] |= FLAG_LOADED_PROP; @@ -1138,4 +1179,60 @@ public final class EntityBeanIntercept implements Serializable { } return ret; } + + private boolean isChangedProp(int i) { + if ((flags[i] & FLAG_CHANGED_PROP) != 0) { + return true; + } else if (mutableInfo == null || mutableInfo[i] == null || mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) { + return false; + } else { + // mark for change + flags[i] |= FLAG_CHANGED_PROP; + dirty = true; // this makes the bean automatically dirty! + return true; + } + } + + /** + * Return the MutableValueInfo for the given property or null. + */ + public MutableValueInfo mutableInfo(int propertyIndex) { + return mutableInfo == null ? null : mutableInfo[propertyIndex]; + } + + /** + * Set the MutableValueInfo for the given property. + */ + public void mutableInfo(int propertyIndex, MutableValueInfo info) { + if (mutableInfo == null) { + mutableInfo = new MutableValueInfo[flags.length]; + } + flags[propertyIndex] |= FLAG_MUTABLE_HASH_SET; + mutableInfo[propertyIndex] = info; + } + + /** + * Dirty detection set the next mutable property content and info . + *

+ * Set here as the mutable property dirty detection is based on json content comparison. + * We only want to perform the json serialisation once so storing it here as part of + * dirty detection so that we can get it back to bind in insert or update etc. + */ + public void mutableNext(int propertyIndex, MutableValueNext next) { + if (mutableNext == null) { + mutableNext = new MutableValueNext[flags.length]; + } + mutableNext[propertyIndex] = next; + } + + /** + * Update the 'next' mutable info returning the content that was obtained via dirty detection. + */ + public String mutableNext(int propertyIndex) { + if (mutableNext == null) { + return null; + } + return mutableNext[propertyIndex].content(); + } + } diff --git a/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java b/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java new file mode 100644 index 000000000..27716b13c --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java @@ -0,0 +1,42 @@ +package io.ebean.bean; + +/** + * Holds information on mutable values (like plain beans stored as json). + *

+ * Used internally in EntityBeanIntercept for dirty detection on mutable values. + * Typically, mutation detection is based on a hash/checksum of json content or the + * original json content itself. + *

+ * Refer to the mapping options {@code @DbJson(mutationDetection)}. + */ +public interface MutableValueInfo { + + /** + * Compares the given json returning null if deemed unchanged or returning + * the MutableValueNext to use if deemed dirty/changed. + *

+ * Returning MutableValueNext allows an implementation based on hash/checksum + * to only perform that computation once. + * + * @return Null if deemed unchanged or the MutableValueNext if deemed changed. + */ + MutableValueNext nextDirty(String json); + + /** + * Compares the given object to an internal value. + *

+ * This is used to support changelog/beanState. The implementation can serialize the + * object into json form and compare it against the original json. + */ + boolean isEqualToObject(Object obj); + + /** + * Creates a new instance from the internal json string. + *

+ * This is used to provide an original/old value for change logging / persist listeners. + * This is only available for properties that have {@code @DbJson(keepSource=true)}. + */ + default Object get() { + return null; + } +} diff --git a/ebean-api/src/main/java/io/ebean/bean/MutableValueNext.java b/ebean-api/src/main/java/io/ebean/bean/MutableValueNext.java new file mode 100644 index 000000000..401d3c223 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/bean/MutableValueNext.java @@ -0,0 +1,17 @@ +package io.ebean.bean; + +/** + * Represents a next value to use for mutable content properties (DbJson with jackson beans). + */ +public interface MutableValueNext { + + /** + * Return the next content to use. Provided such that we serialise to json once. + */ + String content(); + + /** + * Return the next MutableValueInfo to use after an update. + */ + MutableValueInfo info(); +} diff --git a/ebean-api/src/main/java/io/ebean/common/ModifyHolder.java b/ebean-api/src/main/java/io/ebean/common/ModifyHolder.java index 6da14d3a5..957b0fa88 100644 --- a/ebean-api/src/main/java/io/ebean/common/ModifyHolder.java +++ b/ebean-api/src/main/java/io/ebean/common/ModifyHolder.java @@ -3,9 +3,7 @@ package io.ebean.common; import io.ebean.bean.EntityBean; import java.io.Serializable; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.*; /** * Holds sets of additions and deletions from a 'owner' List Set or Map. @@ -23,19 +21,19 @@ class ModifyHolder implements Serializable { /** * Deletions list for manyToMany persistence. */ - private Set modifyDeletions = new LinkedHashSet<>(); + private Map modifyDeletions = new IdentityHashMap<>(); /** * Additions list for manyToMany persistence. */ - private Set modifyAdditions = new LinkedHashSet<>(); + private Map modifyAdditions = new IdentityHashMap<>(); private boolean touched; void reset() { touched = false; - modifyDeletions = new LinkedHashSet<>(); - modifyAdditions = new LinkedHashSet<>(); + modifyDeletions = new IdentityHashMap<>(); + modifyAdditions = new IdentityHashMap<>(); } /** @@ -50,51 +48,46 @@ class ModifyHolder implements Serializable { } private boolean undoDeletion(E bean) { - return (bean != null) && modifyDeletions.remove(bean); + return (bean != null) && modifyDeletions.remove(bean) != null; } void modifyAddition(E bean) { if (bean != null) { touched = true; - if (bean instanceof EntityBean) { ((EntityBean) bean)._ebean_getIntercept().setDeletedFromCollection(false); } - // If it is to delete then just remove the deletion if (!undoDeletion(bean)) { - // Insert - modifyAdditions.add(bean); + modifyAdditions.put(bean, bean); } } } private boolean undoAddition(Object bean) { - return (bean != null) && modifyAdditions.remove(bean); + return (bean != null) && modifyAdditions.remove(bean) != null; } @SuppressWarnings("unchecked") void modifyRemoval(Object bean) { if (bean != null) { touched = true; - if (bean instanceof EntityBean) { ((EntityBean) bean)._ebean_getIntercept().setDeletedFromCollection(true); } - // If it is to be added then just remove the addition if (!undoAddition(bean)) { - modifyDeletions.add((E) bean); + modifyDeletions.put((E) bean, bean); } } } Set getModifyAdditions() { - return modifyAdditions; + return modifyAdditions.keySet(); } Set getModifyRemovals() { - return modifyDeletions; + return modifyDeletions.keySet(); } /** diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java index bc3ae587f..873304c3a 100644 --- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java @@ -7,9 +7,7 @@ import io.ebean.EbeanVersion; import io.ebean.PersistenceContextScope; import io.ebean.Query; import io.ebean.Transaction; -import io.ebean.annotation.Encrypted; -import io.ebean.annotation.PersistBatch; -import io.ebean.annotation.Platform; +import io.ebean.annotation.*; import io.ebean.cache.ServerCachePlugin; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbEncrypt; @@ -194,10 +192,9 @@ public class DatabaseConfig { private JsonConfig.Include jsonInclude = JsonConfig.Include.ALL; /** - * When true then by default DbJson beans are assumed to be dirty. - * I believe we want to change this default to false in the future. + * The default mode used for {@code @DbJson} with Jackson ObjectMapper. */ - private boolean jsonDirtyByDefault = true; + private MutationDetection jsonMutationDetection = MutationDetection.HASH; /** * The database platform name. Used to imply a DatabasePlatform to use. @@ -322,6 +319,8 @@ public class DatabaseConfig { */ private ExternalTransactionManager externalTransactionManager; + private boolean skipDataSourceCheck; + /** * The data source (if programmatically provided). */ @@ -744,23 +743,21 @@ public class DatabaseConfig { } /** - * Return true if DbJson beans are assumed dirty by default. - *

- * That is, when true beans that do not implement ModifyAwareType are by - * default assumed to be dirty and included in updates. + * Return the default MutableDetection to use with {@code @DbJson} using Jackson. + * + * @see DbJson#mutationDetection() */ - public boolean isJsonDirtyByDefault() { - return jsonDirtyByDefault; + public MutationDetection getJsonMutationDetection() { + return jsonMutationDetection; } /** - * Set to false if we want DbJson beans to not be assumed to be dirty. - *

- * That is, when true beans that do not implement ModifyAwareType are by - * default assumed to be dirty and included in updates. + * Set the default MutableDetection to use with {@code @DbJson} using Jackson. + * + * @see DbJson#mutationDetection() */ - public void setJsonDirtyByDefault(boolean jsonDirtyByDefault) { - this.jsonDirtyByDefault = jsonDirtyByDefault; + public void setJsonMutationDetection(MutationDetection jsonMutationDetection) { + this.jsonMutationDetection = jsonMutationDetection; } /** @@ -1656,6 +1653,20 @@ public class DatabaseConfig { this.autoTuneConfig = autoTuneConfig; } + /** + * Return true if the startup DataSource check should be skipped. + */ + public boolean skipDataSourceCheck() { + return skipDataSourceCheck; + } + + /** + * Set to true to skip the startup DataSource check. + */ + public void setSkipDataSourceCheck(boolean skipDataSourceCheck) { + this.skipDataSourceCheck = skipDataSourceCheck; + } + /** * Return the DataSource. */ @@ -2935,8 +2946,9 @@ public class DatabaseConfig { jsonInclude = p.getEnum(JsonConfig.Include.class, "jsonInclude", jsonInclude); jsonDateTime = p.getEnum(JsonConfig.DateTime.class, "jsonDateTime", jsonDateTime); jsonDate = p.getEnum(JsonConfig.Date.class, "jsonDate", jsonDate); - jsonDirtyByDefault = p.getBoolean("jsonDirtyByDefault", jsonDirtyByDefault); + jsonMutationDetection = p.getEnum(MutationDetection.class, "jsonMutationDetection", jsonMutationDetection); + skipDataSourceCheck = p.getBoolean("skipDataSourceCheck", skipDataSourceCheck); runMigration = p.getBoolean("migration.run", runMigration); ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate); ddlRun = p.getBoolean("ddl.run", ddlRun); diff --git a/ebean-api/src/main/java/io/ebean/meta/AbstractMetricVisitor.java b/ebean-api/src/main/java/io/ebean/meta/AbstractMetricVisitor.java index 323b572ec..51df586d9 100644 --- a/ebean-api/src/main/java/io/ebean/meta/AbstractMetricVisitor.java +++ b/ebean-api/src/main/java/io/ebean/meta/AbstractMetricVisitor.java @@ -18,22 +18,22 @@ public abstract class AbstractMetricVisitor implements MetricVisitor { } @Override - public boolean isReset() { + public boolean reset() { return reset; } @Override - public boolean isCollectTransactionMetrics() { + public boolean collectTransactionMetrics() { return collectTransactionMetrics; } @Override - public boolean isCollectQueryMetrics() { + public boolean collectQueryMetrics() { return collectQueryMetrics; } @Override - public boolean isCollectL2Metrics() { + public boolean collectL2Metrics() { return collectL2Metrics; } diff --git a/ebean-api/src/main/java/io/ebean/meta/BasicMetricVisitor.java b/ebean-api/src/main/java/io/ebean/meta/BasicMetricVisitor.java index c821c63a7..0db953159 100644 --- a/ebean-api/src/main/java/io/ebean/meta/BasicMetricVisitor.java +++ b/ebean-api/src/main/java/io/ebean/meta/BasicMetricVisitor.java @@ -27,17 +27,17 @@ public class BasicMetricVisitor extends AbstractMetricVisitor implements ServerM } @Override - public List getTimedMetrics() { + public List timedMetrics() { return timed; } @Override - public List getQueryMetrics() { + public List queryMetrics() { return query; } @Override - public List getCountMetrics() { + public List countMetrics() { return count; } diff --git a/ebean-api/src/main/java/io/ebean/meta/MetaCountMetric.java b/ebean-api/src/main/java/io/ebean/meta/MetaCountMetric.java index 8b6af3594..cdaa2d7fa 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetaCountMetric.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetaCountMetric.java @@ -8,6 +8,13 @@ public interface MetaCountMetric extends MetaMetric { /** * Return the total count. */ - long getCount(); + long count(); + /** + * Migrate to count() + */ + @Deprecated + default long getCount() { + return count(); + } } diff --git a/ebean-api/src/main/java/io/ebean/meta/MetaMetric.java b/ebean-api/src/main/java/io/ebean/meta/MetaMetric.java index 0d716fe23..671dc05c1 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetaMetric.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetaMetric.java @@ -8,6 +8,13 @@ public interface MetaMetric { /** * Return the metric name. */ - String getName(); + String name(); + /** + * Migrate to name(). + */ + @Deprecated + default String getName() { + return name(); + } } diff --git a/ebean-api/src/main/java/io/ebean/meta/MetaQueryMetric.java b/ebean-api/src/main/java/io/ebean/meta/MetaQueryMetric.java index 514b700dc..1c9b5fe12 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetaQueryMetric.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetaQueryMetric.java @@ -8,21 +8,45 @@ public interface MetaQueryMetric extends MetaTimedMetric { /** * The type of entity or DTO bean. */ - Class getType(); + Class type(); + + /** + * Migrate to type(). + */ + @Deprecated + default Class getType() { + return type(); + } /** * The label for the query (can be null). */ - String getLabel(); + String label(); + + /** + * Migrate to label(). + */ + @Deprecated + default String getLabel() { + return label(); + } /** * The actual SQL of the query. */ - String getSql(); + String sql(); + + /** + * Migrate to sql(). + */ + @Deprecated + default String getSql() { + return sql(); + } /** * Return the hash of the plan. */ - String getHash(); + String hash(); } diff --git a/ebean-api/src/main/java/io/ebean/meta/MetaQueryPlan.java b/ebean-api/src/main/java/io/ebean/meta/MetaQueryPlan.java index fb2b6d095..c1ae44a82 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetaQueryPlan.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetaQueryPlan.java @@ -10,45 +10,45 @@ public interface MetaQueryPlan { /** * Return the bean type for the query. */ - Class getBeanType(); + Class beanType(); /** * Return the label of the query. */ - String getLabel(); + String label(); /** * Return the profile location for the query. */ - ProfileLocation getProfileLocation(); + ProfileLocation profileLocation(); /** * Return the sql of the query. */ - String getSql(); + String sql(); /** * Return the hash of the plan. */ - String getHash(); + String hash(); /** * Return a description of the bind values. */ - String getBind(); + String bind(); /** * Return the raw plan. */ - String getPlan(); + String plan(); /** * Return the query execution time associated with the bind values capture. */ - long getQueryTimeMicros(); + long queryTimeMicros(); /** * Return the total count of times bind capture has occurred. */ - long getCaptureCount(); + long captureCount(); } diff --git a/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java b/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java index c6ba13516..f95f3248d 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetaTimedMetric.java @@ -9,27 +9,68 @@ public interface MetaTimedMetric extends MetaMetric { /** * Return the metric location if defined. */ - String getLocation(); + String location(); + + /** + * Migrate to location() + */ + @Deprecated + default String getLocation() { + return location(); + } /** * Return the total count. */ - long getCount(); + long count(); + + /** + * Migrate to count() + */ + @Deprecated + default long getCount() { + return count(); + } /** * Return the total execution time in micros. */ - long getTotal(); + long total(); + + /** + * Migrate to total() + */ + @Deprecated + default long getTotal() { + return total(); + } /** * Return the max execution time in micros. */ - long getMax(); + long max(); + + /** + * Migrate to max() + */ + @Deprecated + default long getMax() { + return max(); + } /** * Return the mean execution time in micros. */ - long getMean(); + long mean(); + + + /** + * Migrate to mean() + */ + @Deprecated + default long getMean() { + return mean(); + } /** * Return true if this is the first metrics collection for this query. diff --git a/ebean-api/src/main/java/io/ebean/meta/MetricVisitor.java b/ebean-api/src/main/java/io/ebean/meta/MetricVisitor.java index 18c7a72dc..fac9f050e 100644 --- a/ebean-api/src/main/java/io/ebean/meta/MetricVisitor.java +++ b/ebean-api/src/main/java/io/ebean/meta/MetricVisitor.java @@ -8,22 +8,22 @@ public interface MetricVisitor { /** * Return true if the metrics should be reset. */ - boolean isReset(); + boolean reset(); /** * Return true if we should visit the transaction metrics. */ - boolean isCollectTransactionMetrics(); + boolean collectTransactionMetrics(); /** * Return true if we should visit the ORM and SQL query metrics. */ - boolean isCollectQueryMetrics(); + boolean collectQueryMetrics(); /** * Return true if we should visit the L2 cache metrics. */ - boolean isCollectL2Metrics(); + boolean collectL2Metrics(); /** * Visit has started. diff --git a/ebean-api/src/main/java/io/ebean/meta/QueryPlanInit.java b/ebean-api/src/main/java/io/ebean/meta/QueryPlanInit.java index 3c19caa39..5cce68994 100644 --- a/ebean-api/src/main/java/io/ebean/meta/QueryPlanInit.java +++ b/ebean-api/src/main/java/io/ebean/meta/QueryPlanInit.java @@ -32,7 +32,7 @@ public class QueryPlanInit { * Return the query execution time threshold which must be exceeded to initiate * query plan collection. */ - public long getThresholdMicros() { + public long thresholdMicros() { return thresholdMicros; } @@ -40,7 +40,7 @@ public class QueryPlanInit { * Set the query execution time threshold which must be exceeded to initiate * query plan collection. */ - public void setThresholdMicros(long thresholdMicros) { + public void thresholdMicros(long thresholdMicros) { this.thresholdMicros = thresholdMicros; } @@ -54,14 +54,14 @@ public class QueryPlanInit { /** * Return the specific hashes that we want to collect query plans on. */ - public Set getHashes() { + public Set hashes() { return hashes; } /** * Set the specific hashes that we want to collect query plans on. */ - public void setHashes(Set hashes) { + public void hashes(Set hashes) { this.hashes = hashes; } } diff --git a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java index c4a69b1aa..421e0c151 100644 --- a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java +++ b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java @@ -18,7 +18,7 @@ public class QueryPlanRequest { * have been around for a while (e.g. 5 mins) and so reasonably represent * bind values that match the slowest execution for this query plan. */ - public long getSince() { + public long since() { return since; } @@ -28,14 +28,14 @@ public class QueryPlanRequest { * * @param since The minimum age of the bind values capture. */ - public void setSince(long since) { + public void since(long since) { this.since = since; } /** * Return the maximum number of plans to capture. */ - public int getMaxCount() { + public int maxCount() { return maxCount; } @@ -45,7 +45,7 @@ public class QueryPlanRequest { * Use this to limit how much query plan capturing is done as query * plan capture is actual database load. */ - public void setMaxCount(int maxCount) { + public void maxCount(int maxCount) { this.maxCount = maxCount; } @@ -54,7 +54,7 @@ public class QueryPlanRequest { *

* Query plan collection will stop once this time is exceeded. */ - public long getMaxTimeMillis() { + public long maxTimeMillis() { return maxTimeMillis; } @@ -65,7 +65,7 @@ public class QueryPlanRequest { * this to ensure the query plan capture does not use excessive amount * of time - put too much load on the database. */ - public void setMaxTimeMillis(long maxTimeMillis) { + public void maxTimeMillis(long maxTimeMillis) { this.maxTimeMillis = maxTimeMillis; } } diff --git a/ebean-api/src/main/java/io/ebean/meta/ServerMetrics.java b/ebean-api/src/main/java/io/ebean/meta/ServerMetrics.java index 8f0488d62..b8a50d85d 100644 --- a/ebean-api/src/main/java/io/ebean/meta/ServerMetrics.java +++ b/ebean-api/src/main/java/io/ebean/meta/ServerMetrics.java @@ -10,16 +10,39 @@ public interface ServerMetrics { /** * Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate. */ - List getTimedMetrics(); + List timedMetrics(); + + /** + * Migrate to timedMetrics(). + */ + @Deprecated + default List getTimedMetrics() { + return timedMetrics(); + } /** * Return the query metrics. */ - List getQueryMetrics(); + List queryMetrics(); + + /** + * Migrate to queryMetrics(). + */ + @Deprecated + default List getQueryMetrics() { + return queryMetrics(); + } /** * Return the Counter metrics. */ - List getCountMetrics(); + List countMetrics(); + /** + * Migrate to countMetrics(). + */ + @Deprecated + default List getCountMetrics() { + return countMetrics(); + } } diff --git a/ebean-api/src/main/java/io/ebean/meta/ServerMetricsAsJson.java b/ebean-api/src/main/java/io/ebean/meta/ServerMetricsAsJson.java index f48ecb5b6..7e42302c8 100644 --- a/ebean-api/src/main/java/io/ebean/meta/ServerMetricsAsJson.java +++ b/ebean-api/src/main/java/io/ebean/meta/ServerMetricsAsJson.java @@ -8,12 +8,12 @@ import java.util.Comparator; public interface ServerMetricsAsJson { /** - * Set to false to exclude profile location and sql. + * Set to false in order to exclude profile location and sql. */ ServerMetricsAsJson withExtraAttributes(boolean withLocation); /** - * Set to false to exclude SQL hash. + * Set to false in order to exclude SQL hash. */ ServerMetricsAsJson withHash(boolean withHash); diff --git a/ebean-api/src/main/java/io/ebean/meta/SortMetric.java b/ebean-api/src/main/java/io/ebean/meta/SortMetric.java index 9df2c704b..30104f1ff 100644 --- a/ebean-api/src/main/java/io/ebean/meta/SortMetric.java +++ b/ebean-api/src/main/java/io/ebean/meta/SortMetric.java @@ -32,7 +32,7 @@ public class SortMetric { @Override public int compare(MetaCountMetric o1, MetaCountMetric o2) { - return stringCompare(o1.getName(), o2.getName()); + return stringCompare(o1.name(), o2.name()); } } @@ -43,8 +43,8 @@ public class SortMetric { @Override public int compare(MetaTimedMetric o1, MetaTimedMetric o2) { - int i = stringCompare(o1.getName(), o2.getName()); - return i != 0 ? i : Long.compare(o1.getCount(), o2.getCount()); + int i = stringCompare(o1.name(), o2.name()); + return i != 0 ? i : Long.compare(o1.count(), o2.count()); } } @@ -55,7 +55,7 @@ public class SortMetric { @Override public int compare(MetaTimedMetric o1, MetaTimedMetric o2) { - return Long.compare(o2.getCount(), o1.getCount()); + return Long.compare(o2.count(), o1.count()); } } @@ -66,7 +66,7 @@ public class SortMetric { @Override public int compare(MetaTimedMetric o1, MetaTimedMetric o2) { - return Long.compare(o2.getTotal(), o1.getTotal()); + return Long.compare(o2.total(), o1.total()); } } @@ -77,7 +77,7 @@ public class SortMetric { @Override public int compare(MetaTimedMetric o1, MetaTimedMetric o2) { - return Long.compare(o2.getMean(), o1.getMean()); + return Long.compare(o2.mean(), o1.mean()); } } @@ -88,7 +88,7 @@ public class SortMetric { @Override public int compare(MetaTimedMetric o1, MetaTimedMetric o2) { - return Long.compare(o2.getMax(), o1.getMax()); + return Long.compare(o2.max(), o1.max()); } } } diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index 6b6e9511a..c47f7fe08 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT @@ -26,7 +26,7 @@ io.ebean ebean-core - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT provided diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index 446c823f2..fff064c90 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean bom @@ -14,12 +14,12 @@ 1.0 - 1.0 - 12.4.0 + 1.1 + 12.11.0 4.1 7.0 - 12.9.0 - 12.9.1 + 12.11.0 + 12.11.0 @@ -81,88 +81,88 @@ io.ebean ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-api - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-core - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-core-type - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-ddl-generator - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-externalmapping-api - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-externalmapping-xml - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-autotune - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-querybean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean querybean-generator - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT provided io.ebean kotlin-querybean-generator - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT provided io.ebean ebean-test - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT test io.ebean ebean-postgis - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-redis - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index 9d9d22dfc..d8eab3e25 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT diff --git a/ebean-core-type/src/main/java/io/ebean/core/type/DataBinder.java b/ebean-core-type/src/main/java/io/ebean/core/type/DataBinder.java index be6a3b559..b08bb529a 100644 --- a/ebean-core-type/src/main/java/io/ebean/core/type/DataBinder.java +++ b/ebean-core-type/src/main/java/io/ebean/core/type/DataBinder.java @@ -163,4 +163,15 @@ public interface DataBinder { * Bind an array value. */ void setArray(String arrayType, Object[] elements) throws SQLException; + + /** + * Push json from dirty detection to be available for binding. + */ + void pushJson(String json); + + /** + * Pop json made during dirty detection for scalarType binding. + */ + String popJson(); + } diff --git a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java index a8ace12be..ef61b9122 100644 --- a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java +++ b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java @@ -48,4 +48,14 @@ public interface DataReader { Object getObject() throws SQLException; InputStream getBinaryStream() throws SQLException; + + /** + * Push json from dirty detection to be available for binding. + */ + void pushJson(String json); + + /** + * Pop json made during dirty detection for scalarType binding. + */ + String popJson(); } diff --git a/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java b/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java index a971a0115..6c01811c5 100644 --- a/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java +++ b/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java @@ -2,6 +2,7 @@ package io.ebean.core.type; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; + import io.ebean.text.StringFormatter; import io.ebean.text.StringParser; @@ -34,6 +35,10 @@ import java.sql.SQLException; */ public interface ScalarType extends StringParser, StringFormatter, ScalarDataReader { + default boolean isJsonMapper() { + return false; + } + /** * Return true if this is a binary type and can not support parse() and format() from/to string. * This allows Ebean to optimise marshalling types to string. diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 5febb1ea6..b99bf98fb 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean-core @@ -52,13 +52,13 @@ io.avaje classpath-scanner - 4.2 + 6.0 io.ebean ebean-migration-auto - 1.0 + 1.1 @@ -72,7 +72,7 @@ io.ebean ebean-ddl-generator - 12.9.4-RC1 + 12.11.0 test @@ -87,19 +87,19 @@ io.ebean ebean-api - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-core-type - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT io.ebean ebean-externalmapping-api - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT @@ -302,7 +302,7 @@ io.ebean ebean-maven-plugin - 12.9.1 + 12.10.0 test diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java index afb1e44b8..64a0a0287 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindParams.java @@ -4,11 +4,7 @@ import io.ebeaninternal.server.persist.MultiValueWrapper; import io.ebeaninternal.server.querydefn.NaturalKeyBindParam; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; /** @@ -54,12 +50,11 @@ public class BindParams implements Serializable { positionedParameters.clear(); } - public int queryBindHash() { - int hc = namedParameters.hashCode(); - for (Param positionedParameter : positionedParameters) { - hc = hc * 92821 + positionedParameter.hashCode(); + public void queryBindHash(BindValuesKey key) { + key.add(positionedParameters.size()); + for (Param param : positionedParameters) { + param.queryBindHash(key); } - return hc; } /** @@ -424,7 +419,14 @@ public class BindParams implements Serializable { @Override public boolean equals(Object o) { - return o != null && (o == this || (o instanceof Param) && hashCode() == o.hashCode()); + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Param param = (Param) o; + return isInParam == param.isInParam && isOutParam == param.isOutParam && type == param.type && Objects.equals(inValue, param.inValue); + } + + void queryBindHash(BindValuesKey key) { + key.add(isInParam).add(isOutParam).add(type).add(inValue); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/BindValuesKey.java b/ebean-core/src/main/java/io/ebeaninternal/api/BindValuesKey.java new file mode 100644 index 000000000..4a6eac324 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/api/BindValuesKey.java @@ -0,0 +1,35 @@ +package io.ebeaninternal.api; + +import java.util.ArrayList; +import java.util.List; + +/** + * BindValues used for L2 query cache key matching. + *

+ * The equals/hashCode implementation must meet the requirement that the query bind values + * match for L2 query cache hit (given the query plan hash is already a match). + */ +public class BindValuesKey { + + private final List values = new ArrayList<>(); + + /** + * Add a bind value. + */ + public BindValuesKey add(Object value) { + values.add(value); + return this; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BindValuesKey && ((BindValuesKey) obj).values.equals(values); + } + + @Override + public int hashCode() { + return values.hashCode(); + } + + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java index c14cb88d7..faddb7bee 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/HashQuery.java @@ -6,15 +6,14 @@ package io.ebeaninternal.api; public class HashQuery { private final CQueryPlanKey planHash; - - private final int bindHash; + private final BindValuesKey bindValuesKey; /** * Create the HashQuery. */ - public HashQuery(CQueryPlanKey planHash, int bindHash) { + public HashQuery(CQueryPlanKey planHash, BindValuesKey bindValuesKey) { this.planHash = planHash; - this.bindHash = bindHash; + this.bindValuesKey = bindValuesKey; } @Override @@ -25,7 +24,7 @@ public class HashQuery { @Override public int hashCode() { int hc = 92821 * planHash.hashCode(); - hc = 92821 * hc + bindHash; + hc = 92821 * hc + bindValuesKey.hashCode(); return hc; } @@ -37,8 +36,7 @@ public class HashQuery { if (!(obj instanceof HashQuery)) { return false; } - HashQuery e = (HashQuery) obj; - return e.bindHash == bindHash && e.planHash.equals(planHash); + return e.bindValuesKey.equals(bindValuesKey) && e.planHash.equals(planHash); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java index fef666a63..9d72463a5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadBeanRequest.java @@ -92,9 +92,7 @@ public class LoadBeanRequest extends LoadRequest { * Return the list of Id values for the beans in the lazy load buffer. */ public List getIdList() { - List idList = new ArrayList<>(); - BeanDescriptor desc = loadBuffer.getBeanDescriptor(); for (EntityBeanIntercept ebi : batch) { idList.add(desc.getId(ebi.getOwner())); @@ -106,10 +104,8 @@ public class LoadBeanRequest extends LoadRequest { * Configure the query for lazy loading execution. */ public void configureQuery(SpiQuery query, List idList) { - query.setMode(SpiQuery.Mode.LAZYLOAD_BEAN); query.setPersistenceContext(loadBuffer.getPersistenceContext()); - String mode = isLazy() ? "+lazy" : "+query"; query.setLoadDescription(mode, getDescription()); @@ -117,9 +113,7 @@ public class LoadBeanRequest extends LoadRequest { // cascade the batch size (if set) for further lazy loading query.setLazyLoadBatchSize(getBatchSize()); } - loadBuffer.configureQuery(query, lazyLoadProperty); - if (idList.size() == 1) { query.where().idEq(idList.get(0)); } else { @@ -131,19 +125,16 @@ public class LoadBeanRequest extends LoadRequest { * Load the beans into the L2 cache if that is requested and check for load failures due to deletes. */ public void postLoad(List list) { - Set loadedIds = new HashSet<>(); - BeanDescriptor desc = loadBuffer.getBeanDescriptor(); // collect Ids and maybe load bean cache - for (Object aList : list) { - EntityBean loadedBean = (EntityBean) aList; + for (Object bean : list) { + EntityBean loadedBean = (EntityBean) bean; loadedIds.add(desc.getId(loadedBean)); } if (isLoadCache()) { desc.cacheBeanPutAll(list); } - if (lazyLoadProperty != null) { for (EntityBeanIntercept ebi : batch) { // check if the underlying row in DB was deleted. Mark the bean as 'failed' if diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java index 007e9f29f..8fe986259 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiExpression.java @@ -54,9 +54,9 @@ public interface SpiExpression extends Expression { void queryPlanHash(StringBuilder builder); /** - * Return the hash value for the values that will be bound. + * Build the key for bind values of the query. */ - int queryBindHash(); + void queryBindKey(BindValuesKey key); /** * Return true if the expression is the same with respect to bind values. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java index 12942e7d6..5ba0b6eeb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -629,13 +629,11 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod CQueryPlanKey prepare(SpiOrmQueryRequest request); /** - * Calculate a hash based on the bind values used in the query. + * Build the key for the bind values used in the query (for l2 query cache). *

- * Combined with queryPlanHash() to return getQueryHash (a unique hash for a - * query). - *

+ * Combined with queryPlanHash() to return queryHash (a unique key for a query). */ - int queryBindHash(); + void queryBindKey(BindValuesKey key); /** * Identifies queries that are exactly the same including bind variables. diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryPlan.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryPlan.java index 96a1eade7..0b4c3ea0a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryPlan.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryPlan.java @@ -18,7 +18,7 @@ public interface SpiQueryPlan { String getName(); /** - * The hash for the query plan. + * The hash of the sql. */ String getHash(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java index 67844c631..a88f1bbe9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/AbstractSqlQueryRequest.java @@ -23,25 +23,16 @@ import javax.persistence.PersistenceException; public abstract class AbstractSqlQueryRequest implements CancelableQuery { protected final SpiSqlBinding query; - protected final SpiEbeanServer server; - protected SpiTransaction transaction; - private boolean createdTransaction; - protected String sql; - protected ResultSet resultSet; - protected String bindLog = ""; - protected PreparedStatement pstmt; - protected long startNano; - private final ReentrantLock lock = new ReentrantLock(); - + /** * Create the BeanFindRequest. */ @@ -161,7 +152,8 @@ public abstract class AbstractSqlQueryRequest implements CancelableQuery { this.bindLog = binder.bind(bindParams, pstmt, conn); } if (isLogSql()) { - transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")")); + long micros = (System.nanoTime() - startNano) / 1000L; + transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ") --micros(", micros + ")")); } } finally { lock.unlock(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java index cdefcc294..2c8bbfecc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java @@ -255,6 +255,9 @@ public class DefaultContainer implements SpiContainer { } throw new RuntimeException("DataSource not set?"); } + if (config.skipDataSourceCheck()) { + return true; + } try (Connection connection = config.getDataSource().getConnection()) { if (connection.getAutoCommit()) { logger.warn("DataSource [{}] has autoCommit defaulting to true!", config.getName()); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java index 051d331ba..20d7ec223 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java @@ -18,8 +18,8 @@ class DefaultQueryPlanListener implements QueryPlanListener { String dbName = capture.getDatabase().getName(); for (MetaQueryPlan plan : capture.getPlans()) { log.info("queryPlan db:{} label:{} queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}", - dbName, plan.getLabel(), plan.getQueryTimeMicros(), plan.getProfileLocation(), - plan.getSql(), plan.getBind(), plan.getPlan()); + dbName, plan.label(), plan.queryTimeMicros(), plan.profileLocation(), + plan.sql(), plan.bind(), plan.plan()); } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 87580f90b..4f0eed00b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -397,6 +397,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (dbSchema != null) { migrationRunner.setDefaultDbSchema(dbSchema); } + migrationRunner.setPlatform(config.getDatabasePlatform().getPlatform().base().name().toLowerCase()); migrationRunner.loadProperties(config.getProperties()); migrationRunner.run(config.getDataSource()); } @@ -415,8 +416,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private void collectQueryPlans() { QueryPlanRequest request = new QueryPlanRequest(); - request.setMaxCount(config.getQueryPlanCaptureMaxCount()); - request.setMaxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis()); + request.maxCount(config.getQueryPlanCaptureMaxCount()); + request.maxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis()); // obtains query explain plans ... List plans = metaInfoManager.queryPlanCollectNow(request); @@ -1283,14 +1284,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } @Override - public boolean exists(Query ormQuery, Transaction transaction) { - Query ormQueryCopy = ormQuery.copy(); - ormQueryCopy.setMaxRows(1); + public boolean exists(Query ormQuery, Transaction transaction) { + Query ormQueryCopy = ormQuery.copy().setMaxRows(1); SpiOrmQueryRequest request = createQueryRequest(Type.ID_LIST, ormQueryCopy, transaction); try { request.initTransIfRequired(); - List ids = request.findIds(); - return !ids.isEmpty(); + return !request.findIds().isEmpty(); } finally { request.endTransIfRequired(); } @@ -2056,17 +2055,15 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return transactionManager; } - public void register(BeanPersistController c) { - List> list = beanDescriptorManager.getBeanDescriptorList(); - for (BeanDescriptor aList : list) { - aList.register(c); + public void register(BeanPersistController controller) { + for (BeanDescriptor desc : beanDescriptorManager.getBeanDescriptorList()) { + desc.register(controller); } } public void deregister(BeanPersistController c) { - List> list = beanDescriptorManager.getBeanDescriptorList(); - for (BeanDescriptor aList : list) { - aList.deregister(c); + for (BeanDescriptor desc : beanDescriptorManager.getBeanDescriptorList()) { + desc.deregister(c); } } @@ -2329,13 +2326,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public void visitMetrics(MetricVisitor visitor) { visitor.visitStart(); - if (visitor.isCollectTransactionMetrics()) { + if (visitor.collectTransactionMetrics()) { transactionManager.visitMetrics(visitor); } - if (visitor.isCollectL2Metrics()) { + if (visitor.collectL2Metrics()) { serverCacheManager.visitMetrics(visitor); } - if (visitor.isCollectQueryMetrics()) { + if (visitor.collectQueryMetrics()) { beanDescriptorManager.visitMetrics(visitor); dtoBeanManager.visitMetrics(visitor); relationalQueryEngine.visitMetrics(visitor); @@ -2352,7 +2349,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { List queryPlanInit(QueryPlanInit initRequest) { if (initRequest.isAll()) { - queryPlanManager.setDefaultThreshold(initRequest.getThresholdMicros()); + queryPlanManager.setDefaultThreshold(initRequest.thresholdMicros()); } return beanDescriptorManager.queryPlanInit(initRequest); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java index 2b1a55f6e..c023f2d0d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java @@ -28,11 +28,8 @@ public final class DtoQueryRequest extends AbstractSqlQueryRequest { private static final String ENC_PREFIX_UPPER = EncryptAlias.PREFIX.toUpperCase(); private final SpiDtoQuery query; - private final DtoQueryEngine queryEngine; - private DtoQueryPlan plan; - private DataReader dataReader; DtoQueryRequest(SpiEbeanServer server, DtoQueryEngine engine, SpiDtoQuery query) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java index 972ee2754..5274b4ced 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetrics.java @@ -78,11 +78,11 @@ class DumpMetrics { out("-- Dumping metrics for " + server.getName() + " -- "); ServerMetrics serverMetrics = server.getMetaInfoManager().collectMetrics(); - for (MetaTimedMetric metric : serverMetrics.getTimedMetrics()) { + for (MetaTimedMetric metric : serverMetrics.timedMetrics()) { log(metric); } - List countMetrics = serverMetrics.getCountMetrics(); + List countMetrics = serverMetrics.countMetrics(); if (!countMetrics.isEmpty()) { out("\n-- Counters --"); countMetrics.sort(SortMetric.COUNT_NAME); @@ -91,7 +91,7 @@ class DumpMetrics { } } - List queryMetrics = serverMetrics.getQueryMetrics(); + List queryMetrics = serverMetrics.queryMetrics(); if (!queryMetrics.isEmpty()) { out("\n-- Queries --"); queryMetrics.sort(sortBy); @@ -104,8 +104,8 @@ class DumpMetrics { private void logCount(MetaCountMetric metric) { StringBuilder sb = new StringBuilder(); - sb.append(padNameTimed(metric.getName())).append(" "); - sb.append(" count:").append(pad(metric.getCount())); + sb.append(padNameTimed(metric.name())).append(" "); + sb.append(" count:").append(pad(metric.count())); out(sb.toString()); } @@ -120,38 +120,38 @@ class DumpMetrics { appendQueryName(metric, sb); appendCounters(metric, sb); if (dumpHash) { - sb.append("\n hash:").append(metric.getHash()); + sb.append("\n hash:").append(metric.hash()); } appendProfileAndSql(metric, sb); out(sb.toString()); } private void appendQueryName(MetaQueryMetric metric, StringBuilder sb) { - sb.append("query:").append(padName(metric.getName())).append(" "); + sb.append("query:").append(padName(metric.name())).append(" "); } private void appendProfileAndSql(MetaQueryMetric metric, StringBuilder sb) { - String location = metric.getLocation(); + String location = metric.location(); if (dumpLoc && location != null) { sb.append("\n loc:").append(location); } if (dumpSql) { - sb.append(" \n\n sql:").append(metric.getSql()).append("\n\n"); + sb.append(" \n\n sql:").append(metric.sql()).append("\n\n"); } } private void log(MetaTimedMetric metric) { StringBuilder sb = new StringBuilder(); - sb.append(padNameTimed(metric.getName())).append(" "); + sb.append(padNameTimed(metric.name())).append(" "); appendCounters(metric, sb); out(sb.toString()); } private void appendCounters(MetaTimedMetric timedMetric, StringBuilder sb) { - sb.append(" count:").append(pad(timedMetric.getCount())) - .append(" total:").append(pad(timedMetric.getTotal())) - .append(" mean:").append(pad(timedMetric.getMean())) - .append(" max:").append(pad(timedMetric.getMax())); + sb.append(" count:").append(pad(timedMetric.count())) + .append(" total:").append(pad(timedMetric.total())) + .append(" mean:").append(pad(timedMetric.mean())) + .append(" max:").append(pad(timedMetric.max())); } private String padName(String name) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java index 9854cafea..4bc1e5035 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsData.java @@ -31,9 +31,9 @@ class DumpMetricsData { private void collect(ServerMetrics serverMetrics) { - final List timedMetrics = serverMetrics.getTimedMetrics(); - final List countMetrics = serverMetrics.getCountMetrics(); - final List queryMetrics = serverMetrics.getQueryMetrics(); + final List timedMetrics = serverMetrics.timedMetrics(); + final List countMetrics = serverMetrics.countMetrics(); + final List queryMetrics = serverMetrics.queryMetrics(); for (MetaTimedMetric metric : timedMetrics) { add(metric); @@ -47,7 +47,7 @@ class DumpMetricsData { } private MetricData create(MetaMetric metric) { - MetricData data = new MetricData(metric.getName()); + MetricData data = new MetricData(metric.name()); list.add(data); return data; } @@ -55,30 +55,30 @@ class DumpMetricsData { private void add(MetaTimedMetric metric) { final MetricData data = create(metric); appendCounters(data, metric); - data.setLoc(metric.getLocation()); + data.setLoc(metric.location()); } private void addCount(MetaCountMetric metric) { final MetricData data = create(metric); - data.setCount(metric.getCount()); + data.setCount(metric.count()); } private void addQuery(MetaQueryMetric metric) { final MetricData data = create(metric); appendCounters(data, metric); appendLocationAndSql(data, metric); - data.setHash(metric.getHash()); + data.setHash(metric.hash()); } private void appendLocationAndSql(MetricData data, MetaQueryMetric metric) { - data.setLoc(metric.getLocation()); - data.setSql(metric.getSql()); + data.setLoc(metric.location()); + data.setSql(metric.sql()); } private void appendCounters(MetricData data, MetaTimedMetric timedMetric) { - data.setCount(timedMetric.getCount()); - data.setTotal(timedMetric.getTotal()); - data.setMean(timedMetric.getMean()); - data.setMax(timedMetric.getMax()); + data.setCount(timedMetric.count()); + data.setTotal(timedMetric.total()); + data.setMean(timedMetric.mean()); + data.setMax(timedMetric.max()); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java index ed13151c4..90200f79b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DumpMetricsJson.java @@ -84,11 +84,11 @@ class DumpMetricsJson implements ServerMetricsAsJson { private void collect(ServerMetrics serverMetrics) { try { start(); - for (MetaTimedMetric metric : serverMetrics.getTimedMetrics()) { + for (MetaTimedMetric metric : serverMetrics.timedMetrics()) { logTimed(metric); } - List countMetrics = serverMetrics.getCountMetrics(); + List countMetrics = serverMetrics.countMetrics(); if (!countMetrics.isEmpty()) { if (sortBy != null) { countMetrics.sort(SortMetric.COUNT_NAME); @@ -98,7 +98,7 @@ class DumpMetricsJson implements ServerMetricsAsJson { } } - List queryMetrics = serverMetrics.getQueryMetrics(); + List queryMetrics = serverMetrics.queryMetrics(); if (!queryMetrics.isEmpty()) { if (sortBy != null) { queryMetrics.sort(sortBy); @@ -170,7 +170,7 @@ class DumpMetricsJson implements ServerMetricsAsJson { } objStart(); key("name"); - val(metric.getName()); + val(metric.name()); } private void metricEnd() throws IOException { @@ -180,7 +180,7 @@ class DumpMetricsJson implements ServerMetricsAsJson { private void logCount(MetaCountMetric metric) throws IOException { metricStart(metric); key("count"); - val(metric.getCount()); + val(metric.count()); metricEnd(); } @@ -188,7 +188,7 @@ class DumpMetricsJson implements ServerMetricsAsJson { metricStart(metric); appendTiming(metric); if (isIncludeDetail(metric)) { - appendExtra("loc", metric.getLocation()); + append("loc", metric.location()); } metricEnd(); } @@ -197,11 +197,11 @@ class DumpMetricsJson implements ServerMetricsAsJson { metricStart(metric); appendTiming(metric); if (withHash) { - appendExtra("hash", metric.getHash()); + append("hash", metric.hash()); } if (isIncludeDetail(metric)) { - appendExtra("loc", metric.getLocation()); - appendExtra("sql", metric.getSql()); + append("loc", metric.location()); + append("sql", metric.sql()); } metricEnd(); } @@ -210,7 +210,7 @@ class DumpMetricsJson implements ServerMetricsAsJson { return includeExtraAttributes == 2 || includeExtraAttributes == 1 && metric.initialCollection(); } - private void appendExtra(String key, String val) throws IOException { + private void append(String key, String val) throws IOException { if (val != null) { key(key); val(val); @@ -218,13 +218,14 @@ class DumpMetricsJson implements ServerMetricsAsJson { } private void appendTiming(MetaTimedMetric timedMetric) throws IOException { - key("count"); - val(timedMetric.getCount()); - key("total"); - val(timedMetric.getTotal()); - key("mean"); - val(timedMetric.getMean()); - key("max"); - val(timedMetric.getMax()); + append("count", timedMetric.count()); + append("total", timedMetric.total()); + append("mean", timedMetric.mean()); + append("max", timedMetric.max()); + } + + private void append(String key, long value) throws IOException { + key(key); + val(value); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index a5130ef22..ef3874008 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -18,11 +18,8 @@ import java.util.function.Predicate; public final class RelationalQueryRequest extends AbstractSqlQueryRequest { private final RelationalQueryEngine queryEngine; - private String[] propertyNames; - private int estimateCapacity; - private int rows; /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java index f7c1a5fea..aba5533e0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java @@ -1,6 +1,5 @@ package io.ebeaninternal.server.core.bootup; -import io.avaje.classpath.scanner.ClassFilter; import io.ebean.annotation.DocStore; import io.ebean.config.DatabaseConfig; import io.ebean.config.IdGenerator; @@ -27,27 +26,23 @@ import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.Table; import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; /** * Interesting classes for a EbeanServer such as Embeddable, Entity, * ScalarTypes, Finders, Listeners and Controllers. */ -public class BootupClasses implements ClassFilter { +public class BootupClasses implements Predicate> { private static final Logger logger = LoggerFactory.getLogger(BootupClasses.class); private final List> embeddableList = new ArrayList<>(); - private final List> entityList = new ArrayList<>(); - private final List>> scalarTypeList = new ArrayList<>(); - private final List>> scalarConverterList = new ArrayList<>(); - private final List>> attributeConverterList = new ArrayList<>(); // The following objects are instantiated on first request @@ -55,19 +50,12 @@ public class BootupClasses implements ClassFilter { // instance list, that holds the instance. Once a class is instantiated // (or added) it will get removed from the candidate list private final List> idGeneratorCandidates = new ArrayList<>(); - private final List> beanPersistControllerCandidates = new ArrayList<>(); - private final List> beanPostLoadCandidates = new ArrayList<>(); - private final List> beanPostConstructListenerCandidates = new ArrayList<>(); - private final List> beanFindControllerCandidates = new ArrayList<>(); - private final List> beanPersistListenerCandidates = new ArrayList<>(); - private final List> beanQueryAdapterCandidates = new ArrayList<>(); - private final List> serverConfigStartupCandidates = new ArrayList<>(); private final List idGeneratorInstances = new ArrayList<>(); @@ -98,7 +86,7 @@ public class BootupClasses implements ClassFilter { public BootupClasses(List> list) { if (list != null) { for (Class cls : list) { - isMatch(cls); + test(cls); } } } @@ -188,13 +176,11 @@ public class BootupClasses implements ClassFilter { } public void addChangeLogInstances(DatabaseConfig config) { - readAuditPrepare = config.getReadAuditPrepare(); readAuditLogger = config.getReadAuditLogger(); changeLogPrepare = config.getChangeLogPrepare(); changeLogListener = config.getChangeLogListener(); changeLogRegister = config.getChangeLogRegister(); - // if not already set create the implementations found // via classpath scanning if (readAuditPrepare == null && readAuditPrepareClass != null) { @@ -341,18 +327,14 @@ public class BootupClasses implements ClassFilter { } @Override - public boolean isMatch(Class cls) { - + public boolean test(Class cls) { if (isEmbeddable(cls)) { embeddableList.add(cls); - } else if (isEntity(cls)) { entityList.add(cls); - } else { return isInterestingInterface(cls); } - return true; } @@ -364,7 +346,6 @@ public class BootupClasses implements ClassFilter { */ @SuppressWarnings("unchecked") private boolean isInterestingInterface(Class cls) { - if (Modifier.isAbstract(cls.getModifiers())) { // do not include abstract classes as we can // not instantiate them diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 63599b7ad..055f2605a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -1559,7 +1559,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { void queryPlanInit(QueryPlanInit request, List list) { for (CQueryPlan queryPlan : queryPlanCache.values()) { if (request.includeHash(queryPlan.getHash())) { - queryPlan.queryPlanInit(request.getThresholdMicros()); + queryPlan.queryPlanInit(request.thresholdMicros()); list.add(queryPlan.createMeta(null, null)); } } @@ -1572,7 +1572,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { iudMetrics.visit(visitor); for (CQueryPlan queryPlan : queryPlanCache.values()) { if (!queryPlan.isEmptyStats()) { - visitor.visitQuery(queryPlan.getSnapshot(visitor.isReset())); + visitor.visitQuery(queryPlan.getSnapshot(visitor.reset())); } } } @@ -2022,7 +2022,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { public boolean isTableManaged(String tableName) { return owner.isTableManaged(tableName); } - + /** * Return the order column property. */ @@ -2519,7 +2519,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { if (propName.indexOf('(') > -1) { return findSqlTreeFormula(propName, path); } - return _findBeanProperty(propName); + return findProperty(propName); } /** @@ -3198,9 +3198,9 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { public void checkMutableProperties(EntityBeanIntercept ebi) { for (BeanProperty beanProperty : propertiesMutable) { int propertyIndex = beanProperty.getPropertyIndex(); - if (!ebi.isDirtyProperty(propertyIndex) && ebi.isLoadedProperty(propertyIndex)) { + if (ebi.isLoadedProperty(propertyIndex)) { Object value = beanProperty.getValue(ebi.getOwner()); - if (value != null && beanProperty.isDirtyValue(value)) { + if (beanProperty.checkMutable(value, ebi.isDirtyProperty(propertyIndex), ebi)) { // mutable scalar value which is considered dirty so mark // it as such so that it is included in an update ebi.markPropertyAsChanged(propertyIndex); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java index af6e7b9e4..5def0dc64 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java @@ -42,11 +42,7 @@ class BeanEmbeddedMetaFactory { int dbScale = dbScale(column, sourceProperties[i]); String colDefn = getDbColumnDefn(column, sourceProperties[i]); BeanPropertyOverride overrides = new BeanPropertyOverride(dbColumn, dbNullable, dbLength, dbScale, colDefn); - if (sourceProperties[i] instanceof BeanPropertyAssocOne) { - embeddedProperties[i] = new BeanPropertyAssocOne((BeanPropertyAssocOne)sourceProperties[i], overrides); - } else { - embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides); - } + embeddedProperties[i] = sourceProperties[i].override(overrides); } return new BeanEmbeddedMeta(embeddedProperties); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index 8793942b6..413d0b460 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonToken; import io.ebean.ValuePair; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; +import io.ebean.bean.MutableValueInfo; import io.ebean.bean.PersistenceContext; import io.ebean.config.EncryptKey; import io.ebean.config.dbplatform.DbEncryptFunction; @@ -13,6 +14,7 @@ import io.ebean.core.type.DocPropertyType; import io.ebean.core.type.ScalarType; import io.ebean.plugin.Property; import io.ebean.text.StringParser; +import io.ebean.text.TextException; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.api.SpiQuery; @@ -31,11 +33,7 @@ import io.ebeaninternal.server.properties.BeanPropertySetter; import io.ebeaninternal.server.query.STreeProperty; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.query.SqlJoinType; -import io.ebeaninternal.server.type.DataBind; -import io.ebeaninternal.server.type.LocalEncryptedType; -import io.ebeaninternal.server.type.ScalarTypeBoolean; -import io.ebeaninternal.server.type.ScalarTypeEnum; -import io.ebeaninternal.server.type.ScalarTypeLogicalType; +import io.ebeaninternal.server.type.*; import io.ebeaninternal.util.ValueUtil; import io.ebeanservice.docstore.api.mapping.DocMappingBuilder; import io.ebeanservice.docstore.api.mapping.DocPropertyMapping; @@ -52,6 +50,7 @@ import java.io.IOException; import java.lang.reflect.Field; import java.sql.SQLException; import java.sql.Types; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -366,13 +365,14 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { return InternString.intern(s); } + public BeanProperty override(BeanPropertyOverride override) { + return new BeanProperty(this, override); + } + /** - * Create a Matching BeanProperty with some attributes overridden. - *

- * Primarily for supporting Embedded beans with overridden dbColumn - * mappings. + * Create a Matching BeanProperty with some attributes overridden for Embedded beans. */ - public BeanProperty(BeanProperty source, BeanPropertyOverride override) { + protected BeanProperty(BeanProperty source, BeanPropertyOverride override) { this.descriptor = source.descriptor; this.propertyIndex = source.propertyIndex; this.name = source.getName(); @@ -642,15 +642,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { } public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { - try { - Object value = scalarType.read(ctx.getDataReader()); - if (bean != null) { - setValue(bean, value); - } - return value; - } catch (Exception e) { - throw new PersistenceException("Error readSet on " + descriptor + "." + name, e); - } + return readSet(ctx.getDataReader(), bean); } @SuppressWarnings("unchecked") @@ -828,6 +820,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { return scalarType.parse(value); } + /** + * creates a mutableHash for the given JSON value. + */ + public MutableValueInfo createMutableInfo(String json) { + throw new UnsupportedOperationException(); + } + /** * Read the value for this property from L2 cache entry and set it to the bean. *

@@ -909,7 +908,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { /** * Return the name of the property. */ - @Override @Nonnull + @Override + @Nonnull public String getName() { return name; } @@ -1018,8 +1018,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { * Return true if the mutable value is considered dirty. * This is only used for 'mutable' scalar types like hstore etc. */ - boolean isDirtyValue(Object value) { - return scalarType.isDirty(value); + boolean checkMutable(Object value, boolean alreadyDirty, EntityBeanIntercept ebi) { + return alreadyDirty || value != null && scalarType.isDirty(value); } /** @@ -1281,7 +1281,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { @Override public Object localEncrypt(Object value) { - return ((LocalEncryptedType)scalarType).localEncrypt(value); + return ((LocalEncryptedType) scalarType).localEncrypt(value); } /** @@ -1394,7 +1394,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { /** * Return the property type. */ - @Override @Nonnull + @Override + @Nonnull public Class getPropertyType() { return propertyType; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index ca81267eb..9a69b8c9c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -94,10 +94,12 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr } } - /** - * Copy constructor for ManyToOne inside Embeddable. - */ - public BeanPropertyAssocOne(BeanPropertyAssocOne source, BeanPropertyOverride override) { + @Override + public BeanPropertyAssocOne override(BeanPropertyOverride override) { + return new BeanPropertyAssocOne<>(this, override); + } + + protected BeanPropertyAssocOne(BeanPropertyAssocOne source, BeanPropertyOverride override) { super(source, override); primaryKeyExport = source.primaryKeyExport; primaryKeyJoin = source.primaryKeyJoin; @@ -255,12 +257,8 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr } private SqlUpdate deleteByParentIdList(List parentIds) { - - StringBuilder sb = new StringBuilder(100); - sb.append(deleteByParentIdInSql); - sb.append(targetIdBinder.getIdInValueExpr(false, parentIds.size())); - - DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString()); + String sql = deleteByParentIdInSql + targetIdBinder.getIdInValueExpr(false, parentIds.size()); + DefaultSqlUpdate delete = new DefaultSqlUpdate(sql); bindParentIds(delete, parentIds); return delete; } @@ -501,7 +499,7 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr return targetDescriptor.getIdProperty(); } - ScalarType getIdScalarType() { + ScalarType getIdScalarType() { return targetDescriptor.getIdProperty().scalarType; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonBasic.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonBasic.java new file mode 100644 index 000000000..6593186a6 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonBasic.java @@ -0,0 +1,58 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.bean.EntityBean; +import io.ebean.core.type.DataReader; +import io.ebean.text.TextException; +import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Map; + +/** + * A DbJson property that does not use Jackson ObjectMapper. + */ +public class BeanPropertyJsonBasic extends BeanProperty { + + public BeanPropertyJsonBasic(BeanDescriptor descriptor, DeployBeanProperty deploy) { + super(descriptor, deploy); + } + + protected BeanPropertyJsonBasic(BeanProperty source, BeanPropertyOverride override) { + super(source, override); + } + + @Override + public BeanProperty override(BeanPropertyOverride override) { + return new BeanPropertyJsonBasic(this, override); + } + + protected Object checkForEmpty(EntityBean bean) { + final Object value = getValue(bean); + if (value instanceof Collection && ((Collection) value).isEmpty() + || value instanceof Map && ((Map) value).isEmpty()) { + return value; + } + return null; + } + + @Override + public Object readSet(DataReader reader, EntityBean bean) throws SQLException { + try { + Object value = scalarType.read(reader); + if (value == null) { + value = checkForEmpty(bean); + } + if (bean != null) { + setValue(bean, value); + } + return value; + } catch (TextException e) { + throw e; + } catch (Exception e) { + throw new PersistenceException("Error readSet on " + descriptor + "." + name, e); + } + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java new file mode 100644 index 000000000..fdea2e449 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java @@ -0,0 +1,208 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.annotation.MutationDetection; +import io.ebean.bean.EntityBean; +import io.ebean.bean.EntityBeanIntercept; +import io.ebean.bean.MutableValueInfo; +import io.ebean.bean.MutableValueNext; +import io.ebean.core.type.DataReader; +import io.ebean.core.type.ScalarType; +import io.ebean.text.TextException; +import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import io.ebeaninternal.server.util.Checksum; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; +import java.util.Objects; + +/** + * Handle json property with MutationDetection of SOURCE or HASH only. + */ +public class BeanPropertyJsonMapper extends BeanPropertyJsonBasic { + + private final boolean sourceDetection; + + public BeanPropertyJsonMapper(BeanDescriptor desc, DeployBeanProperty deployProp) { + super(desc, deployProp); + this.sourceDetection = deployProp.getMutationDetection() == MutationDetection.SOURCE; + } + + private BeanPropertyJsonMapper(BeanPropertyJsonMapper source, BeanPropertyOverride override) { + super(source, override); + this.sourceDetection = source.sourceDetection; + } + + @Override + public BeanProperty override(BeanPropertyOverride override) { + return new BeanPropertyJsonMapper(this, override); + } + + @Override + public MutableValueInfo createMutableInfo(String json) { + if (sourceDetection) { + return new SourceMutableValue(scalarType, json); + } else { + return new ChecksumMutableValue(scalarType, json); + } + } + + /** + * Next when no prior MutableValueInfo. + */ + private MutableValueNext next(String json) { + if (sourceDetection) { + return new SourceMutableValue(scalarType, json); + } else { + return new NextPair(json, new ChecksumMutableValue(scalarType, json)); + } + } + + /** + * Return true if the json property is considered dirty. + */ + @Override + boolean checkMutable(Object value, boolean alreadyDirty, EntityBeanIntercept ebi) { + // mutation detection based on json content or checksum of json content + // only perform serialisation to json once + final String json = scalarType.format(value); + final MutableValueInfo oldHash = ebi.mutableInfo(propertyIndex); + if (oldHash == null) { + if (value == null) { + return false; // no change, still null + } + ebi.mutableNext(propertyIndex, next(json)); + return true; + } + // only perform compute of checksum/hash once (if checksum based) + final MutableValueNext next = oldHash.nextDirty(json); + if (next != null) { + ebi.mutableNext(propertyIndex, next); + return true; + } + return false; + } + + @Override + public Object readSet(DataReader reader, EntityBean bean) throws SQLException { + try { + Object value = scalarType.read(reader); + if (value == null) { + value = checkForEmpty(bean); + } + if (bean != null) { + setValue(bean, value); + String json = reader.popJson(); + if (json != null) { + final MutableValueInfo hash = createMutableInfo(json); + bean._ebean_getIntercept().mutableInfo(propertyIndex, hash); + } + } + return value; + } catch (TextException e) { + throw e; + } catch (Exception e) { + throw new PersistenceException("Error readSet on " + descriptor + "." + name, e); + } + } + + private static final class NextPair implements MutableValueNext { + + private final String json; + private final MutableValueInfo next; + + NextPair(String json, MutableValueInfo next) { + this.json = json; + this.next = next; + } + + @Override + public String content() { + return json; + } + + @Override + public MutableValueInfo info() { + return next; + } + } + + /** + * Hold checksum of json source content to use for dirty detection. + *

+ * Does not support rebuilding 'oldValue' as no original json content. + */ + private static final class ChecksumMutableValue implements MutableValueInfo { + + private final ScalarType parent; + private final long checksum; + + ChecksumMutableValue(ScalarType parent, String json) { + this.parent = parent; + this.checksum = Checksum.checksum(json); + } + + /** + * Create with pre-computed checksum. + */ + ChecksumMutableValue(ScalarType parent, long checksum) { + this.parent = parent; + this.checksum = checksum; + } + + @Override + public MutableValueNext nextDirty(String json) { + final long nextChecksum = Checksum.checksum(json); + return nextChecksum == checksum ? null : new NextPair(json, new ChecksumMutableValue(parent, nextChecksum)); + } + + @Override + public boolean isEqualToObject(Object obj) { + return Checksum.checksum(parent.format(obj)) == checksum; + } + + @Override + public Object get() { + return null; // cannot create object from json + } + } + + /** + * Hold json source content. This supports rebuilding the 'oldValue'. + */ + private static final class SourceMutableValue implements MutableValueInfo, MutableValueNext { + + private final String originalJson; + private final ScalarType parent; + + SourceMutableValue(ScalarType parent, String json) { + this.parent = parent; + this.originalJson = json; + } + + @Override + public MutableValueNext nextDirty(String json) { + return Objects.equals(originalJson, json) ? null : new SourceMutableValue(parent, json); + } + + @Override + public boolean isEqualToObject(Object obj) { + return Objects.equals(originalJson, parent.format(obj)); + } + + @Override + public Object get() { + // rebuild the 'oldValue' for change log etc + return parent.parse(originalJson); + } + + @Override + public String content() { + return originalJson; + } + + @Override + public MutableValueInfo info() { + return this; + } + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java index 366ba5a97..49dfccdc8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java @@ -28,11 +28,6 @@ class ElementEntityBean implements EntityBean { return properties[pos]; } - @Override - public String _ebean_getMarker() { - return null; - } - @Override public Object _ebean_newInstance() { return new ElementEntityBean(properties); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index 79ca0beb1..f0184948e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -702,13 +702,11 @@ public class DeployBeanDescriptor { } public void sortProperties() { - ArrayList list = new ArrayList<>(propMap.values()); list.sort(PROP_ORDER); - propMap = new LinkedHashMap<>(list.size()); - for (DeployBeanProperty aList : list) { - addBeanProperty(aList); + for (DeployBeanProperty property : list) { + addBeanProperty(property); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index e20c898ce..5e5455b2f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -1,18 +1,6 @@ package io.ebeaninternal.server.deploy.meta; -import io.ebean.annotation.CreatedTimestamp; -import io.ebean.annotation.DocCode; -import io.ebean.annotation.DocProperty; -import io.ebean.annotation.DocSortable; -import io.ebean.annotation.Formula; -import io.ebean.annotation.Platform; -import io.ebean.annotation.SoftDelete; -import io.ebean.annotation.UpdatedTimestamp; -import io.ebean.annotation.WhenCreated; -import io.ebean.annotation.WhenModified; -import io.ebean.annotation.Where; -import io.ebean.annotation.WhoCreated; -import io.ebean.annotation.WhoModified; +import io.ebean.annotation.*; import io.ebean.config.ScalarTypeConverter; import io.ebean.config.dbplatform.DbDefaultValue; import io.ebean.config.dbplatform.DbEncrypt; @@ -39,7 +27,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Type; import java.sql.Types; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -109,6 +96,7 @@ public class DeployBeanProperty { private boolean jsonSerialize = true; private boolean jsonDeserialize = true; + private MutationDetection mutationDetection; private boolean dbEncrypted; private DbEncryptFunction dbEncryptFunction; @@ -327,6 +315,14 @@ public class DeployBeanProperty { this.jsonDeserialize = jsonDeserialize; } + public MutationDetection getMutationDetection() { + return mutationDetection; + } + + public void setMutationDetection(MutationDetection dirtyDetection) { + this.mutationDetection = dirtyDetection; + } + /** * Return the sortOrder for the properties. */ @@ -1201,4 +1197,11 @@ public class DeployBeanProperty { return false; } + boolean isJsonMapper() { + return scalarType != null && scalarType.isJsonMapper(); + } + + boolean isJsonType() { + return mutationDetection != null; + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java index f6280b260..5466d5bca 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java @@ -1,15 +1,7 @@ package io.ebeaninternal.server.deploy.meta; import io.ebean.bean.EntityBean; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanDescriptorMap; -import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import io.ebeaninternal.server.deploy.BeanPropertyIdClass; -import io.ebeaninternal.server.deploy.BeanPropertyOrderColumn; -import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection; -import io.ebeaninternal.server.deploy.InheritInfo; +import io.ebeaninternal.server.deploy.*; import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; import io.ebeaninternal.server.properties.BeanPropertySetter; import io.ebeaninternal.server.type.ScalarTypeString; @@ -30,47 +22,28 @@ public class DeployBeanPropertyLists { private static final NoopSetter NOOP_SETTER = new NoopSetter(); private BeanProperty versionProperty; - private BeanProperty unmappedJson; - private BeanProperty draft; - private BeanProperty draftDirty; - private BeanProperty tenant; - private final BeanDescriptor desc; - private final LinkedHashMap propertyMap; - private BeanProperty id; private final List local = new ArrayList<>(); - private final List mutable = new ArrayList<>(); - private final List> manys = new ArrayList<>(); - private final List nonManys = new ArrayList<>(); - private final List aggs = new ArrayList<>(); - private final List> ones = new ArrayList<>(); - private final List> onesImported = new ArrayList<>(); - private final List> embedded = new ArrayList<>(); - private final List baseScalar = new ArrayList<>(); - private final List transients = new ArrayList<>(); - private final List nonTransients = new ArrayList<>(); - private final BeanPropertyAssocOne unidirectional; private final BeanProperty orderColumn; - @SuppressWarnings({"unchecked"}) public DeployBeanPropertyLists(BeanDescriptorMap owner, BeanDescriptor desc, DeployBeanDescriptor deploy) { this.desc = desc; @@ -86,7 +59,7 @@ public class DeployBeanPropertyLists { this.orderColumn = deployOrderColumn != null ? new BeanPropertyOrderColumn(desc, deployOrderColumn) : null; DeployBeanPropertyAssocOne deployUnidirectional = deploy.getUnidirectional(); - this.unidirectional = deployUnidirectional == null ? null : new BeanPropertyAssocOne(owner, desc, deployUnidirectional); + this.unidirectional = deployUnidirectional == null ? null : new BeanPropertyAssocOne<>(owner, desc, deployUnidirectional); this.propertyMap = new LinkedHashMap<>(); @@ -127,7 +100,7 @@ public class DeployBeanPropertyLists { } if (orderColumn != null) { - orderColumn.setDeployOrder(order++); + orderColumn.setDeployOrder(order); allocateToList(orderColumn); propertyMap.put(orderColumn.getName(), orderColumn); } @@ -154,7 +127,6 @@ public class DeployBeanPropertyLists { } private void setImportedPrimaryKeysFor(DeployBeanDescriptor deploy, DeployBeanPropertyAssocOne id) { - for (DeployBeanProperty prop : id.getTargetDeploy().properties()) { DeployBeanProperty match = findImported(deploy, prop); if (match != null) { @@ -164,7 +136,6 @@ public class DeployBeanPropertyLists { } private DeployBeanProperty findImported(DeployBeanDescriptor deploy, DeployBeanProperty embeddedScalar) { - // the logical name and db column we are looking for a match on String name = embeddedScalar.getName(); String dbColumn = embeddedScalar.getDbColumn(); @@ -180,7 +151,6 @@ public class DeployBeanPropertyLists { return assocOne; } } - return null; } @@ -368,7 +338,6 @@ public class DeployBeanPropertyLists { } public BeanProperty getSoftDeleteProperty() { - for (BeanProperty prop : nonManys) { if (prop.isSoftDelete()) { return prop; @@ -385,7 +354,6 @@ public class DeployBeanPropertyLists { * Return the properties set via generated values on insert. */ public BeanProperty[] getGeneratedInsert() { - List list = new ArrayList<>(); for (BeanProperty prop : nonTransients) { GeneratedProperty gen = prop.getGeneratedProperty(); @@ -400,7 +368,6 @@ public class DeployBeanPropertyLists { * Return the properties set via generated values on update. */ public BeanProperty[] getGeneratedUpdate() { - List list = new ArrayList<>(); for (BeanProperty prop : nonTransients) { GeneratedProperty gen = prop.getGeneratedProperty(); @@ -438,8 +405,7 @@ public class DeployBeanPropertyLists { } } } - - return (BeanPropertyAssocOne[]) list.toArray(new BeanPropertyAssocOne[0]); + return list.toArray(new BeanPropertyAssocOne[0]); } private BeanPropertyAssocMany[] getMany2Many() { @@ -449,8 +415,7 @@ public class DeployBeanPropertyLists { list.add(prop); } } - - return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[0]); + return list.toArray(new BeanPropertyAssocMany[0]); } private BeanPropertyAssocMany[] getMany(Mode mode) { @@ -471,25 +436,26 @@ public class DeployBeanPropertyLists { break; } } - - return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[0]); + return list.toArray(new BeanPropertyAssocMany[0]); } @SuppressWarnings({"unchecked", "rawtypes"}) private BeanProperty createBeanProperty(BeanDescriptorMap owner, DeployBeanProperty deployProp) { - if (deployProp instanceof DeployBeanPropertyAssocOne) { return new BeanPropertyAssocOne(owner, desc, (DeployBeanPropertyAssocOne) deployProp); } - if (deployProp instanceof DeployBeanPropertySimpleCollection) { return new BeanPropertySimpleCollection(desc, (DeployBeanPropertySimpleCollection) deployProp); } - if (deployProp instanceof DeployBeanPropertyAssocMany) { return new BeanPropertyAssocMany(desc, (DeployBeanPropertyAssocMany) deployProp); } - + if (deployProp.isJsonMapper()) { + return new BeanPropertyJsonMapper(desc, deployProp); + } + if (deployProp.isJsonType()) { + return new BeanPropertyJsonBasic(desc, deployProp); + } return new BeanProperty(desc, deployProp); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java index 65c7cf45e..68cb1c741 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -1,10 +1,6 @@ package io.ebeaninternal.server.deploy.parse; -import io.ebean.annotation.DbArray; -import io.ebean.annotation.DbJson; -import io.ebean.annotation.DbJsonB; -import io.ebean.annotation.DbJsonType; -import io.ebean.annotation.DbMap; +import io.ebean.annotation.*; import io.ebean.config.DatabaseConfig; import io.ebean.config.EncryptDeploy; import io.ebean.config.EncryptDeployManager; @@ -209,26 +205,22 @@ public class DeployUtil { } } - /** - * This property is marked as a Lob object. - */ void setDbJsonType(DeployBeanProperty prop, DbJson dbJsonType) { - int dbType = getDbJsonStorage(dbJsonType.storage()); - setDbJsonType(prop, dbType, dbJsonType.length()); + setDbJsonType(prop, dbType, dbJsonType.length(), dbJsonType.mutationDetection()); } void setDbJsonBType(DeployBeanProperty prop, DbJsonB dbJsonB) { - setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length()); + setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length(), dbJsonB.mutationDetection()); } - private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) { - + private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength, MutationDetection mutationDetection) { + prop.setDbType(dbType); + prop.setMutationDetection(mutationDetection); ScalarType scalarType = typeManager.getJsonScalarType(prop, dbType, dbLength); if (scalarType == null) { throw new RuntimeException("No ScalarType for JSON property [" + prop + "] [" + dbType + "]"); } - prop.setDbType(dbType); prop.setScalarType(scalarType); if (dbType == Types.VARCHAR || dbLength > 0) { // determine the db column size diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java index d30649149..363bcb08d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AbstractTextExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -37,9 +38,9 @@ public abstract class AbstractTextExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return 0; - } + public void queryBindKey(BindValuesKey key) { + // do nothing, only execute against document store + }; @Override public boolean isSameByBind(SpiExpression other) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java index 5a931ec5a..56761dcf2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/AllEqualsExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -122,14 +123,11 @@ class AllEqualsExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - - int hc = 92821; + public void queryBindKey(BindValuesKey key) { + key.add(propMap.size()); for (Object value : propMap.values()) { - hc = hc * 92821 + (value == null ? 0 : value.hashCode()); + key.add(value); } - - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java index 8c12bec60..7a9e903f6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayContainsExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -49,12 +50,11 @@ public class ArrayContainsExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = values[0].hashCode(); - for (int i = 1; i < values.length; i++) { - hc = hc * 92821 + values[i].hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(values.length); + for (Object value : values) { + key.add(value); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java index 815223dfe..f9cd530c8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ArrayIsEmptyExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -33,8 +34,8 @@ public class ArrayIsEmptyExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return empty ? 0 : 92821; + public void queryBindKey(BindValuesKey key) { + key.add(empty); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java index bfdfa1d52..ed3d58d8b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -49,10 +50,8 @@ class BetweenExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = low().hashCode(); - hc = hc * 92821 + high().hashCode(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(low()).add(high()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java index a50ec888f..55b08380d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BetweenPropertyExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.util.SplitName; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -95,8 +96,8 @@ class BetweenPropertyExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - return val().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(val()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java index 073ee51df..460af37ec 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/BitwiseExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -39,8 +40,8 @@ class BitwiseExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return Long.hashCode(flags); + public void queryBindKey(BindValuesKey key) { + key.add(flags).add(match); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java index 52e39fbfa..3a6224d9c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; @@ -69,8 +70,8 @@ class CaseInsensitiveEqualExpression extends AbstractValueExpression { } @Override - public int queryBindHash() { - return val().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(val()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java index aade99f5a..cd8a59857 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExampleExpression.java @@ -5,6 +5,7 @@ import io.ebean.LikeType; import io.ebean.bean.EntityBean; import io.ebean.event.BeanQueryRequest; import io.ebean.util.SplitName; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -136,10 +137,8 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { list = buildExpressions(desc); - if (list != null) { - for (SpiExpression aList : list) { - aList.containsMany(desc, whereManyJoins); - } + for (SpiExpression expr : list) { + expr.containsMany(desc, whereManyJoins); } } @@ -186,8 +185,8 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio @Override public void validate(SpiExpressionValidation validation) { - for (SpiExpression aList : list) { - aList.validate(validation); + for (SpiExpression expr : list) { + expr.validate(validation); } } @@ -228,25 +227,20 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio */ @Override public void queryPlanHash(StringBuilder builder) { - builder.append("Example["); - for (SpiExpression aList : list) { - aList.queryPlanHash(builder); + for (SpiExpression expr : list) { + expr.queryPlanHash(builder); builder.append(","); } builder.append("]"); } - /** - * Return a hash for the actual bind values used. - */ @Override - public int queryBindHash() { - int hc = DefaultExampleExpression.class.getName().hashCode(); - for (SpiExpression aList : list) { - hc = hc * 92821 + aList.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + key.add(list.size()); + for (SpiExpression expr : list) { + expr.queryBindKey(key); } - return hc; } @Override @@ -267,7 +261,6 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio * Build the List of expressions. */ private ArrayList buildExpressions(BeanDescriptor beanDescriptor) { - ArrayList list = new ArrayList<>(); addExpressions(list, beanDescriptor, entity, null); return list; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index ab80ae9e5..ed3f57e14 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -26,6 +26,7 @@ import io.ebean.search.MultiMatch; import io.ebean.search.TextCommonTerms; import io.ebean.search.TextQueryString; import io.ebean.search.TextSimple; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -109,10 +110,8 @@ public class DefaultExpressionList implements SpiExpressionList { * @return A single SpiExpression that has the nestedPath set */ SpiExpression wrap(List list, String nestedPath, Junction.Type type) { - DefaultExpressionList wrapper = new DefaultExpressionList<>(query, expr, null, list, false); wrapper.setAllDocNested(nestedPath); - if (type != null) { return new JunctionExpression<>(type, wrapper); } else { @@ -121,15 +120,15 @@ public class DefaultExpressionList implements SpiExpressionList { } void simplifyEntries() { - for (SpiExpression element : list) { - element.simplify(); + for (SpiExpression expr : list) { + expr.simplify(); } } @Override public void prefixProperty(String path) { - for (SpiExpression exp : list) { - exp.prefixProperty(path); + for (SpiExpression expr : list) { + expr.prefixProperty(path); } } @@ -174,7 +173,6 @@ public class DefaultExpressionList implements SpiExpressionList { context.startNested(allDocNestedPath); } int size = list.size(); - SpiExpression first = list.get(0); boolean explicitBool = first instanceof SpiJunction; boolean implicitBool = !explicitBool && size > 1; @@ -210,7 +208,6 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException { - if (allDocNestedPath != null) { context.startNested(allDocNestedPath); } @@ -227,8 +224,8 @@ public class DefaultExpressionList implements SpiExpressionList { if (idEquals != null) { idEquals.writeDocQuery(context); } - for (SpiExpression aList : list) { - aList.writeDocQuery(context); + for (SpiExpression expr : list) { + expr.writeDocQuery(context); } context.endBool(); } @@ -278,16 +275,15 @@ public class DefaultExpressionList implements SpiExpressionList { */ @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { - - for (SpiExpression aList : list) { - aList.containsMany(desc, whereManyJoins); + for (SpiExpression expr : list) { + expr.containsMany(desc, whereManyJoins); } } @Override public void validate(SpiExpressionValidation validation) { - for (SpiExpression aList : list) { - aList.validate(validation); + for (SpiExpression expr : list) { + expr.validate(validation); } } @@ -630,7 +626,6 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public void addSql(SpiExpressionRequest request) { - for (int i = 0, size = list.size(); i < size; i++) { SpiExpression expression = list.get(i); if (i > 0) { @@ -642,15 +637,15 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public void addBindValues(SpiExpressionRequest request) { - for (SpiExpression aList : list) { - aList.addBindValues(request); + for (SpiExpression expr : list) { + expr.addBindValues(request); } } @Override public void prepareExpression(BeanQueryRequest request) { - for (SpiExpression aList : list) { - aList.prepareExpression(request); + for (SpiExpression expr : list) { + expr.prepareExpression(request); } } @@ -667,23 +662,19 @@ public class DefaultExpressionList implements SpiExpressionList { if (allDocNestedPath != null) { builder.append("path:").append(allDocNestedPath).append(" "); } - for (SpiExpression aList : list) { - aList.queryPlanHash(builder); + for (SpiExpression expr : list) { + expr.queryPlanHash(builder); builder.append(","); } builder.append("]"); } - /** - * Calculate a hash based on the expressions. - */ @Override - public int queryBindHash() { - int hash = DefaultExpressionList.class.getName().hashCode(); - for (SpiExpression aList : list) { - hash = hash * 92821 + aList.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + key.add(list.size()); + for (SpiExpression expr : list) { + expr.queryBindKey(key); } - return hash; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java index 32ae2bccc..4ba939066 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiEbeanServer; @@ -91,8 +92,8 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress } @Override - public int queryBindHash() { - return subQuery.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + subQuery.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java index b5280b868..94cab6de7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -77,8 +78,8 @@ class IdExpression extends NonPrepareExpression implements SpiExpression { } @Override - public int queryBindHash() { - return value.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(value); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java index 4c08b24d2..edf56b443 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IdInExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -133,8 +134,11 @@ public class IdInExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - return idCollection.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(idCollection.size()); + for (Object elem : idCollection) { + key.add(elem); + } } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java index 7009a8b44..0bc5a4588 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InExpression.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.expression; import io.ebean.bean.EntityBean; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -176,12 +177,11 @@ class InExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = 92821; + public void queryBindKey(BindValuesKey key) { + key.add(bindValues.size()); for (Object bindValue : bindValues) { - hc = 92821 * hc + bindValue.hashCode(); + key.add(bindValue); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java index 96cc7c182..e5bd27413 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InPairsExpression.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.expression; import io.ebean.Pairs; import io.ebean.Pairs.Entry; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -124,12 +125,11 @@ class InPairsExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = 92821; + public void queryBindKey(BindValuesKey key) { + key.add(entries.size()); for (Pairs.Entry entry : entries) { - hc = 92821 * hc + entry.hashCode(); + key.add(entry.getA()).add(entry.getB()); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java index 83507af4b..48647d0c2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -72,8 +73,8 @@ class InQueryExpression extends AbstractExpression implements UnsupportedDocStor } @Override - public int queryBindHash() { - return subQuery.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + subQuery.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java index e1d170b43..04b242ba8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -47,10 +48,8 @@ class InRangeExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = low().hashCode(); - hc = hc * 92821 + high().hashCode(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(low()).add(high()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java index d3a3a1da6..d6b878bfa 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/IsEmptyExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -103,8 +104,8 @@ class IsEmptyExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return 1; + public void queryBindKey(BindValuesKey key) { + // no bind values } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java index f60b125cb..1d954dcc1 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JsonPathExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -83,10 +84,8 @@ class JsonPathExpression extends AbstractExpression { } @Override - public int queryBindHash() { - int hc = (value == null) ? 0 : value.hashCode(); - hc = (upperValue == null) ? hc : hc * 92821 + upperValue.hashCode(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(value).add(upperValue); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index 0aae30886..5637fba7f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -25,6 +25,7 @@ import io.ebean.search.MultiMatch; import io.ebean.search.TextCommonTerms; import io.ebean.search.TextQueryString; import io.ebean.search.TextSimple; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -113,9 +114,8 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void writeDocQuery(DocQueryContext context) throws IOException { context.startBool(type); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.writeDocQuery(context); + for (SpiExpression expr : exprList.internalList()) { + expr.writeDocQuery(context); } context.endBool(); } @@ -123,9 +123,8 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void writeDocQueryJunction(DocQueryContext context) throws IOException { context.startBoolGroupList(type); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.writeDocQuery(context); + for (SpiExpression expr : exprList.internalList()) { + expr.writeDocQuery(context); } context.endBoolGroupList(); } @@ -138,18 +137,15 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void containsMany(BeanDescriptor desc, ManyWhereJoins manyWhereJoin) { - List list = exprList.internalList(); - // get the current state for 'require outer joins' boolean parentOuterJoins = manyWhereJoin.isRequireOuterJoins(); if (type == Type.OR) { // turn on outer joins required for disjunction expressions manyWhereJoin.setRequireOuterJoins(true); } - - for (SpiExpression aList : list) { - aList.containsMany(desc, manyWhereJoin); + for (SpiExpression expr : list) { + expr.containsMany(desc, manyWhereJoin); } if (type == Type.OR && !parentOuterJoins) { // restore state to not forcing outer joins @@ -176,18 +172,14 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void addBindValues(SpiExpressionRequest request) { - - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.addBindValues(request); + for (SpiExpression expr : exprList.internalList()) { + expr.addBindValues(request); } } @Override public void addSql(SpiExpressionRequest request) { - List list = exprList.internalList(); - if (!list.isEmpty()) { request.append(type.prefix()); request.append("("); @@ -204,9 +196,8 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void prepareExpression(BeanQueryRequest request) { - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.prepareExpression(request); + for (SpiExpression expr : exprList.internalList()) { + expr.prepareExpression(request); } } @@ -216,22 +207,18 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public void queryPlanHash(StringBuilder builder) { builder.append(type).append("["); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - aList.queryPlanHash(builder); + for (SpiExpression expr : exprList.internalList()) { + expr.queryPlanHash(builder); builder.append(","); } builder.append("]"); } @Override - public int queryBindHash() { - int hc = JunctionExpression.class.getName().hashCode(); - List list = exprList.internalList(); - for (SpiExpression aList : list) { - hc = hc * 92821 + aList.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + for (SpiExpression expr : exprList.internalList()) { + expr.queryBindKey(key); } - return hc; } @Override @@ -275,7 +262,6 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.textCommonTerms(search, options); } - @Override public ExpressionList allEq(Map propertyMap) { return exprList.allEq(propertyMap); @@ -1025,7 +1011,6 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression @Override public String nestedPath(BeanDescriptor desc) { - PrepareDocNested.prepare(exprList, desc, type); String nestedPath = exprList.allDocNestedPath; if (nestedPath != null) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java index 6886203c4..f40501929 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LikeExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.LikeType; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; @@ -70,8 +71,8 @@ class LikeExpression extends AbstractValueExpression { } @Override - public int queryBindHash() { - return strValue().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(strValue()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java index 482aac6e8..a66e2e88a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/LogicExpression.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.expression; import io.ebean.Expression; import io.ebean.Junction; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -168,10 +169,8 @@ abstract class LogicExpression implements SpiExpression { } @Override - public int queryBindHash() { - int hc = expOne.queryBindHash(); - hc = hc * 92821 + expTwo.queryBindHash(); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(expOne).add(expTwo); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java index c3376f656..f64933df5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NativeILikeExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.LikeType; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; @@ -54,8 +55,8 @@ class NativeILikeExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return val.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(val); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java index 77f46828b..b76bfef4d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NestedPathWrapperExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -79,8 +80,8 @@ class NestedPathWrapperExpression implements SpiExpression { } @Override - public int queryBindHash() { - return delegate.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + delegate.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java index 28c9e3c78..6c8d709e7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NoopExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -74,9 +75,8 @@ class NoopExpression implements SpiExpression { } @Override - public int queryBindHash() { + public void queryBindKey(BindValuesKey key) { // no bind values - return 0; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java index 60ade47b0..462e60d9e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NotExpression.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.expression; import io.ebean.Expression; import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.NaturalKeyQueryData; import io.ebeaninternal.api.SpiExpression; @@ -99,8 +100,8 @@ final class NotExpression implements SpiExpression { } @Override - public int queryBindHash() { - return exp.queryBindHash(); + public void queryBindKey(BindValuesKey key) { + exp.queryBindKey(key); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java index 90894dbde..2808acce7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/NullExpression.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.expression; import io.ebean.util.SplitName; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -94,7 +95,7 @@ class NullExpression extends AbstractExpression { } @Override - public int queryBindHash() { - return (notNull ? 1 : 0); + public void queryBindKey(BindValuesKey key) { + key.add(notNull); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java index 295ddc75b..9e1c3e8dc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/RawExpression.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.expression; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.ManyWhereJoins; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; @@ -73,12 +74,11 @@ class RawExpression extends NonPrepareExpression { } @Override - public int queryBindHash() { - int hc = sql.hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(values.length); for (Object value : values) { - hc = hc * 92821 + value.hashCode(); + key.add(value); } - return hc; } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java index 0f8de90ec..bc23b84f2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java @@ -5,6 +5,7 @@ import io.ebean.plugin.ExpressionPath; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.server.el.ElPropertyValue; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.NaturalKeyQueryData; import java.io.IOException; @@ -121,8 +122,8 @@ public class SimpleExpression extends AbstractValueExpression { } @Override - public int queryBindHash() { - return value().hashCode(); + public void queryBindKey(BindValuesKey key) { + key.add(value()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BindValues.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BindValues.java deleted file mode 100644 index 6d71e8d79..000000000 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BindValues.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.ebeaninternal.server.persist; - -import java.util.ArrayList; - -/** - * Holds a list of bind values for binding to a PreparedStatement. - */ -class BindValues { - - private final ArrayList list = new ArrayList<>(); - - /** - * Create with a Binder. - */ - public BindValues() { - } - - /** - * Add a bind value with its JDBC datatype. - * - * @param value the bind value - * @param dbType the type as per java.sql.Types - */ - public void add(Object value, int dbType, String name) { - list.add(new Value(value, dbType, name)); - } - - /** - * List of bind values. - */ - public ArrayList values() { - return list; - } - - /** - * A Value has additionally the JDBC data type. - */ - public static class Value { - - private final Object value; - - private final int dbType; - - private final String name; - - /** - * Create the value. - */ - Value(Object value, int dbType, String name) { - this.value = value; - this.dbType = dbType; - this.name = name; - } - - /** - * Return the type as per java.sql.Types. - */ - public int getDbType() { - return dbType; - } - - /** - * Return the value. - */ - public Object getValue() { - return value; - } - - /** - * Return the property name. - */ - public String getName() { - return name; - } - - @Override - public String toString() { - return String.valueOf(value); - } - } -} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java index 1d94d7a81..600a7e21a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/Binder.java @@ -77,33 +77,6 @@ public class Binder { return asOfStandardsBased; } - /** - * Bind the values to the Prepared Statement. - */ - public void bind(BindValues bindValues, DataBind dataBind, StringBuilder bindBuf) throws SQLException { - String logPrefix = ""; - ArrayList list = bindValues.values(); - for (BindValues.Value bindValue : list) { - Object val = bindValue.getValue(); - int dt = bindValue.getDbType(); - bindObject(dataBind, val, dt); - - if (bindBuf != null) { - bindBuf.append(logPrefix); - if (logPrefix.isEmpty()) { - logPrefix = ", "; - } - bindBuf.append(bindValue.getName()); - bindBuf.append("="); - if (isLob(dt)) { - bindBuf.append("[LOB]"); - } else { - bindBuf.append(val); - } - } - } - } - /** * Bind the parameters to the preparedStatement returning the bind log. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java index f273b9dbc..1f1da9fc0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java @@ -295,7 +295,16 @@ public class SaveManyBeans extends SaveManyBase { } transaction.depth(+1); - + if (deletions != null && !deletions.isEmpty()) { + for (Object other : deletions) { + EntityBean otherDelete = (EntityBean) other; + // the object from the 'other' side of the ManyToMany + // build a intersection row for 'delete' + IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherDelete, publish); + SpiSqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD); + persister.executeOrQueue(sqlDelete, transaction, queue); + } + } if (additions != null && !additions.isEmpty()) { for (Object other : additions) { EntityBean otherBean = (EntityBean) other; @@ -318,16 +327,6 @@ public class SaveManyBeans extends SaveManyBase { } } } - if (deletions != null && !deletions.isEmpty()) { - for (Object other : deletions) { - EntityBean otherDelete = (EntityBean) other; - // the object from the 'other' side of the ManyToMany - // build a intersection row for 'delete' - IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherDelete, publish); - SpiSqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD); - persister.executeOrQueue(sqlDelete, transaction, queue); - } - } // decrease the depth back to what it was transaction.depth(-1); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java index 189613808..0a4bb6b8a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java @@ -63,6 +63,11 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { } } + @Override + public void pushJson(String json) { + dataBind.pushJson(json); + } + @Override public long now() { return now; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonInsert.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonInsert.java new file mode 100644 index 000000000..1cbb7eeca --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonInsert.java @@ -0,0 +1,42 @@ +package io.ebeaninternal.server.persist.dmlbind; + +import io.ebean.bean.EntityBean; +import io.ebean.bean.MutableValueInfo; +import io.ebeaninternal.server.deploy.BeanProperty; + +import java.sql.SQLException; + +/** + * For JSON Jackson properties - dirty detection via MD5 of json content. + */ +class BindablePropertyJsonInsert extends BindableProperty { + + private final int propertyIndex; + + BindablePropertyJsonInsert(BeanProperty prop) { + super(prop); + this.propertyIndex = prop.getPropertyIndex(); + } + + /** + * Normal binding of a property value from the bean. + */ + @Override + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + if (bean == null) { + request.bind(null, prop); + } else { + Object value = prop.getValue(bean); + if (value == null) { + request.bind(null, prop); + } else { + // on insert store hash and push json + final String json = prop.format(value); + final MutableValueInfo hash = prop.createMutableInfo(json); + bean._ebean_getIntercept().mutableInfo(propertyIndex, hash); + request.pushJson(json); + request.bind(value, prop); + } + } + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java new file mode 100644 index 000000000..cfa056a63 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java @@ -0,0 +1,33 @@ +package io.ebeaninternal.server.persist.dmlbind; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.BeanProperty; + +import java.sql.SQLException; + +/** + * For JSON Jackson properties - dirty detection via MD5 of json content. + */ +class BindablePropertyJsonUpdate extends BindableProperty { + + private final int propertyIndex; + + BindablePropertyJsonUpdate(BeanProperty prop) { + super(prop); + this.propertyIndex = prop.getPropertyIndex(); + } + + /** + * Normal binding of a property value from the bean. + */ + @Override + public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { + if (bean == null) { + request.bind(null, prop); + } else { + // update mutableInfo and push json + request.pushJson(bean._ebean_getIntercept().mutableNext(propertyIndex)); + request.bind(prop.getValue(bean), prop); + } + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java index 4a1fc6754..e06862e86 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java @@ -57,4 +57,9 @@ public interface BindableRequest { * Return true if this is an update request. */ boolean isUpdate(); + + /** + * Push json content for scalarType bind(). + */ + void pushJson(String json); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java index b47b4ecb6..d5c59daf3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.persist.dmlbind; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import io.ebeaninternal.server.deploy.BeanPropertyJsonMapper; import io.ebeaninternal.server.persist.dml.DmlMode; /** @@ -23,14 +24,12 @@ class FactoryProperty { * Create a Bindable for the property given the mode and withLobs flag. */ public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs, boolean allowManyToOne) { - if (DmlMode.INSERT == mode && !prop.isDbInsertable()) { return null; } if (DmlMode.UPDATE == mode && !prop.isDbUpdatable()) { return null; } - if (prop.isLob() && !withLobs) { // Lob exclusion return null; @@ -38,11 +37,16 @@ class FactoryProperty { if (prop.isDbEncrypted()){ return new BindableEncryptedProperty(prop, bindEncryptDataFirst); } - if (allowManyToOne && prop instanceof BeanPropertyAssocOne) { return new BindableAssocOne((BeanPropertyAssocOne)prop); } - + if (prop instanceof BeanPropertyJsonMapper) { + if (DmlMode.INSERT == mode) { + return new BindablePropertyJsonInsert(prop); + } else if (DmlMode.UPDATE == mode) { + return new BindablePropertyJsonUpdate(prop); + } + } return new BindableProperty(prop); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java index 507938a96..d7d5b0fd2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/BasicProfileLocation.java @@ -13,7 +13,7 @@ final class BasicProfileLocation implements ProfileLocation { BasicProfileLocation(String fullLocation) { this.fullLocation = fullLocation; - this.location = shortDesc(fullLocation); + this.location = UtilLocation.loc(fullLocation); this.label = UtilLocation.label(location); } @@ -57,15 +57,4 @@ final class BasicProfileLocation implements ProfileLocation { // do nothing } - private String shortDesc(String location) { - int lastPer = location.lastIndexOf('.'); - if (lastPer > -1) { - lastPer = location.lastIndexOf('.', lastPer - 1); - if (lastPer > -1) { - return location.substring(lastPer + 1); - } - } - return location; - } - } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DCountMetric.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DCountMetric.java index 2cf7a25a7..d93e0dbdb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DCountMetric.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DCountMetric.java @@ -49,7 +49,7 @@ class DCountMetric implements CountMetric { @Override public void visit(MetricVisitor visitor) { - long val = visitor.isReset() ? count.sumThenReset() : count.sum(); + long val = visitor.reset() ? count.sumThenReset() : count.sum(); if (val > 0) { visitor.visitCount(new DCountMetricStats(name, val)); } @@ -66,12 +66,12 @@ class DCountMetric implements CountMetric { } @Override - public String getName() { + public String name() { return name; } @Override - public long getCount() { + public long count() { return count; } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java index c5ff7d3ac..63fa0e11a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DProfileLocation.java @@ -8,17 +8,13 @@ import io.ebean.ProfileLocation; class DProfileLocation implements ProfileLocation { private static final String IO_EBEAN = "io.ebean"; - private static final String UNKNOWN = "unknown"; private String fullLocation; - private String location; - private String label; private final int lineNumber; - private int traceCount; DProfileLocation() { @@ -49,10 +45,10 @@ class DProfileLocation implements ProfileLocation { return false; } final String loc = create(); - final String shortDesc = shortDesc(loc); - label = UtilLocation.label(shortDesc); - location = shortDesc; - fullLocation = loc; + final String location = UtilLocation.loc(loc); + this.label = UtilLocation.label(location); + this.location = location; + this.fullLocation = loc; initWith(label); return true; } @@ -113,20 +109,4 @@ class DProfileLocation implements ProfileLocation { return traceLine.substring(0, traceLine.length() - 1) + ":" + lineNumber + ")"; } } - - private String shortDesc(String location) { - int pos = location.lastIndexOf('('); - if (pos == -1) { - pos = location.length(); - } - - pos = location.lastIndexOf('.', pos); - if (pos > -1) { - pos = location.lastIndexOf('.', pos - 1); - if (pos > -1) { - return location.substring(pos + 1); - } - } - return location; - } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java index 0690b5fdd..67f724051 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMeta.java @@ -22,15 +22,8 @@ class DQueryPlanMeta { name += "_" + label; } this.name = name; - this.hash = initHash(); - } - - private String initHash() { - StringBuilder sb = new StringBuilder(sql).append("|").append(name); - if (profileLocation != null) { - sb.append("|").append(profileLocation.location()); - } - return Md5.hash(sb.toString()); + String loc = profileLocation == null ? null : profileLocation.location(); + this.hash = Md5.hash(sql, name, loc); } public Class getType() { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java index 21559a152..e24da73c7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DQueryPlanMetric.java @@ -19,7 +19,7 @@ class DQueryPlanMetric implements QueryPlanMetric { @Override public void visit(MetricVisitor visitor) { - TimedMetricStats stats = metric.collect(visitor.isReset()); + TimedMetricStats stats = metric.collect(visitor.reset()); if (stats != null) { visitor.visitQuery(new Stats(meta, stats, collected)); collected = true; @@ -45,11 +45,11 @@ class DQueryPlanMetric implements QueryPlanMetric { @Override public String toString() { - return meta + " " + stats + " sql:" + getSql(); + return meta + " " + stats + " sql:" + sql(); } @Override - public Class getType() { + public Class type() { return meta.getType(); } @@ -59,48 +59,48 @@ class DQueryPlanMetric implements QueryPlanMetric { } @Override - public String getHash() { + public String hash() { return meta.getHash(); } @Override - public String getLabel() { + public String label() { return meta.getLabel(); } @Override - public String getSql() { + public String sql() { return meta.getSql(); } @Override - public String getName() { + public String name() { return meta.getName(); } @Override - public String getLocation() { + public String location() { return meta.getLocation(); } @Override - public long getCount() { - return stats.getCount(); + public long count() { + return stats.count(); } @Override - public long getTotal() { - return stats.getTotal(); + public long total() { + return stats.total(); } @Override - public long getMax() { - return stats.getMax(); + public long max() { + return stats.max(); } @Override - public long getMean() { - return stats.getMean(); + public long mean() { + return stats.mean(); } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java index 54146abe9..a4243e31a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimeMetricStats.java @@ -7,18 +7,14 @@ import io.ebean.metric.TimedMetricStats; */ class DTimeMetricStats implements TimedMetricStats { - private String name; - private final boolean collected; - - private String location; - private final long count; - private final long total; - private final long max; + private String name; + private String location; + DTimeMetricStats(String name, boolean collected, long count, long total, long max) { this.name = name; this.collected = collected; @@ -60,12 +56,12 @@ class DTimeMetricStats implements TimedMetricStats { } @Override - public String getName() { + public String name() { return name; } @Override - public String getLocation() { + public String location() { return location; } @@ -73,7 +69,7 @@ class DTimeMetricStats implements TimedMetricStats { * Return the count of values collected. */ @Override - public long getCount() { + public long count() { return count; } @@ -81,7 +77,7 @@ class DTimeMetricStats implements TimedMetricStats { * Return the total of all the values. */ @Override - public long getTotal() { + public long total() { return total; } @@ -89,7 +85,7 @@ class DTimeMetricStats implements TimedMetricStats { * Return the Max value collected. */ @Override - public long getMax() { + public long max() { return max; } @@ -97,7 +93,7 @@ class DTimeMetricStats implements TimedMetricStats { * Return the mean value rounded up. */ @Override - public long getMean() { + public long mean() { return (count < 1) ? 0L : Math.round((double)(total / count)); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedMetric.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedMetric.java index 63eb275f6..cc344c3e6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedMetric.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedMetric.java @@ -71,7 +71,7 @@ class DTimedMetric implements TimedMetric { @Override public void visit(MetricVisitor visitor) { - DTimeMetricStats metric = collect(visitor.isReset()); + DTimeMetricStats metric = collect(visitor.reset()); if (metric != null) { visitor.visitTimed(metric); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedProfileLocation.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedProfileLocation.java index 7ef4e9084..27537737f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedProfileLocation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/DTimedProfileLocation.java @@ -48,7 +48,7 @@ class DTimedProfileLocation extends DProfileLocation implements TimedProfileLoca @Override public void visit(MetricVisitor visitor) { - TimedMetricStats collect = timedMetric.collect(visitor.isReset()); + TimedMetricStats collect = timedMetric.collect(visitor.reset()); if (collect != null) { if (overrideMetricName) { collect.setName(fullName); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java b/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java index 7b70dfcb7..65ab1ce4d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/profile/UtilLocation.java @@ -2,15 +2,30 @@ package io.ebeaninternal.server.profile; final class UtilLocation { - static String label(String shortDescription) { - int pos = shortDescription.indexOf("("); - if (pos == -1) { - return shortDescription; + static String loc(String full) { + final int pos = full.lastIndexOf('('); + if (pos > -1) { + return full.substring(0, pos); } else { - return trimInit(shortDescription.substring(0, pos)); + return full; } } + static String label(String location) { + return trimInit(shortDesc(location)); + } + + private static String shortDesc(String location) { + int pos = location.lastIndexOf('.'); + if (pos > -1) { + pos = location.lastIndexOf('.', pos - 1); + if (pos > -1) { + return location.substring(pos + 1); + } + } + return location; + } + /** * Trim constructor init to be "safe" without greater than or less than chars. */ @@ -21,5 +36,4 @@ final class UtilLocation { } return desc; } - } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index e7674a31c..7fc636af0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -541,9 +541,13 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran updateStatistics(); } + long micros() { + return (System.nanoTime() - startNano) / 1000L; + } + private void updateStatistics() { try { - executionTimeMicros = (System.nanoTime() - startNano) / 1000L; + executionTimeMicros = micros(); if (autoTuneProfiling) { profilingListener.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 1162e914d..41439e4b6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -189,6 +189,12 @@ class CQueryBuilder { SpiQuery query = request.getQuery(); query.setSingleAttribute(); + if (!query.isIncludeSoftDeletes()) { + BeanDescriptor desc = request.getBeanDescriptor(); + if (desc.isSoftDelete()) { + query.addSoftDeletePredicate(desc.getSoftDeletePredicate(alias(query.getAlias()))); + } + } CQueryPredicates predicates = new CQueryPredicates(binder, request); CQueryPlan queryPlan = request.getQueryPlan(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java index a1a211c0a..03074b43f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java @@ -79,15 +79,10 @@ public class CQueryEngine { private int executeUpdate(OrmQueryRequest request, CQueryUpdate query) { try { int rows = query.execute(); - if (request.isLogSql()) { - String logSql = query.getGeneratedSql(); - logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows)); - request.logSql(logSql); + request.logSql(Str.add(query.getGeneratedSql(), "; --bind(", query.getBindLog(), ") --micros(", query.micros() + ") --rows(", rows + ")")); } - return rows; - } catch (SQLException e) { throw translate(request, query.getBindLog(), query.getGeneratedSql(), e); } @@ -97,7 +92,6 @@ public class CQueryEngine { * Build and execute the findSingleAttributeList query. */ public List findSingleAttributeList(OrmQueryRequest request) { - CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request); request.setCancelableQuery(rcQuery); return findAttributeList(request, rcQuery); @@ -108,14 +102,13 @@ public class CQueryEngine { try { List list = (List) rcQuery.findList(); if (request.isLogSql()) { - logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog()); + logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog(), rcQuery.micros()); } if (request.isLogSummary()) { request.getTransaction().logSummary(rcQuery.getSummary()); } if (request.isQueryCachePut()) { request.addDependentTables(rcQuery.getDependentTables()); - list = Collections.unmodifiableList(list); request.putToQueryCache(list); if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) { @@ -123,7 +116,6 @@ public class CQueryEngine { } } return list; - } catch (SQLException e) { throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e); } @@ -139,10 +131,8 @@ public class CQueryEngine { String msg = "ERROR executing query, bindLog[" + bindLog + "] error[" + StringHelper.removeNewLines(e.getMessage()) + "]"; t.logSummary(msg); } - // ensure 'rollback' is logged if queryOnly transaction t.getConnection(); - // build a decent error message for the exception String m = "Query threw SQLException:" + e.getMessage() + " Bind values:[" + bindLog + "] Query was:" + sql; return dbPlatform.translate(m, e); @@ -152,46 +142,37 @@ public class CQueryEngine { * Build and execute the find Id's query. */ public List findIds(OrmQueryRequest request) { - CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request); request.setCancelableQuery(rcQuery); return findAttributeList(request, rcQuery); } - private void logGeneratedSql(OrmQueryRequest request, String sql, String bindLog) { - request.logSql(Str.add(sql, "; --bind(", bindLog, ")")); + private void logGeneratedSql(OrmQueryRequest request, String sql, String bindLog, long micros) { + request.logSql(Str.add(sql, "; --bind(", bindLog, ") --micros(", micros + ")")); } /** * Build and execute the row count query. */ public int findCount(OrmQueryRequest request) { - CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request); request.setCancelableQuery(rcQuery); try { - int count = rcQuery.findCount(); - if (request.isLogSql()) { - logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog()); + logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog(), rcQuery.micros()); } - if (request.isLogSummary()) { request.getTransaction().logSummary(rcQuery.getSummary()); } - if (request.getQuery().isFutureFetch()) { request.getTransaction().end(); } - if (request.isQueryCachePut()) { request.addDependentTables(rcQuery.getDependentTables()); request.putToQueryCache(count); } - return count; - } catch (SQLException e) { throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e); } @@ -254,9 +235,7 @@ public class CQueryEngine { * Execute the find versions query returning version beans. */ public List> findVersions(OrmQueryRequest request) { - SpiQuery query = request.getQuery(); - String sysPeriodLower = getSysPeriodLower(query); if (query.isVersionsBetween() && !historySupport.isStandardsBased()) { query.where().lt(sysPeriodLower, query.getVersionEnd()); @@ -272,26 +251,21 @@ public class CQueryEngine { if (request.isLogSql()) { logSql(cquery); } - List> versions = cquery.readVersions(); // just order in memory rather than use NULLS LAST as that // is not universally supported, not expect huge list here versions.sort(OrderVersionDesc.INSTANCE); deriveVersionDiffs(versions, request); - if (request.isLogSummary()) { logFindManySummary(cquery); } - if (request.isAuditReads()) { cquery.auditFindMany(); } - return versions; } catch (SQLException e) { throw cquery.createPersistenceException(e); - } finally { if (cquery != null) { cquery.close(); @@ -300,9 +274,7 @@ public class CQueryEngine { } private void deriveVersionDiffs(List> versions, OrmQueryRequest request) { - BeanDescriptor descriptor = request.getBeanDescriptor(); - if (!versions.isEmpty()) { Version current = versions.get(0); if (versions.size() > 1) { @@ -367,10 +339,8 @@ public class CQueryEngine { * Find a list/map/set of beans. */ BeanCollection findMany(OrmQueryRequest request) { - CQuery cquery = queryBuilder.buildQuery(request); request.setCancelableQuery(cquery); - try { if (defaultFetchSizeFindList > 0) { request.setDefaultFetchBuffer(defaultFetchSizeFindList); @@ -380,30 +350,24 @@ public class CQueryEngine { logger.trace("Future fetch already cancelled"); return null; } - if (request.isLogSql()) { logSql(cquery); } - BeanCollection beanCollection = cquery.readCollection(); if (request.isLogSummary()) { logFindManySummary(cquery); } - if (request.isAuditReads()) { cquery.auditFindMany(); } - request.executeSecondaryQueries(false); if (request.isQueryCachePut()) { request.addDependentTables(cquery.getDependentTables()); } - return beanCollection; } catch (SQLException e) { throw cquery.createPersistenceException(e); - } finally { if (cquery != null) { cquery.close(); @@ -416,38 +380,27 @@ public class CQueryEngine { */ @SuppressWarnings("unchecked") public T find(OrmQueryRequest request) { - EntityBean bean = null; - CQuery cquery = queryBuilder.buildQuery(request); request.setCancelableQuery(cquery); - try { cquery.prepareBindExecuteQuery(); - if (request.isLogSql()) { logSql(cquery); } - if (cquery.readBean()) { bean = cquery.next(); } - if (request.isLogSummary()) { logFindBeanSummary(cquery); } - if (request.isAuditReads()) { cquery.auditFind(bean); } - request.executeSecondaryQueries(false); - return (T) bean; - } catch (SQLException e) { throw cquery.createPersistenceException(e); - } finally { cquery.close(); } @@ -457,17 +410,13 @@ public class CQueryEngine { * Log the generated SQL to the transaction log. */ private void logSql(CQuery query) { - - String sql = query.getGeneratedSql(); - sql = Str.add(sql, "; --bind(", query.getBindLog(), ")"); - query.getTransaction().logSql(sql); + query.getTransaction().logSql(Str.add(query.getGeneratedSql(), "; --bind(", query.getBindLog(), ") --micros(", query.micros() + ")")); } /** * Log the FindById summary to the transaction log. */ private void logFindBeanSummary(CQuery q) { - SpiQuery query = q.getQueryRequest().getQuery(); String loadMode = query.getLoadMode(); String loadDesc = query.getLoadDescription(); @@ -504,7 +453,6 @@ public class CQueryEngine { msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros()); msg.append("] rows[").append(q.getLoadedRowDetail()); msg.append("] bind[").append(q.getBindLog()).append("]"); - q.getTransaction().logSummary(msg.toString()); } @@ -512,7 +460,6 @@ public class CQueryEngine { * Log the FindMany to the transaction log. */ private void logFindManySummary(CQuery q) { - SpiQuery query = q.getQueryRequest().getQuery(); String loadMode = query.getLoadMode(); String loadDesc = query.getLoadDescription(); @@ -551,7 +498,6 @@ public class CQueryEngine { msg.append("] rows[").append(q.getLoadedRowDetail()); msg.append("] predicates[").append(q.getLogWhereSql()); msg.append("] bind[").append(q.getBindLog()).append("]"); - q.getTransaction().logSummary(msg.toString()); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java index b68813bd4..a89cd28fa 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryFetchSingleAttribute.java @@ -29,45 +29,19 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Cancelab private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class); private final CQueryPlan queryPlan; - - /** - * The overall find request wrapper object. - */ private final OrmQueryRequest request; - private final BeanDescriptor desc; - private final SpiQuery query; - - /** - * Where clause predicates. - */ private final CQueryPredicates predicates; - - /** - * The final sql that is generated. - */ private final String sql; - private RsetDataReader dataReader; - - /** - * The statement used to create the resultSet. - */ private PreparedStatement pstmt; - private String bindLog; - private long executionTimeMicros; - private int rowCount; - private final ScalarDataReader reader; - private final boolean containsCounts; - private long profileOffset; - private final ReentrantLock lock = new ReentrantLock(); /** @@ -95,19 +69,20 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Cancelab .append("] type[").append(desc.getName()) .append("] predicates[").append(predicates.getLogWhereSql()) .append("] bind[").append(bindLog).append("]"); - return sb.toString(); } + long micros() { + return executionTimeMicros; + } + /** * Execute the query returning the row count. */ List findList() throws SQLException { - long startNano = System.nanoTime(); try { prepareExecute(); - List result = new ArrayList<>(); while (dataReader.next()) { Object value = reader.read(dataReader); @@ -117,16 +92,13 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Cancelab result.add(value); rowCount++; } - executionTimeMicros = (System.nanoTime() - startNano) / 1000L; request.slowQueryCheck(executionTimeMicros, rowCount); if (queryPlan.executionTime(executionTimeMicros)) { queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros); } getTransaction().profileEvent(this); - return result; - } finally { close(); } @@ -158,14 +130,12 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Cancelab profileOffset = t.profileOffset(); Connection conn = t.getInternalConnection(); pstmt = conn.prepareStatement(sql); - if (query.getBufferFetchSizeHint() > 0) { pstmt.setFetchSize(query.getBufferFetchSizeHint()); } if (query.getTimeout() > 0) { pstmt.setQueryTimeout(query.getTimeout()); } - bindLog = predicates.bind(pstmt, conn); } finally { lock.unlock(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java index 295cbe7c2..de365f253 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java @@ -13,12 +13,12 @@ import io.ebeaninternal.api.SpiQueryBindCapture; import io.ebeaninternal.api.SpiQueryPlan; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.core.timezone.DataTimeZone; +import io.ebeaninternal.server.util.Md5; import io.ebeaninternal.server.util.Str; import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import io.ebeaninternal.server.type.DataBind; import io.ebeaninternal.server.type.DataBindCapture; import io.ebeaninternal.server.type.RsetDataReader; -import io.ebeaninternal.server.util.Md5; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,46 +54,31 @@ public class CQueryPlan implements SpiQueryPlan { static final String RESULT_SET_BASED_RAW_SQL = "--ResultSetBasedRawSql"; private final SpiEbeanServer server; - private final ProfileLocation profileLocation; - private final String location; - private final String label; - private final String name; - private final CQueryPlanKey planKey; - private final boolean rawSql; - private final String sql; private final String hash; - private final String logWhereSql; - private final SqlTree sqlTree; /** * Encrypted properties required additional binding. */ private final STreeProperty[] encryptedProps; - private final CQueryPlanStats stats; - private final Class beanType; - final DataTimeZone dataTimeZone; - private final int asOfTableCount; /** * Key used to identify the query plan in audit logging. */ private volatile String auditQueryHash; - private final Set dependentTables; - private final SpiQueryBindCapture bindCapture; /** @@ -106,9 +91,9 @@ public class CQueryPlan implements SpiQueryPlan { this.planKey = request.getQueryPlanKey(); SpiQuery query = request.getQuery(); this.profileLocation = query.getProfileLocation(); + this.location = (profileLocation == null) ? null : profileLocation.location(); this.label = query.getPlanLabel(); this.name = deriveName(label, query.getType(), request.getBeanDescriptor().getSimpleName()); - this.location = location(); this.asOfTableCount = query.getAsOfTableCount(); this.sql = sqlRes.getSql(); this.sqlTree = sqlTree; @@ -118,7 +103,7 @@ public class CQueryPlan implements SpiQueryPlan { this.stats = new CQueryPlanStats(this); this.dependentTables = sqlTree.dependentTables(); this.bindCapture = initBindCapture(query); - this.hash = md5Hash(); + this.hash = Md5.hash(sql, name, location); } /** @@ -130,9 +115,9 @@ public class CQueryPlan implements SpiQueryPlan { this.beanType = request.getBeanDescriptor().getBeanType(); SpiQuery query = request.getQuery(); this.profileLocation = query.getProfileLocation(); + this.location = (profileLocation == null) ? null : profileLocation.location(); this.label = query.getPlanLabel(); this.name = deriveName(label, query.getType(), request.getBeanDescriptor().getSimpleName()); - this.location = location(); this.planKey = buildPlanKey(sql, logWhereSql); this.asOfTableCount = 0; this.sql = sql; @@ -143,7 +128,7 @@ public class CQueryPlan implements SpiQueryPlan { this.stats = new CQueryPlanStats(this); this.dependentTables = sqlTree.dependentTables(); this.bindCapture = initBindCaptureRaw(sql, query); - this.hash = md5Hash(); + this.hash = Md5.hash(sql, name, location); } private String deriveName(String label, SpiQuery.Type type, String simpleName) { @@ -169,10 +154,6 @@ public class CQueryPlan implements SpiQueryPlan { return sql.equals(RESULT_SET_BASED_RAW_SQL) || query.getType().isUpdate() ? SpiQueryBindCapture.NOOP : server.createQueryBindCapture(this); } - private String location() { - return (profileLocation == null) ? null : profileLocation.location(); - } - private CQueryPlanKey buildPlanKey(String sql, String logWhereSql) { return new RawSqlQueryPlanKey(sql, false, logWhereSql); } @@ -276,21 +257,6 @@ public class CQueryPlan implements SpiQueryPlan { return rawSql ? planKey.getPartialKey() + "_" + hash : planKey.getPartialKey(); } - /** - * Return the MD5 hash of the sql. - */ - private String md5Hash() { - StringBuilder sb = new StringBuilder(sql) - .append("|").append(name) - .append("|").append(location); - try { - return Md5.hash(sb.toString()); - } catch (Exception e) { - logger.error("Failed to MD5 hash the query", e); - return "error"; - } - } - SqlTree getSqlTree() { return sqlTree; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRequest.java index 86e15f389..315e7a07d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRequest.java @@ -24,10 +24,10 @@ class CQueryPlanRequest { CQueryPlanRequest(Connection connection, QueryPlanRequest req, Iterator iterator) { this.connection = connection; this.iterator = iterator; - this.maxCount = req.getMaxCount(); - long reqSince = req.getSince(); + this.maxCount = req.maxCount(); + long reqSince = req.since(); this.since = (reqSince == 0) ? Long.MAX_VALUE: reqSince; - long maxTimeMillis = req.getMaxTimeMillis(); + long maxTimeMillis = req.maxTimeMillis(); this.maxTime = maxTimeMillis > 0 ? System.currentTimeMillis() + maxTimeMillis : 0; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java index 23de6de5c..ffc749eb9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanStats.java @@ -83,56 +83,56 @@ public final class CQueryPlanStats { @Override public String toString() { - return "label:" + getLabel() + " location:" + getLocation() + " metrics:" + metrics + " sql:" + getSql(); + return "label:" + label() + " location:" + location() + " metrics:" + metrics + " sql:" + sql(); } @Override - public Class getType() { + public Class type() { return queryPlan.getBeanType(); } @Override - public String getLabel() { + public String label() { return queryPlan.getLabel(); } @Override - public String getName() { + public String name() { return queryPlan.getName(); } @Override - public String getLocation() { + public String location() { return queryPlan.getLocation(); } @Override - public long getCount() { - return metrics.getCount(); + public long count() { + return metrics.count(); } @Override - public long getTotal() { - return metrics.getTotal(); + public long total() { + return metrics.total(); } @Override - public long getMax() { - return metrics.getMax(); + public long max() { + return metrics.max(); } @Override - public long getMean() { - return metrics.getMean(); + public long mean() { + return metrics.mean(); } @Override - public String getHash() { + public String hash() { return queryPlan.getHash(); } @Override - public String getSql() { + public String sql() { return queryPlan.getSql(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java index 248fa9058..0c1df9483 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryRowCount.java @@ -22,44 +22,17 @@ import java.util.concurrent.locks.ReentrantLock; class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery { private final CQueryPlan queryPlan; - - /** - * The overall find request wrapper object. - */ private final OrmQueryRequest request; - private final BeanDescriptor desc; - private final SpiQuery query; - - /** - * Where clause predicates. - */ private final CQueryPredicates predicates; - - /** - * The final sql that is generated. - */ private final String sql; - - /** - * The resultSet that is read and converted to objects. - */ private ResultSet rset; - - /** - * The statement used to create the resultSet. - */ private PreparedStatement pstmt; - private String bindLog; - private long executionTimeMicros; - private int rowCount; - private long profileOffset; - private final ReentrantLock lock = new ReentrantLock(); /** @@ -104,11 +77,14 @@ class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery { return sql; } + long micros() { + return executionTimeMicros; + } + /** * Execute the query returning the row count. */ public int findCount() throws SQLException { - long startNano = System.nanoTime(); try { SpiTransaction t = getTransaction(); @@ -118,24 +94,19 @@ class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery { try { query.checkCancelled(); pstmt = conn.prepareStatement(sql); - if (query.getTimeout() > 0) { pstmt.setQueryTimeout(query.getTimeout()); } - bindLog = predicates.bind(pstmt, conn); } finally { lock.unlock(); } rset = pstmt.executeQuery(); query.checkCancelled(); - if (!rset.next()) { throw new PersistenceException("Expecting 1 row but got none?"); } - rowCount = rset.getInt(1); - executionTimeMicros = (System.nanoTime() - startNano) / 1000L; request.slowQueryCheck(executionTimeMicros, rowCount); if (queryPlan.executionTime(executionTimeMicros)) { @@ -143,7 +114,6 @@ class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery { } t.profileEvent(this); return rowCount; - } finally { close(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java index eec5d2e6f..302b1bcf0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryUpdate.java @@ -19,36 +19,18 @@ import java.util.concurrent.locks.ReentrantLock; class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery { private final CQueryPlan queryPlan; - private final OrmQueryRequest request; - private final BeanDescriptor desc; - private final SpiQuery query; - - /** - * Where clause predicates. - */ private final CQueryPredicates predicates; - - /** - * The final sql that is generated. - */ private final String sql; - - /** - * The statement used to create the resultSet. - */ private PreparedStatement pstmt; - private String bindLog; - private int rowCount; - private long profileOffset; - + private long executionTimeMicros; private final ReentrantLock lock = new ReentrantLock(); - + /** * Create the Sql select based on the request. */ @@ -80,7 +62,6 @@ class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery { * Execute the update or delete statement returning the row count. */ public int execute() throws SQLException { - long startNano = System.nanoTime(); try { SpiTransaction t = getTransaction(); @@ -90,19 +71,16 @@ class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery { try { query.checkCancelled(); pstmt = conn.prepareStatement(sql); - if (query.getTimeout() > 0) { pstmt.setQueryTimeout(query.getTimeout()); } - bindLog = predicates.bind(pstmt, conn); } finally { lock.unlock(); } rowCount = pstmt.executeUpdate(); query.checkCancelled(); - - long executionTimeMicros = (System.nanoTime() - startNano) / 1000L; + executionTimeMicros = (System.nanoTime() - startNano) / 1000L; request.slowQueryCheck(executionTimeMicros, rowCount); if (queryPlan.executionTime(executionTimeMicros)) { queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros); @@ -115,6 +93,10 @@ class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery { } } + long micros() { + return executionTimeMicros; + } + private SpiTransaction getTransaction() { return request.getTransaction(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java index 8065a62dc..1c1a41d07 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DQueryPlanOutput.java @@ -16,8 +16,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { private final String sql; private final String bind; private final String plan; - - private String hash; + private final String hash; private long queryTimeMicros; private long captureCount; @@ -32,7 +31,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { } @Override - public String getHash() { + public String hash() { return hash; } @@ -40,7 +39,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { * Return the associated bean. */ @Override - public Class getBeanType() { + public Class beanType() { return beanType; } @@ -48,12 +47,12 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { * Return the query label if set. */ @Override - public String getLabel() { + public String label() { return label; } @Override - public ProfileLocation getProfileLocation() { + public ProfileLocation profileLocation() { return profileLocation; } @@ -61,7 +60,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { * Return the sql of query. */ @Override - public String getSql() { + public String sql() { return sql; } @@ -69,7 +68,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { * Return a description of the bind values used. */ @Override - public String getBind() { + public String bind() { return bind; } @@ -77,7 +76,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { * Return the query plan. */ @Override - public String getPlan() { + public String plan() { return plan; } @@ -86,7 +85,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { * to build the query plan. */ @Override - public long getQueryTimeMicros() { + public long queryTimeMicros() { return queryTimeMicros; } @@ -94,7 +93,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan { * Return the total count of times bind capture has occurred. */ @Override - public long getCaptureCount() { + public long captureCount() { return captureCount; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java index ac1386e11..83c7b2936 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java @@ -18,19 +18,15 @@ public class SqlBeanLoad { private final DbReadContext ctx; private final EntityBean bean; private final EntityBeanIntercept ebi; - private final Class type; private final boolean lazyLoading; - private final boolean refreshLoading; private final boolean rawSql; SqlBeanLoad(DbReadContext ctx, Class type, EntityBean bean, Mode queryMode) { - this.ctx = ctx; this.rawSql = ctx.isRawSql(); this.type = type; this.lazyLoading = queryMode == Mode.LAZYLOAD_BEAN; - this.refreshLoading = queryMode == Mode.REFRESH_BEAN; this.bean = bean; this.ebi = bean == null ? null : bean._ebean_getIntercept(); } @@ -50,34 +46,21 @@ public class SqlBeanLoad { } public Object load(BeanProperty prop) { - if (!rawSql && !prop.isLoadProperty(ctx.isDraftQuery())) { return null; } - if ((bean == null) || (lazyLoading && ebi.isLoadedProperty(prop.getPropertyIndex())) || (type != null && !prop.isAssignableFrom(type))) { - // ignore this property // ... null: bean already in persistence context // ... lazyLoading: partial bean that is lazy loading // ... type: inheritance and not assignable to this instance - prop.loadIgnore(ctx); return null; } - try { - Object dbVal = prop.read(ctx); - if (!refreshLoading) { - prop.setValue(bean, dbVal); - } else { - prop.setValueIntercept(bean, dbVal); - } - - return dbVal; - + return prop.readSet(ctx, bean); } catch (Exception e) { bean._ebean_getIntercept().setLoadError(prop.getPropertyIndex(), e); ctx.handleLoadError(prop.getFullBeanName(), e); @@ -89,10 +72,6 @@ public class SqlBeanLoad { * Load the given value into the property. */ public void load(BeanProperty target, Object dbVal) { - if (!refreshLoading) { - target.setValue(bean, dbVal); - } else { - target.setValueIntercept(bean, dbVal); - } + target.setValue(bean, dbVal); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index e4c936e45..06465355c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -365,11 +365,11 @@ public final class SqlTreeBuilder { * This means it can included individual properties of an embedded bean. *

*/ - private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName) { - STreeProperty p = desc.findProperty(propName); + private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName, String path) { + STreeProperty p = desc.findPropertyWithDynamic(propName, path); if (p == null) { logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it."); - + return; } else if (p instanceof STreePropertyAssoc && p.isEmbedded()) { // if the property is embedded we need to lookup the real column name int pos = propName.indexOf('.'); @@ -383,7 +383,7 @@ public final class SqlTreeBuilder { private void addProperty(SqlTreeProperties selectProps, STreeType desc, OrmQueryProperties queryProps, String propName) { if (subQuery) { - addPropertyToSubQuery(selectProps, desc, propName); + addPropertyToSubQuery(selectProps, desc, propName, queryProps.getPath()); return; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 3a81183e9..db3a605ea 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1,30 +1,7 @@ package io.ebeaninternal.server.querydefn; -import io.ebean.CacheMode; -import io.ebean.CountDistinctOrder; -import io.ebean.Database; -import io.ebean.DtoQuery; -import io.ebean.Expression; -import io.ebean.ExpressionFactory; -import io.ebean.ExpressionList; -import io.ebean.FetchConfig; -import io.ebean.FetchGroup; -import io.ebean.FetchPath; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.OrderBy; +import io.ebean.*; import io.ebean.OrderBy.Property; -import io.ebean.PagedList; -import io.ebean.PersistenceContextScope; -import io.ebean.ProfileLocation; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.QueryType; -import io.ebean.RawSql; -import io.ebean.Transaction; -import io.ebean.UpdateQuery; -import io.ebean.Version; import io.ebean.bean.CallOrigin; import io.ebean.bean.ObjectGraphNode; import io.ebean.bean.ObjectGraphOrigin; @@ -32,29 +9,10 @@ import io.ebean.bean.PersistenceContext; import io.ebean.event.BeanQueryRequest; import io.ebean.event.readaudit.ReadEvent; import io.ebean.plugin.BeanType; -import io.ebeaninternal.api.BindParams; -import io.ebeaninternal.api.CQueryPlanKey; -import io.ebeaninternal.api.CacheIdLookup; -import io.ebeaninternal.api.CacheIdLookupMany; -import io.ebeaninternal.api.CacheIdLookupSingle; -import io.ebeaninternal.api.HashQuery; -import io.ebeaninternal.api.ManyWhereJoins; -import io.ebeaninternal.api.NaturalKeyQueryData; -import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.api.SpiExpression; -import io.ebeaninternal.api.SpiExpressionList; -import io.ebeaninternal.api.SpiExpressionValidation; -import io.ebeaninternal.api.SpiNamedParam; -import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.api.SpiQuerySecondary; -import io.ebeaninternal.api.SpiTransaction; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.autotune.ProfilingListener; import io.ebeaninternal.server.core.SpiOrmQueryRequest; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanNaturalKey; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.InheritInfo; -import io.ebeaninternal.server.deploy.TableJoin; +import io.ebeaninternal.server.deploy.*; import io.ebeaninternal.server.el.ElPropertyDeploy; import io.ebeaninternal.server.expression.DefaultExpressionList; import io.ebeaninternal.server.expression.IdInExpression; @@ -66,14 +24,7 @@ import io.ebeaninternal.server.transaction.ExternalJdbcTransaction; import javax.persistence.PersistenceException; import java.sql.Connection; import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; +import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; @@ -91,8 +42,6 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy(); - private final ReentrantLock lock = new ReentrantLock(); - private final Class beanType; private final ExpressionFactory expressionFactory; @@ -1269,22 +1218,13 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { } } - /** - * Calculate a hash based on the bind values used in the query. - *

- * Used with queryPlanHash() to get a unique hash for a query. - *

- */ @Override - public int queryBindHash() { - int hc = (id == null ? 0 : id.hashCode()); - hc = hc * 92821 + (whereExpressions == null ? 0 : whereExpressions.queryBindHash()); - hc = hc * 92821 + (havingExpressions == null ? 0 : havingExpressions.queryBindHash()); - hc = hc * 92821 + (bindParams == null ? 0 : bindParams.queryBindHash()); - hc = hc * 92821 + (asOf == null ? 0 : asOf.hashCode()); - hc = hc * 92821 + (versionsStart == null ? 0 : versionsStart.hashCode()); - hc = hc * 92821 + (versionsEnd == null ? 0 : versionsEnd.hashCode()); - return hc; + public void queryBindKey(BindValuesKey key) { + key.add(id); + if (whereExpressions != null) whereExpressions.queryBindKey(key); + if (havingExpressions != null) havingExpressions.queryBindKey(key); + if (bindParams != null) bindParams.queryBindHash(key); + key.add(asOf).add(versionsStart).add(versionsEnd); } /** @@ -1298,8 +1238,9 @@ public class DefaultOrmQuery extends AbstractQuery implements SpiQuery { public HashQuery queryHash() { // calculateQueryPlanHash is called just after potential AutoTune tuning // so queryPlanHash is calculated well before this method is called - int hc = queryBindHash(); - return new HashQuery(queryPlanKey, hc); + BindValuesKey bindKey = new BindValuesKey(); + queryBindKey(bindKey); + return new HashQuery(queryPlanKey, bindKey); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java b/ebean-core/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java index 0b5eb4ad6..5a405dbc4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java @@ -9,6 +9,7 @@ import io.ebeaninternal.server.query.DefaultSqlRow; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.sql.Types; public class DRawSqlService implements SpiRawSqlService { @@ -48,7 +49,26 @@ public class DRawSqlService implements SpiRawSqlService { if (ret.containsKey(name)) { name = combine(meta.getSchemaName(i), meta.getTableName(i), name); } - ret.put(name, resultSet.getObject(i)); + + // convert (C/B)LOBs to java objects. + // A java.sql.Clob depends on an open connection, so storing this object in a map + // that is accessed later, when the connection is closed, will result in a "connection is closed" exception. + // From the java.sql.Clob documentation: "... which means that a Clob object contains a logical pointer to the SQL CLOB + // data rather than the data itself." + switch (meta.getColumnType(i)) { + case Types.CLOB: + case Types.NCLOB: + ret.put(name, resultSet.getString(i)); + break; + + case Types.BLOB: + ret.put(name, resultSet.getBytes(i)); + break; + + default: + ret.put(name, resultSet.getObject(i)); + break; + } } return ret; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java index 2959c872c..a1a9ec8cf 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java @@ -36,6 +36,7 @@ public class DataBind implements DataBinder { private List inputStreams; protected int pos; + private String json; public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt, Connection connection) { this.dataTimeZone = dataTimeZone; @@ -43,6 +44,19 @@ public class DataBind implements DataBinder { this.connection = connection; } + @Override + public void pushJson(String json) { + assert this.json == null; // we can only push one value + this.json = json; + } + + @Override + public String popJson() { + String ret = json; + json = null; + return ret; + } + @Override public StringBuilder append(Object entry) { return bindLog.append(entry); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 82c3ff46a..1400b2180 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -2,7 +2,6 @@ package io.ebeaninternal.server.type; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.introspect.AnnotatedField; import io.ebean.annotation.*; import io.ebean.config.DatabaseConfig; import io.ebean.config.JsonConfig; @@ -135,7 +134,7 @@ public final class DefaultTypeManager implements TypeManager { this.postgres = isPostgres(config.getDatabasePlatform()); this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent(); this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null; - this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.isJsonDirtyByDefault()) : null; + this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.getJsonMutationDetection()) : null; this.extraTypeFactory = new DefaultTypeFactory(config); this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform()); this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform()); @@ -426,7 +425,7 @@ public final class DefaultTypeManager implements TypeManager { if (objectMapper == null) { throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present"); } - return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, (AnnotatedField) prop.getJacksonField(), dbType, docType); + return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, prop, dbType, docType); } /** @@ -557,7 +556,7 @@ public final class DefaultTypeManager implements TypeManager { // no override or further mapping required return scalarType; } - ScalarTypeEnum scalarEnum = (ScalarTypeEnum)scalarType; + ScalarTypeEnum scalarEnum = (ScalarTypeEnum) scalarType; if (scalarEnum != null && !scalarEnum.isOverrideBy(type)) { if (type != null && !scalarEnum.isCompatible(type)) { throw new IllegalStateException("Error mapping Enum type:" + enumType + " It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)"); @@ -674,7 +673,7 @@ public final class DefaultTypeManager implements TypeManager { private Object initObjectMapper(DatabaseConfig config) { Object objectMapper = config.getObjectMapper(); if (objectMapper == null) { - objectMapper = new ObjectMapper(); + objectMapper = InitObjectMapper.init(); config.setObjectMapper(objectMapper); } return objectMapper; @@ -750,12 +749,15 @@ public final class DefaultTypeManager implements TypeManager { } private void initialiseJavaTimeTypes(DatabaseConfig config) { + + ZoneId zoneId = getZoneId(config); + typeMap.put(java.nio.file.Path.class, new ScalarTypePath()); addType(java.time.Period.class, new ScalarTypePeriod()); addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate)); addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(jsonDateTime)); - addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime)); - addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime)); + addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime, zoneId)); + addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime, zoneId)); addType(Instant.class, new ScalarTypeInstant(jsonDateTime)); addType(DayOfWeek.class, new ScalarTypeDayOfWeek()); addType(Month.class, new ScalarTypeMonth()); @@ -771,6 +773,11 @@ public final class DefaultTypeManager implements TypeManager { addType(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration()); } + private ZoneId getZoneId(DatabaseConfig config) { + final String dataTimeZone = config.getDataTimeZone(); + return (dataTimeZone == null) ? ZoneOffset.systemDefault() : TimeZone.getTimeZone(dataTimeZone).toZoneId(); + } + private void addType(Class clazz, ScalarType scalarType) { typeMap.put(clazz, scalarType); logicalMap.putIfAbsent(clazz.getSimpleName(), scalarType); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/InitObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/InitObjectMapper.java new file mode 100644 index 000000000..90a86ec79 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/InitObjectMapper.java @@ -0,0 +1,22 @@ +package io.ebeaninternal.server.type; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Initialise the Jackson ObjectMapper. + */ +class InitObjectMapper { + + /** + * Create and return the default ObjectMapper. + */ + static Object init() { + SimpleModule module = new SimpleModule(); + module.addAbstractTypeMapping(Set.class, LinkedHashSet.class); + return new ObjectMapper().registerModule(module); + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java index 9763a5667..b85c8e688 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java @@ -20,22 +20,29 @@ import java.util.Calendar; public class RsetDataReader implements DataReader { private static final int bufferSize = 512; - static final int clobBufferSize = 512; - static final int stringInitialSize = 512; private final DataTimeZone dataTimeZone; - private final ResultSet rset; - protected int pos; + private String json; public RsetDataReader(DataTimeZone dataTimeZone, ResultSet rset) { this.dataTimeZone = dataTimeZone; this.rset = rset; } + @Override + public void pushJson(String json) { + this.json = json; + } + + @Override + public String popJson() { + return json; + } + @Override public void close() throws SQLException { rset.close(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java index 5e24412b5..41cb28d49 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java @@ -7,14 +7,13 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import io.ebean.annotation.MutationDetection; import io.ebean.core.type.DataBinder; import io.ebean.core.type.DataReader; import io.ebean.core.type.DocPropertyType; import io.ebean.core.type.ScalarType; import io.ebean.text.TextException; -import io.ebeaninternal.json.ModifyAwareList; -import io.ebeaninternal.json.ModifyAwareMap; -import io.ebeaninternal.json.ModifyAwareSet; +import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import javax.persistence.PersistenceException; import java.io.DataInput; @@ -22,9 +21,6 @@ import java.io.DataOutput; import java.io.IOException; import java.sql.SQLException; import java.sql.Types; -import java.util.List; -import java.util.Map; -import java.util.Set; /** * Supports @DbJson properties using Jackson ObjectMapper. @@ -34,81 +30,85 @@ class ScalarTypeJsonObjectMapper { /** * Create and return the appropriate ScalarType. */ - static ScalarType createTypeFor(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { - Class type = field.getRawType(); - if (Set.class.equals(type)) { - return new OmSet(jsonManager, field, dbType, docType); + static ScalarType createTypeFor(TypeJsonManager jsonManager, DeployBeanProperty prop, int dbType, DocPropertyType docType) { + AnnotatedField field = (AnnotatedField) prop.getJacksonField(); + MutationDetection mode = prop.getMutationDetection(); + if (mode == MutationDetection.NONE) { + return new NoMutationDetection(jsonManager, field, dbType, docType); + } else if (mode != MutationDetection.DEFAULT) { + return new GenericObject(jsonManager, field, dbType, docType); } - if (List.class.equals(type)) { - return new OmList(jsonManager, field, dbType, docType); - } - if (Map.class.equals(type)) { - return new OmMap(jsonManager, field, dbType); - } - return new GenericObject(jsonManager, field, dbType, type); + // using the global default MutationDetection mode (defaults to HASH) + prop.setMutationDetection(jsonManager.mutationDetection()); + return new GenericObject(jsonManager, field, dbType, docType); } /** - * Maps any type (Object) using Jackson ObjectMapper. + * No mutation detection on this json property. + */ + private static class NoMutationDetection extends Base { + + NoMutationDetection(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { + super(Object.class, jsonManager, field, dbType, docType); + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public boolean isDirty(Object value) { + return false; + } + } + + /** + * Supports HASH and SOURCE dirty detection modes. */ private static class GenericObject extends Base { - GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class rawType) { - super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType); - } - } - - /** - * Type for Sets wrapping the ObjectMapper Set as a ModifyAwareSet. - */ - @SuppressWarnings("rawtypes") - private static class OmSet extends Base { - - OmSet(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { - super(Set.class, jsonManager, field, dbType, docType); + GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { + super(Object.class, jsonManager, field, dbType, docType); } @Override - @SuppressWarnings("unchecked") - public Set read(DataReader reader) throws SQLException { - Set value = super.read(reader); - return value == null ? null : new ModifyAwareSet(value); - } - } - - /** - * Type for Lists wrapping the ObjectMapper List as a ModifyAwareList. - */ - @SuppressWarnings("rawtypes") - private static class OmList extends Base { - - OmList(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { - super(List.class, jsonManager, field, dbType, docType); + public boolean isJsonMapper() { + return true; } @Override - @SuppressWarnings("unchecked") - public List read(DataReader reader) throws SQLException { - List value = super.read(reader); - return value == null ? null : new ModifyAwareList(value); - } - } - - /** - * Type for Map wrapping the ObjectMapper Map as a ModifyAwareMap. - */ - @SuppressWarnings("rawtypes") - private static class OmMap extends Base { - - OmMap(TypeJsonManager jsonManager, AnnotatedField field, int dbType) { - super(Map.class, jsonManager, field, dbType, DocPropertyType.OBJECT); + public Object read(DataReader reader) throws SQLException { + String json = reader.getString(); + // pushJson such that we MD5 and store on EntityBeanIntercept later + reader.pushJson(json); + if (json == null || json.isEmpty()) { + return null; + } + try { + return objectReader.readValue(json, deserType); + } catch (IOException e) { + throw new TextException("Failed to parse JSON [{}] as " + deserType, json, e); + } } @Override - @SuppressWarnings("unchecked") - public Map read(DataReader reader) throws SQLException { - Map value = super.read(reader); - return value == null ? null : new ModifyAwareMap(value); + public void bind(DataBinder binder, Object value) throws SQLException { + // popJson as dirty detection already converted to json string + String rawJson = binder.popJson(); + if (rawJson == null && value != null) { + rawJson = formatValue(value); // not expected, need to check? + } + if (pgType != null) { + binder.setObject(PostgresHelper.asObject(pgType, rawJson)); + } else { + if (value == null) { + // use varchar, otherwise SqlServer/db2 will fail with 'Invalid JDBC data type 5.001.' + binder.setNull(Types.VARCHAR); + } else { + binder.setString(rawJson); + } + } } } @@ -118,44 +118,27 @@ class ScalarTypeJsonObjectMapper { */ private static abstract class Base extends ScalarTypeBase { - private final ObjectWriter objectWriter; - private final ObjectMapper objectReader; - private final JavaType deserType; - private final String pgType; + protected final ObjectWriter objectWriter; + protected final ObjectMapper objectReader; + protected final JavaType deserType; + protected final String pgType; private final DocPropertyType docType; - private final TypeJsonManager.DirtyHandler dirtyHandler; Base(Class cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) { - this(cls, jsonManager, field, dbType, docType, cls); - } - - Base(Class cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType, Class rawType) { super(cls, false, dbType); this.objectReader = jsonManager.objectMapper(); this.pgType = jsonManager.postgresType(dbType); this.docType = docType; - this.dirtyHandler = jsonManager.dirtyHandler(cls, rawType); final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectReader); this.deserType = helper.type(); this.objectWriter = helper.objectWriter(); } - /** - * Consider as a mutable type. Use the isDirty() method to check for dirty state. - */ @Override public boolean isMutable() { return true; } - /** - * Return true if the value should be considered dirty (and included in an update). - */ - @Override - public boolean isDirty(Object value) { - return dirtyHandler.isDirty(value); - } - @Override public T read(DataReader reader) throws SQLException { String json = reader.getString(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java index b62c693d3..b68f3eba9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTime.java @@ -15,8 +15,11 @@ import static io.ebeaninternal.server.type.IsoJsonDateTimeParser.formatIso; */ public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime { - public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode) { + private final ZoneId zoneId; + + public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode, ZoneId zoneId) { super(mode, OffsetDateTime.class, false, Types.TIMESTAMP); + this.zoneId = zoneId; } @Override @@ -46,7 +49,7 @@ public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime { - public ScalarTypeZonedDateTime(JsonConfig.DateTime mode) { + private final ZoneId zoneId; + + public ScalarTypeZonedDateTime(JsonConfig.DateTime mode, ZoneId zoneId) { super(mode, ZonedDateTime.class, false, Types.TIMESTAMP); + this.zoneId = zoneId; } @Override @@ -44,7 +47,7 @@ public class ScalarTypeZonedDateTime extends ScalarTypeBaseDateTime cls, Class rawType) { - if (!Object.class.equals(cls) || ModifyAwareType.class.isAssignableFrom(rawType)) { - // Set, List and Map are modify aware - return modifyAwareHandler; - } - return defaultHandler; - } - /** * Return true if the value should be considered dirty (and included in an update). */ @@ -71,28 +58,4 @@ class TypeJsonManager { } } - static final class ModifyAwareHandler implements DirtyHandler { - @Override - public boolean isDirty(Object value) { - return checkModifyAware(value); - } - } - - /** - * Effectively constant based on {@link DatabaseConfig#isJsonDirtyByDefault()} - */ - static final class DefaultHandler implements DirtyHandler { - - private final boolean dirty; - - DefaultHandler(boolean dirty) { - this.dirty = dirty; - } - - @Override - public boolean isDirty(Object value) { - return dirty; - } - } - } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/util/Checksum.java b/ebean-core/src/main/java/io/ebeaninternal/server/util/Checksum.java new file mode 100644 index 000000000..8295703c1 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/util/Checksum.java @@ -0,0 +1,20 @@ +package io.ebeaninternal.server.util; + +import java.nio.charset.StandardCharsets; +import java.util.zip.CRC32; + +/** + * Compute a checksum for String content. Use when we desire cheaper option than MD5. + */ +public final class Checksum { + + /** + * Return the checksum for the given String input. + */ + public static long checksum(String input) { + CRC32 checksum = new CRC32(); + final byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + checksum.update(bytes, 0, bytes.length); + return checksum.getValue(); + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/util/Md5.java b/ebean-core/src/main/java/io/ebeaninternal/server/util/Md5.java index 1c848f55d..45df901f0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/util/Md5.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/util/Md5.java @@ -8,10 +8,15 @@ public final class Md5 { /** * Return the MD5 hash of the underlying sql. */ - public static String hash(String content) { + public static String hash(String... values) { try { MessageDigest md = MessageDigest.getInstance("MD5"); - return digestToHex(md.digest(content.getBytes(StandardCharsets.UTF_8))); + for (String val : values) { + if (val != null) { + md.update(val.getBytes(StandardCharsets.UTF_8)); + } + } + return digestToHex(md.digest()); } catch (Exception e) { throw new RuntimeException("MD5 hashing failed", e); } @@ -21,8 +26,7 @@ public final class Md5 { * Convert the digest into a hex value. */ private static String digestToHex(byte[] digest) { - - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(32); for (byte aDigest : digest) { sb.append(Integer.toString((aDigest & 0xff) + 0x100, 16).substring(1)); } diff --git a/ebean-core/src/test/java/io/ebean/BaseTestCase.java b/ebean-core/src/test/java/io/ebean/BaseTestCase.java index 0205d5294..988e73b59 100644 --- a/ebean-core/src/test/java/io/ebean/BaseTestCase.java +++ b/ebean-core/src/test/java/io/ebean/BaseTestCase.java @@ -90,14 +90,14 @@ public abstract class BaseTestCase { } protected List visitTimedMetrics() { - return collectMetrics().getTimedMetrics(); + return collectMetrics().timedMetrics(); } protected List sqlMetrics() { List timedMetrics = visitTimedMetrics(); return timedMetrics.stream() - .filter((it) -> it.getName().startsWith("sql.") || it.getName().startsWith("orm.")) + .filter((it) -> it.name().startsWith("sql.") || it.name().startsWith("orm.")) .collect(Collectors.toList()); } diff --git a/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java b/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java index ba30e9a5c..625cc9ae9 100644 --- a/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java +++ b/ebean-core/src/test/java/io/ebean/DtoQuery2Test.java @@ -191,13 +191,13 @@ public class DtoQuery2Test extends BaseTestCase { BasicMetricVisitor basic = new BasicMetricVisitor(false, true, true, true); server().getMetaInfoManager().visitMetrics(basic); - List stats = basic.getQueryMetrics(); + List stats = basic.queryMetrics(); assertThat(stats).hasSize(1); MetaQueryMetric queryMetric = stats.get(0); - assertThat(queryMetric.getLabel()).isEqualTo("basic"); - assertThat(queryMetric.getCount()).isEqualTo(3); - assertThat(queryMetric.getName()).isEqualTo("dto.DCust_basic"); + assertThat(queryMetric.label()).isEqualTo("basic"); + assertThat(queryMetric.count()).isEqualTo(3); + assertThat(queryMetric.name()).isEqualTo("dto.DCust_basic"); server().findDto(DCust.class, "select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name") @@ -207,7 +207,7 @@ public class DtoQuery2Test extends BaseTestCase { BasicMetricVisitor metric2 = server().getMetaInfoManager().visitBasic(); - stats = metric2.getQueryMetrics(); + stats = metric2.queryMetrics(); assertThat(stats).hasSize(2); log.info("stats " + stats); diff --git a/ebean-core/src/test/java/io/ebean/DtoQueryFromOrmTest.java b/ebean-core/src/test/java/io/ebean/DtoQueryFromOrmTest.java index ee26e9409..28b0bd490 100644 --- a/ebean-core/src/test/java/io/ebean/DtoQueryFromOrmTest.java +++ b/ebean-core/src/test/java/io/ebean/DtoQueryFromOrmTest.java @@ -27,12 +27,12 @@ public class DtoQueryFromOrmTest extends BaseTestCase { @AfterClass public static void reportStats() { ServerMetrics metrics = DB.getDefault().getMetaInfoManager().collectMetrics(); - for (MetaQueryMetric metric : metrics.getQueryMetrics()) { + for (MetaQueryMetric metric : metrics.queryMetrics()) { System.out.println(metric); } System.out.println("-- transaction metrics --"); - for (MetaTimedMetric metric : metrics.getTimedMetrics()) { + for (MetaTimedMetric metric : metrics.timedMetrics()) { System.out.println(metric); } } @@ -59,15 +59,15 @@ public class DtoQueryFromOrmTest extends BaseTestCase { ServerMetrics metrics = collectMetrics(); - List stats = metrics.getQueryMetrics(); + List stats = metrics.queryMetrics(); for (MetaQueryMetric stat : stats) { - long meanMicros = stat.getMean(); + long meanMicros = stat.mean(); assertThat(meanMicros).isLessThan(900_000); - assertThat(stat.getLocation()).isSameAs(loc0.location()); + assertThat(stat.location()).isSameAs(loc0.location()); } assertThat(stats).hasSize(1); - assertThat(stats.get(0).getCount()).isEqualTo(4); + assertThat(stats.get(0).count()).isEqualTo(4); } @ForPlatform(Platform.H2) diff --git a/ebean-core/src/test/java/io/ebean/DtoQueryTest.java b/ebean-core/src/test/java/io/ebean/DtoQueryTest.java index eea1c6da3..0331a8215 100644 --- a/ebean-core/src/test/java/io/ebean/DtoQueryTest.java +++ b/ebean-core/src/test/java/io/ebean/DtoQueryTest.java @@ -42,14 +42,14 @@ public class DtoQueryTest extends BaseTestCase { ServerMetrics metrics = collectMetrics(); - List stats = metrics.getQueryMetrics(); + List stats = metrics.queryMetrics(); for (MetaQueryMetric stat : stats) { - long meanMicros = stat.getMean(); + long meanMicros = stat.mean(); assertThat(meanMicros).isLessThan(900_000); } assertThat(stats).hasSize(1); - assertThat(stats.get(0).getCount()).isEqualTo(1); + assertThat(stats.get(0).count()).isEqualTo(1); } @Test @@ -283,13 +283,13 @@ public class DtoQueryTest extends BaseTestCase { BasicMetricVisitor basic = new BasicMetricVisitor(false, true, true, true); server().getMetaInfoManager().visitMetrics(basic); - List stats = basic.getQueryMetrics(); + List stats = basic.queryMetrics(); assertThat(stats).hasSize(1); MetaQueryMetric queryMetric = stats.get(0); - assertThat(queryMetric.getLabel()).isEqualTo("basic"); - assertThat(queryMetric.getCount()).isEqualTo(3); - assertThat(queryMetric.getName()).isEqualTo("dto.DCust_basic"); + assertThat(queryMetric.label()).isEqualTo("basic"); + assertThat(queryMetric.count()).isEqualTo(3); + assertThat(queryMetric.name()).isEqualTo("dto.DCust_basic"); server().findDto(DCust.class, "select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name") @@ -299,7 +299,7 @@ public class DtoQueryTest extends BaseTestCase { ServerMetrics metric2 = server().getMetaInfoManager().collectMetrics(); - stats = metric2.getQueryMetrics(); + stats = metric2.queryMetrics(); assertThat(stats).hasSize(2); log.info("stats " + stats); diff --git a/ebean-core/src/test/java/io/ebean/EbeanServer_refresh.java b/ebean-core/src/test/java/io/ebean/EbeanServer_refresh.java index 1e1900a13..66af5d164 100644 --- a/ebean-core/src/test/java/io/ebean/EbeanServer_refresh.java +++ b/ebean-core/src/test/java/io/ebean/EbeanServer_refresh.java @@ -11,7 +11,7 @@ import java.util.HashMap; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class EbeanServer_refresh { @@ -39,8 +39,11 @@ public class EbeanServer_refresh { assertEquals(rows, 1); + basic.setName("modify"); + assertTrue(DB.getBeanState(basic).isDirty()); server.refresh(basic); assertEquals(basic.getStatus(), EBasic.Status.ACTIVE); + assertFalse(DB.getBeanState(basic).isDirty()); } @Test diff --git a/ebean-core/src/test/java/io/ebean/UpdateQueryTest.java b/ebean-core/src/test/java/io/ebean/UpdateQueryTest.java index ca2caa4c8..5b9c42991 100644 --- a/ebean-core/src/test/java/io/ebean/UpdateQueryTest.java +++ b/ebean-core/src/test/java/io/ebean/UpdateQueryTest.java @@ -39,10 +39,10 @@ public class UpdateQueryTest extends BaseTestCase { assertSql(query).contains("update o_customer set status=?, updtime=? where status = ? and id > ?"); ServerMetrics metrics = collectMetrics(); - List ormQueryMetrics = metrics.getQueryMetrics(); + List ormQueryMetrics = metrics.queryMetrics(); assertThat(ormQueryMetrics).hasSize(1); - assertThat(ormQueryMetrics.get(0).getType()).isEqualTo(Customer.class); - assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateActive"); + assertThat(ormQueryMetrics.get(0).type()).isEqualTo(Customer.class); + assertThat(ormQueryMetrics.get(0).label()).isEqualTo("updateActive"); } @Test @@ -69,10 +69,10 @@ public class UpdateQueryTest extends BaseTestCase { assertSql(sql.get(0)).contains("update o_customer set status = status"); ServerMetrics metrics = collectMetrics(); - List ormQueryMetrics = metrics.getQueryMetrics(); + List ormQueryMetrics = metrics.queryMetrics(); assertThat(ormQueryMetrics).hasSize(1); - assertThat(ormQueryMetrics.get(0).getType()).isEqualTo(Customer.class); - assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateAll"); + assertThat(ormQueryMetrics.get(0).type()).isEqualTo(Customer.class); + assertThat(ormQueryMetrics.get(0).label()).isEqualTo("updateAll"); } @Test diff --git a/ebean-core/src/test/java/io/ebean/common/BeanListTest.java b/ebean-core/src/test/java/io/ebean/common/BeanListTest.java index 11a7429d7..03c08efad 100644 --- a/ebean-core/src/test/java/io/ebean/common/BeanListTest.java +++ b/ebean-core/src/test/java/io/ebean/common/BeanListTest.java @@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection; import org.junit.Test; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -13,9 +12,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class BeanListTest { - private Object object1 = new Object(); - private Object object2 = new Object(); - private Object object3 = new Object(); + private final Object object1 = new Object(); + private final Object object2 = new Object(); + private final Object object3 = new Object(); private List all() { List all = new ArrayList<>(); @@ -33,7 +32,7 @@ public class BeanListTest { } @Test - public void test_setModifyListening_null() throws Exception { + public void test_setModifyListening_null() { BeanList list = new BeanList<>(); list.setModifyListening(null); @@ -45,7 +44,7 @@ public class BeanListTest { } @Test - public void test_setModifyListening_none() throws Exception { + public void test_setModifyListening_none() { BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.NONE); @@ -57,28 +56,28 @@ public class BeanListTest { } @Test - public void testAdd() throws Exception { + public void testAdd() { BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); list.add(object1); - assertThat(list.getModifyAdditions()).containsExactly(object1); + assertThat(list.getModifyAdditions()).containsOnly(object1); assertThat(list.getModifyRemovals()).isEmpty(); list.add(object1); - assertThat(list.getModifyAdditions()).containsExactly(object1); + assertThat(list.getModifyAdditions()).containsOnly(object1); list.add(object2); - assertThat(list.getModifyAdditions()).containsExactly(object1, object2); + assertThat(list.getModifyAdditions()).containsOnly(object1, object2); list.remove(object1); - assertThat(list.getModifyAdditions()).containsExactly(object2); + assertThat(list.getModifyAdditions()).containsOnly(object2); assertThat(list.getModifyRemovals()).isEmpty(); } @Test - public void testAddAll_given_emptyStart() throws Exception { + public void testAddAll_given_emptyStart() { BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -86,12 +85,12 @@ public class BeanListTest { // act list.addAll(all()); - assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3); assertThat(list.getModifyRemovals()).isEmpty(); } @Test - public void test_removals_DeleteThenAddBack_expect_noChange() throws Exception { + public void test_removals_DeleteThenAddBack_expect_noChange() { BeanList list = new BeanList<>(some()); list.setModifyListening(BeanCollection.ModifyListenMode.REMOVALS); @@ -106,33 +105,33 @@ public class BeanListTest { } @Test - public void test_sort_whenAll_expect_noChange() throws Exception { + public void test_sort_whenAll_expect_noChange() { BeanList list = new BeanList<>(all()); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); // act - Collections.sort(list, Comparator.comparingInt(Object::hashCode)); + list.sort(Comparator.comparingInt(Object::hashCode)); assertThat(list.getModifyRemovals()).isEmpty(); assertThat(list.getModifyAdditions()).isEmpty(); } @Test - public void test_sort_whenRemovals_expect_noChange() throws Exception { + public void test_sort_whenRemovals_expect_noChange() { BeanList list = new BeanList<>(all()); list.setModifyListening(BeanCollection.ModifyListenMode.REMOVALS); // act - Collections.sort(list, Comparator.comparingInt(Object::hashCode)); + list.sort(Comparator.comparingInt(Object::hashCode)); assertThat(list.getModifyRemovals()).isEmpty(); assertThat(list.getModifyAdditions()).isEmpty(); } @Test - public void testAdd_given_someAlreadyIn() throws Exception { + public void testAdd_given_someAlreadyIn() { BeanList list = new BeanList<>(some()); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -143,12 +142,12 @@ public class BeanListTest { assertThat(list.contains(object2)).isTrue(); list.add(object2); // object2 added as List allows duplicates - assertThat(list.getModifyAdditions()).containsExactly(object1, object2); + assertThat(list.getModifyAdditions()).containsOnly(object1, object2); assertThat(list.getModifyRemovals()).isEmpty(); } @Test - public void testAddSome_given_someAlreadyIn() throws Exception { + public void testAddSome_given_someAlreadyIn() { BeanList list = new BeanList<>(some()); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -156,43 +155,43 @@ public class BeanListTest { // act list.addAll(all()); - assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3); assertThat(list.getModifyRemovals()).isEmpty(); } @Test - public void testRemove_given_beansInAdditions() throws Exception { + public void testRemove_given_beansInAdditions() { BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); list.addAll(all()); - assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3); // act list.remove(object2); list.remove(object3); - assertThat(list.getModifyAdditions()).containsExactly(object1); + assertThat(list.getModifyAdditions()).containsOnly(object1); assertThat(list.getModifyRemovals()).isEmpty(); } @Test - public void testRemoveAll_given_beansInAdditions() throws Exception { + public void testRemoveAll_given_beansInAdditions() { BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); list.addAll(all()); - assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3); // act list.removeAll(some()); - assertThat(list.getModifyAdditions()).containsExactly(object1); + assertThat(list.getModifyAdditions()).containsOnly(object1); assertThat(list.getModifyRemovals()).isEmpty(); } @Test - public void testRemove_given_beansNotInAdditions() throws Exception { + public void testRemove_given_beansNotInAdditions() { BeanList list = new BeanList<>(all()); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -203,11 +202,11 @@ public class BeanListTest { // assert assertThat(list.getModifyAdditions()).isEmpty(); - assertThat(list.getModifyRemovals()).containsExactly(object2, object3); + assertThat(list.getModifyRemovals()).containsOnly(object2, object3); } @Test - public void testRemoveAll_given_beansNotInAdditions() throws Exception { + public void testRemoveAll_given_beansNotInAdditions() { BeanList list = new BeanList<>(all()); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -217,11 +216,11 @@ public class BeanListTest { // assert assertThat(list.getModifyAdditions()).isEmpty(); - assertThat(list.getModifyRemovals()).containsExactly(object2, object3); + assertThat(list.getModifyRemovals()).containsOnly(object2, object3); } @Test - public void testClear() throws Exception { + public void testClear() { BeanList list = new BeanList<>(all()); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -230,12 +229,12 @@ public class BeanListTest { list.clear(); //assert - assertThat(list.getModifyRemovals()).containsExactly(object1, object2, object3); + assertThat(list.getModifyRemovals()).containsOnly(object1, object2, object3); assertThat(list.getModifyAdditions()).isEmpty(); } @Test - public void testClear_given_someBeansInAdditions() throws Exception { + public void testClear_given_someBeansInAdditions() { BeanList list = new BeanList<>(); list.add(object1); @@ -247,27 +246,27 @@ public class BeanListTest { list.clear(); //assert - assertThat(list.getModifyRemovals()).containsExactly(object1); + assertThat(list.getModifyRemovals()).containsOnly(object1); assertThat(list.getModifyAdditions()).isEmpty(); } @Test - public void testRetainAll_given_beansInAdditions() throws Exception { + public void testRetainAll_given_beansInAdditions() { BeanList list = new BeanList<>(); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); list.addAll(all()); - assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3); // act list.retainAll(some()); - assertThat(list.getModifyAdditions()).containsExactly(object2, object3); + assertThat(list.getModifyAdditions()).containsOnly(object2, object3); assertThat(list.getModifyRemovals()).isEmpty(); } @Test - public void testRetainAll_given_someBeansInAdditions() throws Exception { + public void testRetainAll_given_someBeansInAdditions() { BeanList list = new BeanList<>(); list.add(object1); @@ -278,12 +277,12 @@ public class BeanListTest { // act list.retainAll(some()); - assertThat(list.getModifyAdditions()).containsExactly(object3); - assertThat(list.getModifyRemovals()).containsExactly(object1); + assertThat(list.getModifyAdditions()).containsOnly(object3); + assertThat(list.getModifyRemovals()).containsOnly(object1); } @Test - public void testRetainAll_given_noBeansInAdditions() throws Exception { + public void testRetainAll_given_noBeansInAdditions() { BeanList list = new BeanList<>(all()); list.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -291,7 +290,7 @@ public class BeanListTest { // act list.retainAll(some()); - assertThat(list.getModifyRemovals()).containsExactly(object1); + assertThat(list.getModifyRemovals()).containsOnly(object1); } @Test diff --git a/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java b/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java index 003539a72..3101fc0f2 100644 --- a/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java +++ b/ebean-core/src/test/java/io/ebean/common/BeanMapTest.java @@ -34,30 +34,30 @@ public class BeanMapTest { } @Test - public void testAdd() throws Exception { + public void testAdd() { BeanMap map = new BeanMap<>(); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); map.put("1", object1); map.put("4", null); - assertThat(map.getModifyAdditions()).containsExactly(object1); + assertThat(map.getModifyAdditions()).containsOnly(object1); assertThat(map.getModifyRemovals()).isEmpty(); map.put("1", object1); map.put("4", null); - assertThat(map.getModifyAdditions()).containsExactly(object1); + assertThat(map.getModifyAdditions()).containsOnly(object1); map.put("2", object2); - assertThat(map.getModifyAdditions()).containsExactly(object1, object2); + assertThat(map.getModifyAdditions()).containsOnly(object1, object2); map.remove("1"); - assertThat(map.getModifyAdditions()).containsExactly(object2); + assertThat(map.getModifyAdditions()).containsOnly(object2); assertThat(map.getModifyRemovals()).isEmpty(); } @Test - public void testAddAll_given_emptyStart() throws Exception { + public void testAddAll_given_emptyStart() { BeanMap set = new BeanMap<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -65,28 +65,28 @@ public class BeanMapTest { // act set.putAll(all()); - assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testAdd_given_someAlreadyIn() throws Exception { + public void testAdd_given_someAlreadyIn() { BeanMap map = new BeanMap<>(some()); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); // act - assertThat(map.values().contains(object1)).isFalse(); + assertThat(map.containsValue(object1)).isFalse(); map.put("1", object1); - assertThat(map.values().contains(object2)).isTrue(); + assertThat(map.containsValue(object2)).isTrue(); map.put("2", object2); - assertThat(map.getModifyAdditions()).containsExactly(object1); + assertThat(map.getModifyAdditions()).containsOnly(object1); assertThat(map.getModifyRemovals()).isEmpty(); } @Test - public void testAddSome_given_someAlreadyIn() throws Exception { + public void testAddSome_given_someAlreadyIn() { BeanMap map = new BeanMap<>(some()); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -94,44 +94,44 @@ public class BeanMapTest { // act map.putAll(all()); - assertThat(map.getModifyAdditions()).containsExactly(object1); + assertThat(map.getModifyAdditions()).containsOnly(object1); assertThat(map.getModifyRemovals()).isEmpty(); } @Test - public void testRemove_given_beansInAdditions() throws Exception { + public void testRemove_given_beansInAdditions() { BeanMap map = new BeanMap<>(); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); map.putAll(all()); - assertThat(map.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(map.getModifyAdditions()).containsOnly(object1, object2, object3); // act map.remove("2"); map.remove("3"); - assertThat(map.getModifyAdditions()).containsExactly(object1); + assertThat(map.getModifyAdditions()).containsOnly(object1); assertThat(map.getModifyRemovals()).isEmpty(); } @Test - public void testRemoveAll_given_beansInAdditions() throws Exception { + public void testRemoveAll_given_beansInAdditions() { BeanMap map = new BeanMap<>(); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); map.putAll(all()); - assertThat(map.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(map.getModifyAdditions()).containsOnly(object1, object2, object3); // act map.remove("2"); map.remove("3"); - assertThat(map.getModifyAdditions()).containsExactly(object1); + assertThat(map.getModifyAdditions()).containsOnly(object1); assertThat(map.getModifyRemovals()).isEmpty(); } @Test - public void testRemove_given_beansNotInAdditions() throws Exception { + public void testRemove_given_beansNotInAdditions() { BeanMap map = new BeanMap<>(all()); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -142,11 +142,11 @@ public class BeanMapTest { // assert assertThat(map.getModifyAdditions()).isEmpty(); - assertThat(map.getModifyRemovals()).containsExactly(object2, object3); + assertThat(map.getModifyRemovals()).containsOnly(object2, object3); } @Test - public void testRemoveAll_given_beansNotInAdditions() throws Exception { + public void testRemoveAll_given_beansNotInAdditions() { BeanMap map = new BeanMap<>(all()); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -157,11 +157,11 @@ public class BeanMapTest { // assert assertThat(map.getModifyAdditions()).isEmpty(); - assertThat(map.getModifyRemovals()).containsExactly(object2, object3); + assertThat(map.getModifyRemovals()).containsOnly(object2, object3); } @Test - public void testClear() throws Exception { + public void testClear() { BeanMap map = new BeanMap<>(all()); map.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -170,12 +170,12 @@ public class BeanMapTest { map.clear(); //assert - assertThat(map.getModifyRemovals()).containsExactly(object1, object2, object3); + assertThat(map.getModifyRemovals()).containsOnly(object1, object2, object3); assertThat(map.getModifyAdditions()).isEmpty(); } @Test - public void testClear_given_someBeansInAdditions() throws Exception { + public void testClear_given_someBeansInAdditions() { BeanMap map = newModifyListeningMap(); map.put("2", object2); @@ -185,7 +185,7 @@ public class BeanMapTest { map.clear(); //assert - assertThat(map.getModifyRemovals()).containsExactly(object1); + assertThat(map.getModifyRemovals()).containsOnly(object1); assertThat(map.getModifyAdditions()).isEmpty(); } @@ -228,7 +228,7 @@ public class BeanMapTest { assertThat(map).doesNotContainKeys("1"); assertThat(map.get("1")).isNull(); - assertThat(map.getModifyRemovals()).containsExactly(object1); + assertThat(map.getModifyRemovals()).containsOnly(object1); } @Test @@ -245,7 +245,7 @@ public class BeanMapTest { assertThat(map).isEmpty(); assertThat(keySet).isEmpty(); - assertThat(map.getModifyRemovals()).containsExactly(object1, object2); + assertThat(map.getModifyRemovals()).containsOnly(object1, object2); } @Test @@ -258,20 +258,14 @@ public class BeanMapTest { map.setModifyListening(BeanCollection.ModifyListenMode.ALL); final Set keySet = map.keySet(); - final Iterator iterator = keySet.iterator(); - while (iterator.hasNext()) { - final String key = iterator.next(); - if (key.equals("2")) { - iterator.remove(); - } - } + keySet.removeIf(key -> key.equals("2")); assertThat(map).hasSize(2); assertThat(keySet).hasSize(2); assertThat(keySet).containsExactly("1", "3"); assertThat(map).containsKeys("1", "3"); - assertThat(map.getModifyRemovals()).containsExactly(object2); + assertThat(map.getModifyRemovals()).containsOnly(object2); } @Test @@ -294,7 +288,7 @@ public class BeanMapTest { assertThat(keySet).containsExactly("1", "4"); assertThat(map).containsKeys("1", "4"); - assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5); + assertThat(map.getModifyRemovals()).containsOnly(object2, object3, object5); } @@ -318,7 +312,7 @@ public class BeanMapTest { assertThat(keySet).containsExactly("2", "3", "5"); assertThat(map).containsKeys("2", "3", "5"); - assertThat(map.getModifyRemovals()).containsExactly(object1, object4); + assertThat(map.getModifyRemovals()).containsOnly(object1, object4); } @Test(expected = UnsupportedOperationException.class) @@ -348,7 +342,7 @@ public class BeanMapTest { assertThat(entries).isEmpty(); assertThat(map).isEmpty(); - assertThat(map.getModifyRemovals()).containsExactly(object1); + assertThat(map.getModifyRemovals()).containsOnly(object1); } @Test @@ -365,7 +359,7 @@ public class BeanMapTest { assertThat(existed22).isFalse(); assertThat(map).hasSize(4); - assertThat(map.getModifyRemovals()).containsExactly(object1); + assertThat(map.getModifyRemovals()).containsOnly(object1); } @Test @@ -395,7 +389,7 @@ public class BeanMapTest { } assertThat(map).hasSize(3); assertThat(entries).hasSize(3); - assertThat(map.getModifyRemovals()).containsExactly(object2, object5); + assertThat(map.getModifyRemovals()).containsOnly(object2, object5); } @Test @@ -406,7 +400,7 @@ public class BeanMapTest { entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4))); assertThat(map).hasSize(3); assertThat(entries).hasSize(3); - assertThat(map.getModifyRemovals()).containsExactly(object1, object4); + assertThat(map.getModifyRemovals()).containsOnly(object1, object4); } @Test @@ -417,7 +411,7 @@ public class BeanMapTest { entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4))); assertThat(map).hasSize(2); assertThat(entries).hasSize(2); - assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5); + assertThat(map.getModifyRemovals()).containsOnly(object2, object3, object5); } private BeanMap newModifyListeningMap() { diff --git a/ebean-core/src/test/java/io/ebean/common/BeanSetTest.java b/ebean-core/src/test/java/io/ebean/common/BeanSetTest.java index c274d34d7..40253faef 100644 --- a/ebean-core/src/test/java/io/ebean/common/BeanSetTest.java +++ b/ebean-core/src/test/java/io/ebean/common/BeanSetTest.java @@ -31,28 +31,28 @@ public class BeanSetTest { } @Test - public void testAdd() throws Exception { + public void testAdd() { BeanSet set = new BeanSet<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); set.add(object1); - assertThat(set.getModifyAdditions()).containsExactly(object1); + assertThat(set.getModifyAdditions()).containsOnly(object1); assertThat(set.getModifyRemovals()).isEmpty(); set.add(object1); - assertThat(set.getModifyAdditions()).containsExactly(object1); + assertThat(set.getModifyAdditions()).containsOnly(object1); set.add(object2); - assertThat(set.getModifyAdditions()).containsExactly(object1, object2); + assertThat(set.getModifyAdditions()).containsOnly(object1, object2); set.remove(object1); - assertThat(set.getModifyAdditions()).containsExactly(object2); + assertThat(set.getModifyAdditions()).containsOnly(object2); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testAddAll_given_emptyStart() throws Exception { + public void testAddAll_given_emptyStart() { BeanSet set = new BeanSet<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -60,12 +60,12 @@ public class BeanSetTest { // act set.addAll(all()); - assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testAdd_given_someAlreadyIn() throws Exception { + public void testAdd_given_someAlreadyIn() { BeanSet set = new BeanSet<>(some()); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -76,12 +76,12 @@ public class BeanSetTest { assertThat(set.contains(object2)).isTrue(); set.add(object2); - assertThat(set.getModifyAdditions()).containsExactly(object1); + assertThat(set.getModifyAdditions()).containsOnly(object1); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testAddSome_given_someAlreadyIn() throws Exception { + public void testAddSome_given_someAlreadyIn() { BeanSet set = new BeanSet<>(some()); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -89,43 +89,43 @@ public class BeanSetTest { // act set.addAll(all()); - assertThat(set.getModifyAdditions()).containsExactly(object1); + assertThat(set.getModifyAdditions()).containsOnly(object1); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testRemove_given_beansInAdditions() throws Exception { + public void testRemove_given_beansInAdditions() { BeanSet set = new BeanSet<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); set.addAll(all()); - assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3); // act set.remove(object2); set.remove(object3); - assertThat(set.getModifyAdditions()).containsExactly(object1); + assertThat(set.getModifyAdditions()).containsOnly(object1); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testRemoveAll_given_beansInAdditions() throws Exception { + public void testRemoveAll_given_beansInAdditions() { BeanSet set = new BeanSet<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); set.addAll(all()); - assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3); // act set.removeAll(some()); - assertThat(set.getModifyAdditions()).containsExactly(object1); + assertThat(set.getModifyAdditions()).containsOnly(object1); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testRemove_given_beansNotInAdditions() throws Exception { + public void testRemove_given_beansNotInAdditions() { BeanSet set = new BeanSet<>(all()); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -136,11 +136,11 @@ public class BeanSetTest { // assert assertThat(set.getModifyAdditions()).isEmpty(); - assertThat(set.getModifyRemovals()).containsExactly(object2, object3); + assertThat(set.getModifyRemovals()).containsOnly(object2, object3); } @Test - public void testRemoveAll_given_beansNotInAdditions() throws Exception { + public void testRemoveAll_given_beansNotInAdditions() { BeanSet set = new BeanSet<>(all()); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -150,11 +150,11 @@ public class BeanSetTest { // assert assertThat(set.getModifyAdditions()).isEmpty(); - assertThat(set.getModifyRemovals()).containsExactly(object2, object3); + assertThat(set.getModifyRemovals()).containsOnly(object2, object3); } @Test - public void testClear() throws Exception { + public void testClear() { BeanSet set = new BeanSet<>(all()); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -163,12 +163,12 @@ public class BeanSetTest { set.clear(); //assert - assertThat(set.getModifyRemovals()).containsExactly(object1, object2, object3); + assertThat(set.getModifyRemovals()).containsOnly(object1, object2, object3); assertThat(set.getModifyAdditions()).isEmpty(); } @Test - public void testClear_given_someBeansInAdditions() throws Exception { + public void testClear_given_someBeansInAdditions() { BeanSet set = new BeanSet<>(); set.add(object1); @@ -180,27 +180,27 @@ public class BeanSetTest { set.clear(); //assert - assertThat(set.getModifyRemovals()).containsExactly(object1); + assertThat(set.getModifyRemovals()).containsOnly(object1); assertThat(set.getModifyAdditions()).isEmpty(); } @Test - public void testRetainAll_given_beansInAdditions() throws Exception { + public void testRetainAll_given_beansInAdditions() { BeanSet set = new BeanSet<>(); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); set.addAll(all()); - assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3); + assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3); // act set.retainAll(some()); - assertThat(set.getModifyAdditions()).containsExactly(object2, object3); + assertThat(set.getModifyAdditions()).containsOnly(object2, object3); assertThat(set.getModifyRemovals()).isEmpty(); } @Test - public void testRetainAll_given_someBeansInAdditions() throws Exception { + public void testRetainAll_given_someBeansInAdditions() { BeanSet set = new BeanSet<>(); set.add(object1); @@ -211,12 +211,12 @@ public class BeanSetTest { // act set.retainAll(some()); - assertThat(set.getModifyAdditions()).containsExactly(object3); - assertThat(set.getModifyRemovals()).containsExactly(object1); + assertThat(set.getModifyAdditions()).containsOnly(object3); + assertThat(set.getModifyRemovals()).containsOnly(object1); } @Test - public void testRetainAll_given_noBeansInAdditions() throws Exception { + public void testRetainAll_given_noBeansInAdditions() { BeanSet set = new BeanSet<>(all()); set.setModifyListening(BeanCollection.ModifyListenMode.ALL); @@ -224,7 +224,7 @@ public class BeanSetTest { // act set.retainAll(some()); - assertThat(set.getModifyRemovals()).containsExactly(object1); + assertThat(set.getModifyRemovals()).containsOnly(object1); } } diff --git a/ebean-core/src/test/java/io/ebean/config/DatabaseConfigTest.java b/ebean-core/src/test/java/io/ebean/config/DatabaseConfigTest.java new file mode 100644 index 000000000..368bc9a8f --- /dev/null +++ b/ebean-core/src/test/java/io/ebean/config/DatabaseConfigTest.java @@ -0,0 +1,199 @@ +package io.ebean.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.ebean.annotation.MutationDetection; +import io.ebean.annotation.PersistBatch; +import io.ebean.config.dbplatform.IdType; +import io.ebean.datasource.DataSourceConfig; +import org.junit.Test; + +import java.util.Properties; + +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.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class DatabaseConfigTest { + + @Test + public void testLoadFromEbeanProperties() { + + DatabaseConfig config = new DatabaseConfig(); + config.loadFromProperties(); + + assertEquals(PersistBatch.NONE, config.getPersistBatch()); + assertNotNull(config.getProperties()); + } + + @Test + public void evalPropertiesInput() { + + String home = System.getenv("HOME"); + + Properties props = new Properties(); + props.setProperty("ddl.initSql", "${HOME}/initSql"); + + DatabaseConfig config = new DatabaseConfig(); + config.loadFromProperties(props); + + String ddlInitSql = config.getDdlInitSql(); + assertThat(ddlInitSql).isEqualTo(home+"/initSql"); + } + + @Test + public void testLoadWithProperties() { + + DatabaseConfig config = new DatabaseConfig(); + config.setPersistBatch(PersistBatch.NONE); + config.setPersistBatchOnCascade(PersistBatch.NONE); + config.setAutoReadOnlyDataSource(false); + config.setReadOnlyDataSource(null); + config.setReadOnlyDataSourceConfig(new DataSourceConfig()); + + Properties props = new Properties(); + props.setProperty("persistBatch", "ALL"); + props.setProperty("persistBatchOnCascade", "ALL"); + props.setProperty("dbuuid", "binary"); + props.setProperty("jdbcFetchSizeFindEach", "42"); + props.setProperty("jdbcFetchSizeFindList", "43"); + props.setProperty("backgroundExecutorShutdownSecs", "98"); + props.setProperty("backgroundExecutorSchedulePoolSize", "4"); + props.setProperty("dbOffline", "true"); + props.setProperty("jsonDateTime", "MILLIS"); + props.setProperty("jsonDate", "MILLIS"); + props.setProperty("jsonMutationDetection", "NONE"); + props.setProperty("autoReadOnlyDataSource", "true"); + props.setProperty("disableL2Cache", "true"); + props.setProperty("notifyL2CacheInForeground", "true"); + props.setProperty("idType", "SEQUENCE"); + props.setProperty("mappingLocations", "classpath:/foo;bar"); + props.setProperty("namingConvention", "io.ebean.config.MatchingNamingConvention"); + props.setProperty("idGeneratorAutomatic", "true"); + props.setProperty("enabledL2Regions", "r0,users,orgs"); + props.setProperty("caseSensitiveCollation", "false"); + props.setProperty("loadModuleInfo", "true"); + props.setProperty("forUpdateNoKey", "true"); + props.setProperty("defaultServer", "false"); + props.setProperty("skipDataSourceCheck", "true"); + + props.setProperty("queryPlan.enable", "true"); + props.setProperty("queryPlan.thresholdMicros", "10000"); + props.setProperty("queryPlan.capture", "true"); + props.setProperty("queryPlan.capturePeriodSecs", "42"); + props.setProperty("queryPlan.captureMaxTimeMillis", "560"); + props.setProperty("queryPlan.captureMaxCount", "7"); + + config.loadFromProperties(props); + + assertFalse(config.isDefaultServer()); + assertTrue(config.isDisableL2Cache()); + assertTrue(config.isNotifyL2CacheInForeground()); + assertTrue(config.isDbOffline()); + assertTrue(config.isAutoReadOnlyDataSource()); + assertTrue(config.isAutoLoadModuleInfo()); + assertTrue(config.skipDataSourceCheck()); + + assertTrue(config.isIdGeneratorAutomatic()); + assertFalse(config.getPlatformConfig().isCaseSensitiveCollation()); + assertTrue(config.getPlatformConfig().isForUpdateNoKey()); + + assertThat(config.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class); + + assertEquals(MutationDetection.NONE, config.getJsonMutationDetection()); + config.setJsonMutationDetection(MutationDetection.SOURCE); + assertEquals(MutationDetection.SOURCE, config.getJsonMutationDetection()); + assertEquals(IdType.SEQUENCE, config.getIdType()); + assertEquals(PersistBatch.ALL, config.getPersistBatch()); + assertEquals(PersistBatch.ALL, config.getPersistBatchOnCascade()); + assertEquals(PlatformConfig.DbUuid.BINARY, config.getPlatformConfig().getDbUuid()); + assertEquals(JsonConfig.DateTime.MILLIS, config.getJsonDateTime()); + assertEquals(JsonConfig.Date.MILLIS, config.getJsonDate()); + + assertEquals("r0,users,orgs", config.getEnabledL2Regions()); + + assertEquals(42, config.getJdbcFetchSizeFindEach()); + assertEquals(43, config.getJdbcFetchSizeFindList()); + assertEquals(4, config.getBackgroundExecutorSchedulePoolSize()); + assertEquals(98, config.getBackgroundExecutorShutdownSecs()); + + assertTrue(config.isQueryPlanEnable()); + assertEquals(10000, config.getQueryPlanThresholdMicros()); + assertTrue(config.isQueryPlanCapture()); + assertEquals(42, config.getQueryPlanCapturePeriodSecs()); + assertEquals(560, config.getQueryPlanCaptureMaxTimeMillis()); + assertEquals(7, config.getQueryPlanCaptureMaxCount()); + + assertThat(config.getMappingLocations()).containsExactly("classpath:/foo","bar"); + + config.setPersistBatch(PersistBatch.NONE); + config.setPersistBatchOnCascade(PersistBatch.NONE); + + Properties props1 = new Properties(); + props1.setProperty("ebean.persistBatch", "ALL"); + props1.setProperty("ebean.persistBatchOnCascade", "ALL"); + + config.setNotifyL2CacheInForeground(true); + config.setDisableL2Cache(true); + props1.setProperty("ebean.disableL2Cache", "false"); + props1.setProperty("ebean.notifyL2CacheInForeground", "false"); + + config.loadFromProperties(props1); + assertFalse(config.isDisableL2Cache()); + assertFalse(config.isNotifyL2CacheInForeground()); + + assertEquals(PersistBatch.ALL, config.getPersistBatch()); + assertEquals(PersistBatch.ALL, config.getPersistBatchOnCascade()); + + config.setEnabledL2Regions("r0,orgs"); + assertEquals("r0,orgs", config.getEnabledL2Regions()); + } + + @Test + public void test_defaults() { + + DatabaseConfig config = new DatabaseConfig(); + assertTrue(config.isIdGeneratorAutomatic()); + assertTrue(config.isDefaultServer()); + assertFalse(config.isAutoPersistUpdates()); + assertFalse(config.skipDataSourceCheck()); + + config.setIdGeneratorAutomatic(false); + assertFalse(config.isIdGeneratorAutomatic()); + assertEquals(JsonConfig.DateTime.ISO8601, config.getJsonDateTime()); + assertEquals(JsonConfig.Date.ISO8601, config.getJsonDate()); + assertEquals(MutationDetection.HASH, config.getJsonMutationDetection()); + assertTrue(config.getPlatformConfig().isCaseSensitiveCollation()); + assertTrue(config.isAutoLoadModuleInfo()); + + assertFalse(config.isQueryPlanEnable()); + assertEquals(Long.MAX_VALUE, config.getQueryPlanThresholdMicros()); + assertFalse(config.isQueryPlanCapture()); + assertEquals(600, config.getQueryPlanCapturePeriodSecs()); + assertEquals(10000L, config.getQueryPlanCaptureMaxTimeMillis()); + assertEquals(10, config.getQueryPlanCaptureMaxCount()); + + config.setLoadModuleInfo(false); + assertFalse(config.isAutoLoadModuleInfo()); + config.setAutoPersistUpdates(true); + assertTrue(config.isAutoPersistUpdates()); + config.setSkipDataSourceCheck(true); + assertTrue(config.skipDataSourceCheck()); + } + + @Test + public void test_putServiceObject() { + + ObjectMapper objectMapper = new ObjectMapper(); + + DatabaseConfig config = new DatabaseConfig(); + config.putServiceObject(objectMapper); + + ObjectMapper mapper0 = config.getServiceObject(ObjectMapper.class); + ObjectMapper mapper1 = (ObjectMapper)config.getServiceObject("objectMapper"); + + assertThat(objectMapper).isSameAs(mapper0); + assertThat(objectMapper).isSameAs(mapper1); + } +} diff --git a/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java b/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java deleted file mode 100644 index dde2cb216..000000000 --- a/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package io.ebean.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.ebean.annotation.PersistBatch; -import io.ebean.config.dbplatform.IdType; -import io.ebean.datasource.DataSourceConfig; -import org.junit.Test; - -import java.util.Properties; - -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.assertNotNull; -import static org.junit.Assert.assertTrue; - -public class ServerConfigTest { - - @Test - public void testLoadFromEbeanProperties() { - - ServerConfig serverConfig = new ServerConfig(); - serverConfig.loadFromProperties(); - - assertEquals(PersistBatch.NONE, serverConfig.getPersistBatch()); - assertNotNull(serverConfig.getProperties()); - } - - @Test - public void evalPropertiesInput() { - - String home = System.getenv("HOME"); - - Properties props = new Properties(); - props.setProperty("ddl.initSql", "${HOME}/initSql"); - - ServerConfig serverConfig = new ServerConfig(); - serverConfig.loadFromProperties(props); - - String ddlInitSql = serverConfig.getDdlInitSql(); - assertThat(ddlInitSql).isEqualTo(home+"/initSql"); - } - - @Test - public void testLoadWithProperties() { - - ServerConfig serverConfig = new ServerConfig(); - serverConfig.setPersistBatch(PersistBatch.NONE); - serverConfig.setPersistBatchOnCascade(PersistBatch.NONE); - serverConfig.setAutoReadOnlyDataSource(false); - serverConfig.setReadOnlyDataSource(null); - serverConfig.setReadOnlyDataSourceConfig(new DataSourceConfig()); - - Properties props = new Properties(); - props.setProperty("persistBatch", "ALL"); - props.setProperty("persistBatchOnCascade", "ALL"); - props.setProperty("dbuuid", "binary"); - props.setProperty("jdbcFetchSizeFindEach", "42"); - props.setProperty("jdbcFetchSizeFindList", "43"); - props.setProperty("backgroundExecutorShutdownSecs", "98"); - props.setProperty("backgroundExecutorSchedulePoolSize", "4"); - props.setProperty("dbOffline", "true"); - props.setProperty("jsonDateTime", "MILLIS"); - props.setProperty("jsonDate", "MILLIS"); - props.setProperty("jsonDirtyByDefault", "false"); - props.setProperty("autoReadOnlyDataSource", "true"); - props.setProperty("disableL2Cache", "true"); - props.setProperty("notifyL2CacheInForeground", "true"); - props.setProperty("idType", "SEQUENCE"); - props.setProperty("mappingLocations", "classpath:/foo;bar"); - props.setProperty("namingConvention", "io.ebean.config.MatchingNamingConvention"); - props.setProperty("idGeneratorAutomatic", "true"); - props.setProperty("enabledL2Regions", "r0,users,orgs"); - props.setProperty("caseSensitiveCollation", "false"); - props.setProperty("loadModuleInfo", "true"); - props.setProperty("forUpdateNoKey", "true"); - props.setProperty("defaultServer", "false"); - - props.setProperty("queryPlan.enable", "true"); - props.setProperty("queryPlan.thresholdMicros", "10000"); - props.setProperty("queryPlan.capture", "true"); - props.setProperty("queryPlan.capturePeriodSecs", "42"); - props.setProperty("queryPlan.captureMaxTimeMillis", "560"); - props.setProperty("queryPlan.captureMaxCount", "7"); - - serverConfig.loadFromProperties(props); - - assertFalse(serverConfig.isDefaultServer()); - assertTrue(serverConfig.isDisableL2Cache()); - assertTrue(serverConfig.isNotifyL2CacheInForeground()); - assertTrue(serverConfig.isDbOffline()); - assertTrue(serverConfig.isAutoReadOnlyDataSource()); - assertTrue(serverConfig.isAutoLoadModuleInfo()); - - assertTrue(serverConfig.isIdGeneratorAutomatic()); - assertFalse(serverConfig.getPlatformConfig().isCaseSensitiveCollation()); - assertTrue(serverConfig.getPlatformConfig().isForUpdateNoKey()); - - assertThat(serverConfig.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class); - - assertEquals(IdType.SEQUENCE, serverConfig.getIdType()); - assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch()); - assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade()); - assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid()); - assertEquals(JsonConfig.DateTime.MILLIS, serverConfig.getJsonDateTime()); - assertEquals(JsonConfig.Date.MILLIS, serverConfig.getJsonDate()); - assertFalse(serverConfig.isJsonDirtyByDefault()); - serverConfig.setJsonDirtyByDefault(true); - assertTrue(serverConfig.isJsonDirtyByDefault()); - - assertEquals("r0,users,orgs", serverConfig.getEnabledL2Regions()); - - assertEquals(42, serverConfig.getJdbcFetchSizeFindEach()); - assertEquals(43, serverConfig.getJdbcFetchSizeFindList()); - assertEquals(4, serverConfig.getBackgroundExecutorSchedulePoolSize()); - assertEquals(98, serverConfig.getBackgroundExecutorShutdownSecs()); - - assertTrue(serverConfig.isQueryPlanEnable()); - assertEquals(10000, serverConfig.getQueryPlanThresholdMicros()); - assertTrue(serverConfig.isQueryPlanCapture()); - assertEquals(42, serverConfig.getQueryPlanCapturePeriodSecs()); - assertEquals(560, serverConfig.getQueryPlanCaptureMaxTimeMillis()); - assertEquals(7, serverConfig.getQueryPlanCaptureMaxCount()); - - assertThat(serverConfig.getMappingLocations()).containsExactly("classpath:/foo","bar"); - - serverConfig.setPersistBatch(PersistBatch.NONE); - serverConfig.setPersistBatchOnCascade(PersistBatch.NONE); - - Properties props1 = new Properties(); - props1.setProperty("ebean.persistBatch", "ALL"); - props1.setProperty("ebean.persistBatchOnCascade", "ALL"); - - serverConfig.setNotifyL2CacheInForeground(true); - serverConfig.setDisableL2Cache(true); - props1.setProperty("ebean.disableL2Cache", "false"); - props1.setProperty("ebean.notifyL2CacheInForeground", "false"); - - serverConfig.loadFromProperties(props1); - assertFalse(serverConfig.isDisableL2Cache()); - assertFalse(serverConfig.isNotifyL2CacheInForeground()); - - assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch()); - assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade()); - - serverConfig.setEnabledL2Regions("r0,orgs"); - assertEquals("r0,orgs", serverConfig.getEnabledL2Regions()); - } - - @Test - public void test_defaults() { - - ServerConfig serverConfig = new ServerConfig(); - assertTrue(serverConfig.isIdGeneratorAutomatic()); - assertTrue(serverConfig.isDefaultServer()); - assertFalse(serverConfig.isAutoPersistUpdates()); - - serverConfig.setIdGeneratorAutomatic(false); - assertFalse(serverConfig.isIdGeneratorAutomatic()); - assertEquals(JsonConfig.DateTime.ISO8601, serverConfig.getJsonDateTime()); - assertEquals(JsonConfig.Date.ISO8601, serverConfig.getJsonDate()); - assertTrue(serverConfig.isJsonDirtyByDefault()); - assertTrue(serverConfig.getPlatformConfig().isCaseSensitiveCollation()); - assertTrue(serverConfig.isAutoLoadModuleInfo()); - - assertFalse(serverConfig.isQueryPlanEnable()); - assertEquals(Long.MAX_VALUE, serverConfig.getQueryPlanThresholdMicros()); - assertFalse(serverConfig.isQueryPlanCapture()); - assertEquals(600, serverConfig.getQueryPlanCapturePeriodSecs()); - assertEquals(10000L, serverConfig.getQueryPlanCaptureMaxTimeMillis()); - assertEquals(10, serverConfig.getQueryPlanCaptureMaxCount()); - - serverConfig.setLoadModuleInfo(false); - assertFalse(serverConfig.isAutoLoadModuleInfo()); - serverConfig.setAutoPersistUpdates(true); - assertTrue(serverConfig.isAutoPersistUpdates()); - } - - @Test - public void test_putServiceObject() { - - ObjectMapper objectMapper = new ObjectMapper(); - - ServerConfig config = new ServerConfig(); - config.putServiceObject(objectMapper); - - ObjectMapper mapper0 = config.getServiceObject(ObjectMapper.class); - ObjectMapper mapper1 = (ObjectMapper)config.getServiceObject("objectMapper"); - - assertThat(objectMapper).isSameAs(mapper0); - assertThat(objectMapper).isSameAs(mapper1); - } -} diff --git a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index f0f0c0779..b09a0f54e 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/ebean-core/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -650,7 +650,7 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } @Override - public boolean exists(Query ormQuery, Transaction transaction) { + public boolean exists(Query ormQuery, Transaction transaction) { return false; } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanIudMetricsTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanIudMetricsTest.java index d53f0e973..9f26e978c 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanIudMetricsTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanIudMetricsTest.java @@ -23,11 +23,11 @@ public class BeanIudMetricsTest { BasicMetricVisitor basic = new BasicMetricVisitor(); iudMetrics.visit(basic); - List timed = basic.getTimedMetrics(); + List timed = basic.timedMetrics(); assertThat(timed).hasSize(1); - assertThat(timed.get(0).getCount()).isEqualTo(4); - assertThat(timed.get(0).getName()).isEqualTo("iud.one.insertBatch"); + assertThat(timed.get(0).count()).isEqualTo(4); + assertThat(timed.get(0).name()).isEqualTo("iud.one.insertBatch"); iudMetrics.addBatch(PersistRequest.Type.UPDATE, startNanos, 1); iudMetrics.addBatch(PersistRequest.Type.DELETE_SOFT, startNanos, 2); @@ -37,15 +37,15 @@ public class BeanIudMetricsTest { basic = new BasicMetricVisitor(); iudMetrics.visit(basic); - timed = basic.getTimedMetrics(); + timed = basic.timedMetrics(); assertThat(timed).hasSize(3); - assertThat(timed.get(0).getCount()).isEqualTo(16); - assertThat(timed.get(0).getName()).isEqualTo("iud.one.insertBatch"); - assertThat(timed.get(1).getCount()).isEqualTo(3); - assertThat(timed.get(1).getName()).isEqualTo("iud.one.updateBatch"); - assertThat(timed.get(2).getCount()).isEqualTo(12); - assertThat(timed.get(2).getName()).isEqualTo("iud.one.deleteBatch"); + assertThat(timed.get(0).count()).isEqualTo(16); + assertThat(timed.get(0).name()).isEqualTo("iud.one.insertBatch"); + assertThat(timed.get(1).count()).isEqualTo(3); + assertThat(timed.get(1).name()).isEqualTo("iud.one.updateBatch"); + assertThat(timed.get(2).count()).isEqualTo(12); + assertThat(timed.get(2).name()).isEqualTo("iud.one.deleteBatch"); } @Test @@ -63,15 +63,15 @@ public class BeanIudMetricsTest { BasicMetricVisitor basic = new BasicMetricVisitor(); iudMetrics.visit(basic); - List timed = basic.getTimedMetrics(); + List timed = basic.timedMetrics(); assertThat(timed).hasSize(3); - assertThat(timed.get(0).getCount()).isEqualTo(1); - assertThat(timed.get(0).getName()).isEqualTo("iud.one.insert"); - assertThat(timed.get(1).getCount()).isEqualTo(2); - assertThat(timed.get(1).getName()).isEqualTo("iud.one.update"); - assertThat(timed.get(2).getCount()).isEqualTo(2); - assertThat(timed.get(2).getName()).isEqualTo("iud.one.delete"); + assertThat(timed.get(0).count()).isEqualTo(1); + assertThat(timed.get(0).name()).isEqualTo("iud.one.insert"); + assertThat(timed.get(1).count()).isEqualTo(2); + assertThat(timed.get(1).name()).isEqualTo("iud.one.update"); + assertThat(timed.get(2).count()).isEqualTo(2); + assertThat(timed.get(2).name()).isEqualTo("iud.one.delete"); } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/TestNotEnhancedMappedSuper.java b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/TestNotEnhancedMappedSuper.java index 012c13c39..014ddb4b6 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/TestNotEnhancedMappedSuper.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/TestNotEnhancedMappedSuper.java @@ -33,7 +33,7 @@ public class TestNotEnhancedMappedSuper extends BaseTestCase { NotEnhancedMappedSuper mappedSuper = new NotEnhancedMappedSuper(); boolean enhanced = (mappedSuper instanceof EntityBean); - Assert.assertFalse(enhanced); + Assert.assertTrue(enhanced); } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java index 1953f2959..eb12ff460 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/expression/RawExpressionTest.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.expression; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import io.ebeaninternal.api.BindValuesKey; public class RawExpressionTest extends BaseExpressionTest { @@ -61,11 +62,16 @@ public class RawExpressionTest extends BaseExpressionTest { } public void assert_queryBindHash_isDifferent(RawExpression exp0, RawExpression exp1) { - assertThat(exp0.queryBindHash()).isNotEqualTo(exp1.queryBindHash()); + assertThat(bindKey(exp0)).isNotEqualTo(bindKey(exp1)); } public void assert_queryBindHash_isSame(RawExpression exp0, RawExpression exp1) { - assertThat(exp0.queryBindHash()).isEqualTo(exp1.queryBindHash()); + assertThat(bindKey(exp0)).isEqualTo(bindKey(exp1)); } + private BindValuesKey bindKey(RawExpression query) { + BindValuesKey bindValuesKey = new BindValuesKey(); + query.queryBindKey(bindValuesKey); + return bindValuesKey; + } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java index 6dfdad9f6..9d99b17ce 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/profile/BasicProfileLocationTest.java @@ -14,7 +14,11 @@ public class BasicProfileLocationTest { assertThat(loc.obtain()).isTrue(); assertThat(loc.fullLocation()).endsWith(":12)"); - assertThat(loc.location()).isEqualTo("NativeMethodAccessorImpl.invoke0(Native Method:12)"); + if (System.getProperty("java.version").startsWith("1.8")) { + assertThat(loc.location()).isEqualTo("sun.reflect.NativeMethodAccessorImpl.invoke0"); + } else { + assertThat(loc.location()).isEqualTo("java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0"); + } assertThat(loc.label()).isEqualTo("NativeMethodAccessorImpl.invoke0"); } @@ -24,7 +28,7 @@ public class BasicProfileLocationTest { BasicProfileLocation loc = new BasicProfileLocation("com.foo.Bar.all"); assertThat(loc.obtain()).isFalse(); assertThat(loc.fullLocation()).isEqualTo("com.foo.Bar.all"); - assertThat(loc.location()).isEqualTo("Bar.all"); + assertThat(loc.location()).isEqualTo("com.foo.Bar.all"); assertThat(loc.label()).isEqualTo("Bar.all"); } @@ -34,7 +38,7 @@ public class BasicProfileLocationTest { BasicProfileLocation loc = new BasicProfileLocation("foo.Bar.all"); assertThat(loc.obtain()).isFalse(); assertThat(loc.fullLocation()).isEqualTo("foo.Bar.all"); - assertThat(loc.location()).isEqualTo("Bar.all"); + assertThat(loc.location()).isEqualTo("foo.Bar.all"); assertThat(loc.label()).isEqualTo("Bar.all"); } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricMapTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricMapTest.java index 43f0e0426..cb6d4d662 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricMapTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricMapTest.java @@ -21,17 +21,17 @@ public class DTimedMetricMapTest { BasicMetricVisitor visitor = new BasicMetricVisitor(); metricMap.visit(visitor); - MetaTimedMetric timedMetric = visitor.getTimedMetrics().get(0); - assertThat(timedMetric.getCount()).isEqualTo(1); - assertThat(timedMetric.getTotal()).isGreaterThan(10); + MetaTimedMetric timedMetric = visitor.timedMetrics().get(0); + assertThat(timedMetric.count()).isEqualTo(1); + assertThat(timedMetric.total()).isGreaterThan(10); metricMap.addSinceNanos("some", nanos); visitor = new BasicMetricVisitor(); metricMap.visit(visitor); - timedMetric = visitor.getTimedMetrics().get(0); - assertThat(timedMetric.getCount()).isEqualTo(1); - assertThat(timedMetric.getTotal()).isGreaterThan(10); + timedMetric = visitor.timedMetrics().get(0); + assertThat(timedMetric.count()).isEqualTo(1); + assertThat(timedMetric.total()).isGreaterThan(10); } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricTest.java index 28e651b88..6dbbb45d3 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/profile/DTimedMetricTest.java @@ -17,16 +17,16 @@ public class DTimedMetricTest { metric.addSinceNanos(start); DTimeMetricStats stats = metric.collect(true); - assertThat(stats.getCount()).isEqualTo(1); - assertThat(stats.getTotal()).isGreaterThan(10); - assertThat(stats.getMax()).isEqualTo(stats.getTotal()); + assertThat(stats.count()).isEqualTo(1); + assertThat(stats.total()).isGreaterThan(10); + assertThat(stats.max()).isEqualTo(stats.total()); metric.addSinceNanos(start); stats = metric.collect(true); - assertThat(stats.getCount()).isEqualTo(1); - assertThat(stats.getTotal()).isGreaterThan(10); - assertThat(stats.getMax()).isEqualTo(stats.getTotal()); + assertThat(stats.count()).isEqualTo(1); + assertThat(stats.total()).isGreaterThan(10); + assertThat(stats.max()).isEqualTo(stats.total()); } @Test @@ -40,16 +40,16 @@ public class DTimedMetricTest { metric.addBatchSince(start, 5); DTimeMetricStats stats = metric.collect(true); - assertThat(stats.getCount()).isEqualTo(5); - assertThat(stats.getTotal()).isGreaterThan(10000); - assertThat(stats.getMax()).isEqualTo(stats.getTotal() / 5); - assertThat(stats.getMax()).isGreaterThan(10000 / 5); + assertThat(stats.count()).isEqualTo(5); + assertThat(stats.total()).isGreaterThan(10000); + assertThat(stats.max()).isEqualTo(stats.total() / 5); + assertThat(stats.max()).isGreaterThan(10000 / 5); metric.addBatchSince(start, 2); stats = metric.collect(true); - assertThat(stats.getCount()).isEqualTo(2); - assertThat(stats.getTotal()).isGreaterThan(10000); - assertThat(stats.getMax()).isEqualTo(stats.getTotal() / 2); + assertThat(stats.count()).isEqualTo(2); + assertThat(stats.total()).isGreaterThan(10000); + assertThat(stats.max()).isEqualTo(stats.total() / 2); } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/profile/SortMetricTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/profile/SortMetricTest.java index c67d14b30..29658566c 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/profile/SortMetricTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/profile/SortMetricTest.java @@ -26,7 +26,7 @@ public class SortMetricTest { list.add(create("a")); list.sort(sortMetric); - String names = list.stream().map(DTimeMetricStats::getName).collect(Collectors.joining()); + String names = list.stream().map(DTimeMetricStats::name).collect(Collectors.joining()); assertEquals("nullabcd", names); } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java index 1bdbcd2f7..0b26078ff 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/profile/UtilLocationTest.java @@ -8,8 +8,13 @@ public class UtilLocationTest { @Test public void label() { - assertThat(UtilLocation.label("foo")).isEqualTo("foo"); - assertThat(UtilLocation.label("ProfileLocationTest$Other.(ProfileLocationTest.java:47)")).isEqualTo("ProfileLocationTest$Other.init"); + assertThat(UtilLocation.label("ProfileLocationTest$Other.")).isEqualTo("ProfileLocationTest$Other.init"); + } + + @Test + public void loc() { + assertThat(UtilLocation.loc("org.foo.MyFoo.doIt(MyFoo.java:12)")).isEqualTo("org.foo.MyFoo.doIt"); + assertThat(UtilLocation.label("org.foo.MyFoo.doIt")).isEqualTo("MyFoo.doIt"); } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/BindValuesKeyTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/BindValuesKeyTest.java new file mode 100644 index 000000000..1a7d3b39f --- /dev/null +++ b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/BindValuesKeyTest.java @@ -0,0 +1,38 @@ +package io.ebeaninternal.server.querydefn; + +import io.ebeaninternal.api.BindValuesKey; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BindValuesKeyTest { + + @Test + public void update_with_null() { + + BindValuesKey hash = new BindValuesKey(); + hash.add(1).add(null).add("hello"); + + BindValuesKey hash2 = new BindValuesKey(); + hash2.add(1).add(null).add("hello"); + + assertThat(hash).isEqualTo(hash2); + } + + @Test + public void notEqual() { + + BindValuesKey hash = new BindValuesKey(); + hash.add(1).add(null).add("hello"); + + BindValuesKey hash2 = new BindValuesKey(); + hash2.add(1).add("hello"); + + BindValuesKey hash3 = new BindValuesKey(); + hash2.add(1).add(null); + + assertThat(hash).isNotEqualTo(hash2); + assertThat(hash).isNotEqualTo(hash3); + assertThat(hash2).isNotEqualTo(hash3); + } +} diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java index d9e621221..b2b4902ad 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -4,6 +4,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.BaseTestCase; import io.ebean.CacheMode; import io.ebean.Ebean; +import io.ebeaninternal.api.BindValuesKey; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.core.OrmQueryRequest; import org.junit.Test; @@ -62,7 +63,7 @@ public class DefaultOrmQueryTest extends BaseTestCase { prepare(q1, q2); assertThat(q1.createQueryPlanKey()).isNotEqualTo(q2.createQueryPlanKey()); - assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); + assertThat(bindKey(q1)).isNotEqualTo(bindKey(q2)); } @Test @@ -73,7 +74,7 @@ public class DefaultOrmQueryTest extends BaseTestCase { prepare(q1, q2); assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); - assertThat(q1.queryBindHash()).isNotEqualTo(q2.queryBindHash()); + assertThat(bindKey(q1)).isNotEqualTo(bindKey(q2)); } @Test @@ -84,7 +85,7 @@ public class DefaultOrmQueryTest extends BaseTestCase { prepare(q1, q2); assertThat(q1.createQueryPlanKey()).isEqualTo(q2.createQueryPlanKey()); - assertThat(q1.queryBindHash()).isEqualTo(q2.queryBindHash()); + assertThat(bindKey(q1)).isEqualTo(bindKey(q2)); } @Test @@ -110,4 +111,10 @@ public class DefaultOrmQueryTest extends BaseTestCase { OrmQueryRequest r2 = createQueryRequest(SpiQuery.Type.LIST, q2, null); q2.prepare(r2); } + + private BindValuesKey bindKey(DefaultOrmQuery query) { + BindValuesKey key = new BindValuesKey(); + query.queryBindKey(key); + return key; + } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilder.java b/ebean-core/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilder.java index 4e0a32e24..8d4feeaf9 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilder.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilder.java @@ -9,17 +9,25 @@ import io.ebean.RawSqlBuilder; import io.ebean.SqlRow; import io.ebean.annotation.ForPlatform; import io.ebean.annotation.Platform; +import io.ebean.datasource.DataSourceConfig; +import io.ebeaninternal.server.core.DefaultServer; import io.ebeaninternal.server.rawsql.SpiRawSql.Sql; import org.junit.Test; import org.tests.model.basic.Customer; +import org.tests.model.basic.EBasicClob; +import org.tests.model.basic.PersistentFileContent; import org.tests.model.basic.ResetBasicData; import org.tests.model.rawsql.ERawSqlAggBean; import javax.sql.DataSource; +import java.nio.charset.StandardCharsets; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -283,4 +291,58 @@ public class TestRawSqlBuilder extends BaseTestCase { } } + @Test + public void testCLobClosedConnection() throws Exception { + final EBasicClob eBasicClob = new EBasicClob(); + eBasicClob.setName("eBasicClob"); + final String description = "This is the CLob description"; + eBasicClob.setDescription(description); + DB.save(eBasicClob); + + final String sql = "select description from ebasic_clob where id = ?"; + + List rows = new ArrayList<>(); + final DataSourceConfig config = ((DefaultServer) DB.getDefault()).getServerConfig().getDataSourceConfig(); + + try (Connection connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword()); + PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setLong(1, eBasicClob.getId()); + + try (ResultSet resultSet = stmt.executeQuery()) { + while (resultSet.next()) { + rows.add(RawSqlBuilder.sqlRow(resultSet, "true", false)); + } + } + } + + assertThat(rows).hasSize(1); + assertThat(rows.get(0).getString("description")).isEqualTo(description); + } + + @Test + public void testBLobClosedConnection() throws Exception { + final PersistentFileContent pfc = new PersistentFileContent(); + final byte[] bytes = "This is the blob as String".getBytes(StandardCharsets.UTF_8); + pfc.setContent(bytes); + DB.save(pfc); + + List rows = new ArrayList<>(); + final DataSourceConfig config = ((DefaultServer) DB.getDefault()).getServerConfig().getDataSourceConfig(); + + final String sql = "select content from persistent_file_content where id = ?"; + try (Connection connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword()); + PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setLong(1, pfc.getId()); + + try (ResultSet resultSet = stmt.executeQuery()) { + while (resultSet.next()) { + rows.add(RawSqlBuilder.sqlRow(resultSet, "true", false)); + } + } + } + + assertThat(rows).hasSize(1); + assertThat(rows.get(0).get("content")).isEqualTo(bytes); + } + } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java index ba12fcd05..b0b2b1eb0 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeOffsetDateTimeTest.java @@ -5,6 +5,8 @@ import org.junit.Test; import java.sql.Timestamp; import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.TimeZone; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -14,12 +16,12 @@ import static org.junit.Assert.assertTrue; public class ScalarTypeOffsetDateTimeTest { - ScalarTypeOffsetDateTime type = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.MILLIS); + ScalarTypeOffsetDateTime type = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.MILLIS, ZoneOffset.systemDefault()); OffsetDateTime warmUp = OffsetDateTime.now(); @Test - public void testConvertToMillis() throws Exception { + public void testConvertToMillis() { warmUp.hashCode(); @@ -30,7 +32,43 @@ public class ScalarTypeOffsetDateTimeTest { } @Test - public void testConvertFromTimestamp() throws Exception { + public void convertFromInstant_with_UTC_expect_matchingZoneOffset() { + final TimeZone timeZoneToUse = TimeZone.getTimeZone("UTC"); + final ZoneOffset expectedZoneOffset = ZoneOffset.UTC; + + convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedZoneOffset); + } + + @Test + public void convertFromInstant_with_EST_expect_matchingZoneOffset() { + final TimeZone timeZoneToUse = TimeZone.getTimeZone("EST"); + final ZoneOffset expectedOffset = OffsetDateTime.now(timeZoneToUse.toZoneId()).getOffset(); + + convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedOffset); + } + + private void convertFromInstantWithConfiguredTimeZone(TimeZone timeZoneToUse, ZoneOffset expectedZoneOffset) { + TimeZone previous = TimeZone.getDefault(); + try { + OffsetDateTime dateTime = OffsetDateTime.parse("2021-01-01T00:00:00+11:00"); + + // test ScalarTypeOffsetDateTime with the configured timeZone to use + ScalarTypeOffsetDateTime type = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.MILLIS, timeZoneToUse.toZoneId()); + + // effectively we desire to ignore the system timezone and use the configured one + TimeZone.setDefault(timeZoneToUse); + + final OffsetDateTime offsetDateTime = type.convertFromInstant(dateTime.toInstant()); + + assertEquals(expectedZoneOffset, offsetDateTime.getOffset()); + + } finally { + TimeZone.setDefault(previous); + } + } + + @Test + public void testConvertFromTimestamp() { Timestamp now = new Timestamp(System.currentTimeMillis()); @@ -69,11 +107,11 @@ public class ScalarTypeOffsetDateTimeTest { JsonTester jsonTester = new JsonTester<>(type); jsonTester.test(now); - ScalarTypeOffsetDateTime typeNanos = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.NANOS); + ScalarTypeOffsetDateTime typeNanos = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.NANOS, ZoneOffset.systemDefault()); jsonTester = new JsonTester<>(typeNanos); jsonTester.test(now); - ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601, ZoneOffset.systemDefault()); jsonTester = new JsonTester<>(typeIso); jsonTester.test(now); } @@ -81,7 +119,7 @@ public class ScalarTypeOffsetDateTimeTest { @Test public void isoJsonFormatParse() { - ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601, ZoneOffset.systemDefault()); OffsetDateTime now = OffsetDateTime.now(); String asJson = typeIso.toJsonISO8601(now); diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java index ea501b273..168041723 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeZonedDateTimeTest.java @@ -4,7 +4,11 @@ import io.ebean.config.JsonConfig; import org.junit.Test; import java.sql.Timestamp; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.TimeZone; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; @@ -12,12 +16,12 @@ import static org.junit.Assert.*; public class ScalarTypeZonedDateTimeTest { - ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS); + ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS, ZoneId.systemDefault()); ZonedDateTime warmUp = ZonedDateTime.now(); @Test - public void testConvertToMillis() throws Exception { + public void testConvertToMillis() { warmUp.hashCode(); @@ -29,7 +33,7 @@ public class ScalarTypeZonedDateTimeTest { } @Test - public void testConvertFromTimestamp() throws Exception { + public void testConvertFromTimestamp() { Timestamp now = new Timestamp(System.currentTimeMillis()); @@ -39,6 +43,41 @@ public class ScalarTypeZonedDateTimeTest { assertEquals(now, timestamp); } + @Test + public void convertFromInstant_with_UTC_expect_matchingZoneOffset() { + final TimeZone timeZoneToUse = TimeZone.getTimeZone("UTC"); + final ZoneOffset expectedZoneOffset = ZoneOffset.UTC; + + convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedZoneOffset); + } + + @Test + public void convertFromInstant_with_EST_expect_matchingZoneOffset() { + final TimeZone timeZoneToUse = TimeZone.getTimeZone("EST"); + final ZoneOffset expectedOffset = OffsetDateTime.now(timeZoneToUse.toZoneId()).getOffset(); + + convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedOffset); + } + + private void convertFromInstantWithConfiguredTimeZone(TimeZone timeZoneToUse, ZoneOffset expectedZoneOffset) { + TimeZone previous = TimeZone.getDefault(); + try { + OffsetDateTime dateTime = OffsetDateTime.parse("2021-01-01T00:00:00+11:00"); + + // test ScalarTypeOffsetDateTime with the configured timeZone to use + ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS, timeZoneToUse.toZoneId()); + + // effectively we desire to ignore the system timezone and use the configured one + TimeZone.setDefault(timeZoneToUse); + + final ZonedDateTime zonedDateTime = type.convertFromInstant(dateTime.toInstant()); + + assertEquals(expectedZoneOffset, zonedDateTime.getOffset()); + + } finally { + TimeZone.setDefault(previous); + } + } @Test public void testToJdbcType() throws Exception { @@ -68,11 +107,11 @@ public class ScalarTypeZonedDateTimeTest { JsonTester jsonTester = new JsonTester<>(type); jsonTester.test(now); - ScalarTypeZonedDateTime typeNanos = new ScalarTypeZonedDateTime(JsonConfig.DateTime.NANOS); + ScalarTypeZonedDateTime typeNanos = new ScalarTypeZonedDateTime(JsonConfig.DateTime.NANOS, ZoneId.systemDefault()); jsonTester = new JsonTester<>(typeNanos); jsonTester.test(now); - ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601, ZoneId.systemDefault()); jsonTester = new JsonTester<>(typeIso); jsonTester.test(now); } @@ -80,7 +119,7 @@ public class ScalarTypeZonedDateTimeTest { @Test public void toJsonISO8601() { - ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601); + ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601, ZoneId.systemDefault()); ZonedDateTime now = ZonedDateTime.now(); String asJson = typeIso.toJsonISO8601(now); diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/util/ChecksumTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/util/ChecksumTest.java new file mode 100644 index 000000000..9eceabb0a --- /dev/null +++ b/ebean-core/src/test/java/io/ebeaninternal/server/util/ChecksumTest.java @@ -0,0 +1,23 @@ +package io.ebeaninternal.server.util; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ChecksumTest { + + @Test + public void checksum() { + final long val = Checksum.checksum("Hello world"); + assertThat(val).isEqualTo(2346098258L); + assertThat(Checksum.checksum("Hello world")).isEqualTo(val); + assertThat(Checksum.checksum("hello world")).isNotEqualTo(val); + } + + @Test + public void checksum_shortString() { + final long val0 = Checksum.checksum("2012-01-11"); + final long val1 = Checksum.checksum("2012-10-02"); + assertThat(val0).isNotEqualTo(val1); + } +} diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/util/Md5Test.java b/ebean-core/src/test/java/io/ebeaninternal/server/util/Md5Test.java index d9db27d3d..9f0106cae 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/util/Md5Test.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/util/Md5Test.java @@ -3,16 +3,51 @@ package io.ebeaninternal.server.util; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; public class Md5Test { @Test public void hash() throws Exception { - String content = "some random content we wish to hash"; String hash1 = Md5.hash(content); String hash2 = Md5.hash(content); assertEquals(hash1, hash2); + assertEquals(hash1, "62c20bf679ff56cb746452ab5c88e3ed"); } + @Test + public void hashDifferent() throws Exception { + String hash1 = Md5.hash("one"); + String hash2 = Md5.hash("two"); + String hash3 = Md5.hash("onetwo"); + + assertNotEquals(hash1, hash2); + assertNotEquals(hash2, hash3); + assertEquals(hash1, "f97c5d29941bfb1b2fdab0874906ab82"); + } + + @Test + public void hashMulti() { + String hash1 = Md5.hash("one", "two"); + String hash2 = Md5.hash("onetwo"); + + assertEquals(hash1, hash2); + assertEquals(hash1, "5b9164ad6f496d9dee12ec7634ce253f"); + } + + @Test + public void hashMulti_when_null() { + String hash1 = Md5.hash("one", null); + String hash2 = Md5.hash("one"); + + assertEquals(hash1, hash2); + assertEquals(hash1, "f97c5d29941bfb1b2fdab0874906ab82"); + } + + @Test + public void when_null() { + String hash1 = Md5.hash(null, null); + assertEquals(hash1, "d41d8cd98f00b204e9800998ecf8427e"); + } } diff --git a/ebean-core/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java b/ebean-core/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java index 6e956510a..2f9cdf7f1 100644 --- a/ebean-core/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java +++ b/ebean-core/src/test/java/org/tests/batchinsert/TestBatchInsertFlush.java @@ -80,15 +80,15 @@ public class TestBatchInsertFlush extends BaseTestCase { } ServerMetrics metrics = collectMetrics(); - List txnStats = metrics.getTimedMetrics(); + List txnStats = metrics.timedMetrics(); for (MetaTimedMetric txnMetric : txnStats) { System.out.println(txnMetric); } assertThat(txnStats).hasSize(4); - assertThat(txnStats.get(0).getName()).isEqualTo("txn.main"); - assertThat(txnStats.get(1).getName()).isEqualTo("txn.named.TestBatchInsertFlush.no_cascade"); - assertThat(txnStats.get(2).getName()).isEqualTo("iud.TSDetail.insertBatch"); - assertThat(txnStats.get(3).getName()).isEqualTo("iud.TSMaster.insertBatch"); + assertThat(txnStats.get(0).name()).isEqualTo("txn.main"); + assertThat(txnStats.get(1).name()).isEqualTo("txn.named.TestBatchInsertFlush.no_cascade"); + assertThat(txnStats.get(2).name()).isEqualTo("iud.TSDetail.insertBatch"); + assertThat(txnStats.get(3).name()).isEqualTo("iud.TSMaster.insertBatch"); } @Test diff --git a/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java b/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java index f9e732220..4c4d56109 100644 --- a/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java +++ b/ebean-core/src/test/java/org/tests/cache/TestQueryCache.java @@ -3,7 +3,7 @@ package org.tests.cache; import io.ebean.BaseTestCase; import io.ebean.CacheMode; import io.ebean.DB; -import io.ebean.Ebean; +import io.ebean.ExpressionList; import io.ebean.bean.BeanCollection; import io.ebean.cache.ServerCache; import org.ebeantest.LoggedSqlCollector; @@ -14,6 +14,7 @@ import org.tests.model.basic.ResetBasicData; import org.tests.model.cache.EColAB; import java.util.List; +import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; @@ -26,8 +27,7 @@ public class TestQueryCache extends BaseTestCase { new EColAB("02", "10").save(); List list1 = - Ebean.getServer(null) - .find(EColAB.class) + DB.find(EColAB.class) .setUseQueryCache(true) .where() .eq("columnA", "01") @@ -35,8 +35,7 @@ public class TestQueryCache extends BaseTestCase { .findList(); List list2 = - Ebean.getServer(null) - .find(EColAB.class) + DB.find(EColAB.class) .setUseQueryCache(true) .where() .eq("columnA", "02") @@ -57,7 +56,7 @@ public class TestQueryCache extends BaseTestCase { new EColAB("03", "SingleAttribute").save(); new EColAB("03", "SingleAttribute").save(); - List colA_first = Ebean.getServer(null) + List colA_first = DB .find(EColAB.class) .setUseQueryCache(true) .setDistinct(true) @@ -66,7 +65,7 @@ public class TestQueryCache extends BaseTestCase { .eq("columnB", "SingleAttribute") .findSingleAttributeList(); - List colA_Second = Ebean.getServer(null) + List colA_Second = DB .find(EColAB.class) .setUseQueryCache(true) .setDistinct(true) @@ -77,7 +76,7 @@ public class TestQueryCache extends BaseTestCase { assertThat(colA_Second).isSameAs(colA_first); - List colA_NotDistinct = Ebean.getServer(null) + List colA_NotDistinct = DB .find(EColAB.class) .setUseQueryCache(true) .select("columnA") @@ -89,7 +88,7 @@ public class TestQueryCache extends BaseTestCase { // ensure that findCount & findSingleAttribute use different // slots in cache. If not a "Cannot cast List to int" should happen. - int count = Ebean.getServer(null) + int count = DB .find(EColAB.class) .setUseQueryCache(true) .select("columnA") @@ -107,13 +106,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "count") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "count") @@ -126,7 +125,7 @@ public class TestQueryCache extends BaseTestCase { // and now, ensure that we hit the database LoggedSqlCollector.start(); - int count2 = Ebean.find(EColAB.class) + int count2 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.OFF) .where() .eq("columnB", "count") @@ -142,13 +141,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "abc") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "def") @@ -167,13 +166,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "uvw") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.PUT) .where() .eq("columnB", "uvw") @@ -193,13 +192,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - int count0 = Ebean.find(EColAB.class) + int count0 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.PUT) .where() .eq("columnB", "xyz") .findCount(); - int count1 = Ebean.find(EColAB.class) + int count1 = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "xyz") @@ -214,26 +213,26 @@ public class TestQueryCache extends BaseTestCase { @Test @SuppressWarnings("unchecked") - public void test() { + public void testReadOnlyFind() { ResetBasicData.reset(); - ServerCache customerCache = Ebean.getServerCacheManager().getQueryCache(Customer.class); + ServerCache customerCache = DB.getServerCacheManager().getQueryCache(Customer.class); customerCache.clear(); - List list = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() + List list = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() .ilike("name", "Rob").findList(); BeanCollection bc = (BeanCollection) list; Assert.assertTrue(bc.isReadOnly()); Assert.assertFalse(bc.isEmpty()); Assert.assertTrue(!list.isEmpty()); - Assert.assertTrue(Ebean.getBeanState(list.get(0)).isReadOnly()); + Assert.assertTrue(DB.getBeanState(list.get(0)).isReadOnly()); - List list2 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() + List list2 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where() .ilike("name", "Rob").findList(); - List list2B = Ebean.find(Customer.class).setUseQueryCache(true) + List list2B = DB.find(Customer.class).setUseQueryCache(true) // .setReadOnly(true) .where().ilike("name", "Rob").findList(); @@ -245,7 +244,7 @@ public class TestQueryCache extends BaseTestCase { - List list3 = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where() + List list3 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where() .ilike("name", "Rob").findList(); Assert.assertNotSame(list, list3); @@ -269,13 +268,13 @@ public class TestQueryCache extends BaseTestCase { LoggedSqlCollector.start(); - List colA_first = Ebean.find(EColAB.class) + List colA_first = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "someId") .findIds(); - List colA_second = Ebean.find(EColAB.class) + List colA_second = DB.find(EColAB.class) .setUseQueryCache(CacheMode.ON) .where() .eq("columnB", "someId") @@ -289,7 +288,7 @@ public class TestQueryCache extends BaseTestCase { // and now, ensure that we hit the database LoggedSqlCollector.start(); - colA_second = Ebean.find(EColAB.class) + colA_second = DB.find(EColAB.class) .setUseQueryCache(CacheMode.PUT) .where() .eq("columnB", "someId") @@ -299,4 +298,35 @@ public class TestQueryCache extends BaseTestCase { assertThat(sql).hasSize(1); } + @Test + public void findCountDifferentQueriesBit() { + DB.getDefault().getPluginApi().getServerCacheManager().clearAll(); + differentFindCount(q->q.bitwiseAny("id",1), q->q.bitwiseAny("id",0)); + differentFindCount(q->q.bitwiseAll("id",1), q->q.bitwiseAll("id",0)); + // differentFindCount(q->q.bitwiseNot("id",1), q->q.bitwiseNot("id",0)); NOT 1 == AND 1 = 0 + differentFindCount(q->q.bitwiseAnd("id",1, 0), q->q.bitwiseAnd("id",1, 1)); + + differentFindCount(q->q.bitwiseAnd("id",2, 0), q->q.bitwiseAnd("id",4, 0)); + differentFindCount(q->q.bitwiseAnd("id",2, 1), q->q.bitwiseAnd("id",4, 1)); + // Will produce hash collision + differentFindCount(q->q.bitwiseAnd("id",10, 0), q->q.bitwiseAnd("id",0, 928210)); + + } + + void differentFindCount(Consumer> q0, Consumer> q1) { + LoggedSqlCollector.start(); + + ExpressionList el0 = DB.find(EColAB.class).setUseQueryCache(CacheMode.ON).where(); + q0.accept(el0); + el0.findCount(); + + ExpressionList el1 = DB.find(EColAB.class).setUseQueryCache(CacheMode.ON).where(); + q1.accept(el1); + el1.findCount(); + + List sql = LoggedSqlCollector.stop(); + + assertThat(sql).hasSize(2); // different queries + } + } diff --git a/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java b/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java index 2adbbfc4b..a10a41276 100644 --- a/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java +++ b/ebean-core/src/test/java/org/tests/changelog/TestChangeLog.java @@ -16,10 +16,13 @@ import io.ebean.event.changelog.ChangeLogRegister; import io.ebean.event.changelog.ChangeSet; import io.ebean.event.changelog.ChangeType; import io.ebean.event.changelog.TxnState; +import io.ebeantest.LoggedSql; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.tests.model.basic.EBasicChangeLog; +import org.tests.model.json.PlainBean; import java.util.ArrayList; import java.util.List; @@ -130,7 +133,31 @@ public class TestChangeLog extends BaseTestCase { assertThat(change.getEvent()).isEqualTo(ChangeType.DELETE); assertThat(change.getData()).isNull(); } + + @Test + public void testWithJsonMutationDetection() { + EBasicChangeLog bean = new EBasicChangeLog(); + bean.setName(null); + bean.setShortDescription("hello"); + PlainBean jsonBean = new PlainBean(); + bean.setPlainBean(jsonBean); + jsonBean.setName("A"); + server.save(bean); + + BeanChange change = firstChange(); + assertThat(change.getEvent()).isEqualTo(ChangeType.INSERT); + + jsonBean.setName("B"); + LoggedSql.start(); + server.save(bean); + assertThat(LoggedSql.stop()).isNotEmpty(); + + change = firstChange(); + assertThat(change.getEvent()).isEqualTo(ChangeType.UPDATE); + assertThat(change.getData()).contains("\"plainBean\":{\"name\":\"B\""); + assertThat(change.getOldData()).contains("\"plainBean\":{\"name\":\"A\""); + } private Database createServer() { DatabaseConfig config = new DatabaseConfig(); diff --git a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java index 106beb9f2..5b58b3108 100644 --- a/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java +++ b/ebean-core/src/test/java/org/tests/json/TestDbJson_Jackson3.java @@ -1,21 +1,48 @@ package org.tests.json; import io.ebean.BaseTestCase; +import io.ebean.BeanState; import io.ebean.DB; +import io.ebean.ValuePair; +import io.ebean.event.BeanPersistAdapter; +import io.ebean.event.BeanPersistRequest; import io.ebeantest.LoggedSql; import org.junit.Test; import org.tests.model.json.EBasicJsonJackson3; import org.tests.model.json.EBasicJsonList; +import org.tests.model.json.EBasicJsonMulti; import org.tests.model.json.PlainBean; import org.tests.model.json.PlainBeanDirtyAware; import java.util.Arrays; import java.util.List; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; public class TestDbJson_Jackson3 extends BaseTestCase { + public static class EBasicJsonListPersistController extends BeanPersistAdapter { + + private static Map updatedValues; + + @Override + public boolean isRegisterFor(Class cls) { + return EBasicJsonList.class.isAssignableFrom(cls); + } + + @Override + public boolean preInsert(BeanPersistRequest request) { + updatedValues = request.getUpdatedValues(); + return true; + } + + @Override + public boolean preUpdate(BeanPersistRequest request) { + updatedValues = request.getUpdatedValues(); + return true; + } + } @Test public void updateIncludesJsonColumn_when_explicit_isMarkedDirty() { @@ -24,6 +51,7 @@ public class TestDbJson_Jackson3 extends BaseTestCase { EBasicJsonJackson3 bean = new EBasicJsonJackson3(); bean.setName("b1"); bean.setPlainValue(contentBean); + bean.setPlainValue2(contentBean); bean.save(); @@ -32,20 +60,15 @@ public class TestDbJson_Jackson3 extends BaseTestCase { LoggedSql.start(); found.save(); - - List sql = LoggedSql.collect(); - assertThat(sql).hasSize(1); - assertThat(sql.get(0)).contains("update ebasic_json_jackson3 set name=?, version=? where id=? and version=?"); + expectedSql(0, "update ebasic_json_jackson3 set name=?, version=? where id=? and version=?"); found.setName("b1-mod2"); found.getPlainValue().setName("b"); - found.getPlainValue().setMarkedDirty(true); + // found.getPlainValue().setMarkedDirty(true); // Irrelevant for SOURCE or HASH based mutation detection found.save(); - - sql = LoggedSql.stop(); - assertThat(sql).hasSize(1); - assertThat(sql.get(0)).contains("update ebasic_json_jackson3 set name=?, plain_value=?, version=? where id=? and version=?"); + expectedSql(0, "update ebasic_json_jackson3 set name=?, plain_value=?, version=? where id=? and version=?"); + LoggedSql.stop(); final EBasicJsonJackson3 found2 = DB.find(EBasicJsonJackson3.class, bean.getId()); @@ -69,11 +92,153 @@ public class TestDbJson_Jackson3 extends BaseTestCase { found.setName("p1-mod"); found.setBeanList(null); + BeanState state = DB.getBeanState(found); + assertThat(state.getChangedProps()).containsExactlyInAnyOrder("name", "beanList"); + + ValuePair pair = state.getDirtyValues().get("name"); + assertThat(pair.getNewValue()).isEqualTo("p1-mod"); + assertThat(pair.getOldValue()).isEqualTo("p1"); + + pair = state.getDirtyValues().get("beanList"); + assertThat(pair.getNewValue()).isEqualTo(null); + assertThat((List)pair.getOldValue()).hasSize(1) + .extracting(PlainBean::getName).containsExactly("a"); + + LoggedSql.start(); DB.save(found); - final List sql = LoggedSql.stop(); - assertThat(sql).hasSize(1); - assertThat(sql.get(0)).contains("update ebasic_json_list set name=?, bean_list=?, plain_bean=?, version=? where id=?"); + // plain_bean=?, no longer included with MD5 dirty detection + expectedSql(0, "update ebasic_json_list set name=?, bean_list=?, version=? where id=?"); + + assertThat(EBasicJsonListPersistController.updatedValues.entrySet()) + .extracting(Map.Entry::toString) + .containsExactlyInAnyOrder("beanList=null,[name:a]","name=p1-mod,p1","version=2,1"); + + assertThat(DB.getBeanState(found).isDirty()).isFalse(); + + found.getPlainBean().setName("b"); + assertThat(DB.getBeanState(found).isDirty()).isTrue(); + + state = DB.getBeanState(found); + assertThat(state.getChangedProps()).containsExactlyInAnyOrder("plainBean"); + pair = state.getDirtyValues().get("plainBean"); + assertThat(pair.getNewValue()).hasToString("name:b"); + assertThat(pair.getOldValue()).hasToString("name:a"); + + + LoggedSql.start(); + DB.save(found); + + // plain_bean=?, no longer included with MD5 dirty detection + expectedSql(0, "update ebasic_json_list set plain_bean=?, version=? where id=?"); + + assertThat(EBasicJsonListPersistController.updatedValues.entrySet()) + .extracting(Map.Entry::toString) + .containsExactlyInAnyOrder("plainBean=name:b,name:a", "version=3,2"); + + LoggedSql.stop(); + } + + @Test + public void updateIncludesJsonColumn_when_list_loadedAndNotDirtyAware() { + + PlainBean contentBean = new PlainBean("a", 42); + EBasicJsonList bean = new EBasicJsonList(); + bean.setName("p1"); + bean.setPlainBean(contentBean); + bean.setBeanList(Arrays.asList(contentBean)); + + DB.save(bean); + final EBasicJsonList found = DB.find(EBasicJsonList.class, bean.getId()); + found.getBeanList().get(0).setName("p1-mod"); + + BeanState state = DB.getBeanState(found); + assertThat(state.getChangedProps()).containsExactlyInAnyOrder("beanList"); + } + + @Test + public void update_with_differentDbJsonSettings() { + PlainBeanDirtyAware contentBean1 = new PlainBeanDirtyAware("x", 42); + PlainBeanDirtyAware contentBean2 = new PlainBeanDirtyAware("y", 43); + PlainBeanDirtyAware contentBean3 = new PlainBeanDirtyAware("z", 44); + + EBasicJsonJackson3 bean = new EBasicJsonJackson3(); + bean.setName("b1"); + bean.setPlainValue(contentBean1); + bean.setPlainValue2(contentBean2); + bean.setPlainValue3(contentBean3); + + BeanState state = DB.getBeanState(bean); + // a new bean is not considered as dirty (thus have no changed props) + assertThat(state.isDirty()).isFalse(); + assertThat(state.isNewOrDirty()).isTrue(); + assertThat(state.getChangedProps()).isEmpty(); + + bean.save(); + + bean = DB.find(EBasicJsonJackson3.class, bean.getId()); + state = DB.getBeanState(bean); + // a fresh loaded bean is also not considered as dirty + assertThat(state.isDirty()).isFalse(); + assertThat(state.isNewOrDirty()).isFalse(); + assertThat(state.getChangedProps()).isEmpty(); + + bean.getPlainValue().setName("a"); // has SOURCE + + assertThat(state.isDirty()).isTrue(); + assertThat(state.getChangedProps()).containsExactly("plainValue"); + + bean.getPlainValue2().setName("b"); + assertThat(state.getChangedProps()).containsExactlyInAnyOrder("plainValue", "plainValue2"); + + bean.getPlainValue3().setName("c"); // has mutationDetection = NONE + + Map dirtyValues = state.getDirtyValues(); + assertThat(dirtyValues).hasSize(2).containsKeys("plainValue", "plainValue2"); + + assertThat(dirtyValues.get("plainValue")).hasToString("name:a,name:x"); // SOURCE -> origValue present + assertThat(dirtyValues.get("plainValue2")).hasToString("name:b,null"); // without SOURCE no origValue present + + LoggedSql.start(); + bean.save(); + expectedSql(0, "update ebasic_json_jackson3 set plain_value=?, plain_value2=?, version=? where id=?"); + + bean = DB.find(EBasicJsonJackson3.class, bean.getId()); + LoggedSql.collect(); // ignore the select + assertThat(bean.getPlainValue().getName()).isEqualTo("a"); + assertThat(bean.getPlainValue2().getName()).isEqualTo("b"); + assertThat(bean.getPlainValue3().getName()).isEqualTo("z"); // value is not updated + + bean.getPlainValue3().setName("c"); + bean.getPlainValue3().setMarkedDirty(true); // This is ignored because it is MutationDetection.NONE + bean.save(); + // no update as plainValue3 has MutationDetection.NONE (ModifyAwareType = NONE isn't an expected combination to me) + assertThat(LoggedSql.collect()).isEmpty(); + + bean.getPlainValue2().setName("b2"); // effectively HASH mode mutation detection + bean.save(); + expectedSql(0, "update ebasic_json_jackson3 set plain_value2=?, version=? where id=? and version=?"); + + LoggedSql.stop(); + } + + @Test + public void push_pop_test() { + + EBasicJsonMulti bean = new EBasicJsonMulti(); + bean.setPlainValue2(new PlainBeanDirtyAware("x", 42)); + bean.save(); + + bean = DB.find(EBasicJsonMulti.class, bean.getId()); + bean.setPlainValue1(null); // already null + bean.setPlainValue2(null); + bean.setPlainValue3(null); // already null + BeanState state = DB.getBeanState(bean); + assertThat(state.getDirtyValues()).hasSize(1).containsKey("plainValue2"); + } + + private void expectedSql(int i, String s) { + assertThat(LoggedSql.collect().get(i)).contains(s); } } diff --git a/ebean-core/src/test/java/org/tests/json/TestDbJson_List.java b/ebean-core/src/test/java/org/tests/json/TestDbJson_List.java index 28fd78221..647aa6eff 100644 --- a/ebean-core/src/test/java/org/tests/json/TestDbJson_List.java +++ b/ebean-core/src/test/java/org/tests/json/TestDbJson_List.java @@ -76,10 +76,11 @@ public class TestDbJson_List extends BaseTestCase { update_when_dirty(); update_when_dirty_flags(); update_when_dirty_SetListMap(); + + DB.delete(found); } - //@Test//(dependsOnMethods = "insert") - public void json_parse_format() { + private void json_parse_format() { String asJson = DB.json().toJson(found); assertThat(asJson).contains("\"tags\":[\"one\",\"two\"]"); @@ -104,8 +105,7 @@ public class TestDbJson_List extends BaseTestCase { assertThat(fromJson.getBeanMap()).hasSize(2); } - //@Test//(dependsOnMethods = "insert") - public void update_when_notDirty() { + private void update_when_notDirty() { found.setName("mod"); LoggedSqlCollector.start(); @@ -113,10 +113,11 @@ public class TestDbJson_List extends BaseTestCase { List sql = LoggedSqlCollector.stop(); // we don't update the phone numbers (as they are not dirty) - assertSql(sql.get(0)).contains("update ebasic_json_list set name=?, plain_bean=?, version=? where"); + // plain_bean=?, no longer included with MD5 dirty detection + assertSql(sql.get(0)).contains("update ebasic_json_list set name=?, version=? where"); } - public void update_when_dirty() { + private void update_when_dirty() { //found.setName("modAgain"); found.getTags().add("three"); @@ -126,10 +127,11 @@ public class TestDbJson_List extends BaseTestCase { List sql = LoggedSqlCollector.stop(); // we don't update the phone numbers (as they are not dirty) - assertSql(sql.get(0)).contains("update ebasic_json_list set plain_bean=?, tags=?, version=? where id=? and version=?"); + // plain_bean=? not included using MD5 dirty detection + assertSql(sql.get(0)).contains("update ebasic_json_list set tags=?, version=? where id=? and version=?"); } - public void update_when_dirty_flags() { + private void update_when_dirty_flags() { //found.setName("modAgain"); found.getFlags().remove(42L); @@ -139,10 +141,11 @@ public class TestDbJson_List extends BaseTestCase { List sql = LoggedSqlCollector.stop(); // we don't update the phone numbers (as they are not dirty) - assertSql(sql.get(0)).contains("update ebasic_json_list set plain_bean=?, flags=?, version=? where id=? and version=?;"); + // plain_bean=? not included with MD5 dirty detection + assertSql(sql.get(0)).contains("update ebasic_json_list set flags=?, version=? where id=? and version=?;"); } - public void update_when_dirty_SetListMap() { + private void update_when_dirty_SetListMap() { //found.setName("modAgain"); found.getBeanSet().clear(); @@ -154,7 +157,8 @@ public class TestDbJson_List extends BaseTestCase { List sql = LoggedSqlCollector.stop(); // we don't update the phone numbers (as they are not dirty) - assertSql(sql.get(0)).contains("update ebasic_json_list set beans=?, bean_list=?, bean_map=?, plain_bean=?, version=? where id=? and version=?"); + // plain_bean=? not included with MD5 dirty detection + assertSql(sql.get(0)).contains("update ebasic_json_list set beans=?, bean_list=?, bean_map=?, version=? where id=? and version=?"); } @Test @@ -221,4 +225,19 @@ public class TestDbJson_List extends BaseTestCase { DB.delete(bean); } + + @Test + public void testNullToEmpty() { + EBasicJsonList bean = new EBasicJsonList(); + bean.setFlags(null); + bean.setTags(null); + bean.setBeanMap(null); + DB.save(bean); + + bean = DB.find(EBasicJsonList.class).setId(bean.getId()).findOne(); + + assertThat(bean.getFlags()).isEmpty(); + assertThat(bean.getTags()).isEmpty(); + assertThat(bean.getBeanMap()).isEmpty(); + } } diff --git a/ebean-core/src/test/java/org/tests/m2m/TestM2MModifyTest.java b/ebean-core/src/test/java/org/tests/m2m/TestM2MModifyTest.java index 1f12f85c8..3a50fa547 100644 --- a/ebean-core/src/test/java/org/tests/m2m/TestM2MModifyTest.java +++ b/ebean-core/src/test/java/org/tests/m2m/TestM2MModifyTest.java @@ -1,14 +1,15 @@ package org.tests.m2m; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; +import org.junit.Test; import org.tests.model.basic.MRole; import org.tests.model.basic.MUser; -import org.junit.Assert; -import org.junit.Test; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; + public class TestM2MModifyTest extends BaseTestCase { @Test @@ -19,8 +20,8 @@ public class TestM2MModifyTest extends BaseTestCase { MRole r1 = new MRole("r1"); // Save r1 and r2 - Ebean.save(r0); - Ebean.save(r1); + DB.save(r0); + DB.save(r1); // Create a new user MUser u0 = new MUser("usr0"); @@ -28,32 +29,27 @@ public class TestM2MModifyTest extends BaseTestCase { u0.addRole(r1); // Save the user - Ebean.save(u0); + DB.save(u0); List roles = u0.getRoles(); - Assert.assertTrue(roles.size() == 2); + assertThat(roles).hasSize(2); - u0 = Ebean.find(MUser.class, u0.getUserid()); + u0 = DB.find(MUser.class, u0.getUserid()); roles = u0.getRoles(); - int nrRoles = roles.size(); - - Assert.assertTrue(nrRoles == 2); + assertThat(roles).hasSize(2); roles.clear(); roles.add(r0); roles.add(r1); roles.remove(r1); - Ebean.save(u0); + DB.save(u0); - u0 = Ebean.find(MUser.class, u0.getUserid()); + u0 = DB.find(MUser.class, u0.getUserid()); roles = u0.getRoles(); - - nrRoles = roles.size(); - - Assert.assertTrue(nrRoles == 1); + assertThat(roles).hasSize(1); } } diff --git a/ebean-core/src/test/java/org/tests/m2m/TestM2mDeleteObject.java b/ebean-core/src/test/java/org/tests/m2m/TestM2mDeleteObject.java index 3ba05ee68..3ccad9fb8 100644 --- a/ebean-core/src/test/java/org/tests/m2m/TestM2mDeleteObject.java +++ b/ebean-core/src/test/java/org/tests/m2m/TestM2mDeleteObject.java @@ -28,7 +28,7 @@ public class TestM2mDeleteObject extends BaseTestCase { List sqlMetrics = sqlMetrics(); assertThat(sqlMetrics).hasSize(1); - assertThat(sqlMetrics.get(0).getName()).isEqualTo("orm.update.deleteAllPermissions"); + assertThat(sqlMetrics.get(0).name()).isEqualTo("orm.update.deleteAllPermissions"); Tenant t = new Tenant("tenant"); diff --git a/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java b/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java index 88e3e4f10..0ac02e749 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java +++ b/ebean-core/src/test/java/org/tests/model/basic/EBasicChangeLog.java @@ -2,6 +2,7 @@ package org.tests.model.basic; import io.ebean.annotation.Cache; import io.ebean.annotation.ChangeLog; +import io.ebean.annotation.DbJson; import io.ebean.annotation.ReadAudit; import io.ebean.annotation.WhenCreated; import io.ebean.annotation.WhenModified; @@ -12,11 +13,16 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; import javax.validation.constraints.Size; + +import org.tests.model.json.PlainBean; + +import static io.ebean.annotation.MutationDetection.SOURCE; + import java.sql.Timestamp; @Cache(enableQueryCache = true) @ReadAudit -@ChangeLog(updatesThatInclude = {"name", "shortDescription"}) +@ChangeLog(updatesThatInclude = {"name", "shortDescription", "plainBean"}) @Entity public class EBasicChangeLog { @@ -46,6 +52,9 @@ public class EBasicChangeLog { @Version Long version; + + @DbJson(length = 500, mutationDetection = SOURCE) // such that we can rebuild old values + PlainBean plainBean; public Long getId() { return id; @@ -118,4 +127,12 @@ public class EBasicChangeLog { public void setVersion(Long version) { this.version = version; } + + public PlainBean getPlainBean() { + return plainBean; + } + + public void setPlainBean(PlainBean plainBean) { + this.plainBean = plainBean; + } } diff --git a/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java b/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java index c76728fca..79db60023 100644 --- a/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java +++ b/ebean-core/src/test/java/org/tests/model/basic/finder/CustomerFinder.java @@ -37,7 +37,6 @@ public class CustomerFinder extends Finder { } public List byNameStatus(String nameStartsWith, Customer.Status status) { - return query("where status = :status and name istartsWith :name order by name") .setParameter("status", status) .setParameter("name", nameStartsWith) @@ -45,7 +44,6 @@ public class CustomerFinder extends Finder { } public List namesStartingWith(String name) { - return nativeSql("select name from o_customer where name like ? order by name") .setParameter(name + "%") .findSingleAttributeList(); diff --git a/ebean-core/src/test/java/org/tests/model/embedded/EAddress.java b/ebean-core/src/test/java/org/tests/model/embedded/EAddress.java index 86911152b..4b737f0f5 100644 --- a/ebean-core/src/test/java/org/tests/model/embedded/EAddress.java +++ b/ebean-core/src/test/java/org/tests/model/embedded/EAddress.java @@ -1,9 +1,13 @@ package org.tests.model.embedded; +import io.ebean.annotation.DbJson; +import org.tests.model.json.PlainBean; + import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import java.util.Map; @Embeddable public class EAddress { @@ -18,6 +22,12 @@ public class EAddress { @Enumerated(EnumType.STRING) EAddressStatus status; + @DbJson + PlainBean jbean; + + @DbJson + Map jraw; + public String getStreet() { return street; } @@ -42,6 +52,22 @@ public class EAddress { this.city = city; } + public PlainBean getJbean() { + return jbean; + } + + public void setJbean(PlainBean jbean) { + this.jbean = jbean; + } + + public Map getJraw() { + return jraw; + } + + public void setJraw(Map jraw) { + this.jraw = jraw; + } + public EAddressStatus getStatus() { return status; } diff --git a/ebean-core/src/test/java/org/tests/model/embedded/EPerson.java b/ebean-core/src/test/java/org/tests/model/embedded/EPerson.java index 1e85b1041..8a0c60446 100644 --- a/ebean-core/src/test/java/org/tests/model/embedded/EPerson.java +++ b/ebean-core/src/test/java/org/tests/model/embedded/EPerson.java @@ -24,7 +24,8 @@ public class EPerson { @Embedded @AttributeOverrides({ @AttributeOverride(name = "city", column = @Column(name = "addr_city")), - @AttributeOverride(name = "status", column = @Column(name = "addr_status")) + @AttributeOverride(name = "status", column = @Column(name = "addr_status")), + @AttributeOverride(name = "jbean", column = @Column(name = "addr_jbean")) }) EAddress address; diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonJackson3.java b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonJackson3.java index 3d942d58b..cee12d9e7 100644 --- a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonJackson3.java +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonJackson3.java @@ -2,11 +2,15 @@ package org.tests.model.json; import io.ebean.Model; import io.ebean.annotation.DbJson; +import io.ebean.annotation.MutationDetection; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; +import static io.ebean.annotation.MutationDetection.NONE; +import static io.ebean.annotation.MutationDetection.SOURCE; + @Entity public class EBasicJsonJackson3 extends Model { @@ -15,9 +19,15 @@ public class EBasicJsonJackson3 extends Model { String name; - @DbJson(length = 500) + @DbJson(length = 500, mutationDetection = SOURCE) PlainBeanDirtyAware plainValue; + @DbJson(length = 500) + PlainBeanDirtyAware plainValue2; + + @DbJson(length = 500, mutationDetection = NONE) + PlainBeanDirtyAware plainValue3; + @Version long version; @@ -45,6 +55,22 @@ public class EBasicJsonJackson3 extends Model { this.plainValue = plainValue; } + public PlainBeanDirtyAware getPlainValue2() { + return plainValue2; + } + + public void setPlainValue2(PlainBeanDirtyAware plainValue2) { + this.plainValue2 = plainValue2; + } + + public PlainBeanDirtyAware getPlainValue3() { + return plainValue3; + } + + public void setPlainValue3(PlainBeanDirtyAware plainValue3) { + this.plainValue3 = plainValue3; + } + public long getVersion() { return version; } diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java index 22b32e3e1..885e85bf8 100644 --- a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java @@ -7,12 +7,10 @@ import io.ebean.annotation.DbJsonType; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; + +import static io.ebean.annotation.MutationDetection.HASH; +import static io.ebean.annotation.MutationDetection.SOURCE; @Entity public class EBasicJsonList { @@ -22,16 +20,17 @@ public class EBasicJsonList { String name; + // @JsonDeserialize(as=LinkedHashSet.class) @DbJson(length = 700, name = "beans") Set beanSet; - @DbJsonB + @DbJsonB(mutationDetection = HASH) List beanList; @DbJson(length = 700) Map beanMap = new LinkedHashMap<>(); - @DbJson(length = 500) + @DbJson(length = 500, mutationDetection = SOURCE) // such that we can rebuild old values PlainBean plainBean; @DbJson(length = 50) diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMapVarchar.java b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMapVarchar.java index c08c4e9fe..2b470ef45 100644 --- a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMapVarchar.java +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMapVarchar.java @@ -20,7 +20,7 @@ public class EBasicJsonMapVarchar { String name; @DbJson(storage = DbJsonType.VARCHAR)//, length = 2200) - Map content; + Map content; public Long getId() { return id; diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMulti.java b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMulti.java new file mode 100644 index 000000000..23dcd33d9 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonMulti.java @@ -0,0 +1,81 @@ +package org.tests.model.json; + +import io.ebean.Model; +import io.ebean.annotation.DbJson; +import io.ebean.annotation.MutationDetection; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import static io.ebean.annotation.MutationDetection.NONE; +import static io.ebean.annotation.MutationDetection.SOURCE; + +@Entity +public class EBasicJsonMulti extends Model { + + @Id + Long id; + + String name; + + @DbJson(length = 500, mutationDetection = SOURCE) + PlainBeanDirtyAware plainValue1; + + @DbJson(length = 500, mutationDetection = SOURCE) + PlainBeanDirtyAware plainValue2; + + @DbJson(length = 500, mutationDetection = SOURCE) + PlainBeanDirtyAware plainValue3; + + @Version + long version; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public PlainBeanDirtyAware getPlainValue1() { + return plainValue1; + } + + public void setPlainValue1(PlainBeanDirtyAware plainValue1) { + this.plainValue1 = plainValue1; + } + + public PlainBeanDirtyAware getPlainValue2() { + return plainValue2; + } + + public void setPlainValue2(PlainBeanDirtyAware plainValue2) { + this.plainValue2 = plainValue2; + } + + public PlainBeanDirtyAware getPlainValue3() { + return plainValue3; + } + + public void setPlainValue3(PlainBeanDirtyAware plainValue3) { + this.plainValue3 = plainValue3; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicPlain.java b/ebean-core/src/test/java/org/tests/model/json/EBasicPlain.java new file mode 100644 index 000000000..051b4a344 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicPlain.java @@ -0,0 +1,67 @@ +package org.tests.model.json; + +import io.ebean.annotation.DbJson; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +import static io.ebean.annotation.MutationDetection.NONE; + +@Entity +public class EBasicPlain { + + @Id + long id; + + String attr; + + @DbJson(length = 500) + PlainBean plainBean; + + @DbJson(length = 500, mutationDetection = NONE) // only update when property set + PlainBean plainBean2; + + @Version + long version; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getAttr() { + return attr; + } + + public void setAttr(String attr) { + this.attr = attr; + } + + public PlainBean getPlainBean() { + return plainBean; + } + + public void setPlainBean(PlainBean plainBean) { + this.plainBean = plainBean; + } + + public PlainBean getPlainBean2() { + return plainBean2; + } + + public void setPlainBean2(PlainBean plainBean2) { + this.plainBean2 = plainBean2; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/ebean-core/src/test/java/org/tests/model/json/TestJacksonPlainBean.java b/ebean-core/src/test/java/org/tests/model/json/TestJacksonPlainBean.java new file mode 100644 index 000000000..b2237731d --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/json/TestJacksonPlainBean.java @@ -0,0 +1,95 @@ +package org.tests.model.json; + +import io.ebean.DB; +import io.ebeantest.LoggedSql; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestJacksonPlainBean { + + @Test + public void insertNullStayNull() { + + // insert with jackson beans as null + EBasicPlain bean = new EBasicPlain(); + bean.setAttr("n0"); + DB.save(bean); + + LoggedSql.start(); + bean.setAttr("n1"); + DB.save(bean); + expectedSql(0, "update ebasic_plain set attr=?, version=? where id=? and version=?"); + + bean.setPlainBean(new PlainBean("x", 1)); + DB.save(bean); + expectedSql(0, "update ebasic_plain set plain_bean=?, version=? where id=? and version=?"); + + final EBasicPlain found = DB.find(EBasicPlain.class, bean.getId()); + found.setAttr("n2"); + DB.save(found); + expectedSql(1, "update ebasic_plain set attr=?, version=? where id=? and version=?"); + + LoggedSql.stop(); + } + + @Test + public void insertUpdate() { + + DB.getDefault(); + LoggedSql.start(); + + PlainBean content = new PlainBean("foo", 42); + EBasicPlain bean = new EBasicPlain(); + bean.setAttr("attr0"); + bean.setPlainBean(content); + bean.setPlainBean2(new PlainBean("bar", 27)); + + DB.save(bean); + expectedSql(0, "insert into ebasic_plain (attr, plain_bean, plain_bean2, version) values (?,?,?,?)"); + + // inserted plainBean has not been mutated + bean.setAttr("attr1"); + DB.save(bean); + expectedSql(0, "update ebasic_plain set attr=?, version=? where id=? and version=?"); + + // inserted plainBean has now been mutated + content.setName("notFoo"); + bean.setAttr("attr2"); + DB.save(bean); + expectedSql(0, "update ebasic_plain set attr=?, plain_bean=?, version=? where id=? and version=?"); + + + final EBasicPlain found = DB.find(EBasicPlain.class, bean.getId()); + + // update mutating PlainBean only + final PlainBean plainBean = found.getPlainBean(); + plainBean.setName("mod1"); + DB.save(found); + expectedSql(1, "update ebasic_plain set plain_bean=?, version=? where id=? and version=?"); + + // dirtyDetection = false, so not included in update + found.getPlainBean2().setName("Modification Ignored"); + // dirtyDetection = true, mutation detected + plainBean.setName("mod2"); + DB.save(found); + expectedSql(0, "update ebasic_plain set plain_bean=?, version=? where id=? and version=?"); + + // update bean, not mutating PlainBean + found.setAttr("attr3"); + DB.save(found); + expectedSql(0, "update ebasic_plain set attr=?, version=? where id=? and version=?"); + + // dirtyDetection = false, set a new plainBean2 instance, included in update + found.setPlainBean2(new PlainBean("bar", 27)); + DB.save(found); + expectedSql(0, "update ebasic_plain set plain_bean2=?, version=? where id=? and version=?"); + + LoggedSql.stop(); + } + + private void expectedSql(int i, String s) { + assertThat(LoggedSql.collect().get(i)).contains(s); + } + +} diff --git a/ebean-core/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java b/ebean-core/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java new file mode 100644 index 000000000..75790e26a --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java @@ -0,0 +1,33 @@ +package org.tests.model.orphanremoval; + +import io.ebean.Model; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Version; + +@Entity +public class OmBeanListChild extends Model { + + @Id + private Long id; + + private final String name; + + @ManyToOne + private OmBeanListParent parent; + + @Version + private long version; + + public OmBeanListChild(String name) { + this.name = name; + } + + public Long getId() { + return id; + } +} + + diff --git a/ebean-core/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java b/ebean-core/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java new file mode 100644 index 000000000..6a9cd478f --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java @@ -0,0 +1,40 @@ +package org.tests.model.orphanremoval; + +import io.ebean.Model; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Version; +import java.util.List; + +import static javax.persistence.CascadeType.ALL; + +@Entity +public class OmBeanListParent extends Model { + + @Id + private long id; + + @Version + private long version; + + @OneToMany(cascade = ALL, mappedBy = "parent", orphanRemoval = true) + private List children; + + public long getId() { + return id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + // So a BeanList is used and overwriting children will replace the table entries + this.children.clear(); + this.children.addAll(children); + } +} + + diff --git a/ebean-core/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java b/ebean-core/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java new file mode 100644 index 000000000..9c6fa045b --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java @@ -0,0 +1,37 @@ +package org.tests.model.orphanremoval; + +import org.junit.Test; + +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TestOrphanRemovalOverwrite { + + @Test + public void testOverwritingMapping() { + OmBeanListParent parent = new OmBeanListParent(); + parent.save(); + + // Refreshing/querying sets the modifyListening flag on the association's BeanList + parent.refresh(); + + List childList = singletonList(new OmBeanListChild("child1")); + // Adding the children to the BeanList causes _ebean_getIdentity to be invoked before the children have been persisted and + // have Ids. + parent.setChildren(childList); + + // Give the children Ids + parent.save(); + + // Refreshing here generates new objects for the associated children that are referred to by the parent. + parent.refresh(); + + assertNotNull("The children should now have Ids as they are persisted to the db.", childList.get(0).getId()); + assertEquals("The children should have the same Id as the ones on the parent.", + childList.get(0).getId(), parent.getChildren().get(0).getId()); + assertEquals("The children should therefore equal the children on the parent.", childList, parent.getChildren()); + } +} diff --git a/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java b/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java index d93b5c75f..c7b0edfe9 100644 --- a/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java +++ b/ebean-core/src/test/java/org/tests/profile/ProfileLocationTest.java @@ -7,20 +7,26 @@ import static org.assertj.core.api.Assertions.assertThat; public class ProfileLocationTest { - private static ProfileLocation loc = ProfileLocation.create(12, "foo"); - - private static ProfileLocation loc2 = ProfileLocation.create(); + private static final ProfileLocation loc = ProfileLocation.create(12, "foo"); + private static final ProfileLocation locB = ProfileLocation.create(); + private static final ProfileLocation loc2 = ProfileLocation.create(); private boolean doIt() { + locB.obtain(); // simulate a location moving by line number only return loc.obtain(); } @Test public void test_obtain() { assertThat(doIt()).isTrue(); - assertThat(loc.fullLocation()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:15)"); - assertThat(loc.location()).isEqualTo("ProfileLocationTest.doIt(ProfileLocationTest.java:15)"); + assertThat(loc.fullLocation()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:16)"); + assertThat(loc.location()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt"); assertThat(loc.label()).isEqualTo("ProfileLocationTest.doIt"); + + // same hash even when the line number has changed + assertThat(locB.fullLocation()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:15)"); + assertThat(locB.location()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt"); + assertThat(locB.label()).isEqualTo("ProfileLocationTest.doIt"); } @Test @@ -35,7 +41,7 @@ public class ProfileLocationTest { other.hashCode(); assertThat(loc2.label()).isEqualTo("ProfileLocationTest$Other.init"); - assertThat(loc2.location()).isEqualTo("ProfileLocationTest$Other.(ProfileLocationTest.java:44)"); + assertThat(loc2.location()).isEqualTo("org.tests.profile.ProfileLocationTest$Other."); } static class Other { diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java b/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java index de378586c..4e77d49d6 100644 --- a/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java +++ b/ebean-core/src/test/java/org/tests/query/TestQueryAlias.java @@ -1,7 +1,7 @@ package org.tests.query; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import org.junit.Test; import org.tests.model.basic.CKeyParent; @@ -16,11 +16,11 @@ public class TestQueryAlias extends BaseTestCase { ResetBasicData.reset(); - Query sq = Ebean.createQuery(CKeyParent.class) + Query sq = DB.createQuery(CKeyParent.class) .select("id.oneKey").alias("st0") .setAutoTune(false).where().query(); - Query pq = Ebean.find(CKeyParent.class).alias("myt0").where().in("id.oneKey", sq).query(); + Query pq = DB.find(CKeyParent.class).alias("myt0").where().in("id.oneKey", sq).query(); pq.findList(); @@ -36,17 +36,36 @@ public class TestQueryAlias extends BaseTestCase { assertThat(sql).contains("ckey_parent myt0"); assertThat(sql).contains("(myt0.one_key) in (select st0.one_key from ckey_parent st0)"); } + + @Test + public void testExistsWithConcat() { + + ResetBasicData.reset(); + + Query sq = DB.createQuery(CKeyParent.class) + .select("concat(id.oneKey,id.twoKey)").alias("st0") + .setAutoTune(false).where().query(); + + Query pq = DB.find(CKeyParent.class).alias("myt0").where().in("concat(id.oneKey,id.twoKey)", sq).query(); + + pq.findList(); + + String sql = pq.getGeneratedSql(); + + assertThat(sql).contains("ckey_parent myt0"); + assertThat(sql).contains("(concat(myt0.one_key,myt0.two_key)) in (select concat(st0.one_key,st0.two_key) from ckey_parent st0)"); + } @Test public void testNotExists() { ResetBasicData.reset(); - Query sq = Ebean.createQuery(CKeyParent.class) + Query sq = DB.createQuery(CKeyParent.class) .select("id.oneKey").alias("st0") .setAutoTune(false).where().query(); - Query pq = Ebean.find(CKeyParent.class).alias("myt0").where().notIn("id.oneKey", sq).query(); + Query pq = DB.find(CKeyParent.class).alias("myt0").where().notIn("id.oneKey", sq).query(); pq.findList(); diff --git a/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java b/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java index d3dda7443..9fc3d6b57 100644 --- a/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java +++ b/ebean-core/src/test/java/org/tests/query/cancel/SqlQueryCancelTest.java @@ -30,18 +30,18 @@ import io.ebean.annotation.Platform; * Tests, if all kind of queries are cancelable. There are two ways how to * cancel a query:
* At begin: - * + * *
  * query = DB.find(...)
  * query.cancel();
  * query.findList();
  * 
- * + * * The query was caneled before executing. In this case we do hit the DB driver *
*
* During run: - * + * *
  * // Thread 1:              Thread 2
  * query = DB.find(...)
@@ -50,26 +50,26 @@ import io.ebean.annotation.Platform;
  *     ...finding            query.cancel();
  *      ...JDBC-Exception
  * 
- * + * * The test tries to simulate a slow query by installing the * {@link SlowDownEBasic} 'SELECT' trigger. The trigger can be configured to * wait 3 * timing ms and a second thread will cancel the query in * timing ms. - * + * * in this case, we expect a JDBC exception from the driver.
*
* NOTE:
* H2 checks the cancel flag in org.h2.command.Prepared::setCurrentRowNumber * only every 128th row. So we need at least 128 models and we cannot check * queries like findCount or findOne, because they only return one row. - * + * * @author Roland Praml, FOCONIS AG * */ public class SqlQueryCancelTest extends BaseTestCase { - private int timing = 10; - + private final int timing = 20; + @BeforeClass public static void setupTestData() throws SQLException { for (int i = 0; i < 128; i++) { @@ -98,10 +98,10 @@ public class SqlQueryCancelTest extends BaseTestCase { doCancelSqlDuringRun(q -> q.findEachWhile(e -> true)); } - + @Test public void cancelOrmQueryAtBegin() throws SQLException { - doCancelOrmAtBegin(Query::findCount); + doCancelOrmAtBegin(Query::findCount); doCancelOrmAtBegin(Query::findFutureCount); // We cannot test 'findCount' due H2 restrictions doCancelOrmAtBegin(Query::findFutureIds); @@ -206,7 +206,7 @@ public class SqlQueryCancelTest extends BaseTestCase { .isInstanceOf(PersistenceException.class) .hasMessageContaining("Query was cancelled"); } - + @Test public void cancelSqlDtoQueryAtBegin() throws SQLException { @@ -290,7 +290,7 @@ public class SqlQueryCancelTest extends BaseTestCase { private void doCancelOrmFutureDuringRun(Function, Future> test) throws SQLException, InterruptedException, ExecutionException { Query warmup = DB.find(EBasic.class); test.apply(warmup).get(); - + Query query = DB.find(EBasic.class); executeDelayed(query::cancel); assertThatThrownBy(() -> { @@ -311,18 +311,18 @@ public class SqlQueryCancelTest extends BaseTestCase { .isInstanceOf(PersistenceException.class) .hasMessageContaining("Query was cancelled"); } - + private void doCancelOrmDtoDuringRun(Consumer> test) throws SQLException { DtoQuery warmup = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class); test.accept(warmup); - + DtoQuery query = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class); executeDelayed(query::cancel); assertThatThrownBy(() -> test.accept(query)) .isInstanceOf(PersistenceException.class) .hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class); } - + private void doCancelSqlDtoAtBegin(Consumer> test) throws SQLException { DtoQuery query = DB.findDto(EBasicDto.class, "select id, status from e_basic"); query.cancel(); @@ -334,14 +334,14 @@ public class SqlQueryCancelTest extends BaseTestCase { private void doCancelSqlDtoDuringRun(Consumer> test) throws SQLException { DtoQuery warmup = DB.findDto(EBasicDto.class, "select id, status from e_basic"); test.accept(warmup); - + DtoQuery query = DB.findDto(EBasicDto.class, "select id, status from e_basic"); executeDelayed(query::cancel); assertThatThrownBy(() -> test.accept(query)) .isInstanceOf(PersistenceException.class) .hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class); } - + private void executeDelayed(Runnable r) throws SQLException { // We modify the DB here. Otherwise we may hit an internal H2 cache, if the // same query is performed. Queries from the cache cannot be canceled. diff --git a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java index eeefe42c2..d36e36b51 100644 --- a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java +++ b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java @@ -176,7 +176,7 @@ public class TestCustomerFinder extends BaseTestCase { // change default collect query plan threshold to 200 micros QueryPlanInit init0 = new QueryPlanInit(); init0.setAll(true); - init0.setThresholdMicros(2); + init0.thresholdMicros(2); final List plans = server().getMetaInfoManager().queryPlanInit(init0); assertThat(plans.size()).isGreaterThan(1); @@ -186,7 +186,7 @@ public class TestCustomerFinder extends BaseTestCase { // change query plan threshold to 100 micros QueryPlanInit init = new QueryPlanInit(); init.setAll(true); - init.setThresholdMicros(1); + init.thresholdMicros(1); final List appliedToPlans = server().getMetaInfoManager().queryPlanInit(init); assertThat(appliedToPlans.size()).isGreaterThan(4); @@ -195,30 +195,30 @@ public class TestCustomerFinder extends BaseTestCase { ServerMetrics metrics = DB.getDefault().getMetaInfoManager().collectMetrics(); - List planStats = metrics.getQueryMetrics(); + List planStats = metrics.queryMetrics(); assertThat(planStats.size()).isGreaterThan(4); for (MetaQueryMetric planStat : planStats) { System.out.println(planStat); } - for (MetaTimedMetric txnTimed : metrics.getTimedMetrics()) { + for (MetaTimedMetric txnTimed : metrics.timedMetrics()) { System.out.println(txnTimed); } // obtains db query plans ... QueryPlanRequest request = new QueryPlanRequest(); // collect max 1000 plans (use something more like 10) - request.setMaxCount(1_000); + request.maxCount(1_000); // don't collect any more plans if used 10 secs - request.setMaxTimeMillis(10_000); + request.maxTimeMillis(10_000); List plans0 = server().getMetaInfoManager().queryPlanCollectNow(request); assertThat(plans0).isNotEmpty(); for (MetaQueryPlan plan : plans) { - logger.info("queryplan label:{}, queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}", - plan.getLabel(), plan.getQueryTimeMicros(), plan.getProfileLocation(), - plan.getSql(), plan.getBind(), plan.getPlan()); + logger.info("queryPlan label:{}, queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}", + plan.label(), plan.queryTimeMicros(), plan.profileLocation(), + plan.sql(), plan.bind(), plan.plan()); System.out.println(plan); } @@ -242,9 +242,9 @@ public class TestCustomerFinder extends BaseTestCase { assertThat(metricsJson).contains("\"name\":\"txn.main\""); assertThat(metricsJson).contains("\"name\":\"orm.Customer.findList\""); - assertThat(metricsJson).contains("\"loc\":\"CustomerFinder.byNameStatus(CustomerFinder.java:44)\""); + assertThat(metricsJson).contains("\"loc\":\"org.tests.model.basic.finder.CustomerFinder.byNameStatus\""); if (isH2() || isPostgres()) { - assertThat(metricsJson).contains("\"hash\":\"cc20eb930403cfd418db2d0475c6e26a\""); + assertThat(metricsJson).contains("\"hash\":\"de3affa5b4bff07e19c1c012590dcde6\""); assertThat(metricsJson).contains("\"sql\":\"select t0.id, t0.status,"); } } @@ -267,7 +267,7 @@ public class TestCustomerFinder extends BaseTestCase { assertThat(metricsJson).contains("\"name\":\"txn.main\""); assertThat(metricsJson).contains("\"name\":\"orm.Customer.findList\""); assertThat(metricsJson).doesNotContain("\"loc\":"); - assertThat(metricsJson).doesNotContain("\"hash\":"); + assertThat(metricsJson).doesNotContain("\"sqlHash\":"); assertThat(metricsJson).doesNotContain("\"sql\":"); } diff --git a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java index 996f08bbe..f16828ec5 100644 --- a/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java +++ b/ebean-core/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java @@ -407,7 +407,7 @@ public class SqlQueryTests extends BaseTestCase { List sqlMetrics = sqlMetrics(); assertThat(sqlMetrics).hasSize(1); - assertThat(sqlMetrics.get(0).getName()).isEqualTo("sql.query.findEach-Max10Rows"); + assertThat(sqlMetrics.get(0).name()).isEqualTo("sql.query.findEach-Max10Rows"); } @Test diff --git a/ebean-core/src/test/java/org/tests/rawsql/nativesql/TestNativeWithEmbedded.java b/ebean-core/src/test/java/org/tests/rawsql/nativesql/TestNativeWithEmbedded.java index b6e4287a0..39f63c31b 100644 --- a/ebean-core/src/test/java/org/tests/rawsql/nativesql/TestNativeWithEmbedded.java +++ b/ebean-core/src/test/java/org/tests/rawsql/nativesql/TestNativeWithEmbedded.java @@ -1,14 +1,17 @@ package org.tests.rawsql.nativesql; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import org.tests.model.embedded.EAddress; import org.tests.model.embedded.EPerson; +import org.tests.model.json.PlainBean; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -17,25 +20,31 @@ public class TestNativeWithEmbedded extends BaseTestCase { @Test public void test() { + Map rawMap = new LinkedHashMap<>(); + rawMap.put("a","1"); EPerson person = new EPerson(); person.setName("Frank"); EAddress address = new EAddress(); address.setStreet("1 foo st"); address.setCity("barv"); + address.setJbean(new PlainBean("hi", 3)); + address.setJraw(rawMap); person.setAddress(address); - Ebean.save(person); + DB.save(person); - String sql = "select id, name, street, suburb, addr_city, addr_status from eperson where id = ?"; + String sql = "select id, name, street, suburb, addr_city, addr_status, addr_jbean, jraw from eperson where id = ?"; LoggedSqlCollector.start(); - Query query = Ebean.findNative(EPerson.class, sql); + Query query = DB.findNative(EPerson.class, sql); query.setParameter(person.getId()); EPerson one = query.findOne(); assertThat(one.getName()).isEqualTo("Frank"); assertThat(one.getAddress().getStreet()).isEqualTo("1 foo st"); + assertThat(one.getAddress().getJbean().getName()).isEqualTo("hi"); + assertThat(one.getAddress().getJraw().get("a")).isEqualTo("1"); List loggedSql = LoggedSqlCollector.stop(); assertThat(loggedSql).hasSize(1); diff --git a/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteBasic.java b/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteBasic.java index 1dfec06c4..3364417b9 100644 --- a/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteBasic.java +++ b/ebean-core/src/test/java/org/tests/softdelete/TestSoftDeleteBasic.java @@ -58,6 +58,52 @@ public class TestSoftDeleteBasic extends BaseTestCase { } + @Test + public void findSingleAttribute() { + + EBasicSoftDelete bean = new EBasicSoftDelete(); + bean.setName("findSingleAttribute"); + DB.save(bean); + + LoggedSqlCollector.start(); + + final String name0 = DB.find(EBasicSoftDelete.class) + .select("name") + .where().eq("name", "findSingleAttribute") + .findSingleAttribute(); + + List sql0 = LoggedSqlCollector.current(); + assertThat(sql0.get(0)).contains("where t0.name = ? and t0.deleted ="); + assertThat(name0).isEqualTo("findSingleAttribute"); + + // now soft delete the bean + DB.delete(bean); + List sqlUpdate = LoggedSqlCollector.current(); + assertThat(sqlUpdate.get(0)).contains("update ebasic_sdchild set"); + + // use setIncludeSoftDeletes + final String name1 = DB.find(EBasicSoftDelete.class) + .select("name") + .where().eq("name", "findSingleAttribute") + .setIncludeSoftDeletes() + .findSingleAttribute(); + + List sql1 = LoggedSqlCollector.current(); + assertThat(sql1.get(0)).doesNotContain(" and t0.deleted ="); + assertThat(name1).isEqualTo("findSingleAttribute"); + + + // not using setIncludeSoftDeletes, so don't find it + final String name2 = DB.find(EBasicSoftDelete.class) + .select("name") + .where().eq("name", "findSingleAttribute") + .findSingleAttribute(); + + List sql2 = LoggedSqlCollector.stop(); + assertThat(sql2.get(0)).contains(" and t0.deleted ="); + assertThat(name2).isNull(); + } + @Test public void testFindIdsWhenIncludeSoftDeletedChlld() { diff --git a/ebean-core/src/test/java/org/tests/transaction/TestNestedMandatory.java b/ebean-core/src/test/java/org/tests/transaction/TestNestedMandatory.java index 91eb4e14f..de3184595 100644 --- a/ebean-core/src/test/java/org/tests/transaction/TestNestedMandatory.java +++ b/ebean-core/src/test/java/org/tests/transaction/TestNestedMandatory.java @@ -33,7 +33,7 @@ public class TestNestedMandatory extends BaseTestCase { } assertThat(txnMetrics).hasSize(2); - assertThat(txnMetrics.get(1).getName()).isEqualTo("txn.named.outer"); + assertThat(txnMetrics.get(1).name()).isEqualTo("txn.named.outer"); } class Outer { diff --git a/ebean-core/src/test/java/org/tests/transaction/TestTransactionalReadOnly.java b/ebean-core/src/test/java/org/tests/transaction/TestTransactionalReadOnly.java index 99f776f14..28e0be699 100644 --- a/ebean-core/src/test/java/org/tests/transaction/TestTransactionalReadOnly.java +++ b/ebean-core/src/test/java/org/tests/transaction/TestTransactionalReadOnly.java @@ -20,9 +20,9 @@ public class TestTransactionalReadOnly extends BaseTestCase { resetAllMetrics(); executeTransactionalUsingReadOnlyDataSource(); - final List timedMetrics = collectMetrics().getTimedMetrics(); + final List timedMetrics = collectMetrics().timedMetrics(); final Optional txnReadOnly = metric(timedMetrics, "txn.readonly"); - assertThat(txnReadOnly.get().getCount()).isEqualTo(1); + assertThat(txnReadOnly.get().count()).isEqualTo(1); assertThat(metric(timedMetrics, "txn")).isEmpty(); } @@ -32,15 +32,15 @@ public class TestTransactionalReadOnly extends BaseTestCase { resetAllMetrics(); executeTransactionalUsingMainDataSource(); - final List timedMetrics = collectMetrics().getTimedMetrics(); + final List timedMetrics = collectMetrics().timedMetrics(); final Optional txnMain = metric(timedMetrics, "txn.main"); - assertThat(txnMain.get().getCount()).isEqualTo(1); + assertThat(txnMain.get().count()).isEqualTo(1); assertThat(metric(timedMetrics, "txn.readonly")).isEmpty(); } private Optional metric(List timedMetrics, String name) { return timedMetrics.stream() - .filter(metaTimedMetric -> metaTimedMetric.getName().equals(name)) + .filter(metaTimedMetric -> metaTimedMetric.name().equals(name)) .findFirst(); } diff --git a/ebean-core/src/test/java/org/tests/update/TestSqlUpdateInTxn.java b/ebean-core/src/test/java/org/tests/update/TestSqlUpdateInTxn.java index 50ca87970..c539beda8 100644 --- a/ebean-core/src/test/java/org/tests/update/TestSqlUpdateInTxn.java +++ b/ebean-core/src/test/java/org/tests/update/TestSqlUpdateInTxn.java @@ -126,8 +126,8 @@ public class TestSqlUpdateInTxn extends BaseTestCase { List sqlMetrics = sqlMetrics(); assertThat(sqlMetrics).hasSize(1); - assertThat(sqlMetrics.get(0).getName()).isEqualTo("sql.update.auditLargeUpdate"); - assertThat(sqlMetrics.get(0).getCount()).isEqualTo(1); + assertThat(sqlMetrics.get(0).name()).isEqualTo("sql.update.auditLargeUpdate"); + assertThat(sqlMetrics.get(0).count()).isEqualTo(1); } @Test diff --git a/ebean-core/src/test/resources/ebean.mf b/ebean-core/src/test/resources/ebean.mf index c18791604..fc2fd6902 100644 --- a/ebean-core/src/test/resources/ebean.mf +++ b/ebean-core/src/test/resources/ebean.mf @@ -3,4 +3,5 @@ profile-location: true entity-packages: org,misc transactional-packages: org querybean-packages: none +synthetic: true diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 2c7df7a5d..f83a8b02e 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean ddl generation @@ -22,20 +22,20 @@ io.ebean ebean-migration - 12.4.0 + 12.11.0 io.ebean ebean-core-type - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT provided io.ebean ebean-core - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT provided @@ -76,7 +76,7 @@ io.ebean ebean-maven-plugin - 12.9.1 + 12.10.0 test diff --git a/ebean-ddl-generator/src/main/java/io/ebean/dbmigration/DbMigration.java b/ebean-ddl-generator/src/main/java/io/ebean/dbmigration/DbMigration.java index 96b8aa16e..ec6db4597 100644 --- a/ebean-ddl-generator/src/main/java/io/ebean/dbmigration/DbMigration.java +++ b/ebean-ddl-generator/src/main/java/io/ebean/dbmigration/DbMigration.java @@ -16,7 +16,6 @@ import java.util.ServiceLoader; *

* Typically this is run as a main method in src/test once a developer is happy * with the next set of changes to the model. - *

* *

Example: Run for a single specific platform

* @@ -39,11 +38,9 @@ import java.util.ServiceLoader; * are no longer being used by the application. These changes are called * "pending drops" and we must explicitly specify to include these in * a generated migration. - *

*

* Use setGeneratePendingDrop() to specify a prior migration * that has drop column changes that we want to generate a migration for. - *

* *

Example: Generate for pending drops

* @@ -67,7 +64,6 @@ public interface DbMigration { * Create a DbMigration implementation to use. */ static DbMigration create() { - Iterator loader = ServiceLoader.load(DbMigration.class).iterator(); if (loader.hasNext()) { return loader.next(); @@ -76,7 +72,7 @@ public interface DbMigration { } /** - * Set to false to suppress logging to System out. + * Set logging to System out (defaults to true). */ void setLogToSystemOut(boolean logToSystemOut); @@ -105,13 +101,12 @@ public interface DbMigration { void setMigrationPath(String migrationPath); /** - * Set the server to use to determine the current model. - * Typically this is not called explicitly. + * Set the server to use to determine the current model. Usually this is not called explicitly. */ void setServer(Database database); /** - * Set the DatabaseConfig to use. Typically this is not called explicitly. + * Set the DatabaseConfig to use. Usually this is not called explicitly. */ void setServerConfig(DatabaseConfig config); @@ -119,7 +114,6 @@ public interface DbMigration { * Set the specific platform to generate DDL for. *

* If not set this defaults to the platform of the default database. - *

*/ void setPlatform(Platform platform); @@ -127,20 +121,26 @@ public interface DbMigration { * Set the specific platform to generate DDL for. *

* If not set this defaults to the platform of the default database. - *

*/ void setPlatform(DatabasePlatform databasePlatform); /** - * Set to false to turn off strict mode. + * Set to false in order to turn off strict mode. *

* Strict mode checks that a column changed to non-null on an existing table via DB migration has a default * value specified. Set this to false if that isn't the case but it is known that all the existing rows have * a value specified (there are no existing null values for the column). - *

*/ void setStrictMode(boolean strictMode); + /** + * Set to include generation of the index migration file. + *

+ * When true this generates a {@code idx_.migrations} file. This can be used by the migration + * runner to improve performance of running migrations, especially when no migration changes have occurred. + */ + void setIncludeIndex(boolean generateIndexFile); + /** * Set to true to include a generated header comment in the DDL script. */ @@ -182,7 +182,6 @@ public interface DbMigration { * Set to true if ALTER TABLE ADD FOREIGN KEY should be generated with an option to skip validation. *

* Currently this is only useful for Postgres DDL adding the NOT VALID option. - *

*/ void setAddForeignKeySkipCheck(boolean addForeignKeySkipCheck); @@ -191,24 +190,26 @@ public interface DbMigration { *

* Currently this is only useful for Postgres migrations adding a set lock_timeout * statement to the generated database migration. - *

*/ void setLockTimeout(int seconds); /** - * Add an additional platform to write the migration DDL. + * Add a platform to write the migration DDL. *

* Use this when you want to generate sql scripts for multiple database platforms * from the migration (e.g. generate migration sql for MySql, Postgres and Oracle). - *

+ */ + void addPlatform(Platform platform); + + /** + * Add a platform to write with a given prefix. */ void addPlatform(Platform platform, String prefix); /** - * Add an additional databasePlatform to write the migration DDL. + * Add a databasePlatform to write the migration DDL. *

* Use this when you want to add preconfigured database platforms. - *

*/ void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix); @@ -256,9 +257,9 @@ public interface DbMigration { * * migration.setPathToResources("src/main/resources"); * - * migration.addPlatform(Platform.POSTGRES, "pg"); - * migration.addPlatform(Platform.MYSQL, "mysql"); - * migration.addPlatform(Platform.ORACLE, "oracle"); + * migration.addPlatform(Platform.POSTGRES); + * migration.addPlatform(Platform.MYSQL); + * migration.addPlatform(Platform.ORACLE); * * migration.generateMigration(); * diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java index 1c790c607..5bb40fa08 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java @@ -2,7 +2,6 @@ package io.ebeaninternal.dbmigration; import io.ebean.DB; import io.ebean.Database; -import io.ebean.EbeanServer; import io.ebean.annotation.Platform; import io.ebean.config.DatabaseConfig; import io.ebean.config.DbConstraintNaming; @@ -79,20 +78,12 @@ import static io.ebeaninternal.api.PlatformMatch.matchPlatform; public class DefaultDbMigration implements DbMigration { protected static final Logger logger = LoggerFactory.getLogger("io.ebean.GenerateMigration"); - private static final String initialVersion = "1.0"; - private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY"; - private boolean logToSystemOut = true; - - /** - * Set to true if DefaultDbMigration run with online EbeanServer instance. - */ protected final boolean online; - + private boolean logToSystemOut = true; protected SpiEbeanServer server; - protected String pathToResources = "src/main/resources"; protected String migrationPath = "dbmigration"; @@ -101,15 +92,10 @@ public class DefaultDbMigration implements DbMigration { protected String modelSuffix = ".model.xml"; protected DatabasePlatform databasePlatform; - private boolean vanillaPlatform; - protected List platforms = new ArrayList<>(); - protected DatabaseConfig databaseConfig; - protected DbConstraintNaming constraintNaming; - protected Boolean strictMode; protected Boolean includeGeneratedFileComment; protected String header; @@ -119,8 +105,8 @@ public class DefaultDbMigration implements DbMigration { protected String generatePendingDrop; private boolean addForeignKeySkipCheck; private int lockTimeoutSeconds; - protected boolean includeBuiltInPartitioning = true; + protected boolean includeIndex; /** * Create for offline migration generation. @@ -129,19 +115,6 @@ public class DefaultDbMigration implements DbMigration { this.online = false; } - /** - * Create using online EbeanServer. - */ - public DefaultDbMigration(EbeanServer server) { - this.online = true; - setServer(server); - } - - /** - * Set the path from the current working directory to the application resources. - *

- * This defaults to maven style 'src/main/resources'. - */ @Override public void setPathToResources(String pathToResources) { this.pathToResources = pathToResources; @@ -152,19 +125,12 @@ public class DefaultDbMigration implements DbMigration { this.migrationPath = migrationPath; } - /** - * Set the server to use to determine the current model. - * Typically this is not called explicitly. - */ @Override public void setServer(Database database) { this.server = (SpiEbeanServer) database; setServerConfig(server.getServerConfig()); } - /** - * Set the DatabaseConfig to use. Typically this is not called explicitly. - */ @Override public void setServerConfig(DatabaseConfig config) { if (this.databaseConfig == null) { @@ -173,7 +139,6 @@ public class DefaultDbMigration implements DbMigration { if (constraintNaming == null) { this.constraintNaming = databaseConfig.getConstraintNaming(); } - Properties properties = config.getProperties(); if (properties != null) { PropertiesWrapper props = new PropertiesWrapper("ebean", config.getName(), properties, null); @@ -218,6 +183,11 @@ public class DefaultDbMigration implements DbMigration { this.generatePendingDrop = generatePendingDrop; } + @Override + public void setIncludeIndex(boolean includeIndex) { + this.includeIndex = includeIndex; + } + @Override public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) { this.includeGeneratedFileComment = includeGeneratedFileComment; @@ -242,7 +212,7 @@ public class DefaultDbMigration implements DbMigration { @Override public void setPlatform(Platform platform) { vanillaPlatform = true; - setPlatform(getPlatform(platform)); + setPlatform(platform(platform)); } /** @@ -259,16 +229,15 @@ public class DefaultDbMigration implements DbMigration { } } - /** - * Add an additional platform to write the migration DDL. - *

- * Use this when you want to generate sql scripts for multiple database platforms - * from the migration (e.g. generate migration sql for MySql, Postgres and Oracle). - *

- */ + @Override + public void addPlatform(Platform platform) { + String prefix = platform.base().name().toLowerCase(); + addPlatform(platform, prefix); + } + @Override public void addPlatform(Platform platform, String prefix) { - platforms.add(new Pair(getPlatform(platform), prefix)); + platforms.add(new Pair(platform(platform), prefix)); } @Override @@ -286,7 +255,7 @@ public class DefaultDbMigration implements DbMigration { * * DbMigration migration = DbMigration.create(); * migration.setPathToResources("src/main/resources"); - * migration.setPlatform(DbPlatformName.ORACLE); + * migration.setPlatform(Platform.ORACLE); * * migration.generateMigration(); * @@ -298,9 +267,9 @@ public class DefaultDbMigration implements DbMigration { * DbMigration migration = DbMigration.create(); * migration.setPathToResources("src/main/resources"); * - * migration.addPlatform(DbPlatformName.POSTGRES, "pg"); - * migration.addPlatform(DbPlatformName.MYSQL, "mysql"); - * migration.addPlatform(DbPlatformName.ORACLE, "mysql"); + * migration.addPlatform(Platform.POSTGRES); + * migration.addPlatform(Platform.MYSQL); + * migration.addPlatform(Platform.ORACLE); * * migration.generateMigration(); * @@ -310,7 +279,25 @@ public class DefaultDbMigration implements DbMigration { */ @Override public String generateMigration() throws IOException { - return generateMigrationFor(false); + final String version = generateMigrationFor(false); + if (includeIndex) { + generateIndex(); + } + return version; + } + + /** + * Generate the {@code idx_platform.migrations} file. + */ + private void generateIndex() throws IOException { + final File topDir = migrationDirectory(false); + if (!platforms.isEmpty()) { + for (Pair pair : platforms) { + new IndexMigration(topDir, pair).generate(); + } + } else { + new IndexMigration(topDir, databasePlatform).generate(); + } } @Override @@ -318,10 +305,9 @@ public class DefaultDbMigration implements DbMigration { return generateMigrationFor(true); } - private String generateMigrationFor(boolean dbinitMigration) throws IOException { - - // use this flag to stop other plugins like full DDL generation + private String generateMigrationFor(boolean initMigration) throws IOException { if (!online) { + // use this flag to stop other plugins like full DDL generation DbOffline.setGenerateMigration(); if (databasePlatform == null && !platforms.isEmpty()) { // for multiple platform generation the first platform @@ -334,8 +320,8 @@ public class DefaultDbMigration implements DbMigration { configurePlatforms(); } try { - Request request = createRequest(dbinitMigration); - if (!dbinitMigration) { + Request request = createRequest(initMigration); + if (!initMigration) { // repeatable migrations if (platforms.isEmpty()) { generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning()); @@ -404,7 +390,6 @@ public class DefaultDbMigration implements DbMigration { *

*/ private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException { - if (dbPlatform != null) { if (tablePartitioning && includeBuiltInPartitioning) { generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning()); @@ -429,10 +414,8 @@ public class DefaultDbMigration implements DbMigration { * Write (or override) the "repeatable" migration script. */ private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException { - String fullName = repeatableMigrationName(script.isInit(), script.getName()); logger.debug("writing repeatable script {}", fullName); - File file = new File(migrationDir, fullName); try (FileWriter writer = new FileWriter(file)) { writer.write(script.getValue()); @@ -480,12 +463,10 @@ public class DefaultDbMigration implements DbMigration { * Generate the diff migration. */ private String generateDiff(Request request) throws IOException { - List pendingDrops = request.getPendingDrops(); if (!pendingDrops.isEmpty()) { logInfo("Pending un-applied drops in versions %s", pendingDrops); } - Migration migration = request.createDiffMigration(); if (migration == null) { logInfo("no changes detected - no migration written", null); @@ -500,11 +481,8 @@ public class DefaultDbMigration implements DbMigration { * Generate the migration based on the pendingDrops from a prior version. */ private String generatePendingDrop(Request request, String pendingVersion) throws IOException { - Migration migration = request.migrationForPendingDrop(pendingVersion); - String version = generateMigration(request, migration, pendingVersion); - List pendingDrops = request.getPendingDrops(); if (!pendingDrops.isEmpty()) { logInfo("... remaining pending un-applied drops in versions %s", pendingDrops); @@ -512,31 +490,31 @@ public class DefaultDbMigration implements DbMigration { return version; } - private Request createRequest(boolean dbinitMigration) { - return new Request(dbinitMigration); + private Request createRequest(boolean initMigration) { + return new Request(initMigration); } private class Request { - final boolean dbinitMigration; + final boolean initMigration; final File migrationDir; final File modelDir; final CurrentModel currentModel; final ModelContainer migrated; final ModelContainer current; - private Request(boolean dbinitMigration) { - this.dbinitMigration = dbinitMigration; + private Request(boolean initMigration) { + this.initMigration = initMigration; this.currentModel = new CurrentModel(server, constraintNaming); this.current = currentModel.read(); - this.migrationDir = getMigrationDirectory(dbinitMigration); - if (dbinitMigration) { + this.migrationDir = migrationDirectory(initMigration); + if (initMigration) { this.modelDir = null; this.migrated = new ModelContainer(); } else { - this.modelDir = getModelDirectory(migrationDir); + this.modelDir = modelDirectory(migrationDir); MigrationModel migrationModel = new MigrationModel(modelDir, modelSuffix); - this.migrated = migrationModel.read(dbinitMigration); + this.migrated = migrationModel.read(false); } } @@ -549,18 +527,16 @@ public class DefaultDbMigration implements DbMigration { */ String nextVersion() { // always read the next version using the main migration directory (not dbinit) - File migDirectory = getMigrationDirectory(false); - File modelDir = getModelDirectory(migDirectory); - return LastMigration.nextVersion(migDirectory, modelDir, dbinitMigration); + File migDirectory = migrationDirectory(false); + File modelDir = modelDirectory(migDirectory); + return LastMigration.nextVersion(migDirectory, modelDir, initMigration); } /** * Return the migration for the pending drops for a given version. */ Migration migrationForPendingDrop(String pendingVersion) { - Migration migration = migrated.migrationForPendingDrop(pendingVersion); - // register any remaining pending drops migrated.registerPendingHistoryDropColumns(current); return migration; @@ -584,11 +560,9 @@ public class DefaultDbMigration implements DbMigration { } private String generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException { - - String fullVersion = getFullVersion(request.nextVersion(), dropsFor); - + String fullVersion = fullVersion(request.nextVersion(), dropsFor); logInfo("generating migration:%s", fullVersion); - if (!request.dbinitMigration && !writeMigrationXml(dbMigration, request.modelDir, fullVersion)) { + if (!request.initMigration && !writeMigrationXml(dbMigration, request.modelDir, fullVersion)) { logError("migration already exists, not generating DDL"); return null; } else { @@ -623,15 +597,14 @@ public class DefaultDbMigration implements DbMigration { *

* The full version can contain a comment suffix after a "__" double underscore. */ - private String getFullVersion(String nextVersion, String dropsFor) { - - String version = getVersion(); + private String fullVersion(String nextVersion, String dropsFor) { + String version = version(); if (version == null) { version = (nextVersion != null) ? nextVersion : initialVersion; } String fullVersion = applyPrefix + version; - String name = getName(); + String name = name(); if (name != null) { fullVersion += "__" + toUnderScore(name); @@ -726,7 +699,7 @@ public class DefaultDbMigration implements DbMigration { * FlywayDb so each developer sets a unique version so that the migration script * generated is unique (typically just prior to being submitted as a merge request). */ - private String getVersion() { + private String version() { String envVersion = readEnvironment("ddl.migration.version"); if (!isEmpty(envVersion)) { return envVersion.trim(); @@ -746,7 +719,7 @@ public class DefaultDbMigration implements DbMigration { * is a short description of the feature. *

*/ - private String getName() { + private String name() { String envName = readEnvironment("ddl.migration.name"); if (!isEmpty(envName)) { return envName.trim(); @@ -775,24 +748,22 @@ public class DefaultDbMigration implements DbMigration { /** * Return the main migration directory. */ - File getMigrationDirectory() { - return getMigrationDirectory(false); + File migrationDirectory() { + return migrationDirectory(false); } /** * Return the file path to write the xml and sql to. */ - File getMigrationDirectory(boolean dbinitMigration) { - + File migrationDirectory(boolean initMigration) { // path to src/main/resources in typical maven project File resourceRootDir = new File(pathToResources); if (!resourceRootDir.exists()) { String msg = String.format("Error - path to resources %s does not exist. Absolute path is %s", pathToResources, resourceRootDir.getAbsolutePath()); throw new UnknownResourcePathException(msg); } - String resourcePath = getMigrationPath(dbinitMigration); - - // expect to be a path to something like - src/main/resources/dbmigration/model + String resourcePath = migrationPath(initMigration); + // expect to be a path to something like - src/main/resources/dbmigration File path = new File(resourceRootDir, resourcePath); if (!path.exists()) { if (!path.mkdirs()) { @@ -802,14 +773,14 @@ public class DefaultDbMigration implements DbMigration { return path; } - private String getMigrationPath(boolean dbinitMigration) { - return dbinitMigration ? migrationInitPath : migrationPath; + private String migrationPath(boolean initMigration) { + return initMigration ? migrationInitPath : migrationPath; } /** * Return the model directory (relative to the migration directory). */ - private File getModelDirectory(File migrationDirectory) { + private File modelDirectory(File migrationDirectory) { if (modelPath == null || modelPath.isEmpty()) { return migrationDirectory; } @@ -823,7 +794,7 @@ public class DefaultDbMigration implements DbMigration { /** * Return the DatabasePlatform given the platform key. */ - protected DatabasePlatform getPlatform(Platform platform) { + protected DatabasePlatform platform(Platform platform) { switch (platform) { case H2: return new H2Platform(); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/IndexMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/IndexMigration.java new file mode 100644 index 000000000..baedb77b9 --- /dev/null +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/IndexMigration.java @@ -0,0 +1,122 @@ +package io.ebeaninternal.dbmigration; + +import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.migration.MigrationVersion; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +/** + * Generate a migration index file. + *

+ * This is a file that has all the migrations listed in order with checksum of the file content. + */ +class IndexMigration { + + private static final String eol = "\n"; + private final List all = new ArrayList<>(); + private final File topDir; + private final DatabasePlatform databasePlatform; + private final File indexFile; + private final Stack pathStack = new Stack<>(); + + IndexMigration(File topDir, DatabasePlatform databasePlatform) { + this.topDir = topDir; + this.databasePlatform = databasePlatform; + this.indexFile = init(); + } + + IndexMigration(File topDir, DefaultDbMigration.Pair pair) { + this.topDir = new File(topDir, pair.prefix); + this.databasePlatform = pair.platform; + this.indexFile = init(); + } + + File init() { + pathStack.push(""); + String name = "idx_" + databasePlatform.getPlatform().base().name().toLowerCase() + ".migrations"; + return new File(topDir, name); + } + + void generate() throws IOException { + readSqlFiles(topDir); + generateIndex(); + } + + private void generateIndex() throws IOException { + Collections.sort(all); + FileWriter writer = new FileWriter(indexFile); + for (Entry entry : all) { + writeChecksumPadded(writer, entry.checksum); + writer.write(entry.fileName); + writer.write(eol); + } + writer.write(eol); + writer.close(); + } + + private void writeChecksumPadded(FileWriter writer, int checksum) throws IOException { + final String asStr = String.valueOf(checksum); + writer.write(asStr); + writer.write(','); + int max = 15 - asStr.length(); + for (int i = 0; i < max; i++) { + writer.write(' '); + } + } + + private void readSqlFiles(File dir) { + final File[] files = dir.listFiles(); + if (files != null && files.length > 0) { + for (File file : files) { + if (file.isDirectory()) { + readDirectory(file); + } + final String lowerName = file.getName().toLowerCase(); + if (lowerName.endsWith(".sql")) { + addEntry(file); + } + } + } + } + + private void readDirectory(File dir) { + final String current = pathStack.peek(); + pathStack.push(current + dir.getName() + "/"); + readSqlFiles(dir); + pathStack.pop(); + } + + private void addEntry(File sqlFile) { + final String relativePath = pathStack.peek(); + final String fileName = sqlFile.getName(); + final String name = fileName.substring(0, fileName.length() - 4); + final MigrationVersion version = MigrationVersion.parse(name); + final int checksum = MChecksum.calculate(sqlFile); + all.add(new Entry(checksum, version, relativePath + fileName)); + } + + static class Entry implements Comparable { + + private final int checksum; + private final String fileName; + private final MigrationVersion version; + + Entry(int checksum, MigrationVersion version, String fileName) { + this.checksum = checksum; + this.version = version; + this.fileName = fileName; + } + + @Override + public int compareTo(Entry other) { + return version.compareTo(other.version); + } + } + +} diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/LastMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/LastMigration.java index 20b9a19c3..17ac65aa9 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/LastMigration.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/LastMigration.java @@ -18,22 +18,20 @@ class LastMigration { private static final String MODEL_XML = ".model.xml"; /** - * Return the next migation version given the migration directory. + * Return the next migration version given the migration directory. */ - static String nextVersion(File migDir, File modelDir, boolean dbinitMigration) { - + static String nextVersion(File migDir, File modelDir, boolean initMigration) { String last = lastVersion(migDir, modelDir); if (last == null) { return null; } - return (dbinitMigration) ? last : MigrationVersion.parse(last).nextVersion(); + return (initMigration) ? last : MigrationVersion.parse(last).nextVersion(); } /** - * Return the last migation version given the migration directory. + * Return the last migration version given the migration directory. */ static String lastVersion(File migDirectory, File modelDir) { - List versions = new ArrayList<>(); File[] sqlFiles = migDirectory.listFiles(pathname -> includeSqlFile(pathname.getName().toLowerCase())); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/MChecksum.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/MChecksum.java new file mode 100644 index 000000000..9c533992e --- /dev/null +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/MChecksum.java @@ -0,0 +1,29 @@ +package io.ebeaninternal.dbmigration; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.zip.CRC32; + +/** + * Calculates the checksum for the given file content. + */ +class MChecksum { + + /** + * Returns the checksum of the file. Agnostic of encoding and new line character. + */ + static int calculate(File file) { + try { + final CRC32 crc32 = new CRC32(); + BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); + String line; + while ((line = bufferedReader.readLine()) != null) { + final byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8); + crc32.update(lineBytes, 0, lineBytes.length); + } + return (int) crc32.getValue(); + } catch (IOException e) { + throw new UncheckedIOException("Failed to calculate checksum", e); + } + } +} diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/UnknownResourcePathException.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/UnknownResourcePathException.java index 9ecfde157..c8b29872a 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/UnknownResourcePathException.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/UnknownResourcePathException.java @@ -4,7 +4,6 @@ package io.ebeaninternal.dbmigration; * Exception when db migration resource path does not exist. *

* Typically the working directory or pathToResources is incorrect. - *

*/ public class UnknownResourcePathException extends RuntimeException { diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/CurrentModel.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/CurrentModel.java index 11189be94..0ff9a9bc4 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/CurrentModel.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/CurrentModel.java @@ -27,25 +27,17 @@ import static io.ebeaninternal.api.PlatformMatch.matchPlatform; public class CurrentModel { private final SpiEbeanServer server; - private final DatabasePlatform databasePlatform; - private final DbConstraintNaming constraintNaming; - private final boolean platformTypes; - private final boolean jaxbPresent; - private final String ddlHeader; + private final DdlOptions ddlOptions = new DdlOptions(); private ModelContainer model; - private ChangeSet changeSet; - private DdlWrite write; - private DdlOptions ddlOptions = new DdlOptions(); - /** * Construct with a given EbeanServer instance for DDL create all generation, not migration. */ diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MCompoundUniqueConstraint.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MCompoundUniqueConstraint.java index c7c63769d..fbb2e2460 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MCompoundUniqueConstraint.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MCompoundUniqueConstraint.java @@ -20,19 +20,15 @@ import static io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns.s public class MCompoundUniqueConstraint { private final String name; - /** * Flag if true indicates this was specifically created for a OneToOne mapping. */ private final boolean oneToOne; - /** * The columns combined to be unique. */ private final String[] columns; - private final String platforms; - private String[] nullableColumns; /** diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MConfiguration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MConfiguration.java index 43387a5c7..178f35c8d 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MConfiguration.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MConfiguration.java @@ -4,7 +4,7 @@ import io.ebeaninternal.dbmigration.migration.Configuration; import io.ebeaninternal.dbmigration.migration.DefaultTablespace; /** - * Holds configuration such as the default tablespaces to use for tables, + * Holds configuration such as the default tablespace to use for tables, * indexes, history tables etc. */ public class MConfiguration { @@ -32,7 +32,6 @@ public class MConfiguration { *

*/ public void apply(Configuration configuration) { - DefaultTablespace defaultTablespace = configuration.getDefaultTablespace(); if (defaultTablespace != null) { String tables = defaultTablespace.getTables(); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java index 976bde5f3..710241a69 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MIndex.java @@ -13,8 +13,8 @@ import java.util.Objects; */ public class MIndex { - private String tableName; - private String indexName; + private final String tableName; + private final String indexName; private String platforms; private List columns = new ArrayList<>(); private boolean unique; diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTable.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTable.java index 82a687eb7..71faa1c44 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTable.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTable.java @@ -48,67 +48,32 @@ public class MTable { private static final Logger logger = LoggerFactory.getLogger(MTable.class); - /** - * Table name. - */ private final String name; - - /** - * The associated draft table. - */ private MTable draftTable; - /** * Marked true for draft tables. These need to have their FK references adjusted * after all the draft tables have been identified. */ private boolean draft; - private PartitionMeta partitionMeta; - - /** - * Primary key name. - */ private String pkName; - - /** - * Table comment. - */ private String comment; - - /** - * Tablespace to use. - */ private String tablespace; - private String storageEngine; - - /** - * Tablespace to use for indexes on this table. - */ private String indexTablespace; - private IdentityMode identityMode; - - /** - * If set to true this table should has history support. - */ private boolean withHistory; - - /** - * The columns on the table. - */ - private Map columns = new LinkedHashMap<>(); + private final Map columns = new LinkedHashMap<>(); /** * Compound unique constraints. */ - private List uniqueConstraints = new ArrayList<>(); + private final List uniqueConstraints = new ArrayList<>(); /** * Compound foreign keys. */ - private List compoundKeys = new ArrayList<>(); + private final List compoundKeys = new ArrayList<>(); /** * Column name for the 'When created' column. This can be used for the initial effective start date when adding @@ -121,7 +86,7 @@ public class MTable { */ private AddColumn addColumn; - private List droppedColumns = new ArrayList<>(); + private final List droppedColumns = new ArrayList<>(); public MTable(BeanDescriptor descriptor) { this.name = descriptor.getBaseTable(); @@ -153,18 +118,15 @@ public class MTable { * later when creating the CreateTable object. */ public MTable createDraftTable() { - draftTable = new MTable(name + "_draft"); draftTable.draft = true; draftTable.whenCreatedColumn = whenCreatedColumn; // compoundKeys // compoundUniqueConstraints draftTable.identityMode = identityMode; - for (MColumn col : allColumns()) { draftTable.addColumn(col.copyForDraft()); } - return draftTable; } @@ -239,7 +201,6 @@ public class MTable { * Return the CreateTable migration for this table. */ public CreateTable createTable() { - CreateTable createTable = new CreateTable(); createTable.setName(name); createTable.setPkName(pkName); @@ -258,22 +219,18 @@ public class MTable { if (draft) { createTable.setDraft(Boolean.TRUE); } - for (MColumn column : allColumns()) { // filter out draftOnly columns from the base table if (draft || !column.isDraftOnly()) { createTable.getColumn().add(column.createColumn()); } } - for (MCompoundForeignKey compoundKey : compoundKeys) { createTable.getForeignKey().add(compoundKey.createForeignKey()); } - for (MCompoundUniqueConstraint constraint : uniqueConstraints) { createTable.getUniqueConstraint().add(constraint.getUniqueConstraint()); } - return createTable; } @@ -281,7 +238,6 @@ public class MTable { * Compare to another version of the same table to perform a diff. */ public void compare(ModelDiff modelDiff, MTable newTable) { - if (withHistory != newTable.withHistory) { if (withHistory) { DropHistoryTable dropHistoryTable = new DropHistoryTable(); @@ -308,14 +264,12 @@ public class MTable { modelDiff.addTableComment(addTableComment); } - compareCompoundKeys(modelDiff, newTable); compareUniqueKeys(modelDiff, newTable); } private void compareColumns(ModelDiff modelDiff, MTable newTable) { addColumn = null; - Map newColumnMap = newTable.getColumns(); // compare newColumns to existing columns (look for new and diff columns) @@ -374,10 +328,10 @@ public class MTable { currentKeys.removeAll(newTable.getUniqueConstraints()); newKeys.removeAll(getUniqueConstraints()); - for (MCompoundUniqueConstraint currentKey: currentKeys) { + for (MCompoundUniqueConstraint currentKey : currentKeys) { modelDiff.addUniqueConstraint(currentKey.dropUniqueConstraint(name)); } - for (MCompoundUniqueConstraint newKey: newKeys) { + for (MCompoundUniqueConstraint newKey : newKeys) { modelDiff.addUniqueConstraint(newKey.addUniqueConstraint(name)); } } @@ -489,7 +443,6 @@ public class MTable { } public List allHistoryColumns(boolean includeDropped) { - List columnNames = new ArrayList<>(columns.size()); for (MColumn column : columns.values()) { if (column.isIncludeInHistory()) { @@ -595,7 +548,6 @@ public class MTable { * Sometimes the case for a primaryKey that is also a foreign key. */ public MColumn addColumn(String dbCol, String columnDefn, boolean notnull) { - MColumn existingColumn = getColumn(dbCol); if (existingColumn != null) { if (notnull) { @@ -613,7 +565,6 @@ public class MTable { * Add a 'new column' to the AddColumn migration object. */ private void diffNewColumn(MColumn newColumn) { - if (addColumn == null) { addColumn = new AddColumn(); addColumn.setTableName(name); @@ -631,7 +582,6 @@ public class MTable { * Add a 'drop column' to the diff. */ private void diffDropColumn(ModelDiff modelDiff, MColumn existingColumn) { - DropColumn dropColumn = new DropColumn(); dropColumn.setTableName(name); dropColumn.setColumnName(existingColumn.getName()); @@ -640,7 +590,6 @@ public class MTable { // table as well as the base table dropColumn.setWithHistory(Boolean.TRUE); } - modelDiff.addDropColumn(dropColumn); } @@ -661,7 +610,6 @@ public class MTable { *

*/ public void checkDuplicateForeignKeys() { - if (hasDuplicateForeignKeys()) { int counter = 1; for (MCompoundForeignKey fk : compoundKeys) { @@ -687,7 +635,6 @@ public class MTable { * Adjust the references (FK) if it should relate to a draft table. */ public void adjustReferences(ModelContainer modelContainer) { - Collection cols = allColumns(); for (MColumn col : cols) { String references = col.getReferences(); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTableIdentity.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTableIdentity.java index 5e4bb2306..f226ebaec 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTableIdentity.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MTableIdentity.java @@ -17,7 +17,6 @@ public class MTableIdentity { * Return the IdentityMode from CreateTable. */ public static IdentityMode fromCreateTable(CreateTable createTable) { - IdType type = fromType(createTable.getIdentityType()); IdentityGenerated generated = fromGenerated(createTable.getIdentityGenerated()); int start = toInt(createTable.getIdentityStart(), createTable.getSequenceInitial()); @@ -39,7 +38,6 @@ public class MTableIdentity { * Set the IdentityMode to the CreateTable model. */ public static void toCreateTable(IdentityMode identityMode, CreateTable createTable) { - if (!identityMode.isPlatformDefault()) { createTable.setIdentityType(toType(identityMode.getIdType())); } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationModel.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationModel.java index 6fe5ed66b..9c49ad0aa 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationModel.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationModel.java @@ -18,13 +18,9 @@ public class MigrationModel { private static final Logger logger = LoggerFactory.getLogger(MigrationModel.class); private final ModelContainer model = new ModelContainer(); - private final File modelDirectory; - private final String modelSuffix; - private MigrationVersion lastVersion; - public MigrationModel(File modelDirectory, String modelSuffix) { this.modelDirectory = modelDirectory; this.modelSuffix = modelSuffix; @@ -34,16 +30,14 @@ public class MigrationModel { * Read all the migrations returning the model with all * the migrations applied in version order. * - * @param dbinitMigration If true we don't apply model changes, migration is from scratch. + * @param initMigration If true we don't apply model changes, migration is from scratch. */ - public ModelContainer read(boolean dbinitMigration) { - - readMigrations(dbinitMigration); + public ModelContainer read(boolean initMigration) { + readMigrations(initMigration); return model; } - private void readMigrations(boolean dbinitMigration) { - + private void readMigrations(boolean initMigration) { // find all the migration xml files File[] xmlFiles = modelDirectory.listFiles(pathname -> pathname.getName().toLowerCase().endsWith(modelSuffix)); if (xmlFiles == null || xmlFiles.length == 0) { @@ -57,17 +51,12 @@ public class MigrationModel { // sort into version order before applying Collections.sort(resources); - if (!dbinitMigration) { + if (!initMigration) { for (MigrationResource migrationResource : resources) { logger.debug("read {}", migrationResource); - model.apply(migrationResource.read(), migrationResource.getVersion()); + model.apply(migrationResource.read(), migrationResource.version()); } } - - // remember the last version - if (!resources.isEmpty()) { - lastVersion = resources.get(resources.size() - 1).getVersion(); - } } private MigrationVersion createVersion(File xmlFile) { @@ -75,9 +64,4 @@ public class MigrationModel { String versionName = fileName.substring(0, fileName.length() - modelSuffix.length()); return MigrationVersion.parse(versionName); } - - public String getNextVersion(String initialVersion) { - - return lastVersion == null ? initialVersion : lastVersion.nextVersion(); - } } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationResource.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationResource.java index eff454813..ae4950a4e 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationResource.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/MigrationResource.java @@ -31,7 +31,7 @@ public class MigrationResource implements Comparable { /** * Return the version associated with this resource. */ - public MigrationVersion getVersion() { + public MigrationVersion version() { return version; } @@ -39,7 +39,6 @@ public class MigrationResource implements Comparable { * Read and return the migration from the resource. */ public Migration read() { - return MigrationXmlReader.read(migrationFile); } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelContainer.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelContainer.java index 3f75064d2..dc19ef137 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelContainer.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelContainer.java @@ -37,19 +37,16 @@ import java.util.TreeSet; public class ModelContainer { private final Set schemas = new TreeSet<>(); - /** * All the tables in the model. */ private final Map tables = new LinkedHashMap<>(); /** - * All the non unique non foreign key indexes. + * All the non-unique non-foreign key indexes. */ private final Map indexes = new LinkedHashMap<>(); - private final PendingDrops pendingDrops = new PendingDrops(); - private final List partitionedTables = new ArrayList<>(); public ModelContainer() { diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelDiff.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelDiff.java index 2babc7c6c..367c13ca5 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelDiff.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/ModelDiff.java @@ -53,29 +53,24 @@ public class ModelDiff { this.baseModel = new ModelContainer(); } - /** - * Return true if the apply and drop changes are both empty. - * This means there are no migration changes. + * Return true if apply and drop changes are both empty. This means there are no migration changes. */ public boolean isEmpty() { return applyChanges.isEmpty() && dropChanges.isEmpty(); } /** - * Return the diff as a migration potentially containing - * an apply changeSet and a drop changeSet. + * Return the diff as a migration potentially containing an apply changeSet and a drop changeSet. */ public Migration getMigration() { - Migration migration = new Migration(); if (!applyChanges.isEmpty()) { - // add a non empty apply changeSet + // add a non-empty apply changeSet migration.getChangeSet().add(getApplyChangeSet()); } - if (!dropChanges.isEmpty()) { - // add a non empty drop changeSet + // add a non-empty drop changeSet migration.getChangeSet().add(getDropChangeSet()); } return migration; @@ -121,7 +116,6 @@ public class ModelDiff { * Compare to a 'newer' model and collect the differences. */ public void compareTo(ModelContainer newModel) { - Map newTables = newModel.getTables(); for (MTable newTable : newTables.values()) { @@ -179,7 +173,6 @@ public class ModelDiff { * Compare tables looking for add/drop/modify columns etc. */ protected void compareTables(MTable currentTable, MTable newTable) { - currentTable.compare(this, newTable); } @@ -187,7 +180,6 @@ public class ModelDiff { * Compare tables looking for add/drop/modify columns etc. */ protected void compareIndexes(MIndex currentIndex, MIndex newIndex) { - currentIndex.compare(this, newIndex); } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PendingDrops.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PendingDrops.java index 9a0bdd242..bd7e7c96b 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PendingDrops.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PendingDrops.java @@ -24,7 +24,6 @@ public class PendingDrops { * Add a 'pending drops' changeSet for the given version. */ public void add(MigrationVersion version, ChangeSet changeSet) { - Entry entry = map.computeIfAbsent(version.normalised(), k -> new Entry(version)); entry.add(changeSet); } @@ -33,7 +32,6 @@ public class PendingDrops { * Return the list of versions with pending drops. */ public List pendingDrops() { - List versions = new ArrayList<>(); for (Entry value : map.values()) { if (value.hasPendingDrops()) { @@ -48,7 +46,6 @@ public class PendingDrops { * to remove the (unsuppressed) pending drops for this version. */ public boolean appliedDropsFor(ChangeSet changeSet) { - MigrationVersion version = MigrationVersion.parse(changeSet.getDropsFor()); Entry entry = map.get(version.normalised()); @@ -68,7 +65,6 @@ public class PendingDrops { *

*/ public Migration migrationForVersion(String pendingVersion) { - Entry entry = getEntry(pendingVersion); Migration migration = new Migration(); @@ -96,7 +92,6 @@ public class PendingDrops { } private Entry getEntry(String pendingVersion) { - if ("next".equalsIgnoreCase(pendingVersion)) { Iterator it = map.values().iterator(); if (it.hasNext()) { @@ -115,7 +110,6 @@ public class PendingDrops { * Register pending drop columns on history tables to the new model. */ public void registerPendingHistoryDropColumns(ModelContainer newModel) { - for (Entry entry : map.values()) { for (ChangeSet changeSet : entry.list) { newModel.registerPendingHistoryDropColumns(changeSet); @@ -140,7 +134,6 @@ public class PendingDrops { static class Entry { final MigrationVersion version; - final List list = new ArrayList<>(); Entry(MigrationVersion version) { @@ -180,7 +173,6 @@ public class PendingDrops { * removed all the changeSets (and there are no suppressForever ones). */ boolean removeDrops(ChangeSet appliedDrops) { - Iterator iterator = list.iterator(); while (iterator.hasNext()) { ChangeSet next = iterator.next(); @@ -199,20 +191,16 @@ public class PendingDrops { * Remove the applied drops from the pending ones matching by table name and column name. */ private void removeMatchingChanges(ChangeSet pendingDrops, ChangeSet appliedDrops) { - List pending = pendingDrops.getChangeSetChildren(); Iterator iterator = pending.iterator(); while (iterator.hasNext()) { Object pendingDrop = iterator.next(); if (pendingDrop instanceof DropColumn && dropColumnIn((DropColumn) pendingDrop, appliedDrops)) { iterator.remove(); - } else if (pendingDrop instanceof DropTable && dropTableIn((DropTable) pendingDrop, appliedDrops)) { iterator.remove(); - } else if (pendingDrop instanceof DropHistoryTable && dropHistoryTableIn((DropHistoryTable) pendingDrop, appliedDrops)) { iterator.remove(); - } } } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PlatformDdlWriter.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PlatformDdlWriter.java index f56384477..59da934e4 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PlatformDdlWriter.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/PlatformDdlWriter.java @@ -27,9 +27,7 @@ public class PlatformDdlWriter { private static final Logger logger = LoggerFactory.getLogger(PlatformDdlWriter.class); private final DatabaseConfig databaseConfig; - private final PlatformDdl platformDdl; - private final int lockTimeoutSeconds; public PlatformDdlWriter(DatabasePlatform platform, DatabaseConfig dbConfig, int lockTimeoutSeconds) { @@ -42,7 +40,6 @@ public class PlatformDdlWriter { * Write the migration as platform specific ddl. */ public void processMigration(Migration dbMigration, DdlWrite write, File writePath, String fullVersion) throws IOException { - DdlHandler handler = handler(); handler.generateProlog(write); if (lockTimeoutSeconds > 0) { @@ -51,7 +48,6 @@ public class PlatformDdlWriter { write.apply().append(lockSql).endOfStatement().newLine(); } } - List changeSets = dbMigration.getChangeSet(); for (ChangeSet changeSet : changeSets) { if (isApply(changeSet)) { @@ -59,7 +55,6 @@ public class PlatformDdlWriter { } } handler.generateEpilog(write); - writePlatformDdl(write, writePath, fullVersion); } diff --git a/ebean-ddl-generator/src/test/java/io/ebean/BaseTestCase.java b/ebean-ddl-generator/src/test/java/io/ebean/BaseTestCase.java index 3abf51366..4c0410ff3 100644 --- a/ebean-ddl-generator/src/test/java/io/ebean/BaseTestCase.java +++ b/ebean-ddl-generator/src/test/java/io/ebean/BaseTestCase.java @@ -8,9 +8,7 @@ import io.ebean.meta.MetaTimedMetric; import io.ebean.meta.ServerMetrics; import io.ebean.util.StringHelper; import io.ebeaninternal.api.SpiEbeanServer; -import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiTransaction; -import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.expression.platform.DbExpressionHandler; import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory; @@ -88,14 +86,14 @@ public abstract class BaseTestCase { } protected List visitTimedMetrics() { - return collectMetrics().getTimedMetrics(); + return collectMetrics().timedMetrics(); } protected List sqlMetrics() { List timedMetrics = visitTimedMetrics(); return timedMetrics.stream() - .filter((it) -> it.getName().startsWith("sql.") || it.getName().startsWith("orm.")) + .filter((it) -> it.name().startsWith("sql.") || it.name().startsWith("orm.")) .collect(Collectors.toList()); } diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationDropHistoryTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationDropHistoryTest.java index db91e14c9..320406a7c 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationDropHistoryTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationDropHistoryTest.java @@ -54,8 +54,8 @@ public class DbMigrationDropHistoryTest { migration.setServer(server); // First, we clean up the output-directory - assertThat(migration.getMigrationDirectory().getAbsolutePath()).contains("migrationtest-history"); - Files.walk(migration.getMigrationDirectory().toPath()) + assertThat(migration.migrationDirectory().getAbsolutePath()).contains("migrationtest-history"); + Files.walk(migration.migrationDirectory().toPath()) .filter(Files::isRegularFile).map(Path::toFile).forEach(File::delete); // then we generate migration scripts for v1_0 diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationGenerateTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationGenerateTest.java index 1efaacee3..f5bf01a56 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationGenerateTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/DbMigrationGenerateTest.java @@ -27,35 +27,37 @@ public class DbMigrationGenerateTest { private static final Logger logger = LoggerFactory.getLogger(DbMigrationGenerateTest.class); - @Test - public void invokeTest() throws IOException { - main(null); + public static void main(String[] args) throws IOException { + run("ebean-ddl-generator/src/test/resources"); } - public static void main(String[] args) throws IOException { + @Test + public void invokeTest() throws IOException { + run("src/test/resources"); + } - logger.info("start"); + public static void run(String pathToResources) throws IOException { + logger.info("start current directory: " + new File(".").getAbsolutePath()); DefaultDbMigration migration = new DefaultDbMigration(); - + migration.setIncludeIndex(true); // We use src/test/resources as output directory (so we see in GIT if files will change) - - migration.setPathToResources("src/test/resources"); + migration.setPathToResources(pathToResources); migration.setMigrationPath("db/migration"); migration.setMigrationPath(null); // use the default for this test // migration.addPlatform(Platform.GENERIC, "generic"); there is no ddl handler for generic // migration.addPlatform(Platform.SQLANYWHERE, "sqlanywhere"); and sqlanywhere - migration.addPlatform(Platform.DB2, "db2"); - migration.addPlatform(Platform.H2, "h2"); + migration.addPlatform(Platform.DB2); + migration.addPlatform(Platform.H2); migration.addPlatform(Platform.HSQLDB, "hsqldb"); migration.addPlatform(Platform.MYSQL, "mysql"); migration.addPlatform(Platform.MYSQL55, "mysql55"); - migration.addPlatform(Platform.POSTGRES, "postgres"); - migration.addPlatform(Platform.ORACLE, "oracle"); - migration.addPlatform(Platform.SQLITE, "sqlite"); + migration.addPlatform(Platform.POSTGRES); + migration.addPlatform(Platform.ORACLE); + migration.addPlatform(Platform.SQLITE); migration.addPlatform(Platform.SQLSERVER17, "sqlserver17"); - migration.addPlatform(Platform.HANA, "hana"); + migration.addPlatform(Platform.HANA); DatabaseConfig config = new DatabaseConfig(); config.setName("migrationtest"); @@ -70,8 +72,8 @@ public class DbMigrationGenerateTest { migration.setServer(server); // First, we clean up the output-directory - assertThat(migration.getMigrationDirectory().getAbsolutePath()).contains("migrationtest"); - Files.walk(migration.getMigrationDirectory().toPath()) + assertThat(migration.migrationDirectory().getAbsolutePath()).contains("migrationtest"); + Files.walk(migration.migrationDirectory().toPath()) .filter(Files::isRegularFile).map(Path::toFile).forEach(File::delete); // then we generate migration scripts for v1_0 diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/IndexMigrationTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/IndexMigrationTest.java new file mode 100644 index 000000000..a22a42ec7 --- /dev/null +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/IndexMigrationTest.java @@ -0,0 +1,67 @@ +package io.ebeaninternal.dbmigration; + +import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.config.dbplatform.h2.H2Platform; +import io.ebean.config.dbplatform.postgres.PostgresPlatform; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IndexMigrationTest { + + @Test + public void index() throws IOException { + File topDir = new File("src/test/resources/dbmigration/index"); + if (!topDir.exists()) { + throw new IllegalStateException("Not expected - dir does not exist " + topDir.getAbsolutePath()); + } + DatabasePlatform pg = new PostgresPlatform(); + IndexMigration indexMigration = new IndexMigration(topDir, pg); + indexMigration.generate(); + + + File expected = new File(topDir, "idx_postgres.migrations"); + assertThat(expected).exists(); + + final List expectedLines = Arrays.asList( + "-965417868, I__init_1.sql", + "907060870, 1.0__hello.sql", + "-1938594527, 1.1__foo.sql", + "-1960070312, R__view_1.sql"); + + final List lines = Files.readAllLines(expected.toPath(), StandardCharsets.UTF_8); + assertThat(lines).containsAll(expectedLines); + } + + @Test + public void index2_withSubDirectories() throws IOException { + File topDir = new File("src/test/resources/dbmigration/index2"); + if (!topDir.exists()) { + throw new IllegalStateException("Not expected - dir does not exist " + topDir.getAbsolutePath()); + } + DatabasePlatform pg = new H2Platform(); + IndexMigration indexMigration = new IndexMigration(topDir, pg); + indexMigration.generate(); + + File expected = new File(topDir, "idx_h2.migrations"); + assertThat(expected).exists(); + + final List expectedLines = Arrays.asList( + "-965417868, I__init_1.sql", + "-390611389, g1/1.0__a.sql", + "1908338681, g1/1.1__b.sql", + "-1776543936, g2/2.0__a.sql", + "253052666, g2/2.1__2b.sql", + "-1960070312, R__view_1.sql"); + + final List lines = Files.readAllLines(expected.toPath(), StandardCharsets.UTF_8); + assertThat(lines).containsAll(expectedLines); + } +} diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java new file mode 100644 index 000000000..e52c43755 --- /dev/null +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java @@ -0,0 +1,18 @@ +package io.ebeaninternal.dbmigration; + +import org.junit.Test; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MChecksumTest { + + @Test + public void calculate() { + File file = new File("src/test/resources/dbmigration/index/1.0__hello.sql"); + assertThat(file).exists(); + + assertThat(MChecksum.calculate(file)).isEqualTo(907060870); + } +} diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index/1.0__hello.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index/1.0__hello.sql new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index/1.0__hello.sql @@ -0,0 +1 @@ +hello diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index/1.1__foo.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index/1.1__foo.sql new file mode 100644 index 000000000..257cc5642 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index/1.1__foo.sql @@ -0,0 +1 @@ +foo diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index/I__init_1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index/I__init_1.sql new file mode 100644 index 000000000..b1b716105 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index/I__init_1.sql @@ -0,0 +1 @@ +init diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index/R__view_1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index/R__view_1.sql new file mode 100644 index 000000000..0f2416ebf --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index/R__view_1.sql @@ -0,0 +1 @@ +Something diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index/idx_postgres.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/index/idx_postgres.migrations new file mode 100644 index 000000000..2ccea54d7 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index/idx_postgres.migrations @@ -0,0 +1,5 @@ +-965417868, I__init_1.sql +907060870, 1.0__hello.sql +-1938594527, 1.1__foo.sql +-1960070312, R__view_1.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index2/I__init_1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index2/I__init_1.sql new file mode 100644 index 000000000..b1b716105 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index2/I__init_1.sql @@ -0,0 +1 @@ +init diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index2/R__view_1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index2/R__view_1.sql new file mode 100644 index 000000000..0f2416ebf --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index2/R__view_1.sql @@ -0,0 +1 @@ +Something diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index2/g1/1.0__a.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g1/1.0__a.sql new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g1/1.0__a.sql @@ -0,0 +1 @@ +a diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index2/g1/1.1__b.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g1/1.1__b.sql new file mode 100644 index 000000000..617807982 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g1/1.1__b.sql @@ -0,0 +1 @@ +b diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index2/g2/2.0__a.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g2/2.0__a.sql new file mode 100644 index 000000000..94226dabb --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g2/2.0__a.sql @@ -0,0 +1 @@ +2a diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index2/g2/2.1__2b.sql b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g2/2.1__2b.sql new file mode 100644 index 000000000..b8a4cf4af --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index2/g2/2.1__2b.sql @@ -0,0 +1 @@ +2b diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/index2/idx_h2.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/index2/idx_h2.migrations new file mode 100644 index 000000000..ef6e14321 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/index2/idx_h2.migrations @@ -0,0 +1,7 @@ +-965417868, I__init_1.sql +-390611389, g1/1.0__a.sql +1908338681, g1/1.1__b.sql +-1776543936, g2/2.0__a.sql +253052666, g2/2.1__2b.sql +-1960070312, R__view_1.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations new file mode 100644 index 000000000..ec4208068 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations @@ -0,0 +1,6 @@ +441570368, 1.0__initial.sql +-94595879, 1.1.sql +578073685, 1.2__dropsFor_1.1.sql +-509420890, 1.3.sql +-1475628451, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/h2/idx_h2.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/h2/idx_h2.migrations new file mode 100644 index 000000000..fb21ce854 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/h2/idx_h2.migrations @@ -0,0 +1,6 @@ +-745768926, 1.0__initial.sql +39858255, 1.1.sql +1616986842, 1.2__dropsFor_1.1.sql +-1513154593, 1.3.sql +374569329, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/hana/idx_hana.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/hana/idx_hana.migrations new file mode 100644 index 000000000..984c4bb50 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/hana/idx_hana.migrations @@ -0,0 +1,6 @@ +-1536923954, 1.0__initial.sql +1039838314, 1.1.sql +562867593, 1.2__dropsFor_1.1.sql +1566488731, 1.3.sql +1030652294, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/hsqldb/idx_hsqldb.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/hsqldb/idx_hsqldb.migrations new file mode 100644 index 000000000..ee5e2bd41 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/hsqldb/idx_hsqldb.migrations @@ -0,0 +1,6 @@ +2097980375, 1.0__initial.sql +2086418403, 1.1.sql +-1462014216, 1.2__dropsFor_1.1.sql +-2039573992, 1.3.sql +2106151405, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/mysql/idx_mysql.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/mysql/idx_mysql.migrations new file mode 100644 index 000000000..2ce271cdc --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/mysql/idx_mysql.migrations @@ -0,0 +1,6 @@ +1075178692, 1.0__initial.sql +880212944, 1.1.sql +1029390755, 1.2__dropsFor_1.1.sql +-380371830, 1.3.sql +1085680731, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/mysql55/idx_mysql.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/mysql55/idx_mysql.migrations new file mode 100644 index 000000000..9bf86f51e --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/mysql55/idx_mysql.migrations @@ -0,0 +1,6 @@ +-1087663151, 1.0__initial.sql +880212944, 1.1.sql +1029390755, 1.2__dropsFor_1.1.sql +-380371830, 1.3.sql +1085680731, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/idx_oracle.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/idx_oracle.migrations new file mode 100644 index 000000000..533b50b15 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/idx_oracle.migrations @@ -0,0 +1,6 @@ +1164675950, 1.0__initial.sql +-1916315387, 1.1.sql +238598298, 1.2__dropsFor_1.1.sql +483114276, 1.3.sql +1213528478, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/idx_postgres.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/idx_postgres.migrations new file mode 100644 index 000000000..7e74e6b50 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/idx_postgres.migrations @@ -0,0 +1,6 @@ +1329543701, 1.0__initial.sql +-1877647184, 1.1.sql +-1861367028, 1.2__dropsFor_1.1.sql +-1798982281, 1.3.sql +1959776888, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/sqlite/idx_sqlite.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/sqlite/idx_sqlite.migrations new file mode 100644 index 000000000..919841fde --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/sqlite/idx_sqlite.migrations @@ -0,0 +1,6 @@ +1429491518, 1.0__initial.sql +-347121868, 1.1.sql +1359055889, 1.2__dropsFor_1.1.sql +-1764531063, 1.3.sql +-1070218324, 1.4__dropsFor_1.3.sql + diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/sqlserver17/idx_sqlserver.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/sqlserver17/idx_sqlserver.migrations new file mode 100644 index 000000000..f7cd1c04a --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/sqlserver17/idx_sqlserver.migrations @@ -0,0 +1,7 @@ +-2122378240, I__create_procs.sql +-1048913407, 1.0__initial.sql +615613536, 1.1.sql +-1805601919, 1.2__dropsFor_1.1.sql +-1791137342, 1.3.sql +460536923, 1.4__dropsFor_1.3.sql + diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 5eed791a6..e506a6841 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index 76e183c67..d0226a553 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT @@ -33,7 +33,7 @@ io.ebean ebean-externalmapping-api - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT @@ -45,7 +45,7 @@ io.avaje classpath-scanner - 4.2 + 6.0 @@ -59,14 +59,14 @@ io.ebean ebean-core - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT test io.ebean ebean-ddl-generator - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT test diff --git a/ebean-externalmapping-xml/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java b/ebean-externalmapping-xml/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java index 2519ef8f1..8b28975ef 100644 --- a/ebean-externalmapping-xml/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java +++ b/ebean-externalmapping-xml/src/main/java/io/ebeaninternal/xmlmapping/XmlMappingReader.java @@ -6,7 +6,6 @@ import io.ebeaninternal.xmlmapping.model.XmEbean; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -59,7 +58,7 @@ public class XmlMappingReader { try { List mappings = new ArrayList<>(); for (Resource xmlMappingRes : resourceList) { - try (InputStream is = new FileInputStream(xmlMappingRes.getLocationOnDisk())) { + try (InputStream is = xmlMappingRes.inputStream()) { mappings.add(XmlMappingReader.read(is)); } } diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 9a7cb3948..98b44522d 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean postgis @@ -23,7 +23,7 @@ io.ebean ebean-core - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT provided @@ -74,7 +74,7 @@ io.ebean ebean-test - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 3d0600c83..059976afa 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT provided @@ -57,21 +57,21 @@ io.ebean ebean-ddl-generator - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT test io.ebean querybean-generator - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT test io.ebean ebean-test - 12.10.0-SNAPSHOT + 12.11.1-SNAPSHOT test diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java index 728184cf4..9803da8b1 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java @@ -213,20 +213,38 @@ public abstract class TQRootBean { /** * Set a FetchGroup to control what part of the object graph is loaded. *

- * This is an alternative to using select() and fetch() providing a nice clean separation - * between what a query should load and the query predicates. - *

+ * FetchGroup is immutable and threadsafe. We expect to create and store + * FetchGroup to a static final field and reuse the instance. + *

+ * FetchGroup is an alternative to using select() and fetch() providing a nice + * clean separation between what a query should load and the query predicates. * *

{@code
    *
-   * FetchGroup fetchGroup = FetchGroup.of(Customer.class)
-   *   .select("name, status")
-   *   .fetch("contacts", "firstName, lastName, email")
-   *   .build();
+   * // immutable threadsafe
    *
-   * List customers =
+   * static final FetchGroup fetchGroup =
+   *   QCustomer.forFetchGroup()
+   *     .shippingAddress.fetch()
+   *     .contacts.fetch()
+   *     .buildFetchGroup();
    *
-   *   new QCustomer()
+   * List customers = new QCustomer()
+   *   .select(fetchGroup)
+   *   .findList();
+   *
+   * }
+ * + * + *
{@code
+   *
+   * static final FetchGroup fetchGroup =
+   *   FetchGroup.of(Customer.class)
+   *     .select("name, status")
+   *     .fetch("contacts", "firstName, lastName, email")
+   *     .build();
+   *
+   * List customers = new QCustomer()
    *   .select(fetchGroup)
    *   .findList();
    *
diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml
index 13e5695b8..943ab6985 100644
--- a/ebean-redis/pom.xml
+++ b/ebean-redis/pom.xml
@@ -4,7 +4,7 @@
   
     ebean-parent
     io.ebean
-    12.10.0-SNAPSHOT
+    12.11.1-SNAPSHOT
   
 
   ebean-redis
@@ -16,41 +16,41 @@
     
       redis.clients
       jedis
-      3.6.1
+      3.6.3
     
 
     
       io.ebean
       ebean-api
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       provided
     
 
     
       io.ebean
       ebean-core
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       provided
     
 
     
       io.ebean
       ebean-querybean
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       test
     
 
     
       io.ebean
       querybean-generator
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       test
     
 
     
       io.ebean
       ebean-test
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       test
     
 
diff --git a/ebean-redis/src/main/java/io/ebean/redis/RedisCache.java b/ebean-redis/src/main/java/io/ebean/redis/RedisCache.java
index 39e670a7f..a69fb6766 100644
--- a/ebean-redis/src/main/java/io/ebean/redis/RedisCache.java
+++ b/ebean-redis/src/main/java/io/ebean/redis/RedisCache.java
@@ -255,14 +255,13 @@ class RedisCache implements ServerCache {
 
   @Override
   public ServerCacheStatistics getStatistics(boolean reset) {
-
     ServerCacheStatistics cacheStats = new ServerCacheStatistics();
     cacheStats.setCacheName(cacheKey);
     cacheStats.setHitCount(hitCount.get(reset));
     cacheStats.setMissCount(missCount.get(reset));
-    cacheStats.setPutCount(metricPut.collect(reset).getCount());
-    cacheStats.setRemoveCount(metricRemove.collect(reset).getCount());
-    cacheStats.setClearCount(metricClear.collect(reset).getCount());
+    cacheStats.setPutCount(metricPut.collect(reset).count());
+    cacheStats.setRemoveCount(metricRemove.collect(reset).count());
+    cacheStats.setClearCount(metricClear.collect(reset).count());
     return cacheStats;
   }
 }
diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml
index fa0b249da..4b9b05272 100644
--- a/ebean-test/pom.xml
+++ b/ebean-test/pom.xml
@@ -4,7 +4,7 @@
   
     ebean-parent
     io.ebean
-    12.10.0-SNAPSHOT
+    12.11.1-SNAPSHOT
   
 
   ebean test
@@ -29,14 +29,14 @@
     
       io.ebean
       ebean-core
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       provided
     
 
     
       io.ebean
       ebean-ddl-generator
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
     
 
     
diff --git a/ebean/pom.xml b/ebean/pom.xml
index c9c24cb7b..db01d91a1 100644
--- a/ebean/pom.xml
+++ b/ebean/pom.xml
@@ -4,7 +4,7 @@
   
     ebean-parent
     io.ebean
-    12.10.0-SNAPSHOT
+    12.11.1-SNAPSHOT
   
 
   ebean composite
@@ -22,20 +22,20 @@
     
       io.ebean
       ebean-api
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
     
 
     
       io.ebean
       ebean-core
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
     
 
     
     
       io.ebean
       ebean-querybean
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
     
 
   
diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml
index a4b14ff53..1b3f9d848 100644
--- a/kotlin-querybean-generator/pom.xml
+++ b/kotlin-querybean-generator/pom.xml
@@ -4,7 +4,7 @@
   
     ebean-parent
     io.ebean
-    12.10.0-SNAPSHOT
+    12.11.1-SNAPSHOT
   
 
   kotlin querybean generator
@@ -12,7 +12,7 @@
   kotlin-querybean-generator
 
   
-    1.5.0
+    1.5.30-M1
   
 
   
@@ -29,7 +29,7 @@
     
       io.ebean
       ebean-querybean
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       test
     
 
@@ -43,7 +43,7 @@
     
       io.ebean
       ebean-core
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       test
     
 
@@ -51,7 +51,7 @@
       org.jetbrains.kotlin
       kotlin-stdlib-jdk8
       ${kotlin.version}
-      test
+      provided
     
 
     
@@ -64,7 +64,7 @@
     
       io.ebean
       ebean-ddl-generator
-      12.10.0-SNAPSHOT
+      12.11.1-SNAPSHOT
       test
     
 
@@ -111,10 +111,23 @@
               
             
           
+          
+            compile
+            compile
+            
+              compile
+            
+            
+              
+                src/main/java
+                target/generated-sources/kapt/test
+                target/generated-sources/kaptKotlin/test
+              
+            
+          
         
         
           1.8
-
         
       
       
@@ -133,6 +146,13 @@
               testCompile
             
           
+          
+            compile
+            compile
+            
+              compile
+            
+          
         
         
           1.8
@@ -145,7 +165,7 @@
       
         io.ebean
         ebean-maven-plugin
-        12.9.1
+        12.10.0
         
           
             test
diff --git a/pom.xml b/pom.xml
index da28cb2ab..dc3426809 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,12 +4,12 @@
   
     org.avaje
     java8-oss
-    3.1
+    3.2
   
 
   io.ebean
   ebean-parent
-  12.10.0-SNAPSHOT
+  12.11.1-SNAPSHOT
   pom
 
   ebean parent
diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml
index b75980c9e..a240e3b8b 100644
--- a/querybean-generator/pom.xml
+++ b/querybean-generator/pom.xml
@@ -4,7 +4,7 @@
   
     ebean-parent
     io.ebean
-    12.10.0-SNAPSHOT
+    12.11.1-SNAPSHOT
   
 
   querybean generator
diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java
index 78fd99779..3d55fd1ad 100644
--- a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java
+++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java
@@ -188,6 +188,24 @@ class SimpleQueryBeanWriter {
     writer.eol();
     writer.append("  /**").eol();
     writer.append("   * Return a query bean used to build a FetchGroup.").eol();
+    writer.append("   * 

").eol(); + writer.append(" * FetchGroups are immutable and threadsafe and can be used by many").eol(); + writer.append(" * concurrent queries. We typically stored FetchGroup as a static final field.").eol(); + writer.append(" *

").eol(); + writer.append(" * Example creating and using a FetchGroup.").eol(); + writer.append(" *

{@code").eol();
+    writer.append("   * ").eol();
+    writer.append("   * static final FetchGroup fetchGroup = ").eol();
+    writer.append("   *   QCustomer.forFetchGroup()").eol();
+    writer.append("   *     .shippingAddress.fetch()").eol();
+    writer.append("   *     .contacts.fetch()").eol();
+    writer.append("   *     .buildFetchGroup();").eol();
+    writer.append("   * ").eol();
+    writer.append("   * List customers = new QCustomer()").eol();
+    writer.append("   *   .select(fetchGroup)").eol();
+    writer.append("   *   .findList();").eol();
+    writer.append("   * ").eol();
+    writer.append("   * }
").eol(); writer.append(" */").eol(); writer.append(" public static Q%s forFetchGroup() {", shortName).eol(); writer.append(" return new Q%s(FetchGroup.queryFor(%s.class));", shortName, shortName).eol();