diff --git a/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/src/main/java/io/ebean/bean/EntityBeanIntercept.java index 8d23fdbcc..1f0656eec 100644 --- a/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -1,6 +1,7 @@ package io.ebean.bean; import io.ebean.DB; +import io.ebean.Database; import io.ebean.ValuePair; import javax.persistence.EntityNotFoundException; @@ -104,9 +105,6 @@ public final class EntityBeanIntercept implements Serializable { /** * Create a intercept with a given entity. - *
- * Refer to agent ProxyConstructor. - *
*/ public EntityBeanIntercept(Object ownerBean) { this.owner = (EntityBean) ownerBean; @@ -821,14 +819,14 @@ public final class EntityBeanIntercept implements Serializable { synchronized (this) { if (beanLoader == null) { - BeanLoader serverLoader = (BeanLoader) DB.byName(ebeanServerName); - if (serverLoader == null) { - throw new PersistenceException("Server [" + ebeanServerName + "] was not found?"); + final Database database = DB.byName(ebeanServerName); + if (database == null) { + throw new PersistenceException("Database [" + ebeanServerName + "] was not found?"); } // For stand alone reference bean or after deserialisation lazy load // using the ebeanServer. Synchronise only on the bean. - loadBeanInternal(loadProperty, serverLoader); + loadBeanInternal(loadProperty, database.getPluginApi()); return; } } diff --git a/src/main/java/io/ebean/bean/SingleBeanLoader.java b/src/main/java/io/ebean/bean/SingleBeanLoader.java new file mode 100644 index 000000000..fd4ce6651 --- /dev/null +++ b/src/main/java/io/ebean/bean/SingleBeanLoader.java @@ -0,0 +1,48 @@ +package io.ebean.bean; + +import io.ebean.Database; + +/** + * BeanLoader used when single beans are loaded (which is usually not ideal / N+1). + */ +public abstract class SingleBeanLoader implements BeanLoader { + + protected final Database database; + + SingleBeanLoader(Database database) { + this.database = database; + } + + @Override + public String getName() { + return database.getName(); + } + + /** + * Single bean lazy loaded when bean from L2 cache. + */ + public static class L2 extends SingleBeanLoader { + public L2(Database database) { + super(database); + } + + @Override + public void loadBean(EntityBeanIntercept ebi) { + database.getPluginApi().loadBeanL2(ebi); + } + } + + /** + * Single bean lazy loaded when a reference bean. + */ + public static class Ref extends SingleBeanLoader { + public Ref(Database database) { + super(database); + } + + @Override + public void loadBean(EntityBeanIntercept ebi) { + database.getPluginApi().loadBeanRef(ebi); + } + } +} diff --git a/src/main/java/io/ebean/plugin/SpiServer.java b/src/main/java/io/ebean/plugin/SpiServer.java index d7bf69b62..e672158d8 100644 --- a/src/main/java/io/ebean/plugin/SpiServer.java +++ b/src/main/java/io/ebean/plugin/SpiServer.java @@ -1,6 +1,8 @@ package io.ebean.plugin; import io.ebean.EbeanServer; +import io.ebean.bean.BeanLoader; +import io.ebean.bean.EntityBeanIntercept; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; @@ -10,7 +12,7 @@ import java.util.List; /** * Extensions to Database API made available to plugins. */ -public interface SpiServer extends EbeanServer { +public interface SpiServer extends EbeanServer, BeanLoader { /** * Return the serverConfig. @@ -52,4 +54,19 @@ public interface SpiServer extends EbeanServer { */ DataSource getReadOnlyDataSource(); + /** + * Invoke lazy loading on this single bean (reference bean). + */ + void loadBeanRef(EntityBeanIntercept ebi); + + /** + * Invoke lazy loading on this single bean (L2 cache bean). + */ + void loadBeanL2(EntityBeanIntercept ebi); + + /** + * Invoke lazy loading on this single bean when no BeanLoader is set. + * Typically due to serialisation or multiple stateless updates. + */ + void loadBean(EntityBeanIntercept ebi); } diff --git a/src/main/java/io/ebeaninternal/api/ExtraMetrics.java b/src/main/java/io/ebeaninternal/api/ExtraMetrics.java index dac31b2a9..2d950c40a 100644 --- a/src/main/java/io/ebeaninternal/api/ExtraMetrics.java +++ b/src/main/java/io/ebeaninternal/api/ExtraMetrics.java @@ -2,6 +2,7 @@ package io.ebeaninternal.api; import io.ebean.meta.MetricType; import io.ebean.meta.MetricVisitor; +import io.ebean.metric.CountMetric; import io.ebean.metric.MetricFactory; import io.ebean.metric.TimedMetric; @@ -12,6 +13,9 @@ public class ExtraMetrics { private final TimedMetric bindCapture; private final TimedMetric planCollect; + private final CountMetric loadOneL2; + private final CountMetric loadOneRef; + private final CountMetric loadOneNoLoader; /** * Create the extra metrics. @@ -20,6 +24,9 @@ public class ExtraMetrics { final MetricFactory factory = MetricFactory.get(); this.bindCapture = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.bindcapture"); this.planCollect = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.collect"); + this.loadOneL2 = factory.createCountMetric(MetricType.ORM, "loadone.l2"); + this.loadOneRef = factory.createCountMetric(MetricType.ORM, "loadone.ref"); + this.loadOneNoLoader = factory.createCountMetric(MetricType.ORM, "loadone.noloader"); } /** @@ -36,11 +43,38 @@ public class ExtraMetrics { return planCollect; } + /** + * Increment counter for lazy loading one bean from L2 cache. + * All good when lazy loading also hits L2 cache. + */ + public void incrementLoadOneL2() { + loadOneL2.increment(); + } + + /** + * Increment counter for lazy loading on reference bean. + * We ought to be able to avoid this by changing to a tuned query. + */ + public void incrementLoadOneRef() { + loadOneRef.increment(); + } + + /** + * Increment counter for lazy loading one bean due to no loader. + * Likely due to multiple stateless updates or serialisation. + */ + public void incrementLoadOneNoLoader() { + loadOneNoLoader.increment(); + } + /** * Collect the metrics. */ public void visitMetrics(MetricVisitor visitor) { bindCapture.visit(visitor); planCollect.visit(visitor); + loadOneL2.visit(visitor); + loadOneRef.visit(visitor); + loadOneNoLoader.visit(visitor); } } diff --git a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index 36b1f3901..43424c0c5 100644 --- a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -31,7 +31,7 @@ import java.util.function.Predicate; /** * Service Provider extension to EbeanServer. */ -public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader, BeanCollectionLoader { +public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollectionLoader { /** * Return the log manager. diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 402d0eb61..0df57d6d7 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -580,6 +580,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { @Override public void loadBean(EntityBeanIntercept ebi) { beanLoader.loadBean(ebi); + extraMetrics.incrementLoadOneNoLoader(); + } + + @Override + public void loadBeanRef(EntityBeanIntercept ebi) { + beanLoader.loadBean(ebi); + extraMetrics.incrementLoadOneRef(); + } + + @Override + public void loadBeanL2(EntityBeanIntercept ebi) { + beanLoader.loadBean(ebi); + extraMetrics.incrementLoadOneL2(); } @Override diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 3cdffe723..190e29e77 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -10,6 +10,7 @@ import io.ebean.bean.BeanCollection; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; import io.ebean.bean.PersistenceContext; +import io.ebean.bean.SingleBeanLoader; import io.ebean.cache.QueryCacheEntry; import io.ebean.config.EncryptKey; import io.ebean.config.ServerConfig; @@ -2040,7 +2041,7 @@ public class BeanDescriptor