#535 - Bean lazy loading can execute an extra query when one of the lazy loaded beans has been deleted

This commit is contained in:
Robin Bygrave
2016-01-21 20:51:32 +13:00
parent 64618da6a7
commit 6ab974a330
5 changed files with 55 additions and 24 deletions
@@ -1,5 +1,10 @@
package com.avaje.ebean.bean;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.ValuePair;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
@@ -11,12 +16,6 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.ValuePair;
/**
* This is the object added to every entity bean using byte code enhancement.
* <p>
@@ -92,6 +91,8 @@ public final class EntityBeanIntercept implements Serializable {
private int lazyLoadProperty = -1;
private Object ownerId;
/**
* Create a intercept with a given entity.
* <p>
@@ -349,11 +350,22 @@ public final class EntityBeanIntercept implements Serializable {
* 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
*/
public void checkLazyLoadFailure() {
if (lazyLoadProperty != -1) {
this.lazyLoadFailure = true;
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) {
this.ownerId = ownerId;
}
/**
@@ -748,7 +760,7 @@ public final class EntityBeanIntercept implements Serializable {
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("Bean has been deleted - lazy loading failed");
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
}
if (lazyLoadProperty == -1) {
@@ -763,7 +775,7 @@ public final class EntityBeanIntercept implements Serializable {
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Bean has been deleted - lazy loading failed");
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
}
// bean should be loaded and intercepting now. setLoaded() has
@@ -4,6 +4,8 @@ import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -13,10 +15,14 @@ import java.util.List;
*/
public class LoadBeanRequest extends LoadRequest {
private static final Logger logger = LoggerFactory.getLogger(LoadBeanRequest.class);
private final List<EntityBeanIntercept> batch;
private final LoadBeanBuffer loadBuffer;
private final int lazyLoadPropertyIndex;
private final String lazyLoadProperty;
private final boolean loadCache;
@@ -24,21 +30,24 @@ public class LoadBeanRequest extends LoadRequest {
/**
* Construct for lazy load request.
*/
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, String lazyLoadProperty, boolean loadCache) {
this(LoadBuffer, null, true, lazyLoadProperty, loadCache);
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, int lazyLoadPropertyIndex, String lazyLoadProperty, boolean loadCache) {
this(LoadBuffer, null, true, lazyLoadPropertyIndex, lazyLoadProperty, loadCache);
}
/**
* Construct for secondary query.
*/
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, OrmQueryRequest<?> parentRequest) {
this(LoadBuffer, parentRequest, false, null, false);
this(LoadBuffer, parentRequest, false, -1, null, false);
}
private LoadBeanRequest(LoadBeanBuffer loadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy, String lazyLoadProperty, boolean loadCache) {
private LoadBeanRequest(LoadBeanBuffer loadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy,
int lazyLoadPropertyIndex, String lazyLoadProperty, boolean loadCache) {
super(parentRequest, lazy);
this.loadBuffer = loadBuffer;
this.batch = loadBuffer.getBatch();
this.lazyLoadPropertyIndex = lazyLoadPropertyIndex;
this.lazyLoadProperty = lazyLoadProperty;
this.loadCache = loadCache;
}
@@ -143,10 +152,20 @@ public class LoadBeanRequest extends LoadRequest {
}
}
for (int i = 0; i < batch.size(); i++) {
// Check if the underlying row in DB was deleted. Mark this bean as 'failed' if
// necessary but allow processing to continue until it is accessed by client code
batch.get(i).checkLazyLoadFailure();
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");
}
}
}
}
}
@@ -183,7 +183,7 @@ public class LoadManyRequest extends LoadRequest {
if (logger.isDebugEnabled()) {
EntityBean ownerBean = bc.getOwnerBean();
Object parentId = desc.getId(ownerBean);
logger.debug("BeanCollection after lazy load was empty. type[" + ownerBean.getClass() + "] id[" + parentId + "] owner[" + ownerBean + "]");
logger.debug("BeanCollection after lazy load was empty. type:" + ownerBean.getClass().getName() + " id:" + parentId + " owner:" + ownerBean);
}
} else if (isLoadCache()) {
Object parentId = desc.getId(bc.getOwnerBean());
@@ -85,7 +85,7 @@ public class DefaultBeanLoader {
SpiQuery<?> query = loadRequest.createQuery(server, batchSize);
executeLazyLoadQuery(loadRequest, query);
executeQuery(loadRequest, query);
loadRequest.postLoad();
@@ -214,7 +214,7 @@ public class DefaultBeanLoader {
query.where().idIn(idList);
}
List<?> list = executeLazyLoadQuery(loadRequest, query);
List<?> list = executeQuery(loadRequest, query);
loadRequest.postLoad(list);
@@ -225,7 +225,7 @@ public class DefaultBeanLoader {
/**
* Execute the lazy load query taking into account MySql transaction oddness.
*/
private List<?> executeLazyLoadQuery(LoadRequest loadRequest, SpiQuery<?> query) {
private List<?> executeQuery(LoadRequest loadRequest, SpiQuery<?> query) {
if (onIterateUseExtraTxn && loadRequest.isParentFindIterate()) {
// MySql - we need a different transaction to execute the secondary query
SpiTransaction extraTxn = server.createQueryTransaction();
@@ -190,7 +190,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
}
}
LoadBeanRequest req = new LoadBeanRequest(this, ebi.getLazyLoadProperty(), context.hitCache);
LoadBeanRequest req = new LoadBeanRequest(this, ebi.getLazyLoadPropertyIndex(), ebi.getLazyLoadProperty(), context.hitCache);
context.desc.getEbeanServer().loadBean(req);
}