diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java index 4625f6919..a4de477ac 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -603,7 +603,7 @@ public class SqlTreeBuilder { // no extra join required for embedded beans return null; } - SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp); + SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp, elGetValue.containsMany()); joinRegister.put(propertyName, extraJoin); return extraJoin; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index 804b66d8b..1b6ac4288 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -29,11 +29,14 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode { private final boolean manyJoin; + private final boolean pathContainsMany; + private List children; - public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty) { + public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty, boolean pathContainsMany) { this.prefix = prefix; this.assocBeanProperty = assocBeanProperty; + this.pathContainsMany = pathContainsMany; this.manyJoin = assocBeanProperty instanceof BeanPropertyAssocMany; } @@ -95,13 +98,16 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode { } } - if (!manyToMany) { + if (pathContainsMany) { + // "promote" to left outer as the path contains a many + assocBeanProperty.addJoin(SqlJoinType.OUTER, prefix, ctx); + } else if (!manyToMany) { assocBeanProperty.addJoin(joinType, prefix, ctx); } if (children != null) { - if (manyJoin) { + if (manyJoin || pathContainsMany) { // if AUTO then make all descendants use OUTER JOIN joinType = joinType.autoToOuter(); } diff --git a/src/test/java/com/avaje/tests/inheritance/TestInheritInsert.java b/src/test/java/com/avaje/tests/inheritance/TestInheritInsert.java index 4802a04fe..210812d9e 100644 --- a/src/test/java/com/avaje/tests/inheritance/TestInheritInsert.java +++ b/src/test/java/com/avaje/tests/inheritance/TestInheritInsert.java @@ -2,6 +2,8 @@ package com.avaje.tests.inheritance; import java.util.List; +import com.avaje.tests.model.basic.CarAccessory; +import com.avaje.tests.model.basic.CarFuse; import org.junit.Assert; import org.junit.Test; @@ -13,6 +15,9 @@ import com.avaje.tests.model.basic.Truck; import com.avaje.tests.model.basic.Vehicle; import com.avaje.tests.model.basic.VehicleDriver; +import static org.assertj.core.api.StrictAssertions.assertThat; +import static org.junit.Assert.assertNotNull; + public class TestInheritInsert extends BaseTestCase { @Test @@ -27,7 +32,7 @@ public class TestInheritInsert extends BaseTestCase { Truck t0 = (Truck) v; Assert.assertEquals(Double.valueOf(10d), t0.getCapacity()); Assert.assertEquals(Double.valueOf(10d), ((Truck) v).getCapacity()); - Assert.assertNotNull(t0.getId()); + assertNotNull(t0.getId()); } else { Assert.assertTrue("v not a Truck?", false); } @@ -43,7 +48,7 @@ public class TestInheritInsert extends BaseTestCase { if (v instanceof Truck) { Double capacity = ((Truck) v).getCapacity(); Assert.assertEquals(Double.valueOf(10d), capacity); - Assert.assertNotNull(v.getId()); + assertNotNull(v.getId()); } else { Assert.assertTrue("v not a Truck?", false); } @@ -73,9 +78,9 @@ public class TestInheritInsert extends BaseTestCase { query.where().eq("vehicle.licenseNumber", "MARIOS_CAR_LICENSE"); List drivers = query.findList(); - Assert.assertNotNull(drivers); + assertNotNull(drivers); Assert.assertEquals(1, drivers.size()); - Assert.assertNotNull(drivers.get(0)); + assertNotNull(drivers.get(0)); Assert.assertEquals("Mario", drivers.get(0).getName()); Assert.assertEquals("MARIOS_CAR_LICENSE", drivers.get(0).getVehicle().getLicenseNumber()); @@ -86,4 +91,34 @@ public class TestInheritInsert extends BaseTestCase { Ebean.save(car); } + + @Test + public void test_AtOrderBy_on_ChildOfChild() { + + Car car = new Car(); + car.setLicenseNumber("ABC"); + Ebean.save(car); + + CarFuse fuse = new CarFuse(); + fuse.setLocationCode("xdfg"); + Ebean.save(fuse); + + CarAccessory accessory = new CarAccessory(car, fuse); + Ebean.save(accessory); + + + Query query = Ebean.find(Car.class) + .fetch("accessories") + .where() + .eq("id", car.getId()) + .query(); + + Car result = query.findUnique(); + + assertThat(query.getGeneratedSql()).contains("order by t0.id, t2.location_code"); + assertThat(query.getGeneratedSql()).contains("left outer join car_fuse t2 on t2.id = t1.fuse_id"); + + assertNotNull(result); + } + } diff --git a/src/test/java/com/avaje/tests/model/basic/Car.java b/src/test/java/com/avaje/tests/model/basic/Car.java index cd6bac1f6..9dc74e43d 100644 --- a/src/test/java/com/avaje/tests/model/basic/Car.java +++ b/src/test/java/com/avaje/tests/model/basic/Car.java @@ -1,51 +1,51 @@ package com.avaje.tests.model.basic; -import java.util.HashSet; -import java.util.Set; - import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.Inheritance; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import java.util.HashSet; +import java.util.Set; @Entity @Inheritance @DiscriminatorValue("C") public class Car extends Vehicle { - private static final long serialVersionUID = 4716705779684333446L; + private static final long serialVersionUID = 4716705779684333446L; - private String driver; + private String driver; - - @ManyToOne - TruckRef carRef; - - @OneToMany(mappedBy="car") - private Set accessories = new HashSet(); - - public String getDriver() { - return driver; - } + @ManyToOne + TruckRef carRef; - public void setDriver(String driver) { - this.driver = driver; - } + @OneToMany(mappedBy = "car") + @OrderBy("fuse.locationCode") + private Set accessories = new HashSet(); - public TruckRef getCarRef() { - return carRef; - } + public String getDriver() { + return driver; + } - public void setCarRef(TruckRef carRef) { - this.carRef = carRef; - } + public void setDriver(String driver) { + this.driver = driver; + } - public Set getAccessories() { - return accessories; - } + public TruckRef getCarRef() { + return carRef; + } - public void setAccessories(Set accessories) { - this.accessories = accessories; - } + public void setCarRef(TruckRef carRef) { + this.carRef = carRef; + } + + public Set getAccessories() { + return accessories; + } + + public void setAccessories(Set accessories) { + this.accessories = accessories; + } } diff --git a/src/test/java/com/avaje/tests/model/basic/CarAccessory.java b/src/test/java/com/avaje/tests/model/basic/CarAccessory.java index a4c139c1e..1087c12ca 100644 --- a/src/test/java/com/avaje/tests/model/basic/CarAccessory.java +++ b/src/test/java/com/avaje/tests/model/basic/CarAccessory.java @@ -4,28 +4,44 @@ import javax.persistence.Entity; import javax.persistence.ManyToOne; @Entity -public class CarAccessory extends BasicDomain{ - - private static final long serialVersionUID = 1L; +public class CarAccessory extends BasicDomain { - private String name; - - @ManyToOne - private Car car; + private static final long serialVersionUID = 1L; - public String getName() { - return name; - } + private String name; - public void setName(String name) { - this.name = name; - } + @ManyToOne(optional = false) + private CarFuse fuse; - public Car getCar() { - return car; - } + @ManyToOne + private Car car; - public void setCar(Car car) { - this.car = car; - } + public CarAccessory(Car car, CarFuse fuse) { + this.car = car; + this.fuse = fuse; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + public CarFuse getFuse() { + return fuse; + } + + public void setFuse(CarFuse fuse) { + this.fuse = fuse; + } } diff --git a/src/test/java/com/avaje/tests/model/basic/CarFuse.java b/src/test/java/com/avaje/tests/model/basic/CarFuse.java new file mode 100644 index 000000000..1148180d9 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/basic/CarFuse.java @@ -0,0 +1,29 @@ +package com.avaje.tests.model.basic; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class CarFuse { + + @Id + Long id; + + String locationCode; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLocationCode() { + return locationCode; + } + + public void setLocationCode(String locationCode) { + this.locationCode = locationCode; + } +} diff --git a/src/test/java/com/avaje/tests/text/json/TestTextJsonInheritance.java b/src/test/java/com/avaje/tests/text/json/TestTextJsonInheritance.java index 2a0999f0b..e4f626c85 100644 --- a/src/test/java/com/avaje/tests/text/json/TestTextJsonInheritance.java +++ b/src/test/java/com/avaje/tests/text/json/TestTextJsonInheritance.java @@ -3,6 +3,8 @@ package com.avaje.tests.text.json; import java.io.IOException; import java.util.List; +import com.avaje.tests.model.basic.CarAccessory; +import com.avaje.tests.model.basic.CarFuse; import org.junit.Assert; import org.junit.Test; @@ -38,6 +40,8 @@ public class TestTextJsonInheritance extends BaseTestCase { private void setupData() { + Ebean.createUpdate(CarAccessory.class, "delete from CarAccessory").execute(); + Ebean.createUpdate(CarFuse.class, "delete from CarFuse").execute(); Ebean.createUpdate(Trip.class, "delete from trip").execute(); Ebean.createUpdate(VehicleDriver.class, "delete from vehicleDriver").execute(); Ebean.createUpdate(Vehicle.class, "delete from vehicle").execute();