Compare commits

...
24 changed files with 814 additions and 20 deletions
+2 -2
View File
@@ -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>
+114
View File
@@ -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();
}
+5
View File
@@ -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
+115
View File
@@ -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();
}
}