mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-24 03:57:45 +00:00
#1605 - RuntimeException: Nothing in batch? ... when lazy loading on partially populated L2 bean cache
This commit is contained in:
@@ -1502,19 +1502,18 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
* Hit the bean cache trying to load a list/batch of entities.
|
||||
* Return the set of entities that were successfully loaded from L2 cache.
|
||||
*/
|
||||
public boolean cacheBeanLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) {
|
||||
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
|
||||
public Set<EntityBeanIntercept> cacheBeanLoadAll(List<EntityBeanIntercept> list, PersistenceContext persistenceContext, int lazyLoadProperty, String propertyName) {
|
||||
return cacheHelp.beanCacheLoadAll(list, persistenceContext, lazyLoadProperty, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
*/
|
||||
public boolean cacheBeanLoad(EntityBeanIntercept ebi, PersistenceContext context) {
|
||||
EntityBean bean = ebi.getOwner();
|
||||
Object id = getId(bean);
|
||||
return cacheBeanLoad(bean, ebi, id, context);
|
||||
public boolean cacheBeanLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) {
|
||||
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -700,7 +701,56 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
* Load a batch of entities from L2 bean cache checking the lazy loaded property is loaded.
|
||||
*/
|
||||
Set<EntityBeanIntercept> beanCacheLoadAll(List<EntityBeanIntercept> list, PersistenceContext context, int lazyLoadProperty, String propertyName) {
|
||||
|
||||
Map<Object, EntityBeanIntercept> ebis = new HashMap<>();
|
||||
for (EntityBeanIntercept ebi : list) {
|
||||
ebis.put(desc.cacheKeyForBean(ebi.getOwner()), ebi);
|
||||
}
|
||||
|
||||
|
||||
Map<Object, Object> hits = getBeanCache().getAll(ebis.keySet());
|
||||
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD ALL {}({}) - got hits ({})", cacheName, ebis.keySet(), hits.size());
|
||||
}
|
||||
|
||||
Set<EntityBeanIntercept> loaded = new HashSet<>();
|
||||
|
||||
Iterator<Map.Entry<Object, Object>> iterator = hits.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<Object, Object> hit = iterator.next();
|
||||
|
||||
Object key = hit.getKey();
|
||||
EntityBeanIntercept ebi = ebis.remove(key);
|
||||
CachedBeanData cacheData = (CachedBeanData) hit.getValue();
|
||||
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(propertyName)) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, key, propertyName);
|
||||
}
|
||||
iterator.remove();
|
||||
|
||||
} else {
|
||||
CachedBeanDataToBean.load(desc, ebi.getOwner(), cacheData, context);
|
||||
loaded.add(ebi);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ebis.isEmpty() && beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, ebis.keySet());
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the single bean from the cache.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, String key, PersistenceContext context) {
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.ebeaninternal.server.querydefn.OrmQueryProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Default implementation of LoadBeanContext.
|
||||
@@ -174,22 +175,19 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.hitCache && context.desc.cacheBeanLoad(ebi, persistenceContext)) {
|
||||
// successfully hit the L2 cache so don't invoke DB lazy loading
|
||||
list.remove(ebi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.hitCache) {
|
||||
// check each of the beans in the batch to see if they are in the L2 cache.
|
||||
// bean successfully loaded from L2 cache so remove from batch load
|
||||
list.removeIf(batchEbi -> batchEbi != ebi && context.desc.cacheBeanLoad(batchEbi, persistenceContext));
|
||||
Set<EntityBeanIntercept> hits = context.desc.cacheBeanLoadAll(list, persistenceContext, ebi.getLazyLoadPropertyIndex(), ebi.getLazyLoadProperty());
|
||||
|
||||
list.removeAll(hits);
|
||||
if (list.isEmpty() || hits.contains(ebi)) {
|
||||
// successfully hit the L2 cache so don't invoke DB lazy loading
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LoadBeanRequest req = new LoadBeanRequest(this, ebi.getLazyLoadProperty(), context.hitCache);
|
||||
context.desc.getEbeanServer().loadBean(req);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.tests.cache.personinfo;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
@Cache
|
||||
public class PersonCacheEmail {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@ManyToOne
|
||||
private PersonCacheInfo personInfo;
|
||||
|
||||
private String email;
|
||||
|
||||
public PersonCacheEmail(String id, String email) {
|
||||
this.id = id;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public PersonCacheInfo getPersonInfo() {
|
||||
return personInfo;
|
||||
}
|
||||
|
||||
public void setPersonInfo(PersonCacheInfo personInfo) {
|
||||
this.personInfo = personInfo;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.tests.cache.personinfo;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
@Cache
|
||||
public class PersonCacheInfo {
|
||||
|
||||
@Id
|
||||
private String personId;
|
||||
|
||||
private String name;
|
||||
|
||||
public PersonCacheInfo(String personId, String name) {
|
||||
this.personId = personId;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPersonId() {
|
||||
return personId;
|
||||
}
|
||||
|
||||
public void setPersonId(String personId) {
|
||||
this.personId = personId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.tests.cache.personinfo;
|
||||
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class PersonCacheTests {
|
||||
|
||||
private void insert(int id, String email) {
|
||||
|
||||
PersonCacheInfo person = new PersonCacheInfo("P00" + id, "P00" + id);
|
||||
|
||||
PersonCacheEmail personEmail = new PersonCacheEmail("E00" + id, email);
|
||||
personEmail.setPersonInfo(person);
|
||||
|
||||
Ebean.save(person);
|
||||
Ebean.save(personEmail);
|
||||
}
|
||||
|
||||
private void addTestData() {
|
||||
|
||||
insert(1, "testA");
|
||||
insert(2, "testB");
|
||||
insert(3, "testC");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInQuery() {
|
||||
|
||||
addTestData();
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
Ebean.find(PersonCacheInfo.class)
|
||||
.select("personId") // do not fetch name
|
||||
.setUseCache(true)
|
||||
.findList();
|
||||
|
||||
ServerCache beanCache = Ebean.getDefaultServer().getServerCacheManager().getBeanCache(PersonCacheInfo.class);
|
||||
beanCache.getStatistics(true);
|
||||
|
||||
List<Object> ids = Arrays.asList(new String[]{"E001", "E002", "E003"});
|
||||
|
||||
List<PersonCacheEmail> emailList =
|
||||
Ebean.find(PersonCacheEmail.class)
|
||||
.where().idIn(ids)
|
||||
.setUseCache(true)
|
||||
.findList();
|
||||
|
||||
|
||||
for (PersonCacheEmail email : emailList) {
|
||||
// get property that isn't in the cached data
|
||||
assertThat(email.getPersonInfo().getName()).isNotNull();
|
||||
System.out.println(email.getPersonInfo().getName());
|
||||
}
|
||||
|
||||
List<String> sql = LoggedSqlCollector.current();
|
||||
assertThat(sql).hasSize(3);
|
||||
|
||||
assertThat(beanCache.getStatistics(true).getHitCount()).isEqualTo(3);
|
||||
|
||||
System.out.println("Fetch again ...");
|
||||
|
||||
emailList =
|
||||
Ebean.find(PersonCacheEmail.class)
|
||||
.where().idIn(ids)
|
||||
.setUseCache(true)
|
||||
.findList();
|
||||
|
||||
|
||||
for (PersonCacheEmail email : emailList) {
|
||||
// get property but it is in the cache data now
|
||||
System.out.println(email.getPersonInfo().getName());
|
||||
assertThat(email.getPersonInfo().getName()).isNotNull();
|
||||
}
|
||||
|
||||
sql = LoggedSqlCollector.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
|
||||
assertThat(beanCache.getStatistics(true).getHitCount()).isEqualTo(3);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user