#1795 - Refactor bind padding for Id IN expressions (based on internal multiValueSupport and simple id)

This commit is contained in:
rob bygrave
2019-08-18 16:33:36 +12:00
parent d7d8c3e651
commit 9155716752
14 changed files with 214 additions and 113 deletions
@@ -24,6 +24,11 @@ public interface BeanQueryRequest<T> {
*/
Query<T> getQuery();
/**
* Return true if an Id IN expression should have the bind parameters padded.
*/
boolean isPadInExpression();
/**
* Return true if multi-value binding using Array or Table Values is supported.
*/
@@ -4,8 +4,6 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
@@ -17,8 +15,6 @@ import java.util.Set;
*/
public class LoadBeanRequest extends LoadRequest {
private static final Logger logger = LoggerFactory.getLogger(LoadBeanRequest.class);
private final List<EntityBeanIntercept> batch;
private final LoadBeanBuffer loadBuffer;
@@ -56,7 +52,7 @@ public class LoadBeanRequest extends LoadRequest {
return loadBuffer.getBeanDescriptor().getBeanType();
}
public boolean isLoadCache() {
private boolean isLoadCache() {
return loadCache;
}
@@ -75,17 +71,10 @@ public class LoadBeanRequest extends LoadRequest {
/**
* Return the load context.
*/
public LoadBeanBuffer getLoadContext() {
private LoadBeanBuffer getLoadContext() {
return loadBuffer;
}
/**
* Return the property that invoked the lazy loading.
*/
public String getLazyLoadProperty() {
return lazyLoadProperty;
}
public int getBatchSize() {
return getLoadContext().getBatchSize();
}
@@ -93,30 +82,14 @@ public class LoadBeanRequest extends LoadRequest {
/**
* Return the list of Id values for the beans in the lazy load buffer.
*/
public List<Object> getIdList(int batchSize) {
public List<Object> getIdList() {
List<Object> idList = new ArrayList<>(batchSize);
List<Object> idList = new ArrayList<>();
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
for (EntityBeanIntercept ebi : batch) {
EntityBean bean = ebi.getOwner();
idList.add(desc.getId(bean));
idList.add(desc.getId(ebi.getOwner()));
}
if (!desc.isMultiValueIdSupported() && !idList.isEmpty()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
// for performance make up the Id's to the batch size
// so we get the same query (for Ebean and the db)
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
// just add the first Id again
idList.add(firstId);
}
}
}
return idList;
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.api;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.util.StringHelper;
import io.ebeaninternal.server.core.BindPadding;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -65,13 +66,6 @@ public class LoadManyRequest extends LoadRequest {
return batch;
}
/**
* Return the load context.
*/
public LoadManyBuffer getLoadContext() {
return loadContext;
}
/**
* Return true if lazy loading should only load the id values.
* <p>
@@ -80,14 +74,14 @@ public class LoadManyRequest extends LoadRequest {
* used.
* </p>
*/
public boolean isOnlyIds() {
private boolean isOnlyIds() {
return onlyIds;
}
/**
* Return true if we should load the Collection ids into the cache.
*/
public boolean isLoadCache() {
private boolean isLoadCache() {
return loadCache;
}
@@ -98,22 +92,16 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBatchSize();
}
private List<Object> getParentIdList(int batchSize) {
private List<Object> getParentIdList() {
ArrayList<Object> idList = new ArrayList<>(batchSize);
List<Object> idList = new ArrayList<>();
BeanPropertyAssocMany<?> many = getMany();
for (BeanCollection<?> bc : batch) {
idList.add(many.getParentId(bc.getOwnerBean()));
}
if (!many.getTargetDescriptor().isMultiValueIdSupported()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
}
}
if (many.getTargetDescriptor().isPadInExpression()) {
BindPadding.padIds(idList);
}
return idList;
@@ -123,7 +111,7 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBeanProperty();
}
public SpiQuery<?> createQuery(SpiEbeanServer server, int batchSize) {
public SpiQuery<?> createQuery(SpiEbeanServer server) {
BeanPropertyAssocMany<?> many = getMany();
@@ -142,10 +130,7 @@ public class LoadManyRequest extends LoadRequest {
}
query.setLazyLoadForParents(many);
List<Object> idList = getParentIdList(batchSize);
many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore());
many.addWhereParentIdIn(query, getParentIdList(), loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
@@ -0,0 +1,57 @@
package io.ebeaninternal.server.core;
import java.util.List;
/**
* Supports padding bind parameters for IN expressions.
* <p>
* We do this in order to get better hit ratio on DB query plans.
* </p>
*/
public final class BindPadding {
/**
* Pad out the Ids into common bucket sizes.
*
* @param idCollection The collection of Ids values being bound.
*/
public static void padIds(List<Object> idCollection) {
int extraIds = padding(idCollection.size());
if (extraIds > 0) {
// for performance make up the Id's to the batch size
// so we get the same query (for Ebean and the db)
Object firstId = idCollection.get(0);
for (int i = 0; i < extraIds; i++) {
// just add the first Id again
idCollection.add(firstId);
}
}
}
/**
* Extra padding on binding id's in order to get better hit ratio on DB prepared statements / query plans.
*/
static int padding(int size) {
if (size == 1) {
return 0;
}
if (size <= 5) {
return 5 - size;
}
if (size <= 10) {
return 10 - size;
}
if (size <= 20) {
return 20 - size;
}
if (size <= 40) {
return 40 - size;
}
if (size <= 50) {
return 50 - size;
}
return size <= 100 ? 100 - size : 0;
}
}
@@ -38,54 +38,14 @@ class DefaultBeanLoader {
this.onIterateUseExtraTxn = server.getDatabasePlatform().useExtraTransactionOnIterateSecondaryQueries();
}
/**
* Return a batch size that might be less than the requestedBatchSize.
* <p>
* This means we can have large and variable requestedBatchSizes.
* </p>
* <p>
* We want to restrict the number of different batch sizes as we want to
* re-use the query plan cache and get DB statement re-use.
* </p>
*/
private int getBatchSize(int batchSize) {
if (batchSize == 1) {
// there is only one bean/collection to load
return 1;
}
if (batchSize <= 5) {
// anything less than 5 becomes 5
return 5;
}
if (batchSize <= 10) {
return 10;
}
if (batchSize <= 20) {
return 20;
}
if (batchSize <= 50) {
return 50;
}
if (batchSize <= 100) {
return 100;
}
return batchSize;
}
void refreshMany(EntityBean parentBean, String propertyName) {
refreshMany(parentBean, propertyName, null);
}
void loadMany(LoadManyRequest loadRequest) {
List<BeanCollection<?>> batch = loadRequest.getBatch();
int batchSize = getBatchSize(batch.size());
SpiQuery<?> query = loadRequest.createQuery(server, batchSize);
SpiQuery<?> query = loadRequest.createQuery(server);
executeQuery(loadRequest, query);
loadRequest.postLoad();
}
@@ -97,7 +57,7 @@ class DefaultBeanLoader {
loadManyInternal(parentBean, propertyName, null, false, onlyIds);
}
void refreshMany(EntityBean parentBean, String propertyName, Transaction t) {
private void refreshMany(EntityBean parentBean, String propertyName, Transaction t) {
loadManyInternal(parentBean, propertyName, t, true, false);
}
@@ -147,8 +107,7 @@ class DefaultBeanLoader {
query.setLoadDescription("+lazy", null);
}
String idProperty = parentDesc.getIdBinder().getIdProperty();
query.select(idProperty);
query.select(parentDesc.getIdBinder().getIdProperty());
if (onlyIds) {
query.fetch(many.getName(), many.getTargetIdProperty());
@@ -192,9 +151,7 @@ class DefaultBeanLoader {
throw new RuntimeException("Nothing in batch?");
}
int batchSize = getBatchSize(batch.size());
List<Object> idList = loadRequest.getIdList(batchSize);
List<Object> idList = loadRequest.getIdList();
if (idList.isEmpty()) {
// everything was loaded from cache
return;
@@ -315,6 +272,5 @@ class DefaultBeanLoader {
}
desc.resetManyProperties(dbBean);
}
}
@@ -115,6 +115,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
}
@Override
public boolean isPadInExpression() {
return beanDescriptor.isPadInExpression();
}
@Override
public boolean isMultiValueIdSupported() {
return beanDescriptor.isMultiValueIdSupported();
@@ -1835,6 +1835,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return multiValueSupported && isSimpleId();
}
/**
* Return true if Id IN expression should have bind parameters padded.
*/
public boolean isPadInExpression() {
return !multiValueSupported && isSimpleId();
}
/**
* Return the sql for binding an id. This is the columns with table alias that
* make up the id.
@@ -5,6 +5,7 @@ import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.server.core.BindPadding;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.id.IdBinder;
@@ -46,6 +47,10 @@ public class IdInExpression extends NonPrepareExpression {
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
multiValueIdSupported = request.isMultiValueIdSupported();
if (!multiValueIdSupported && !idCollection.isEmpty() && request.isPadInExpression()) {
// pad out the ids for better hit ratio on DB query plans
BindPadding.padIds(idCollection);
}
}
@Override
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.loadcontext;
import io.ebean.CacheMode;
import io.ebean.bean.BeanLoader;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
@@ -20,6 +21,8 @@ import java.util.Set;
*/
class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
private final boolean cache;
private List<LoadBuffer> bufferList;
private LoadBuffer currentBuffer;
@@ -29,6 +32,7 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(firstBatchSize);
this.cache = (queryProps == null) ? false : queryProps.isCache();
}
/**
@@ -43,6 +47,9 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
private void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
if (cache) {
query.setBeanCacheMode(CacheMode.ON);
}
setLabel(query);
parent.propagateQueryState(query, desc.isDocStoreMapped());
query.setParentNode(objectGraphNode);