mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -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
|
||||
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/build.yml)
|
||||
[](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean)
|
||||
[](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)
|
||||
|
||||
|
||||
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/jdk-ea.yml)
|
||||
[](https://github.com/ebean-orm/ebean-datasource/actions/workflows/jdk-ea.yml)
|
||||
[](https://github.com/ebean-orm/ebean-migration/actions/workflows/jdk-ea.yml)
|
||||
|
||||
+30
-5
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
+8
@@ -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.
|
||||
*/
|
||||
|
||||
+56
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+9
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+52
-4
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+1
-1
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+238
@@ -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;
|
||||
}
|
||||
}
|
||||
+133
@@ -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
-1
@@ -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
-1
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user