Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Roland Praml
2022-07-28 15:25:13 +02:00
73 changed files with 1418 additions and 333 deletions
+29
View File
@@ -1,3 +1,30 @@
## Ebean ORM for Java & Kotlin
**Multiple abstraction levels**: Ebean provides multiple levels of query abstraction [ORM Queries, mixed with SQL](https://ebean.io/docs/intro/queries/orm-query), [DTO Queries](https://ebean.io/docs/intro/queries/dto-query), [SqlQuery and JDBC](https://ebean.io/docs/intro/queries/sql-query).
Work at the highest level of abstraction and drop down levels as needed.
**Database migrations**: Built in [DB migration](https://ebean.io/docs/db-migrations/) generation and running. Support for "rebase" migrations as well as repeatable, init and 'normal' migrations.
**Awesome SQL**: Ebean produces SQL that you would hand craft yourself. Use great SQL, never generate SQL cartesian product, always honor relational limit/offset.
**Automated query tuning**: For ORM queries Ebean can profile the object graph being used and either [automatically tune the query](https://ebean.io/docs/query/background/autotune).
**Docker test containers**: [Docker test containers](https://ebean.io/docs/testing/) for all the supported databases. Get 100% test coverage on all the features of the database we use.
**Type safe queries**: We can build queries using type safe [query beans](https://ebean.io/docs/query/query-beans). IDE auto-complete when writing queries, compile time checking and it's FUN.
**Performance isn't optional**: Optimise queries to only fetch what we need (partial objects). Automatically avoid N+1 via a smart load context.
#### Benefits of ORM
* Automatically avoid N+1
* L2 caching to reduce database load
* Queries mixing database and L2 cache
* Automatically tune ORM queries
* Elasticsearch for search or L3 cache
## Actions
[![Build](https://github.com/ebean-orm/ebean/actions/workflows/build.yml/badge.svg)](https://github.com/ebean-orm/ebean/actions/workflows/build.yml)
[![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ebean-orm/ebean/blob/master/LICENSE)
@@ -15,6 +42,8 @@
#### Builds against EA (Early Access) versions of Java (19, Loom, panama etc)
[![ebean EA](https://github.com/ebean-orm/ebean/actions/workflows/jdk-ea.yml/badge.svg)](https://github.com/ebean-orm/ebean/actions/workflows/jdk-ea.yml)
[![datasource EA](https://github.com/ebean-orm/ebean-datasource/actions/workflows/jdk-ea.yml/badge.svg)](https://github.com/ebean-orm/ebean-datasource/actions/workflows/jdk-ea.yml)
[![migration EA](https://github.com/ebean-orm/ebean-migration/actions/workflows/jdk-ea.yml/badge.svg)](https://github.com/ebean-orm/ebean-migration/actions/workflows/jdk-ea.yml)
+30 -5
View File
@@ -27,7 +27,13 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-config</artifactId>
<version>2.0</version>
<version>2.2</version>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-applog-slf4j</artifactId>
<version>1.0</version>
</dependency>
<!--
@@ -37,25 +43,25 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-lang</artifactId>
<version>1.0</version>
<version>1.1</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>persistence-api</artifactId>
<version>2.2.5</version>
<version>3.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>8.0</version>
<version>8.1</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-types</artifactId>
<version>2.2</version>
<version>3.0</version>
</dependency>
<dependency>
@@ -103,4 +109,23 @@
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/ebean-maven-version.txt</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/ebean-maven-version.txt</exclude>
</excludes>
</resource>
</resources>
</build>
</project>
@@ -3,8 +3,7 @@ package io.ebean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.util.Properties;
/**
@@ -29,9 +28,11 @@ public final class EbeanVersion {
private static void readVersion() {
try {
try (InputStream in = ClassLoader.getSystemResourceAsStream("META-INF/maven/io.ebean/ebean-api/pom.properties")) {
try (InputStream in = ClassLoader.getSystemResourceAsStream("META-INF/ebean-maven-version.txt")) {
if (in != null) {
version = readVersion(in);
try (LineNumberReader reader = new LineNumberReader(new InputStreamReader(in))) {
version = reader.readLine();
}
}
}
log.info("ebean version: {}", version);
@@ -88,7 +89,7 @@ public final class EbeanVersion {
}
/**
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
* Returns the ebean version (read from META-INF/ebean-maven-version.txt)
*/
public static String getVersion() {
return version;
+2 -1
View File
@@ -1579,10 +1579,11 @@ public interface Query<T> extends CancelableQuery {
Query<T> setReadOnly(boolean readOnly);
/**
* Will be deprecated - migrate to use setBeanCacheMode(CacheMode.RECACHE).
* Deprecated - migrate to use setBeanCacheMode(CacheMode.PUT) or other CacheMode.
* <p>
* When set to true all the beans from this query are loaded into the bean cache.
*/
@Deprecated
Query<T> setLoadBeanCache(boolean loadBeanCache);
/**
@@ -6,6 +6,12 @@ import java.io.Serializable;
import java.util.Map;
import java.util.Set;
/**
* This is the object associated to every entity bean using byte code enhancement.
* <p>
* This provides per property state such as loaded state, changed state, original values
* as well as bean level dirty state etc.
*/
public interface EntityBeanIntercept extends Serializable {
/**
@@ -282,8 +288,14 @@ public interface EntityBeanIntercept extends Serializable {
*/
void markPropertyAsChanged(int propertyIndex);
/**
* Set the changed state for the given property.
*/
void setChangedProperty(int propertyIndex);
/**
* Set the changed and loaded state for the given property.
*/
void setChangeLoaded(int propertyIndex);
/**
@@ -291,6 +303,9 @@ public interface EntityBeanIntercept extends Serializable {
*/
void setEmbeddedPropertyDirty(int propertyIndex);
/**
* Set the original value for the property.
*/
void setOriginalValue(int propertyIndex, Object value);
/**
@@ -358,6 +373,9 @@ public interface EntityBeanIntercept extends Serializable {
*/
StringBuilder getLoadedPropertyKey();
/**
* Return the loaded state for all the properties.
*/
boolean[] getLoaded();
/**
@@ -385,6 +403,9 @@ public interface EntityBeanIntercept extends Serializable {
*/
void initialisedMany(int propertyIndex);
/**
* Invoke the PreGetterCallback if it has been set due to getter for the given property.
*/
void preGetterCallback(int propertyIndex);
/**
@@ -402,8 +423,14 @@ public interface EntityBeanIntercept extends Serializable {
*/
void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue);
/**
* Set the property changed state, bean dirtyState and property original value.
*/
void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue);
/**
* Set the dirty state on the bean.
*/
void setDirtyStatus();
/**
@@ -482,6 +509,9 @@ public interface EntityBeanIntercept extends Serializable {
*/
void setDeletedFromCollection(boolean deletedFromCollection);
/**
* Return true if the bean was orphan deleted from a collection.
*/
boolean isOrphanDelete();
/**
@@ -494,7 +524,10 @@ public interface EntityBeanIntercept extends Serializable {
*/
Map<String, Exception> getLoadErrors();
boolean isChangedProp(int i);
/**
* Return true if the property has its changed state set.
*/
boolean isChangedProp(int propertyIndex);
/**
* Return the MutableValueInfo for the given property or null.
@@ -6,10 +6,20 @@ import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* EntityBeanIntercept optimised for read only use.
* <p>
* For the read only use this intercept doesn't need to hold any state that is normally
* required for updates such as per property changed, loaded, dirty state, original values
* bean state etc.
*/
public class InterceptReadOnly implements EntityBeanIntercept {
private final EntityBean owner;
/**
* Create with a given entity.
*/
public InterceptReadOnly(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
}
@@ -21,7 +21,6 @@ import java.util.concurrent.locks.ReentrantLock;
* <p>
* This provides the mechanisms to support deferred fetching of reference beans
* and oldValues generation for concurrency checking.
* </p>
*/
public final class InterceptReadWrite implements EntityBeanIntercept {
@@ -32,24 +31,17 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
private static final int STATE_LOADED = 2;
/**
* Used when a bean is partially filled.
* Used when a bean is partially loaded.
*/
private static final byte FLAG_LOADED_PROP = 1;
private static final byte FLAG_CHANGED_PROP = 2;
private static final byte FLAG_CHANGEDLOADED_PROP = 3;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
* Flags indicating if a property is a dirty embedded bean. Used to distinguish between an
* embedded bean being completely overwritten vs one with embedded properties that are dirty.
*/
private static final byte FLAG_EMBEDDED_DIRTY = 4;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_ORIG_VALUE_SET = 8;
/**
* Flags indicating if the mutable hash is set.
*/
@@ -78,10 +70,9 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
private boolean readOnly;
private boolean dirty;
/**
* Flag set to disable lazy loading - typically for SQL "report" type entity beans.
* Flag set to disable lazy loading.
*/
private boolean disableLazyLoad;
/**
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
*/
@@ -107,7 +98,7 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
private MutableValueNext[] mutableNext;
/**
* Create a intercept with a given entity.
* Create with a given entity.
*/
public InterceptReadWrite(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
@@ -8,22 +8,33 @@ import java.util.List;
*/
public class BasicMetricVisitor extends AbstractMetricVisitor implements ServerMetrics {
private final String name;
private final List<MetaTimedMetric> timed = new ArrayList<>();
private final List<MetaQueryMetric> query = new ArrayList<>();
private final List<MetaCountMetric> count = new ArrayList<>();
public BasicMetricVisitor() {
this("db");
}
/**
* Construct to reset and collect everything.
*/
public BasicMetricVisitor() {
super(true, true, true, true);
public BasicMetricVisitor(String name) {
this(name, true, true, true, true);
}
/**
* Construct specifying reset and what to collect.
*/
public BasicMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics, boolean collectL2Metrics) {
public BasicMetricVisitor(String name, boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics, boolean collectL2Metrics) {
super(reset, collectTransactionMetrics, collectQueryMetrics, collectL2Metrics);
this.name = name;
}
@Override
public String name() {
return name;
}
@Override
@@ -16,6 +16,17 @@ public interface MetaInfoManager {
*/
ServerMetrics collectMetrics();
/**
* Given the already collected metrics provide them in json form.
* <p>
* Expected to be used when we wish to collect the metrics and report them
* to some service and additionally format them as json for say output into
* an application log.
*
* @param metrics The already collected metrics
*/
ServerMetricsAsJson metricsAsJson(ServerMetrics metrics);
/**
* Collect the metrics in raw JSON form.
* <pre>{@code
@@ -7,6 +7,11 @@ import java.util.List;
*/
public interface ServerMetrics {
/**
* Return the name of the database these metrics were obtained for.
*/
String name();
/**
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
*/
@@ -0,0 +1 @@
${project.version}
+1 -1
View File
@@ -70,7 +70,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>
@@ -199,7 +199,7 @@ final class DefaultBeanLoader {
query.setLazyLoadProperty(ebi.getLazyLoadProperty());
if (draft) {
query.asDraft();
} else if (mode == SpiQuery.Mode.LAZYLOAD_BEAN) {
} else if (mode == SpiQuery.Mode.LAZYLOAD_BEAN && desc.isSoftDelete()) {
query.setIncludeSoftDeletes();
}
if (embeddedOwnerIndex > -1) {
@@ -47,6 +47,11 @@ final class DefaultMetaInfoManager implements MetaInfoManager {
return visitBasic();
}
@Override
public ServerMetricsAsJson metricsAsJson(ServerMetrics metrics) {
return new DumpMetricsJson(metrics);
}
@Override
public ServerMetricsAsJson collectMetricsAsJson() {
return new DumpMetricsJson(server);
@@ -59,7 +64,7 @@ final class DefaultMetaInfoManager implements MetaInfoManager {
@Override
public BasicMetricVisitor visitBasic() {
BasicMetricVisitor basic = new BasicMetricVisitor();
BasicMetricVisitor basic = new BasicMetricVisitor(server.name());
visitMetrics(basic);
return basic;
}
@@ -29,17 +29,13 @@ final class DumpMetricsData {
}
private void collect(ServerMetrics serverMetrics) {
final List<MetaTimedMetric> timedMetrics = serverMetrics.timedMetrics();
final List<MetaCountMetric> countMetrics = serverMetrics.countMetrics();
final List<MetaQueryMetric> queryMetrics = serverMetrics.queryMetrics();
for (MetaTimedMetric metric : timedMetrics) {
for (MetaTimedMetric metric : serverMetrics.timedMetrics()) {
add(metric);
}
for (MetaCountMetric metric : countMetrics) {
for (MetaCountMetric metric : serverMetrics.countMetrics()) {
addCount(metric);
}
for (MetaQueryMetric metric : queryMetrics) {
for (MetaQueryMetric metric : serverMetrics.queryMetrics()) {
addQuery(metric);
}
}
@@ -17,9 +17,11 @@ import java.util.List;
final class DumpMetricsJson implements ServerMetricsAsJson {
private final Database database;
private final ServerMetrics metrics;
private final String name;
private Appendable writer;
/**
* By default include sql and location attributes for the initial collection only.
* By default, include sql and location attributes for the initial collection only.
*/
private int includeExtraAttributes = 1;
private boolean withHeader = true;
@@ -31,6 +33,14 @@ final class DumpMetricsJson implements ServerMetricsAsJson {
DumpMetricsJson(Database database) {
this.database = database;
this.name = database.name();
this.metrics = null;
}
DumpMetricsJson(ServerMetrics metrics) {
this.database = null;
this.metrics = metrics;
this.name = metrics.name();
}
@Override
@@ -66,14 +76,18 @@ final class DumpMetricsJson implements ServerMetricsAsJson {
@Override
public String json() {
writer = new StringWriter();
collect(database.metaInfo().collectMetrics());
collect(obtainMetrics());
return writer.toString();
}
@Override
public void write(Appendable buffer) {
writer = buffer;
collect(database.metaInfo().collectMetrics());
collect(obtainMetrics());
}
private ServerMetrics obtainMetrics() {
return metrics != null ? metrics : database.metaInfo().collectMetrics();
}
private void collect(ServerMetrics serverMetrics) {
@@ -112,7 +126,7 @@ final class DumpMetricsJson implements ServerMetricsAsJson {
if (withHeader) {
objStart();
key("db");
val(database.name());
val(name);
key("metrics");
listStart();
}
@@ -6,7 +6,6 @@ import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.core.type.DataReader;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.query.STreePropertyAssocMany;
import java.util.Map;
@@ -70,11 +69,6 @@ public interface DbReadContext {
*/
void register(BeanPropertyAssocMany<?> many, BeanCollection<?> bc);
/**
* Return the property that is associated with the many. There can only be
* one. This can be null.
*/
STreePropertyAssocMany getManyProperty();
/**
* Set back the bean that has just been loaded with its id.
@@ -46,20 +46,29 @@ final class DtoMetaBuilder {
}
static String propertyName(String methodName) {
final String name = methodName.substring(3);
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
if (isTraditionalSetterMethod(methodName)) {
final String name = methodName.substring(3);
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
} else {
// accessor style setter method
return methodName;
}
}
private static boolean isTraditionalSetterMethod(String methodName) {
return methodName.startsWith("set") && methodName.length() > 3 && Character.isUpperCase(methodName.charAt(3));
}
/**
* Include a public "setter" method - 1 argument, returns void.
*/
static boolean includeMethod(Method method) {
String name = method.getName();
final int modifiers = method.getModifiers();
return Modifier.isPublic(modifiers)
&& !Modifier.isStatic(modifiers)
&& Void.TYPE.equals(method.getReturnType())
&& method.getParameterTypes().length == 1
&& method.getName().startsWith("set") && method.getName().length() > 3;
&& (!name.equals("wait") && !name.equals("equals"));
}
private void readConstructors() {
@@ -101,7 +101,7 @@ public final class DLoadContext implements LoadContext {
this.useDocStore = query.isUseDocStore();
this.asOf = query.getAsOf();
this.asDraft = query.isAsDraft();
this.includeSoftDeletes = query.isIncludeSoftDeletes();
this.includeSoftDeletes = query.isIncludeSoftDeletes() && query.getMode() == SpiQuery.Mode.NORMAL;
this.readOnly = query.isReadOnly();
this.disableReadAudit = query.isDisableReadAudit();
this.disableLazyLoading = query.isDisableLazyLoading();
@@ -234,7 +234,7 @@ public final class DLoadContext implements LoadContext {
}
}
protected SpiEbeanServer getEbeanServer() {
SpiEbeanServer getEbeanServer() {
return ebeanServer;
}
@@ -242,7 +242,7 @@ public final class DLoadContext implements LoadContext {
* Return the parent state which defines the sharedInstance and readOnly status
* which needs to be propagated to other beans and collections.
*/
protected Boolean isReadOnly() {
Boolean isReadOnly() {
return readOnly;
}
@@ -283,7 +283,7 @@ public final class DLoadContext implements LoadContext {
DLoadBeanContext getBeanContextWithInherit(String path, BeanPropertyAssocOne<?> property) {
String key = path + ":" + property.targetDescriptor().name();
return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, null));
return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path));
}
private void registerSecondaryNode(boolean many, OrmQueryProperties props) {
@@ -313,8 +313,8 @@ public final class DLoadContext implements LoadContext {
return new DLoadBeanContext(this, p.targetDescriptor(), path, queryProps);
}
private DLoadBeanContext createBeanContext(BeanPropertyAssoc<?> property, String path, OrmQueryProperties queryProps) {
return new DLoadBeanContext(this, property.targetDescriptor(), path, queryProps);
private DLoadBeanContext createBeanContext(BeanPropertyAssoc<?> property, String path) {
return new DLoadBeanContext(this, property.targetDescriptor(), path, null);
}
private BeanProperty getBeanProperty(BeanDescriptor<?> desc, String path) {
@@ -126,7 +126,7 @@ final class SaveManyBeans extends SaveManyBase {
// performance optimisation for large collections
targetDescriptor.preAllocateIds(collection.size());
}
if (!insertedParent && many.isOrphanRemoval() && request.isForcedUpdate()) {
if (forcedUpdateOrphanRemoval()) {
// collect the Id's (to exclude from deleteManyDetails)
List<Object> detailIds = collectIds(collection, targetDescriptor, isMap);
// deleting missing children - children not in our collected detailIds
@@ -140,6 +140,10 @@ final class SaveManyBeans extends SaveManyBase {
transaction.depth(-1);
}
private boolean forcedUpdateOrphanRemoval() {
return !insertedParent && many.isOrphanRemoval() && request.isForcedUpdate();
}
private void saveAllBeans(final BeanProperty orderColumn) {
Object mapKeyValue = null;
boolean skipSavingThisBean;
@@ -340,7 +344,7 @@ final class SaveManyBeans extends SaveManyBase {
return;
}
if (!(value instanceof BeanCollection<?>)) {
if (!insertedParent && cascade && isChangedProperty()) {
if (!forcedUpdateOrphanRemoval() && (!insertedParent && cascade && isChangedProperty())) {
persister.addToFlushQueue(many.deleteByParentId(request.beanId(), null), transaction, 0);
insertAllChildren = true;
}
@@ -605,14 +605,6 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
return logWhereSql;
}
/**
* Return the property that is associated with the many. There can only be one
* per SqlSelect. This can be null.
*/
@Override
public STreePropertyAssocMany getManyProperty() {
return manyProperty;
}
public String getBindLog() {
return bindLog;
@@ -36,6 +36,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
private final SpiQuery.TemporalMode temporalMode;
private final boolean temporalVersions;
final IdBinder lazyLoadParentIdBinder;
private final STreePropertyAssocMany loadingChildProperty;
SqlTreeLoadBean(SqlTreeNodeBean node) {
this.lazyLoadParent = node.lazyLoadParent;
@@ -55,6 +56,16 @@ class SqlTreeLoadBean implements SqlTreeLoad {
this.properties = node.properties;
this.pathMap = node.pathMap;
this.children = node.createLoadChildren();
this.loadingChildProperty = loadingChildProperty();
}
private STreePropertyAssocMany loadingChildProperty() {
for (SqlTreeLoad child : children) {
if (child instanceof SqlTreeLoadManyRoot) {
return ((SqlTreeLoadManyRoot) child).manyProp();
}
}
return null;
}
boolean isRoot() {
@@ -259,6 +270,9 @@ class SqlTreeLoadBean implements SqlTreeLoad {
if (disableLazyLoad) {
// bean does not have an Id or is SqlSelect based
ebi.setDisableLazyLoad(true);
if (!partialObject) {
ebi.setFullyLoadedBean(true);
}
} else if (partialObject) {
if (readId) {
// register for lazy loading
@@ -280,10 +294,9 @@ class SqlTreeLoadBean implements SqlTreeLoad {
* included in the actual query.
*/
private void createListProxies() {
STreePropertyAssocMany fetchedMany = ctx.getManyProperty();
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != fetchedMany) {
if (many != loadingChildProperty) {
if (readOnlyNoIntercept) {
many.createEmptyReference(localBean);
} else {
@@ -352,7 +365,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
* context we need to check if it is already contained in the collection.
*/
final boolean isContextBean() {
return localBean == null;
return localBean == null || queryMode == Mode.LAZYLOAD_BEAN;
}
}
@@ -14,6 +14,10 @@ final class SqlTreeLoadManyRoot extends SqlTreeLoadBean {
this.manyProp = node.manyProp;
}
STreePropertyAssocMany manyProp() {
return manyProp;
}
@Override
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
// pass in null for parentBean because added to a collection rather than set to the parentBean
@@ -1270,7 +1270,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final Query<T> setLoadBeanCache(boolean loadBeanCache) {
this.useBeanCache = CacheMode.PUT;
this.useBeanCache = loadBeanCache ? CacheMode.PUT : CacheMode.OFF;
return this;
}
@@ -13,7 +13,7 @@ final class ScalarTypeTimeZone extends ScalarTypeBaseVarchar<TimeZone> {
@Override
public int getLength() {
return 20;
return 32;
}
@Override
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.dto;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
@@ -12,42 +11,54 @@ import static org.assertj.core.api.Assertions.assertThat;
public class DtoMetaBuilderTest {
@Test
public void includeMethod() {
void includeMethod() {
Map<String, Method> methods = getIncludedMethodsFor(D0.class);
assertThat(methods).hasSize(2);
assertThat(methods.get("setName")).isNotNull();
assertThat(methods.get("setId")).isNotNull();
assertThat(methods).containsKeys("setId", "setName");
}
@Test
public void includeMethod_when_notStrictlySetters() {
void includeMethod_when_notStrictlySetters() {
Map<String, Method> methods = getIncludedMethodsFor(D1.class);
assertThat(methods).hasSize(3);
assertThat(methods.get("setNameThen")).isNotNull();
assertThat(methods.get("setIdFor")).isNotNull();
assertThat(methods.get("setI")).isNotNull();
assertThat(methods).hasSize(5);
assertThat(methods).containsKeys("setNameThen", "setIdFor", "setI", "setA", "set");
}
@Test
public void propertyType() {
void includeMethod_when_fluidAccessors() {
Map<String, Method> methods = getIncludedMethodsFor(D2FluidAccessors.class);
assertThat(methods).hasSize(5);
assertThat(methods).containsKeys("nameThen", "idFor", "a", "i", "set");
}
@Test
void includeMethod_when_plainAccessors() {
Map<String, Method> methods = getIncludedMethodsFor(D2PlainAccessors.class);
assertThat(methods).hasSize(5);
assertThat(methods).containsKeys("nameThen", "idFor", "a", "i", "set");
}
@Test
void propertyType() {
Map<String, Method> methods = getIncludedMethodsFor(D0.class);
assertThat(methods).hasSize(2);
Assertions.assertThat(DtoMetaProperty.propertyClass(methods.get("setName"))).isEqualTo(String.class);
assertThat(DtoMetaProperty.propertyClass(methods.get("setName"))).isEqualTo(String.class);
assertThat(DtoMetaProperty.propertyClass(methods.get("setId"))).isEqualTo(long.class);
assertThat(DtoMetaProperty.propertyType(methods.get("setName"))).isEqualTo(String.class);
assertThat(DtoMetaProperty.propertyType(methods.get("setId"))).isEqualTo(long.class);
}
@Test
public void propertyName() {
Assertions.assertThat(DtoMetaBuilder.propertyName("setName")).isEqualTo("name");
void propertyName() {
assertThat(DtoMetaBuilder.propertyName("setName")).isEqualTo("name");
assertThat(DtoMetaBuilder.propertyName("setId")).isEqualTo("id");
assertThat(DtoMetaBuilder.propertyName("setI")).isEqualTo("i");
assertThat(DtoMetaBuilder.propertyName("setfoo")).isEqualTo("foo");
assertThat(DtoMetaBuilder.propertyName("setFoo")).isEqualTo("foo");
}
@@ -120,4 +131,47 @@ public class DtoMetaBuilderTest {
return this;
}
}
@SuppressWarnings("unused")
static class D2FluidAccessors {
public D2FluidAccessors nameThen(String name) {
return this;
}
public D2FluidAccessors idFor(long id) {
return this;
}
public D2FluidAccessors i(long val) {
return this;
}
public D2FluidAccessors set(long val) {
return this;
}
public D2FluidAccessors a(long val) {
return this;
}
}
@SuppressWarnings("unused")
static class D2PlainAccessors {
public void nameThen(String name) {
}
public void idFor(long id) {
}
public void i(long val) {
}
public void set(long val) {
}
public void a(long val) {
}
}
}
@@ -0,0 +1,15 @@
package io.ebeaninternal.server.type;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ScalarTypeTimeZoneTest {
ScalarTypeTimeZone type = new ScalarTypeTimeZone();
@Test
void getLength() {
assertEquals(32, type.getLength());
}
}
@@ -198,6 +198,9 @@ public class PlatformDdl {
* Write all the table columns converting to platform types as necessary.
*/
public void writeTableColumns(DdlBuffer apply, List<Column> columns, DdlIdentity identity) {
if ("true".equalsIgnoreCase(System.getProperty("ebean.ddl.sortColumns", "true"))) {
columns = sortColumns(columns);
}
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
apply.append(",");
@@ -218,6 +221,11 @@ public class PlatformDdl {
}
}
protected List<Column> sortColumns(List<Column> columns) {
// do nothing by default
return columns;
}
/**
* Write the column definition to the create table statement.
*/
@@ -4,6 +4,14 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.Column;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
/**
* Postgres specific DDL.
@@ -70,4 +78,52 @@ public class PostgresDdl extends PlatformDdl {
.append(columnSetType).append(type)
.append(" using ").append(alter.getColumnName()).append("::").append(type);
}
@Override
protected List<Column> sortColumns(List<Column> columns) {
List<DDLColumnSort> sorting = new ArrayList<>(columns.size());
for (int i = 0, end = columns.size(); i < end; i++) {
Column column = columns.get(i);
sorting.add(new DDLColumnSort(column, ddlColumnOrdering(i, column)));
}
Collections.sort(sorting);
return sorting.stream().map(it -> it.column).collect(toList());
}
private int ddlColumnOrdering(int i, Column column) {
String type = column.getType().toLowerCase();
if (type.startsWith("decimal")) {
return i + 1_000;
}
if (isVariableLength(type) || isLob(type)) {
return i + 10_000;
}
return i;
}
private boolean isLob(String type) {
return type.startsWith("clob") || type.startsWith("longvarchar") || type.startsWith("blob") || type.startsWith("longvarbinary");
}
private boolean isVariableLength(String type) {
return type.startsWith("varchar") || type.startsWith("varbinary") || type.startsWith("json");
}
static final class DDLColumnSort implements Comparable<DDLColumnSort> {
private final Column column;
private final int ordering;
DDLColumnSort(Column column, int ordering) {
this.column = column;
this.ordering = ordering;
}
@Override
public int compareTo(DDLColumnSort o) {
return Integer.compare(ordering, o.ordering);
}
}
}
@@ -1,11 +1,19 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.migration.Column;
public class YugabyteDdl extends PostgresDdl {
import java.util.List;
public final class YugabyteDdl extends PostgresDdl {
public YugabyteDdl(DatabasePlatform platform) {
super(platform);
this.historyDdl = new YugabyteHistoryDdl();
}
@Override
protected List<Column> sortColumns(List<Column> columns) {
return columns;
}
}
@@ -161,7 +161,7 @@ public class ModelContainer {
applyChange((AlterForeignKey) change);
} else if (change instanceof AddTableComment) {
applyChange((AddTableComment) change);
} else if (change instanceof Sql) {
} else if (change instanceof Sql || change instanceof CreateSchema) {
// do nothing
} else {
throw new IllegalArgumentException("No rule for " + change);
@@ -1,18 +1,66 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.platform.postgres.PostgresPlatform;
import io.ebeaninternal.dbmigration.migration.Column;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class PostgresDdlTest {
class PostgresDdlTest {
private PostgresDdl postgresDdl = new PostgresDdl(new PostgresPlatform());
final PostgresDdl postgresDdl = new PostgresDdl(new PostgresPlatform());
@Test
public void setLockTimeout() {
void setLockTimeout() {
final String sql = postgresDdl.setLockTimeout(5);
assertThat(sql).isEqualTo("set lock_timeout = 5000");
}
@Test
void sortColumns_noAdjustment() {
List<Column> cols = postgresDdl.sortColumns(columns("integer", "bigint"));
assertThat(cols).extracting("type").containsExactly("integer", "bigint");
List<Column> colsReverse = postgresDdl.sortColumns(columns("bigint", "integer"));
assertThat(colsReverse).extracting("type").containsExactly("bigint", "integer");
}
@Test
void sortColumns_decimalVarchar() {
List<Column> cols = postgresDdl.sortColumns(columns("decimal(1)", "varchar(1)", "varchar(2)", "int", "decimal(2)"));
assertThat(cols).extracting("type").containsExactly("int", "decimal(1)", "decimal(2)", "varchar(1)", "varchar(2)");
}
@Test
void sortColumns_varbinary() {
List<Column> cols = postgresDdl.sortColumns(columns("decimal(1)", "varbinary(1)", "varbinary(2)", "int", "decimal(2)"));
assertThat(cols).extracting("type").containsExactly("int", "decimal(1)", "decimal(2)", "varbinary(1)", "varbinary(2)");
}
@Test
void sortColumns_json() {
List<Column> cols = postgresDdl.sortColumns(columns("json", "jsonb", "int"));
assertThat(cols).extracting("type").containsExactly("int", "json", "jsonb");
}
@Test
void sortColumns_clobs() {
List<Column> cols = postgresDdl.sortColumns(columns("clob", "blob", "longvarchar(1)", "int", "longvarbinary(2)"));
assertThat(cols).extracting("type").containsExactly("int", "clob", "blob", "longvarchar(1)", "longvarbinary(2)");
}
private List<Column> columns(String... types) {
int counter = 0;
List<Column> cols = new ArrayList<>();
for (String s : types) {
Column col = new Column();
col.setName("c" + counter++);
col.setType(s);
cols.add(col);
}
return cols;
}
}
@@ -1,11 +1,7 @@
package io.ebeaninternal.dbmigration.model;
import io.ebean.migration.MigrationVersion;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.CreateTable;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.migration.Migration;
import io.ebeaninternal.dbmigration.migration.*;
import io.ebeaninternal.dbmigration.migrationreader.MigrationXmlReader;
import org.junit.jupiter.api.Test;
@@ -13,11 +9,10 @@ import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class ModelContainerApplyTest {
class ModelContainerApplyTest {
@Test
public void testApply() {
void testApply() {
Migration migration = MigrationXmlReader.read("/container/test-create-table.xml");
List<ChangeSet> changeSets = migration.getChangeSet();
@@ -40,4 +35,23 @@ public class ModelContainerApplyTest {
assertThat(foo.isWithHistory()).isEqualTo(false);
assertThat(foo.allColumns()).extracting("name").contains("col1", "col3", "added_to_foo");
}
@Test
void createSchema() {
CreateSchema createSchema = new CreateSchema();
createSchema.setName("foo");
Migration migration = newMigration(createSchema);
ModelContainer model = new ModelContainer();
model.apply(migration, MigrationVersion.parse("1.1"));
}
private Migration newMigration(Object change) {
ChangeSet changeSet = new ChangeSet();
changeSet.getChangeSetChildren().add(change);
Migration migration = new Migration();
migration.getChangeSet().add(changeSet);
return migration;
}
}
@@ -16,7 +16,7 @@ import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
public class ModelBuild_explicitSequencesTest extends BaseTestCase {
class ModelBuild_explicitSequencesTest extends BaseTestCase {
private SpiEbeanServer createServer(boolean postgres) {
@@ -38,7 +38,7 @@ public class ModelBuild_explicitSequencesTest extends BaseTestCase {
}
@Test
public void test() throws IOException {
void test() throws IOException {
SpiEbeanServer ebeanServer = createServer(false);
try {
CurrentModel currentModel = new CurrentModel(ebeanServer);
@@ -54,7 +54,7 @@ public class ModelBuild_explicitSequencesTest extends BaseTestCase {
}
@Test
public void test_asPostgres() throws IOException {
void test_asPostgres() throws IOException {
SpiEbeanServer ebeanServer = createServer(true);
try {
CurrentModel currentModel = new CurrentModel(ebeanServer);
@@ -7,8 +7,8 @@ create table PERSONS (
create table PHONES (
id bigint generated by default as identity not null,
phone_number varchar(7) not null,
person_id bigint not null,
phone_number varchar(7) not null,
constraint uq_phones_phone_number unique (phone_number),
constraint pk_phones primary key (id)
);
+1 -1
View File
@@ -98,7 +98,7 @@
<configuration>
<tiles>
<!-- other tiles ... -->
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>
+1 -1
View File
@@ -90,7 +90,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>
+1 -1
View File
@@ -104,7 +104,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>
+2 -2
View File
@@ -35,7 +35,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-lang</artifactId>
<version>1.0</version>
<version>1.1</version>
</dependency>
<dependency>
@@ -107,7 +107,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>
@@ -9,10 +9,9 @@ import java.lang.annotation.Target;
* Used to denote a query bean that has already been enhanced.
* <p>
* Used by the agent to detect already enhanced type query beans to skip enhancement processing.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.CLASS)
public @interface AlreadyEnhancedMarker {
}
@@ -11,7 +11,7 @@ import java.lang.annotation.Target;
* This is code generated by the query bean generator (annotation processor).
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.CLASS)
public @interface Generated {
/**
@@ -247,4 +247,15 @@ public abstract class PBaseValueEqual<R, T> extends TQPropertyBase<R> {
public final R isIn(Query<?> subQuery) {
return in(subQuery);
}
/**
* Is NOT in the result of a subquery.
*
* @param subQuery values provided by a subQuery
* @return the root query bean instance
*/
public final R notIn(Query<?> subQuery) {
expr().notIn(_name, subQuery);
return _root;
}
}
@@ -852,9 +852,11 @@ public abstract class TQRootBean<T, R> {
}
/**
* When set to true all the beans from this query are loaded into the bean
* cache.
* Deprecated migrate to setBeanCacheMode() or setUseCache().
* <p>
* When set to true all the beans from this query are loaded into the bean cache.
*/
@Deprecated
public R setLoadBeanCache(boolean loadBeanCache) {
query.setLoadBeanCache(loadBeanCache);
return root;
@@ -9,10 +9,9 @@ import java.lang.annotation.Target;
* Used to denote a type query bean.
* <p>
* These are typically generated beans used to build queries using type safe query criteria.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.CLASS)
public @interface TypeQueryBean {
/**
@@ -1,2 +1,4 @@
entity-packages: org.example.domain
querybean-packages: org.example.domain,org.querytest
debug: 0
synthetic: false
+1 -1
View File
@@ -80,7 +80,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>
+1 -1
View File
@@ -266,7 +266,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>
@@ -5,6 +5,7 @@ import io.ebean.config.DatabaseConfig;
import io.ebean.datasource.DataSourceConfig;
import io.ebean.test.config.platform.PlatformAutoConfig;
import io.ebean.test.config.provider.ProviderAutoConfig;
import io.ebean.test.containers.DockerHost;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,11 +30,17 @@ public class AutoConfigureForTesting implements AutoConfigure {
@Override
public void preConfigure(DatabaseConfig config) {
Properties properties = config.getProperties();
if (properties != null) {
// trigger determination of docker.host system property if not already done
// and re-evaluate properties in case there is use of ${docker.host} in jdbc url etc
DockerHost.host();
io.avaje.config.Config.asConfiguration().evalModify(properties);
}
if (!config.isDefaultServer()) {
log.info("skip automatic testing config on non-default server name:{} register:{}", config.getName(), config.isRegister());
return;
}
Properties properties = config.getProperties();
if (isExtraServer(config, properties)) {
setupExtraDataSourceIfNecessary(config);
return;
@@ -2,6 +2,7 @@ package io.ebean.test.config.platform;
import io.ebean.config.DatabaseConfig;
import io.ebean.datasource.DataSourceConfig;
import io.ebean.test.containers.DockerHost;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,7 +39,6 @@ class Config {
private final DatabaseConfig config;
private boolean containerDropCreate;
private final Properties dockerProperties = new Properties();
private final DockerHost dockerHost = new DockerHost();
Config(String db, String platform, String databaseName, DatabaseConfig config) {
this.db = db;
@@ -241,8 +241,7 @@ class Config {
}
String host() {
String explicitDockerHost = getKey("dockerHost", null);
return getKey("host", dockerHost.dockerHost(explicitDockerHost));
return getKey("host", getKey("dockerHost", DockerHost.host()));
}
/**
@@ -385,10 +384,6 @@ class Config {
}
private void initDockerProperties() {
if (dockerHost.runningInDocker()) {
// tell ebean-docker-test we are not using localhost (for jdbc DB setup commands)
dockerProperties.setProperty(dockerKey("host"), dockerHost.dockerHost());
}
dockerProperties.setProperty(dockerKey("port"), String.valueOf(port));
dockerProperties.setProperty(dockerKey("dbName"), databaseName);
if (schema != null) {
@@ -1,55 +0,0 @@
package io.ebean.test.config.platform;
import java.io.File;
import java.util.Locale;
/**
* Helper to detect if running inside docker and determine host name for that case.
*/
class DockerHost {
private final boolean runningInDocker;
private String dockerHost;
DockerHost() {
runningInDocker = initInDocker();
}
boolean runningInDocker() {
return runningInDocker;
}
String dockerHost() {
return dockerHost;
}
String dockerHost(String explicitHost) {
if (!runningInDocker) {
return "localhost";
}
dockerHost = explicitHost != null ? explicitHost : defaultDockerHost();
return dockerHost;
}
/**
* Return true if running inside a docker container (we are using docker in docker).
*/
boolean initInDocker() {
return new File("/.dockerenv").exists();
}
/**
* Return the default host name to use when running in docker.
* <p>
* Can instead be explicitly specified via <code>ebean.test.dockerHost</code>.
*/
String defaultDockerHost() {
String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
if (os.contains("mac") || os.contains("darwin") || os.contains("win")) {
return "host.docker.internal";
} else {
return "172.17.0.1";
}
}
}
@@ -1,5 +1,6 @@
package io.ebean.test.config.platform;
import io.ebean.test.containers.DockerHost;
import io.ebean.test.containers.RedisContainer;
import java.util.Properties;
@@ -10,11 +11,8 @@ class RedisSetup {
String version = properties.getProperty("ebean.test.redis");
version = properties.getProperty("ebean.test.redis.version", version);
if (version != null) {
DockerHost dockerHost = new DockerHost();
if (dockerHost.runningInDocker()) {
String host = dockerHost.dockerHost(properties.getProperty("ebean.test.dockerHost"));
properties.setProperty("redis.host", host);
}
String host = properties.getProperty("ebean.test.dockerHost", DockerHost.host());
properties.setProperty("redis.host", host);
RedisContainer.builder(version)
.properties(properties)
.build()
@@ -1,87 +0,0 @@
package io.ebean.test.config.platform;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class DockerHostTest {
@Test
void runningInDocker_when_false_alwaysUseLocalhost() {
DockerHost dockerHost = new DockerHost();
assertFalse(dockerHost.runningInDocker());
assertEquals("localhost", dockerHost.dockerHost("foo"));
}
@Test
void runningInDocker_when_true_useExplicit() {
TDDockerHost dockerHost = new TDDockerHost();
assertTrue(dockerHost.runningInDocker());
assertEquals("my-host", dockerHost.dockerHost("my-host"));
}
@Disabled
@Test
void runningInDocker_when_trueAndLinux_useDefault() {
TDDockerHost dockerHost = new TDDockerHost();
assertTrue(dockerHost.runningInDocker());
assertEquals("172.17.0.1", dockerHost.dockerHost(null));
}
@Test
void runningInDocker_when_windowsDefault() {
TDDockerHost dockerHost = new TDDockerHost();
assertTrue(dockerHost.runningInDocker());
String origName = System.getProperty("os.name");
System.setProperty("os.name", "win");
try {
assertEquals("host.docker.internal",dockerHost.defaultDockerHost());
assertEquals("host.docker.internal", dockerHost.dockerHost(null));
} finally {
System.setProperty("os.name", origName);
}
}
@Test
void runningInDocker_when_macDefault() {
TDDockerHost dockerHost = new TDDockerHost();
assertTrue(dockerHost.runningInDocker());
String origName = System.getProperty("os.name");
System.setProperty("os.name", "mac");
try {
assertEquals("host.docker.internal",dockerHost.defaultDockerHost());
assertEquals("host.docker.internal", dockerHost.dockerHost(null));
} finally {
System.setProperty("os.name", origName);
}
}
@Test
void runningInDocker_when_linuxDefault() {
TDDockerHost dockerHost = new TDDockerHost();
assertTrue(dockerHost.runningInDocker());
String origName = System.getProperty("os.name");
System.setProperty("os.name", "linux");
try {
assertEquals("172.17.0.1",dockerHost.defaultDockerHost());
assertEquals("172.17.0.1", dockerHost.dockerHost(null));
} finally {
System.setProperty("os.name", origName);
}
}
static class TDDockerHost extends DockerHost {
@Override
boolean initInDocker() {
return true;
}
}
}
@@ -24,12 +24,36 @@ public class DtoQuery2Test extends BaseTestCase {
private static final Logger log = LoggerFactory.getLogger(DtoQuery2Test.class);
@Test
public void dto_findList_constructorMatch() {
void dto_findList_fluidAccessors() {
ResetBasicData.reset();
List<DCustFluidAccessors> list = server().findDto(DCustFluidAccessors.class, "select id, name from o_customer").findList();
assertThat(list).isNotEmpty();
for (DCustFluidAccessors cust: list) {
assertThat(cust.id()).isNotNull();
assertThat(cust.name()).isNotNull();
}
}
@Test
void dto_findList_plainAccessors() {
ResetBasicData.reset();
List<DCustPlainAccessors> list = server().findDto(DCustPlainAccessors.class, "select id, name from o_customer").findList();
assertThat(list).isNotEmpty();
for (DCustPlainAccessors cust: list) {
assertThat(cust.id()).isNotNull();
assertThat(cust.name()).isNotNull();
}
}
@Test
void dto_findList_constructorMatch() {
ResetBasicData.reset();
DtoQuery<DCust> dtoQuery = server().findDto(DCust.class, "select id, name from o_customer");
List<DCust> list = dtoQuery.findList();
log.info(list.toString());
@@ -37,7 +61,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findIterator_closeWithResources() {
void dto_findIterator_closeWithResources() {
ResetBasicData.reset();
int counter = 0;
@@ -55,7 +79,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findIterator() {
void dto_findIterator() {
ResetBasicData.reset();
final int expectedCount = server().find(Customer.class).findCount();
@@ -80,7 +104,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findStream() {
void dto_findStream() {
ResetBasicData.reset();
final int expectedCount = server().find(Customer.class).findCount();
@@ -104,8 +128,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findEach_constructorMatch() {
void dto_findEach_constructorMatch() {
ResetBasicData.reset();
LoggedSql.start();
@@ -118,8 +141,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findEachWhile_constructorMatch() {
void dto_findEachWhile_constructorMatch() {
ResetBasicData.reset();
LoggedSql.start();
@@ -135,8 +157,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findOneEmpty() {
void dto_findOneEmpty() {
ResetBasicData.reset();
Optional<DCust> rob = server().findDto(DCust.class, "select id, name from o_customer where name = :name")
@@ -153,8 +174,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findOne() {
void dto_findOne() {
ResetBasicData.reset();
DCust fiona = server().findDto(DCust.class, "select id, name from o_customer where name = :name")
@@ -172,8 +192,7 @@ public class DtoQuery2Test extends BaseTestCase {
@Test
public void dto_queryPlanHits() {
void dto_queryPlanHits() {
ResetBasicData.reset();
resetAllMetrics();
@@ -191,7 +210,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
// collect without reset
BasicMetricVisitor basic = new BasicMetricVisitor(false, true, true, true);
BasicMetricVisitor basic = new BasicMetricVisitor("db", false, true, true, true);
server().metaInfo().visitMetrics(basic);
List<MetaQueryMetric> stats = basic.queryMetrics();
@@ -218,8 +237,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findList_relaxedMode() {
void dto_findList_relaxedMode() {
ResetBasicData.reset();
List<DCust2> list = server().findDto(DCust2.class, "select id, '42' as something_we_cannot_map, name from o_customer")
@@ -231,8 +249,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findList_relaxedMode_defaultConstructor() {
void dto_findList_relaxedMode_defaultConstructor() {
ResetBasicData.reset();
List<DCust2> list = server().findDto(DCust2.class, "select id, '42' as something_we_cannot_map, name from o_customer")
@@ -244,8 +261,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findList_constructorPlusMatch() {
void dto_findList_constructorPlusMatch() {
ResetBasicData.reset();
String sql = "select c.id, c.name, count(o.id) as totalOrders\n" +
@@ -263,8 +279,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto_findList_setters() {
void dto_findList_setters() {
ResetBasicData.reset();
DtoQuery<DCust2> dtoQuery = server().findDto(DCust2.class, "select id, name from o_customer");
@@ -275,8 +290,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto3_findList_constructorMatch() {
void dto3_findList_constructorMatch() {
ResetBasicData.reset();
List<DCust3> robs = server().findDto(DCust3.class, "select id, name, 42 as totalOrders from o_customer where name like ?")
@@ -290,8 +304,7 @@ public class DtoQuery2Test extends BaseTestCase {
}
@Test
public void dto3_findList_settersMatch() {
void dto3_findList_settersMatch() {
ResetBasicData.reset();
List<DCust3> robs = server().findDto(DCust3.class, "select id, name from o_customer where name = :name")
@@ -305,9 +318,7 @@ public class DtoQuery2Test extends BaseTestCase {
public static class DCust {
final Integer id;
final String name;
int totalOrders;
public DCust(Integer id, String name) {
@@ -340,7 +351,6 @@ public class DtoQuery2Test extends BaseTestCase {
public static class DCust2 {
Integer id;
String name;
@Override
@@ -368,9 +378,7 @@ public class DtoQuery2Test extends BaseTestCase {
public static class DCust3 {
Integer id;
String name;
int totalOrders;
public DCust3() {
@@ -411,4 +419,60 @@ public class DtoQuery2Test extends BaseTestCase {
this.name = name;
}
}
public static class DCustFluidAccessors {
Integer id;
String name;
@Override
public String toString() {
return "id:" + id + " name:" + name;
}
public Integer id() {
return id;
}
public DCustFluidAccessors id(Integer id) {
this.id = id;
return this;
}
public String name() {
return name;
}
public DCustFluidAccessors name(String name) {
this.name = name;
return this;
}
}
public static class DCustPlainAccessors {
Integer id;
String name;
@Override
public String toString() {
return "id:" + id + " name:" + name;
}
public Integer id() {
return id;
}
public void id(Integer id) {
this.id = id;
}
public String name() {
return name;
}
public void name(String name) {
this.name = name;
}
}
}
@@ -298,7 +298,7 @@ public class DtoQueryTest extends BaseTestCase {
}
// collect without reset
BasicMetricVisitor basic = new BasicMetricVisitor(false, true, true, true);
BasicMetricVisitor basic = new BasicMetricVisitor("db", false, true, true, true);
server().metaInfo().visitMetrics(basic);
List<MetaQueryMetric> stats = basic.queryMetrics();
@@ -322,6 +322,8 @@ public class DtoQueryTest extends BaseTestCase {
log.info("stats " + stats);
String asJson = server().metaInfo().metricsAsJson(metric2).withHash(false).withNewLine(false).json();
assertThat(asJson).contains("dto.DCust_basic2");
}
@Test
@@ -1,8 +1,10 @@
package org.tests.basic;
import io.ebean.Query;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
import io.ebeaninternal.api.SpiQuery;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Country;
import org.tests.model.basic.Customer;
@@ -16,6 +18,14 @@ import static org.junit.jupiter.api.Assertions.assertSame;
class TestLoadBeanCache extends BaseTestCase {
@Test
void loadBeanCache_false() {
Query<Country> query = DB.find(Country.class).setLoadBeanCache(false);
SpiQuery<?> spiQuery = (SpiQuery<?>) query;
assertThat(spiQuery.isBeanCachePut()).isFalse();
}
@Test
void testLoad() {
@@ -16,8 +16,26 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
class TestExtraScalarTypes extends BaseTestCase {
@Test
void test() {
void insertLargeTimezone() {
TimeZone tz = TimeZone.getTimeZone("America/Argentina/ComodRivadavia");
var bean = new ESomeType();
bean.setTimeZone(tz);
DB.save(bean);
var found = DB.find(ESomeType.class, bean.getId());
assert found != null;
assertThat(found.getTimeZone()).isEqualTo(tz);
var findByTimezone = DB.find(ESomeType.class).where().eq("timeZone", tz).findList();
assertThat(findByTimezone).hasSize(1);
assertThat(findByTimezone.get(0).getId()).isEqualTo(found.getId());
assertThat(findByTimezone.get(0).getTimeZone()).isEqualTo(tz);
DB.delete(found);
}
@Test
void test() {
Locale locale = Locale.ENGLISH;
Currency currency = Currency.getInstance(Locale.US);
TimeZone tz = TimeZone.getDefault();
@@ -11,8 +11,25 @@ public class OMVertexOther {
private UUID id;
private final String name;
private String other;
public OMVertexOther(String name) {
this.name = name;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public String getOther() {
return other;
}
public void setOther(String other) {
this.other = other;
}
}
@@ -0,0 +1,68 @@
package org.tests.o2m;
import io.ebean.CacheMode;
import io.ebean.DB;
import io.ebean.Transaction;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
class TestOneToManyDuplicateInTxn {
@Test
void findTwice() {
OMVertex master = new OMVertex(UUID.randomUUID());
OMVertexOther child = new OMVertexOther("child");
master.getRelated().add(child);
DB.save(master);
try (Transaction txn = DB.beginTransaction()) {
OMVertex first = DB.find(OMVertex.class)
.setDisableLazyLoading(true)
.fetch("related")
.where().eq("id", master.getId())
.findOne();
assertThat(first.getRelated()).hasSize(1);
OMVertex second = DB.find(OMVertex.class)
//.setLoadBeanCache(true)
.setBeanCacheMode(CacheMode.PUT) // force query to hit database
.setDisableLazyLoading(true)
.fetch("related")
.where().eq("id", master.getId())
.findOne();
assertThat(second.getRelated()).hasSize(1);
}
}
@Test
void findTwice_partial() {
OMVertex master = new OMVertex(UUID.randomUUID());
OMVertexOther child = new OMVertexOther("child");
master.getRelated().add(child);
DB.save(master);
try (Transaction txn = DB.beginTransaction()) {
OMVertex first = DB.find(OMVertex.class)
.setDisableLazyLoading(true)
.fetch("related", "name") // load a partially loaded bean
.where().eq("id", master.getId())
.findOne();
assertThat(first.getRelated()).hasSize(1);
OMVertex second = DB.find(OMVertex.class)
.setBeanCacheMode(CacheMode.PUT) // force query to hit database
.setDisableLazyLoading(true)
.fetch("related")
.where().eq("id", master.getId())
.findOne();
assertThat(second.getRelated()).hasSize(1);
}
}
}
@@ -0,0 +1,238 @@
package org.tests.o2m;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.o2m.dm.GoodsEntity;
import org.tests.o2m.dm.PersonEntity;
import org.tests.o2m.dm.WorkflowEntity;
import org.tests.o2m.dm.WorkflowOperationEntity;
import java.io.StringWriter;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class TestOneToManyStatelessUpdateResultsInSoftDelete extends BaseTestCase {
@Test
void testStatelessUpdateShouldntDelete() {
LoggedSql.start();
var goods = new GoodsEntity();
var workflow = new WorkflowEntity();
var operation1 = new WorkflowOperationEntity();
goods.setWorkflowEntity(workflow);
workflow.setOperations(List.of(operation1));
DB.save(goods);
LoggedSql.collect();
// statelessly add another operation to the workflow and save goods
var goodsStateless = new GoodsEntity();
goodsStateless.setId(goods.getId());
var workflowStateless = new WorkflowEntity();
workflowStateless.setId(workflow.getId());
var operation1Stateless = new WorkflowOperationEntity();
operation1Stateless.setId(operation1.getId());
var operation2 = new WorkflowOperationEntity();
goodsStateless.setWorkflowEntity(workflowStateless);
workflowStateless.setOperations(List.of(operation1Stateless, operation2));
// With the fix the SQL is now:
/*
txn[1002] update workflow_entity set when_modified=? where id=?
txn[1002] -- bind(2022-07-04 11:10:54.446,1)
txn[1002] update workflow_operation_entity set deleted=true where workflow_id = ? and not ( id in (?) )
txn[1002] -- bind(1, Array[1]={1})
txn[1002] insert into workflow_operation_entity (name, version, when_created, when_modified, deleted, workflow_id) values (?,?,?,?,?,?)
txn[1002] -- bind(null,1,2022-07-04 11:10:54.458,2022-07-04 11:10:54.458,false,1)
txn[1002] update goods_entity set when_modified=?, workflow_entity_id=? where id=?; -- bind(2022-07-04 11:10:54.446,1,1)
*/
/*
- this update generates following statements
1 txn[] delete from workflow_operation_entity where workflow_id=?
2 txn[] -- bind(1)
3 txn[] update workflow_entity set when_modified=? where id=?
4 txn[] -- bind(2022-06-29 15:43:55.573,1)
5 txn[] update workflow_operation_entity set deleted=true where workflow_id = ? and not ( id in (?) )
6 txn[] -- bind(1, Array[1]={1})
7 txn[] insert into workflow_operation_entity (name, version, when_created, when_modified, deleted, workflow_id) values (?,?,?,?,?,?)
8 txn[] -- bind(null,1,2022-06-29 15:43:55.584,2022-06-29 15:43:55.584,false,1)
9 txn[] update goods_entity set when_modified=?, workflow_entity_id=? where id=?; -- bind(2022-06-29 15:43:55.573,1,1)
- number 1 is wrong
- no delete should be issued
- even if it was issued, it should have been soft delete
- the DB.update will throw exception if there is one-to-many relation on workflow_operation_entity
- it would still be referenced from other table
*/
DB.update(goodsStateless);
var updateSql = LoggedSql.stop();
//updateSql.forEach(System.out::println);
var dbGoodsAfterUpdate = DB.find(GoodsEntity.class, goods.getId());
assertThat(dbGoodsAfterUpdate.getWorkflowEntity().getOperations()).hasSize(2);
assertThat(dbGoodsAfterUpdate.getWorkflowEntity().getOperations()).extracting("id").contains(operation1.getId(), operation2.getId());
updateSql.forEach(sql -> assertThat(sql).doesNotContain("delete from workflow_entity"));
}
// same as previous but DB.update throws exception
@Test
void testStatelessUpdateShouldntDeleteThrows() {
LoggedSql.start();
var goods = new GoodsEntity();
goods.setName("ver1");
var workflow = new WorkflowEntity();
workflow.setRevision("ver1");
var operation1 = new WorkflowOperationEntity();
operation1.setName("ver1");
goods.setWorkflowEntity(workflow);
workflow.setOperations(List.of(operation1));
DB.save(goods);
List<String> createSql = LoggedSql.stop();
LoggedSql.start();
// statelessly add another operation to the workflow and save goods
var goodsStateless = new GoodsEntity();
goodsStateless.setId(goods.getId());
goodsStateless.setName("ver2");
var workflowStateless = new WorkflowEntity();
workflowStateless.setRevision("ver2");
workflowStateless.setId(workflow.getId());
var operation1Stateless = new WorkflowOperationEntity();
operation1Stateless.setName("ver2");
operation1Stateless.setId(operation1.getId());
var operation2 = new WorkflowOperationEntity();
operation2.setName("ver2");
goodsStateless.setWorkflowEntity(workflowStateless);
workflowStateless.setOperations(List.of(operation1Stateless, operation2));
// throws
DB.update(goodsStateless);
var updateSql = LoggedSql.stop();
updateSql.forEach(System.out::println);
var dbGoodsAfterUpdate = DB.find(GoodsEntity.class, goods.getId());
assertThat(dbGoodsAfterUpdate.getWorkflowEntity().getOperations()).hasSize(2);
assertThat(dbGoodsAfterUpdate.getWorkflowEntity().getOperations().get(0).getId()).isEqualTo(operation1.getId());
assertThat(dbGoodsAfterUpdate.getWorkflowEntity().getOperations().get(1).getId()).isEqualTo(operation2.getId());
updateSql.forEach(sql -> assertThat(sql).doesNotContain("delete from workflow_entity"));
}
@Test
void duplicateKeyWorkflowEntityInsertInsteadOfUpdate() {
var goods = new GoodsEntity();
goods.setName("ver1");
var workflow = new WorkflowEntity();
workflow.setRevision("ver1");
var operation1 = new WorkflowOperationEntity();
operation1.setName("ver1");
goods.setWorkflowEntity(workflow);
workflow.setOperations(List.of(operation1));
DB.save(goods);
// delete operation
var goodsAfterInsert = DB.find(GoodsEntity.class, goods.getId());
assertThat(goodsAfterInsert.getWorkflowEntity().getOperations()).hasSize(1);
goodsAfterInsert.getWorkflowEntity().setOperations(List.of());
DB.save(goodsAfterInsert);
assertThat(goodsAfterInsert.getWorkflowEntity().getOperations()).isEmpty();
assertThat(DB.find(GoodsEntity.class, goods.getId()).getWorkflowEntity().getOperations()).isEmpty();
// statelessly add new WorkflowOperationEntity
var goodsStateless = new GoodsEntity();
goodsStateless.setId(goods.getId());
var workflowStateless = new WorkflowEntity();
workflowStateless.setId(workflow.getId());
goodsStateless.setWorkflowEntity(workflowStateless);
var operation2 = new WorkflowOperationEntity();
workflowStateless.setOperations(List.of(operation2));
// Using save() throws io.ebean.DuplicateKeyException: Error when batch flush on sql: insert into workflow_entity ...
// Must be an update() and not save() for this to be a "stateless update"
DB.update(goodsStateless);
var ops = workflow.getOperations();
// shouldn't contain deleted operations
assertThat(ops).hasSize(1);
assertThat(goodsStateless.getWorkflowEntity().getOperations().get(0).getId()).isNotEqualTo(operation1.getId());
}
@Test
void softDeleteIncludedInQuery() throws Exception {
var defaultPerson = new PersonEntity();
defaultPerson.setName("test");
DB.save(defaultPerson);
// create GoodsEntity with 1 WorkflowOperationEntity
var goods = new GoodsEntity();
goods.setCreatedBy(defaultPerson);
goods.setName("ver1");
var workflow = new WorkflowEntity();
workflow.setRevision("ver1");
var operation1 = new WorkflowOperationEntity();
operation1.setName("ver1");
goods.setWorkflowEntity(workflow);
workflow.setOperations(List.of(operation1));
DB.save(goods);
// statelessly delete WorkflowOperationEntity
var goodsStateless = new GoodsEntity();
goodsStateless.setId(goods.getId());
var workflowStateless = new WorkflowEntity();
workflowStateless.setId(workflow.getId());
goodsStateless.setWorkflowEntity(workflowStateless);
workflowStateless.setOperations(List.of());
LoggedSql.start();
DB.update(goodsStateless);
// uncommenting this lines makes the test pass
//assertThat(goodsStateless.getWorkflowEntity().getOperations().size()).isEqualTo(0);
var sql = LoggedSql.stop();
sql.forEach(System.out::println);
System.out.println("BEFORE TRY");
LoggedSql.start();
try (var writer = new StringWriter()) {
var mapper = new ObjectMapper();
mapper.writeValue(writer, goodsStateless);
sql = LoggedSql.stop();
sql.forEach(System.out::println);
/*
select t0.id, t0.name, t0.workflow_entity_id, t0.version, t0.when_created, t0.when_modified from goods_entity t0 where t0.id = ?; --bind(4, ) --micros(161)
select t0.id, t0.name, t0.version, t0.when_created, t0.when_modified, t0.created_by, t0.updated_by, t0.workflow_entity_id from goods_entity t0 where t0.id = ?; --bind(4, ) --micros(525)
select t0.id, t0.name, t0.version, t0.when_created, t0.when_modified from person_entity t0 where t0.id = ?; --bind(1, ) --micros(325)
! is this even issue? - select does not check if workflow_entity is deleted
select t0.id, t0.revision, t0.version, t0.when_created, t0.when_modified from workflow_entity t0 where t0.id = ?; --bind(1, ) --micros(439)
select t0.id, t0.revision, t0.version, t0.when_created, t0.when_modified, t0.created_by, t0.updated_by from workflow_entity t0 where t0.id = ?; --bind(1, ) --micros(332)
select t0.id, t0.name, t0.version, t0.when_created, t0.when_modified from person_entity t0 where t0.id = ?; --bind(1, ) --micros(197)
select t0.workflow_id, t0.id, t0.position, t0.name, t0.workflow_id, t0.version, t0.when_created, t0.when_modified, t0.deleted from workflow_operation_entity t0 where (t0.workflow_id) in (?) order by t0.workflow_id, t0.position; --bind(Array[1]={1}) --micros(2776)
select t0.id, t0.position, t0.name, t0.version, t0.when_created, t0.when_modified, t0.deleted, t0.workflow_id, t0.created_by, t0.updated_by from workflow_operation_entity t0 where t0.id = ?; --bind(1, ) --micros(415)
!! ignores soft delete
also to note - when the DM extends BaseDomain instead of HistoryColumns, this bug does not happen
(presumably since @WhoCreated Person createdBy is lazy loaded, when it is eagerly loaded, this bug does not occur)
select t0.workflow_id, t0.id, t0.position, t0.name, t0.workflow_id, t0.version, t0.when_created, t0.when_modified, t0.deleted from workflow_operation_entity t0 where (t0.workflow_id) in (?) order by t0.workflow_id, t0.position; --bind(Array[1]={4}) --micros(585)
select t0.id, t0.position, t0.name, t0.version, t0.when_created, t0.when_modified, t0.deleted, t0.created_by, t0.updated_by, t0.workflow_id from workflow_operation_entity t0 where t0.id = ?; --bind(7, ) --micros(532)
*/
writer.flush();
var serialized = writer.toString();
System.out.println(serialized);
var readGoods = mapper.readValue(writer.toString(), GoodsEntity.class);
assertThat(readGoods.getWorkflowEntity().getOperations()).hasSize(0);
}
}
}
@@ -0,0 +1,29 @@
package org.tests.o2m.dm;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToOne;
@Entity
public class GoodsEntity extends HistoryColumns {
private String name;
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
private WorkflowEntity workflowEntity;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public WorkflowEntity getWorkflowEntity() {
return workflowEntity;
}
public void setWorkflowEntity(WorkflowEntity workflowEntity) {
this.workflowEntity = workflowEntity;
}
}
@@ -0,0 +1,36 @@
package org.tests.o2m.dm;
import org.tests.model.draftable.BaseDomain;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public class HistoryColumns extends BaseDomain {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "created_by")
private PersonEntity createdBy;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "updated_by")
private PersonEntity updatedBy;
public PersonEntity getCreatedBy() {
return createdBy;
}
public void setCreatedBy(PersonEntity createdBy) {
this.createdBy = createdBy;
}
public PersonEntity getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(PersonEntity updatedBy) {
this.updatedBy = updatedBy;
}
}
@@ -0,0 +1,26 @@
package org.tests.o2m.dm;
import org.tests.model.draftable.BaseDomain;
import javax.persistence.Entity;
@Entity
public class PersonEntity extends BaseDomain {
public PersonEntity() {
}
public PersonEntity(Long id) {
this.setId(id);
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,46 @@
package org.tests.o2m.dm;
import io.ebean.annotation.SoftDelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
public class WorkflowEntity extends HistoryColumns {
private String revision;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "workflow_id")
private List<WorkflowOperationEntity> operations = new ArrayList<>();
@SoftDelete
private boolean deleted;
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public String getRevision() {
return revision;
}
public void setRevision(String revision) {
this.revision = revision;
}
public List<WorkflowOperationEntity> getOperations() {
return operations;
}
public void setOperations(List<WorkflowOperationEntity> operations) {
this.operations = operations;
}
}
@@ -0,0 +1,44 @@
package org.tests.o2m.dm;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.ebean.annotation.SoftDelete;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
@Entity
public class WorkflowOperationEntity extends HistoryColumns {
private String name;
@ManyToOne
@JoinColumn(name = "workflow_id")
@JsonIgnore
private WorkflowEntity workflowEntity;
@SoftDelete
private boolean deleted;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public WorkflowEntity getWorkflowEntity() {
return workflowEntity;
}
public void setWorkflowEntity(WorkflowEntity workflowEntity) {
this.workflowEntity = workflowEntity;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
}
@@ -0,0 +1,51 @@
package org.tests.o2m.recurse;
import javax.persistence.*;
import java.util.List;
@Entity
public class RMItem {
@Id
private long itemId;
@ManyToOne
@JoinColumn(name = "item_group_id")
private RMItem itemGroup;
private String name;
@OneToMany(mappedBy = "itemGroup")
private List<RMItem> subItems;
public RMItem() {
}
public RMItem(String name) {
this.name = name;
}
public Long getItemId() {
return itemId;
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
public RMItem getItemGroup() {
return itemGroup;
}
public void setItemGroup(RMItem itemGroup) {
this.itemGroup = itemGroup;
}
public List<RMItem> getSubItems() {
return subItems;
}
public void setSubItems(List<RMItem> subItems) {
this.subItems = subItems;
}
}
@@ -0,0 +1,80 @@
package org.tests.o2m.recurse;
import io.ebean.Model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Version;
@Entity
public class RMItemHolder extends Model {
@Id
long id;
String name;
String notes;
@ManyToOne
//@JoinColumn(name = "item_a_id")
private RMItem itemA;
@ManyToOne
//@JoinColumn(name = "item_b_id")
private RMItem itemB;
@Version
long version;
public RMItemHolder(String name) {
this.name = name;
}
public RMItemHolder() {
}
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 String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public RMItem getItemA() {
return itemA;
}
public void setItemA(RMItem itemA) {
this.itemA = itemA;
}
public RMItem getItemB() {
return itemB;
}
public void setItemB(RMItem itemB) {
this.itemB = itemB;
}
}
@@ -0,0 +1,133 @@
package org.tests.o2m.recurse;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.FetchConfig;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TestFetchOneToManySameTypeTwoPaths {
@Test
void testSubItemListFetch_ofQuery() {
Database server = DB.getDefault();
RMItem itemA = new RMItem("a");
server.save(itemA);
RMItem itemB = new RMItem("b");
server.save(itemB);
for (int i=0; i<2; i++) {
RMItem subItem = new RMItem();
subItem.setItemGroup(itemA);
server.save(subItem);
}
for (int i=0; i<3; i++) {
RMItem subItem = new RMItem();
subItem.setItemGroup(itemB);
server.save(subItem);
}
RMItemHolder customer = new RMItemHolder();
customer.setItemA(itemA);
customer.setItemB(itemB);
server.save(customer);
// This is OK
{
RMItemHolder requestedCustomer = server.find(RMItemHolder.class)
.setDisableLazyLoading(true)
.fetch("itemA.subItems", FetchConfig.ofQuery())
.fetch("itemB.subItems", FetchConfig.ofQuery())
.where()
.eq("id", customer.getId())
.findOne();
assertEquals(2, requestedCustomer.getItemA().getSubItems().size());
assertEquals(3, requestedCustomer.getItemB().getSubItems().size());
}
}
@Test
void testSubItemListFetch_itemAFirst() {
Database server = DB.getDefault();
RMItem itemA = new RMItem("aa");
server.save(itemA);
RMItem itemB = new RMItem("bb");
server.save(itemB);
for (int i=0; i<2; i++) {
RMItem subItem = new RMItem();
subItem.setItemGroup(itemA);
server.save(subItem);
}
for (int i=0; i<3; i++) {
RMItem subItem = new RMItem();
subItem.setItemGroup(itemB);
server.save(subItem);
}
RMItemHolder customer = new RMItemHolder();
customer.setItemA(itemA);
customer.setItemB(itemB);
server.save(customer);
// This fails because requestedCustomer.getItemB().getSubItems() is not loaded
{
RMItemHolder requestedCustomer = server.find(RMItemHolder.class)
.setDisableLazyLoading(true)
.fetch("itemA.subItems")
.fetch("itemB.subItems")
.where()
.eq("id", customer.getId())
.findOne();
assertEquals(2, requestedCustomer.getItemA().getSubItems().size());
assertEquals(3, requestedCustomer.getItemB().getSubItems().size());
}
}
@Test
void testSubItemListFetch_itemBFirst() {
Database server = DB.getDefault();
RMItem itemA = new RMItem("a");
server.save(itemA);
RMItem itemB = new RMItem("b");
server.save(itemB);
for (int i=0; i<2; i++) {
RMItem subItem = new RMItem();
subItem.setItemGroup(itemA);
server.save(subItem);
}
for (int i=0; i<5; i++) {
RMItem subItem = new RMItem();
subItem.setItemGroup(itemB);
server.save(subItem);
}
RMItemHolder customer = new RMItemHolder();
customer.setItemA(itemA);
customer.setItemB(itemB);
server.save(customer);
// This fails because requestedCustomer.getItemA().getSubItems() is not loaded
{
RMItemHolder requestedCustomer = server.find(RMItemHolder.class)
.setDisableLazyLoading(true)
.fetch("itemB.subItems")
.fetch("itemA.subItems")
.where()
.eq("id", customer.getId())
.findOne();
assertEquals(2, requestedCustomer.getItemA().getSubItems().size());
assertEquals(5, requestedCustomer.getItemB().getSubItems().size());
System.out.println("here");
}
}
}
@@ -14,9 +14,9 @@ create table migtest_ckey_detail (
create table migtest_ckey_parent (
one_key integer not null,
version integer not null,
two_key varchar(127) not null,
name varchar(255),
version integer not null,
constraint pk_migtest_ckey_parent primary key (one_key,two_key)
);
@@ -56,10 +56,6 @@ create table migtest_fk_set_null (
create table migtest_e_basic (
id integer generated by default as identity not null,
status varchar(1),
status2 varchar(1) default 'N' not null,
name varchar(127),
description varchar(127),
description_file bytea,
json_list json,
a_lob varchar(255) default 'X' not null,
@@ -67,13 +63,17 @@ create table migtest_e_basic (
old_boolean boolean default false not null,
old_boolean2 boolean,
eref_id integer,
user_id integer not null,
status varchar(1),
status2 varchar(1) default 'N' not null,
name varchar(127),
description varchar(127),
indextest1 varchar(127),
indextest2 varchar(127),
indextest3 varchar(127),
indextest4 varchar(127),
indextest5 varchar(127),
indextest6 varchar(127),
user_id integer not null,
constraint ck_migtest_e_basic_status check ( status in ('N','A','I')),
constraint ck_migtest_e_basic_status2 check ( status2 in ('N','A','I')),
constraint uq_migtest_e_basic_indextest2 unique (indextest2),
@@ -1,4 +1,4 @@
1014378814, 1.0__initial.sql
2094204208, 1.0__initial.sql
-2047324426, 1.1.sql
-261111052, 1.2__dropsFor_1.1.sql
1335621453, 1.3.sql
@@ -14,9 +14,9 @@ create table migtest_ckey_detail (
create table migtest_ckey_parent (
one_key integer not null,
version integer not null,
two_key varchar(127) not null,
name varchar(255),
version integer not null,
constraint pk_migtest_ckey_parent primary key (one_key,two_key)
);
@@ -56,10 +56,6 @@ create table migtest_fk_set_null (
create table migtest_e_basic (
id serial not null,
status varchar(1),
status2 varchar(1) default 'N' not null,
name varchar(127),
description varchar(127),
description_file bytea,
json_list json,
a_lob varchar(255) default 'X' not null,
@@ -67,13 +63,17 @@ create table migtest_e_basic (
old_boolean boolean default false not null,
old_boolean2 boolean,
eref_id integer,
user_id integer not null,
status varchar(1),
status2 varchar(1) default 'N' not null,
name varchar(127),
description varchar(127),
indextest1 varchar(127),
indextest2 varchar(127),
indextest3 varchar(127),
indextest4 varchar(127),
indextest5 varchar(127),
indextest6 varchar(127),
user_id integer not null,
constraint ck_migtest_e_basic_status check ( status in ('N','A','I')),
constraint ck_migtest_e_basic_status2 check ( status2 in ('N','A','I')),
constraint uq_migtest_e_basic_indextest2 unique (indextest2),
@@ -1,4 +1,4 @@
382349675, 1.0__initial.sql
-231429368, 1.0__initial.sql
-606251140, 1.1.sql
-261111052, 1.2__dropsFor_1.1.sql
-893728811, 1.3.sql
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.avaje</groupId>
<artifactId>java11-oss</artifactId>
<version>3.8</version>
<version>3.9</version>
</parent>
<groupId>io.ebean</groupId>
@@ -41,13 +41,13 @@
<nexus.staging.autoReleaseAfterClose>false</nexus.staging.autoReleaseAfterClose>
<jackson.version>2.13.2</jackson.version>
<h2database.version>2.1.212</h2database.version>
<ebean-ddl-runner.version>2.0</ebean-ddl-runner.version>
<ebean-ddl-runner.version>2.2</ebean-ddl-runner.version>
<ebean-migration-auto.version>1.2</ebean-migration-auto.version>
<ebean-migration.version>13.6.0</ebean-migration.version>
<ebean-test-containers.version>6.1</ebean-test-containers.version>
<ebean-datasource.version>8.0</ebean-datasource.version>
<ebean-agent.version>13.6.4</ebean-agent.version>
<ebean-maven-plugin.version>13.6.4</ebean-maven-plugin.version>
<ebean-test-containers.version>6.2</ebean-test-containers.version>
<ebean-datasource.version>8.2</ebean-datasource.version>
<ebean-agent.version>13.6.5</ebean-agent.version>
<ebean-maven-plugin.version>13.6.5</ebean-maven-plugin.version>
<surefire.useModulePath>false</surefire.useModulePath>
</properties>
+2 -2
View File
@@ -52,7 +52,7 @@
<path>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.6.4-FOC3-SNAPSHOT</version>
<version>13.6.6-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
</configuration>
@@ -65,7 +65,7 @@
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:13.6.0</tile>
<tile>io.ebean.tile:enhancement:13.6.5</tile>
</tiles>
</configuration>
</plugin>