mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b3e485beb | ||
|
|
d4be0718a8 | ||
|
|
6d0556a120 | ||
|
|
ec7e4e4935 | ||
|
|
471605619a | ||
|
|
02fe956056 | ||
|
|
db13034fec | ||
|
|
c2489beb82 |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.19.1</version>
|
||||
<version>11.19.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.19.1</tag>
|
||||
<tag>ebean-11.19.2</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -412,6 +412,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> select(String properties);
|
||||
|
||||
/**
|
||||
* Apply the fetchGroup which defines what part of the object graph to load.
|
||||
*/
|
||||
Query<T> select(FetchGroup<T> fetchGroup);
|
||||
|
||||
/**
|
||||
* Set whether this query uses DISTINCT.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Defines what part of the object graph to load (select and fetch clauses).
|
||||
* <p>
|
||||
* Using a FetchGroup effectively sets the select() and fetch() clauses for a query. It is alternative
|
||||
* to specifying the select() and fetch() clauses on the query allowing for more re-use of "what to load"
|
||||
* that can be defined separately from the query and combined with other FetchGroups.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Select example</h3>*
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Select and fetch example</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Combining FetchGroups</h3>
|
||||
* <p>
|
||||
* FetchGroups can be combined together to form another FetchGroup.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Address> FG_ADDRESS = FetchGroup.of(Address.class)
|
||||
* .select("line1, line2, city")
|
||||
* .fetch("country", "name")
|
||||
* .build();
|
||||
*
|
||||
* FetchGroup<Customer> FG_CUSTOMER = FetchGroup.of(Customer.class)
|
||||
* .select("name, version")
|
||||
* .fetch("billingAddress", FG_ADDRESS)
|
||||
* .build();
|
||||
*
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(FG_CUSTOMER)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> The bean type the Fetch group can be applied to
|
||||
*/
|
||||
public interface FetchGroup<T> {
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
* <p>
|
||||
* We use this for simple FetchGroup that only select() properties and do not have additional fetch() clause.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param select The select clause of the FetchGroup
|
||||
*
|
||||
* @return The FetchGroup with the given select clause
|
||||
*/
|
||||
@Nonnull
|
||||
static <T> FetchGroup<T> of(Class<T> cls, String select) {
|
||||
return XServiceProvider.fetchGroupOf(cls, select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroupBuilder with the given select clause that we can add fetch clauses to.
|
||||
* <p>
|
||||
* We chain select() with one or more fetch() clauses to define the object graph to load.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to
|
||||
*/
|
||||
@Nonnull
|
||||
static <T> FetchGroupBuilder<T> of(Class<T> cls) {
|
||||
return XServiceProvider.fetchGroupOf(cls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Builds a FetchGroup by adding fetch clauses.
|
||||
* <p>
|
||||
* We add select() and fetch() clauses to define the object graph we want to load.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup fetchGroup = FetchGroup
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .where()
|
||||
* ...
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FetchGroupBuilder<T> {
|
||||
|
||||
/**
|
||||
* Specify specific properties to select (top level properties).
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> select(String select);
|
||||
|
||||
/**
|
||||
* Fetch all the properties at the given path.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path using a query join with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path lazily with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path, String properties);
|
||||
|
||||
/**
|
||||
* Fetch the path including all its properties using a query join.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties using a query join.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path, String properties);
|
||||
|
||||
/**
|
||||
* Fetch the path including all its properties lazily.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties lazily.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path, String properties);
|
||||
|
||||
/**
|
||||
* Build and return the FetchGroup.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroup<T> build();
|
||||
}
|
||||
@@ -397,6 +397,11 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> select(String fetchProperties);
|
||||
|
||||
/**
|
||||
* Apply the fetchGroup which defines what part of the object graph to load.
|
||||
*/
|
||||
Query<T> select(FetchGroup<T> fetchGroup);
|
||||
|
||||
/**
|
||||
* Specify a path to fetch eagerly including specific properties.
|
||||
* <p>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.service.SpiFetchGroupService;
|
||||
import io.ebean.service.SpiProfileLocationFactory;
|
||||
import io.ebean.service.SpiRawSqlService;
|
||||
|
||||
@@ -15,6 +16,16 @@ class XServiceProvider {
|
||||
|
||||
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
|
||||
|
||||
private static SpiFetchGroupService fetchGroupService = initSpiFetchGroupService();
|
||||
|
||||
private static SpiFetchGroupService initSpiFetchGroupService() {
|
||||
Iterator<SpiFetchGroupService> loader = ServiceLoader.load(SpiFetchGroupService.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiFetchGroupService?");
|
||||
}
|
||||
|
||||
private static SpiRawSqlService initRawSql() {
|
||||
|
||||
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
|
||||
@@ -47,4 +58,17 @@ class XServiceProvider {
|
||||
return profileLocationFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
*/
|
||||
static <T> FetchGroup<T> fetchGroupOf(Class<T> cls, String select) {
|
||||
return fetchGroupService.of(cls, select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroupBuilder with the given select clause.
|
||||
*/
|
||||
static <T> FetchGroupBuilder<T> fetchGroupOf(Class<T> cls) {
|
||||
return fetchGroupService.of(cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchGroupBuilder;
|
||||
|
||||
/**
|
||||
* Service that parses FetchGroup expressions.
|
||||
*/
|
||||
public interface SpiFetchGroupService {
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
*
|
||||
* @param beanType The type of entity bean the fetch group is for
|
||||
* @param select The properties to select (top level properties)
|
||||
*/
|
||||
<T> FetchGroup<T> of(Class<T> beanType, String select);
|
||||
|
||||
/**
|
||||
* Create and return a FetchGroupBuilder starting with a select() clause.
|
||||
*
|
||||
* @param beanType The type of entity bean the fetch group is for
|
||||
* @return The FetchGroupBuilder to add additional select and fetch clauses
|
||||
*/
|
||||
<T> FetchGroupBuilder<T> of(Class<T> beanType);
|
||||
}
|
||||
@@ -213,6 +213,12 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
// by default not including "Many" properties in document store
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerColumn(BeanDescriptor<?> desc, String prefix) {
|
||||
if (targetDescriptor != null) {
|
||||
desc.registerTable(targetDescriptor.getBaseTable(), this);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return the underlying collection of beans.
|
||||
*/
|
||||
|
||||
@@ -304,6 +304,12 @@ class AnnotationAssocManys extends AnnotationParser {
|
||||
private void readJoinTable(JoinTable joinTable, DeployBeanPropertyAssocMany<?> prop) {
|
||||
|
||||
String intTableName = getFullTableName(joinTable);
|
||||
if (intTableName.isEmpty()) {
|
||||
BeanTable localTable = factory.getBeanTable(descriptor.getBeanType());
|
||||
BeanTable otherTable = factory.getBeanTable(prop.getTargetType());
|
||||
intTableName = getM2MJoinTableName(localTable, otherTable);
|
||||
}
|
||||
|
||||
// set the intersection table
|
||||
DeployTableJoin intJoin = new DeployTableJoin();
|
||||
intJoin.setTable(intTableName);
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebean.DtoQuery;
|
||||
import io.ebean.Expression;
|
||||
import io.ebean.ExpressionFactory;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.FutureIds;
|
||||
import io.ebean.FutureList;
|
||||
@@ -460,6 +461,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return query.select(fetchProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> select(FetchGroup fetchGroup) {
|
||||
return query.select(fetchGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> setDistinct(boolean distinct) {
|
||||
return query.setDistinct(distinct);
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.CountDistinctOrder;
|
||||
import io.ebean.DtoQuery;
|
||||
import io.ebean.Expression;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.FutureIds;
|
||||
import io.ebean.FutureList;
|
||||
@@ -759,6 +760,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
|
||||
return exprList.select(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> select(FetchGroup fetchGroup) {
|
||||
return exprList.select(fetchGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> setDistinct(boolean distinct) {
|
||||
return exprList.setDistinct(distinct);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.querydefn.SpiFetchGroup;
|
||||
|
||||
/**
|
||||
* Default FetchGroup implementation.
|
||||
*/
|
||||
class DFetchGroup<T> implements SpiFetchGroup<T> {
|
||||
|
||||
private final OrmQueryDetail detail;
|
||||
|
||||
DFetchGroup(OrmQueryDetail detail) {
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrmQueryDetail detail() {
|
||||
return detail.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrmQueryDetail underlying() {
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.FetchConfig;
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchGroupBuilder;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.querydefn.SpiFetchGroup;
|
||||
|
||||
/**
|
||||
* Default implementation of the FetchGroupBuilder.
|
||||
*/
|
||||
class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
|
||||
|
||||
private static final FetchConfig FETCH_QUERY = new FetchConfig().query();
|
||||
|
||||
private static final FetchConfig FETCH_LAZY = new FetchConfig().lazy();
|
||||
|
||||
private final OrmQueryDetail detail;
|
||||
|
||||
DFetchGroupBuilder() {
|
||||
this.detail = new OrmQueryDetail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> select(String select) {
|
||||
detail.select(select);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetch(String path) {
|
||||
detail.fetch(path, null, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetch(String path, FetchGroup nestedGroup) {
|
||||
return fetchNested(path, nestedGroup, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetchQuery(String path, FetchGroup nestedGroup) {
|
||||
return fetchNested(path, nestedGroup, FETCH_QUERY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetchLazy(String path, FetchGroup nestedGroup) {
|
||||
return fetchNested(path, nestedGroup, FETCH_LAZY);
|
||||
}
|
||||
|
||||
private FetchGroupBuilder<T> fetchNested(String path, FetchGroup nestedGroup, FetchConfig fetchConfig) {
|
||||
|
||||
OrmQueryDetail nestedDetail = ((SpiFetchGroup) nestedGroup).underlying();
|
||||
detail.addNested(path, nestedDetail, fetchConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetchQuery(String path) {
|
||||
detail.fetch(path, null, FETCH_QUERY);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetchLazy(String path) {
|
||||
detail.fetch(path, null, FETCH_LAZY);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetch(String path, String properties) {
|
||||
detail.fetch(path, properties, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetchQuery(String path, String properties) {
|
||||
detail.fetch(path, properties, FETCH_QUERY);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroupBuilder<T> fetchLazy(String path, String properties) {
|
||||
detail.fetch(path, properties, FETCH_LAZY);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchGroup<T> build() {
|
||||
return new DFetchGroup<>(detail);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchGroupBuilder;
|
||||
import io.ebean.service.SpiFetchGroupService;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
/**
|
||||
* Default implementation of SpiFetchGroupService.
|
||||
*/
|
||||
public final class DFetchGroupService implements SpiFetchGroupService {
|
||||
|
||||
@Override
|
||||
public <T> FetchGroup<T> of(Class<T> cls, String select) {
|
||||
return new DFetchGroup<>(detail(select));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> FetchGroupBuilder<T> of(Class<T> cls) {
|
||||
return new DFetchGroupBuilder<>();
|
||||
}
|
||||
|
||||
private OrmQueryDetail detail(String select) {
|
||||
OrmQueryDetail detail = new OrmQueryDetail();
|
||||
detail.select(select);
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import io.ebean.Expression;
|
||||
import io.ebean.ExpressionFactory;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.FetchConfig;
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.FutureIds;
|
||||
import io.ebean.FutureList;
|
||||
@@ -1329,6 +1330,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultOrmQuery<T> select(FetchGroup fetchGroup) {
|
||||
this.detail = ((SpiFetchGroup)fetchGroup).detail();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultOrmQuery<T> fetch(String property) {
|
||||
return fetch(property, null, null);
|
||||
|
||||
@@ -55,6 +55,16 @@ public class OrmQueryDetail implements Serializable {
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a nested OrmQueryDetail to this detail.
|
||||
*/
|
||||
public void addNested(String path, OrmQueryDetail other, FetchConfig config) {
|
||||
fetch(path, other.baseProps.getProperties(), config);
|
||||
for (Map.Entry<String, OrmQueryProperties> entry : other.fetchPaths.entrySet()) {
|
||||
fetch(path + "." + entry.getKey(), entry.getValue().getProperties(), entry.getValue().getFetchConfig());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the hash for the query plan.
|
||||
*/
|
||||
|
||||
@@ -148,27 +148,30 @@ public class OrmQueryProperties implements Serializable {
|
||||
/**
|
||||
* Copy constructor.
|
||||
*/
|
||||
private OrmQueryProperties(OrmQueryProperties source) {
|
||||
|
||||
private OrmQueryProperties(OrmQueryProperties source, FetchConfig sourceFetchConfig) {
|
||||
this.fetchConfig = sourceFetchConfig;
|
||||
this.parentPath = source.parentPath;
|
||||
this.path = source.path;
|
||||
this.rawProperties = source.rawProperties;
|
||||
this.trimmedProperties = source.trimmedProperties;
|
||||
this.cache = source.cache;
|
||||
this.readOnly = source.readOnly;
|
||||
this.fetchConfig = source.fetchConfig;
|
||||
this.filterMany = source.filterMany;
|
||||
this.included = (source.included == null) ? null : new LinkedHashSet<>(source.included);
|
||||
if (includedBeanJoin != null) {
|
||||
this.includedBeanJoin = new HashSet<>(source.includedBeanJoin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the OrmQueryProperties.
|
||||
*/
|
||||
public OrmQueryProperties copy() {
|
||||
return new OrmQueryProperties(this);
|
||||
return new OrmQueryProperties(this, this.fetchConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy with the given fetch config.
|
||||
*/
|
||||
public OrmQueryProperties copy(FetchConfig fetchConfig) {
|
||||
return new OrmQueryProperties(this, fetchConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ebeaninternal.server.querydefn;
|
||||
|
||||
import io.ebean.FetchGroup;
|
||||
|
||||
/**
|
||||
* Service API of FetchGroup.
|
||||
*/
|
||||
public interface SpiFetchGroup<T> extends FetchGroup<T> {
|
||||
|
||||
/**
|
||||
* Return the detail to use for query execution.
|
||||
*/
|
||||
OrmQueryDetail detail();
|
||||
|
||||
/**
|
||||
* Return the underlying detail for copy purposes.
|
||||
*/
|
||||
OrmQueryDetail underlying();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.ebeaninternal.server.query.DFetchGroupService
|
||||
@@ -0,0 +1,115 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Address;
|
||||
import org.tests.model.basic.Contact;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class FetchGroupTest extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void simple() {
|
||||
|
||||
FetchGroup<Customer> fetch = FetchGroup.of(Customer.class, "name, status");
|
||||
|
||||
Query<Customer> query = Customer.find
|
||||
.query()
|
||||
.where()
|
||||
.ilike("name", "rob")
|
||||
.select(fetch);
|
||||
|
||||
query.findList();
|
||||
|
||||
assertThat(sqlOf(query)).contains("select t0.id, t0.name, t0.status from");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void nestedWithQueryJoin() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
FetchGroup<Customer> fetch = FetchGroup.of(Customer.class)
|
||||
.select("name, status")
|
||||
.fetchQuery("contacts", "firstName, lastName, email")
|
||||
.build();
|
||||
|
||||
Query<Customer> query = Customer.find
|
||||
.query()
|
||||
.where()
|
||||
.ilike("name", "rob")
|
||||
.select(fetch);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
query.findList();
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(2);
|
||||
assertThat(sql.get(0)).contains("select t0.id, t0.name, t0.status from o_customer");
|
||||
assertThat(sql.get(1)).contains("select t0.customer_id, t0.id, t0.first_name, t0.last_name, t0.email from contact");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void nestedWithQueryJoin_asNestedFetchGroup() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
FetchGroup<Contact> CT_NAME = FetchGroup.of(Contact.class, "firstName, lastName, email");
|
||||
|
||||
FetchGroup<Customer> fetch = FetchGroup.of(Customer.class)
|
||||
.select("name")
|
||||
.fetchQuery("contacts", CT_NAME)
|
||||
.build();
|
||||
|
||||
Query<Customer> query = Customer.find
|
||||
.query()
|
||||
.where()
|
||||
.ilike("name", "rob")
|
||||
.select(fetch);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
query.findList();
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(2);
|
||||
assertThat(sql.get(0)).contains("select t0.id, t0.name from o_customer");
|
||||
assertThat(sql.get(1)).contains(" from contact");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nested_withNestedFetchGroup() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
FetchGroup<Address> FGAddress = FetchGroup.of(Address.class)
|
||||
.select("line1, line2, city")
|
||||
.fetch("country", "name")
|
||||
.build();
|
||||
|
||||
FetchGroup<Customer> FBCustomer = FetchGroup.of(Customer.class)
|
||||
.select("name, version")
|
||||
.fetch("billingAddress", FGAddress)
|
||||
.build();
|
||||
|
||||
Query<Customer> query = Customer.find
|
||||
.query()
|
||||
.where()
|
||||
.ilike("name", "rob")
|
||||
.select(FBCustomer);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
query.findList();
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("select t0.id, t0.name, t0.version, t1.id, t1.line_1, t1.line_2, t1.city, t2.code, t2.name from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id left join o_country t2 on t2.code = t1.country_code ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,15 +3,14 @@ package org.tests.batchload;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.FetchConfig;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.TransactionalTestCase;
|
||||
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.Order;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -78,13 +77,15 @@ public class TestSecondaryQueries extends TransactionalTestCase {
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
Iterator<Order> orders = Ebean.find(Order.class)
|
||||
.select("status")
|
||||
.setMaxRows(10)
|
||||
.setUseCache(false)
|
||||
.findIterate();
|
||||
while (orders.hasNext()) {
|
||||
orders.next(); // dummy read
|
||||
try (QueryIterator<Order> orders =
|
||||
Ebean.find(Order.class).select("status")
|
||||
.setMaxRows(10)
|
||||
.setUseCache(false)
|
||||
.findIterate()) {
|
||||
|
||||
while (orders.hasNext()) {
|
||||
orders.next(); // dummy read
|
||||
}
|
||||
}
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.tests.o2m.jointable;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Version;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name="mkeygroup")
|
||||
public class JtMonkeyGroup {
|
||||
|
||||
@Id
|
||||
long pid;
|
||||
|
||||
String name;
|
||||
|
||||
/**
|
||||
* No cascading over to Monkey but we do maintain the join table regardless.
|
||||
*/
|
||||
@OneToMany
|
||||
@JoinTable
|
||||
List<JtMonkey> monkeys;
|
||||
|
||||
@Version
|
||||
long version;
|
||||
|
||||
public JtMonkeyGroup(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
public void setPid(long pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<JtMonkey> getMonkeys() {
|
||||
return monkeys;
|
||||
}
|
||||
|
||||
public void setMonkeys(List<JtMonkey> monkeys) {
|
||||
this.monkeys = monkeys;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(long version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.tests.o2m.jointable;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestOneToManyJoinTableNoTableName extends BaseTestCase {
|
||||
|
||||
private JtMonkeyGroup troop = new JtMonkeyGroup("Pink");
|
||||
|
||||
private JtMonkey m0 = new JtMonkey("Sim3");
|
||||
private JtMonkey m1 = new JtMonkey("Tim3");
|
||||
private JtMonkey m2 = new JtMonkey("Uim3");
|
||||
|
||||
private void initialInsert() {
|
||||
Ebean.saveAll(Arrays.asList(troop, m0, m1, m2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void base() {
|
||||
|
||||
initialInsert();
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
// make m0 dirty ... but no cascade saved?
|
||||
m0.setFoodPreference("camera");
|
||||
troop.getMonkeys().add(m0);
|
||||
troop.getMonkeys().add(m1);
|
||||
|
||||
Ebean.save(troop);
|
||||
|
||||
List<String> sql = LoggedSqlCollector.current();
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("insert into mkeygroup_monkey (mkeygroup_pid, monkey_mid) values (?, ?)");
|
||||
|
||||
int intersectionRows = Ebean.createSqlQuery("select count(*) as total from mkeygroup_monkey where mkeygroup_pid = ?")
|
||||
.setParameter(1, troop.getPid())
|
||||
.findOne()
|
||||
.getInteger("total");
|
||||
|
||||
assertThat(intersectionRows).isEqualTo(2);
|
||||
|
||||
LoggedSqlCollector.current();
|
||||
JtMonkeyGroup fetchTroop = Ebean.find(JtMonkeyGroup.class)
|
||||
.fetch("monkeys")
|
||||
.where().idEq(troop.getPid())
|
||||
.findOne();
|
||||
|
||||
assertThat(fetchTroop.getMonkeys()).hasSize(2);
|
||||
|
||||
sql = LoggedSqlCollector.current();
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(trimSql(sql.get(0))).contains("from mkeygroup t0 left join mkeygroup_monkey t1z_ on t1z_.mkeygroup_pid = t0.pid left join monkey t1 on t1.mid = t1z_.monkey_mid where t0.pid = ?");
|
||||
assertThat(trimSql(sql.get(0))).contains("select t0.pid, t0.name, t0.version, t1.mid, t1.name, t1.food_preference, t1.version");
|
||||
|
||||
Ebean.delete(troop);
|
||||
|
||||
sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(2);
|
||||
assertThat(sql.get(0)).contains("delete from mkeygroup_monkey where mkeygroup_pid = ?");
|
||||
assertThat(sql.get(1)).contains("delete from mkeygroup where pid=? and version=?");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.tests.query;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Contact;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestQueryFindNative extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void joinFromManyToOne() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
String sql =
|
||||
"select c.id, c.first_name, c.last_name, t.id, t.name " +
|
||||
" from contact c " +
|
||||
" join o_customer t on t.id = c.customer_id " +
|
||||
" where t.name like ? " +
|
||||
" order by c.first_name, c.last_name";
|
||||
|
||||
List<Contact> contacts =
|
||||
server()
|
||||
.findNative(Contact.class, sql)
|
||||
.setParameter(1, "Rob")
|
||||
.findList();
|
||||
|
||||
|
||||
assertThat(contacts).isNotEmpty();
|
||||
|
||||
Customer customer = contacts.get(0).getCustomer();
|
||||
assertThat(customer).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void joinFromOneToMany() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
String sql =
|
||||
"select cu.id, cu.name, ct.id, ct.first_name " +
|
||||
" from o_customer cu " +
|
||||
" left join contact ct on cu.id = ct.customer_id " +
|
||||
" where cu.name like ? " +
|
||||
" order by name";
|
||||
|
||||
List<Customer> customers =
|
||||
server()
|
||||
.findNative(Customer.class, sql)
|
||||
.setParameter(1, "Rob")
|
||||
.findList();
|
||||
|
||||
assertThat(customers).isNotEmpty();
|
||||
|
||||
List<Contact> contacts = customers.get(0).getContacts();
|
||||
assertThat(contacts).isNotEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user