#549 - Regression: ArrayIndexOutOfBoundsException when lazy loading on bean with inheritance

This commit is contained in:
Robin Bygrave
2016-02-02 11:24:46 +13:00
parent c4f28efc45
commit 67ce6ef58e
5 changed files with 79 additions and 43 deletions
@@ -343,28 +343,10 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Check if the lazy load succeeded. If not then mark this bean as having
* failed lazy loading due to the underlying row being deleted.
* <p>
* We mark the bean this way rather than immediately fail as we might be batch
* lazy loading and this bean might not be used by the client code at all.
* Instead we will fail as soon as the client code tries to use this bean.
* </p>
* @param lazyLoadPropertyIndex the property that is expected to be loaded
* Set lazy load failure flag.
*/
public boolean isLazyLoadFailure(int lazyLoadPropertyIndex) {
if (lazyLoadProperty != -1 || !isLoadedProperty(lazyLoadPropertyIndex)) {
lazyLoadFailure = true;
return true;
}
lazyLoadFailure = false;
return false;
}
/**
* Set the Id of the owner bean.
*/
public void setOwnerId(Object ownerId) {
public void setLazyLoadFailure(Object ownerId) {
this.lazyLoadFailure = true;
this.ownerId = ownerId;
}
@@ -8,7 +8,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Request for loading ManyToOne and OneToOne relationships.
@@ -96,7 +98,7 @@ public class LoadBeanRequest extends LoadRequest {
*/
public List<Object> getIdList(int batchSize) {
ArrayList<Object> idList = new ArrayList<Object>(batchSize);
List<Object> idList = new ArrayList<Object>(batchSize);
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
for (int i = 0; i < batch.size(); i++) {
@@ -124,7 +126,7 @@ public class LoadBeanRequest extends LoadRequest {
/**
* Configure the query for lazy loading execution.
*/
public void configureQuery(SpiQuery<?> query) {
public void configureQuery(SpiQuery<?> query, List<Object> idList) {
query.setMode(SpiQuery.Mode.LAZYLOAD_BEAN);
query.setPersistenceContext(loadBuffer.getPersistenceContext());
@@ -138,6 +140,12 @@ public class LoadBeanRequest extends LoadRequest {
}
loadBuffer.configureQuery(query, getLazyLoadProperty());
if (idList.size() == 1) {
query.where().idEq(idList.get(0));
} else {
query.where().idIn(idList);
}
}
/**
@@ -145,25 +153,27 @@ public class LoadBeanRequest extends LoadRequest {
*/
public void postLoad(List<?> list) {
if (isLoadCache()) {
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
for (int i = 0; i < list.size(); i++) {
desc.cacheBeanPutData((EntityBean) list.get(i));
Set<Object> loadedIds = new HashSet<Object>();
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
// collect Ids and maybe load bean cache
for (int i = 0; i < list.size(); i++) {
EntityBean loadedBean = (EntityBean) list.get(i);
loadedIds.add(desc.getId(loadedBean));
if (isLoadCache()) {
desc.cacheBeanPutData(loadedBean);
}
}
if (lazyLoadPropertyIndex > -1) {
// this is a lazy loading query so check for lazy loading failure (due to deleted rows)
for (int i = 0; i < batch.size(); i++) {
// check if the underlying row in DB was deleted. Mark the bean as 'failed' if
// necessary but allow processing to continue until it is accessed by client code
EntityBeanIntercept ebi = batch.get(i);
// all beans in the batch should have this property loaded now
if (ebi.isLazyLoadFailure(lazyLoadPropertyIndex)) {
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
Object beanId = desc.getId(ebi.getOwner());
ebi.setOwnerId(beanId);
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + beanId + " - expecting when bean has been deleted");
Object id = desc.getId(ebi.getOwner());
if (!loadedIds.contains(id)) {
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + id + " - expecting when bean has been deleted");
ebi.setLazyLoadFailure(id);
}
}
}
@@ -206,16 +206,9 @@ public class DefaultBeanLoader {
}
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(loadRequest.getBeanType());
loadRequest.configureQuery(query);
if (idList.size() == 1) {
query.where().idEq(idList.get(0));
} else {
query.where().idIn(idList);
}
loadRequest.configureQuery(query, idList);
List<?> list = executeQuery(loadRequest, query);
loadRequest.postLoad(list);
// log the query (for testing secondary queries)
@@ -0,0 +1,41 @@
package com.avaje.tests.inheritance;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.basic.Car;
import com.avaje.tests.model.basic.Truck;
import com.avaje.tests.model.basic.Vehicle;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestInheritanceBatchLazyLoad {
@Test
public void lazyLoadProperty_when_propertyNotOnAllInheritanceTypes() {
Car c = new Car();
c.setLicenseNumber("VZVZ1");
c.setDriver("CarDriver");
Ebean.save(c);
Truck t = new Truck();
t.setLicenseNumber("VZVZ2");
t.setCapacity(20D);
Ebean.save(t);
List<Vehicle> list = Ebean.find(Vehicle.class)
.select("licenseNumber")
.where().startsWith("licenseNumber","VZVZ")
.order().asc("licenseNumber")
.findList();
assertThat(list).hasSize(2);
Car car = (Car)list.get(0);
car.getNotes();
}
}
@@ -19,12 +19,14 @@ public class Car extends Vehicle {
private String driver;
@ManyToOne
TruckRef carRef;
private TruckRef carRef;
@OneToMany(mappedBy = "car")
@OrderBy("fuse.locationCode")
private Set<CarAccessory> accessories = new HashSet<CarAccessory>();
private String notes;
public String getDriver() {
return driver;
}
@@ -33,6 +35,14 @@ public class Car extends Vehicle {
this.driver = driver;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public TruckRef getCarRef() {
return carRef;
}