#1973 - L2Cache, Inheritance and Lazy-Loading loads the parent Class not the Child

This commit is contained in:
rob bygrave
2020-03-10 22:52:13 +13:00
parent 91a327a5e7
commit 48abc73e50
12 changed files with 291 additions and 21 deletions
@@ -0,0 +1,54 @@
package io.ebeaninternal.server.cache;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Bean Id value plus discriminator type.
* <p>
* Put into L2 cache such that we know the type of a bean with inheritance.
*/
public class CachedBeanId implements Externalizable {
private String discValue;
private Object id;
public CachedBeanId(String discValue, Object id) {
this.discValue = discValue;
this.id = id;
}
/**
* Construct from serialisation.
*/
public CachedBeanId() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(discValue);
out.writeObject(id);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
discValue = in.readUTF();
id = in.readObject();
}
@Override
public String toString() {
return discValue + ":" + id;
}
public String getDiscValue() {
return discValue;
}
public Object getId() {
return id;
}
}
@@ -699,7 +699,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Create and return a new reference bean matching this beans Id value.
*/
public T createReference() {
return beanDescriptor.createReference(getBeanId(), null);
return beanDescriptor.createRef(getBeanId(), null);
}
/**
@@ -2053,14 +2053,16 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Create a non read only reference bean without checking cacheSharableBeans.
*/
@SuppressWarnings("unchecked")
public T createReference(Object id, PersistenceContext pc) {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
return createRef(id, pc);
}
@SuppressWarnings("unchecked")
public T createRef(Object id, PersistenceContext pc) {
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
@@ -2071,7 +2073,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
ebi.setPersistenceContext(pc);
}
return (T) eb;
} catch (Exception ex) {
throw new PersistenceException(ex);
}
@@ -13,6 +13,7 @@ import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.cache.CachedBeanId;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
@@ -433,11 +434,19 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
if (embedded) {
return targetDescriptor.cacheEmbeddedBeanExtract((EntityBean) ap);
} else if (targetInheritInfo != null) {
return createCacheBeanId(ap);
} else {
return targetDescriptor.getIdProperty().getCacheDataValue((EntityBean) ap);
}
}
private Object createCacheBeanId(Object bean) {
final BeanDescriptor<?> desc = targetDescriptor.descOf(bean.getClass());
final Object id = desc.getIdProperty().getCacheDataValue((EntityBean) bean);
return new CachedBeanId(desc.getDiscValue(), id);
}
@Override
public String format(Object value) {
return targetDescriptor.getIdBinder().cacheKey(value);
@@ -451,19 +460,31 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
if (embedded) {
setValue(bean, targetDescriptor.cacheEmbeddedBeanLoad((CachedBeanData) cacheData, context));
} else {
if (cacheData instanceof String) {
cacheData = targetDescriptor.getIdProperty().scalarType.parse((String) cacheData);
if (cacheData instanceof CachedBeanId) {
setValue(bean, refInheritBean((CachedBeanId) cacheData, context));
} else {
setValue(bean, refBean(targetDescriptor, cacheData, context));
}
// cacheData is the id value, maybe already in persistence context
Object assocBean = targetDescriptor.contextGet(context, cacheData);
if (assocBean == null) {
assocBean = targetDescriptor.createReference(cacheData, context);
}
setValue(bean, assocBean);
}
}
}
private Object refInheritBean(CachedBeanId cacheId, PersistenceContext context) {
final InheritInfo rowInheritInfo = targetInheritInfo.readType(cacheId.getDiscValue());
return refBean(rowInheritInfo.desc(), cacheId.getId(), context);
}
private Object refBean(BeanDescriptor<?> desc, Object id, PersistenceContext context) {
if (id instanceof String) {
id = desc.getIdProperty().scalarType.parse((String) id);
}
Object bean = desc.contextGet(context, id);
if (bean == null) {
bean = desc.createRef(id, context);
}
return bean;
}
@Override
public ScalarDataReader<?> getIdReader() {
return targetDescriptor.getIdProperty();
@@ -640,14 +661,12 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
@Override
public void addTenant(SpiQuery<?> query, Object tenantId) {
T refBean = targetDescriptor.createReference(tenantId, null);
query.where().eq(name, refBean);
query.where().eq(name, targetDescriptor.createRef(tenantId, null));
}
@Override
public void setTenantValue(EntityBean entityBean, Object tenantId) {
T refBean = targetDescriptor.createReference(tenantId, null);
setValue(entityBean, refBean);
setValue(entityBean, targetDescriptor.createRef(tenantId, null));
}
@Override
@@ -0,0 +1,19 @@
package org.tests.inheritance.cache;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
@Entity
public class CIAddress extends CIBaseModel {
@ManyToOne
public CIStreetParent street;
public CIStreetParent getStreet() {
return street;
}
public void setStreet(CIStreetParent street) {
this.street = street;
}
}
@@ -0,0 +1,23 @@
package org.tests.inheritance.cache;
import io.ebean.Model;
import io.ebean.annotation.Cache;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@Cache(enableQueryCache=true)
@MappedSuperclass
public abstract class CIBaseModel extends Model {
@Id
protected long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
@@ -0,0 +1,19 @@
package org.tests.inheritance.cache;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue(value="1")
public class CICustomer extends CICustomerParent {
public String notes;
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
}
@@ -0,0 +1,27 @@
package org.tests.inheritance.cache;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue(value = "0")
public class CICustomerParent extends CIBaseModel {
@ManyToOne
protected CIAddress address;
public CIAddress getAddress() {
return address;
}
public void setAddress(CIAddress adress) {
this.address = adress;
}
}
+21
View File
@@ -0,0 +1,21 @@
package org.tests.inheritance.cache;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "street")
@DiscriminatorValue(value = "1")
public class CIStreet extends CIStreetParent {
protected String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
@@ -0,0 +1,25 @@
package org.tests.inheritance.cache;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue(value = "0")
public class CIStreetParent extends CIBaseModel {
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,63 @@
package org.tests.inheritance.cache;
import io.ebean.DB;
import org.junit.Assert;
import org.junit.Test;
public class TestInheritCacheRefLoad {
@Test
public void findWithRefToInheritStreetBean_expect_correctStreetType() {
DB.find(CICustomerParent.class).delete();
DB.find(CIAddress.class).delete();
DB.find(CIStreet.class).delete();
//=========================================================================
// 1 - Create Data (Customer(dtype 1) -> Address(no dtype) -> Street(dtype 1)
//=========================================================================
CIStreet street = new CIStreet();
street.save();
CIAddress address = new CIAddress();
address.setStreet(street);
address.save();
CICustomer customer = new CICustomer();
customer.setAddress(address);
customer.save();
//=========================================================================
// 2 - Read Data (no Cache Hit)
//=========================================================================
Class streetClass = reloadCustomer(customer, false); //returns Street -> OK
Assert.assertEquals("org.tests.inheritance.cache.CIStreet", streetClass.getName());
streetClass = reloadCustomer(customer, false); //returns Street -> OK
Assert.assertEquals("org.tests.inheritance.cache.CIStreet", streetClass.getName());
//=========================================================================
// 3 - Read Data (L2-Cache Hit)
//=========================================================================
streetClass = reloadCustomer(customer, true); //returns Street -> OK
Assert.assertEquals("org.tests.inheritance.cache.CIStreet", streetClass.getName());
streetClass = reloadCustomer(customer, true); //returns StreetParent -> NOT OK
Assert.assertEquals("org.tests.inheritance.cache.CIStreet", streetClass.getName());
}
public Class reloadCustomer(CICustomer customer, boolean l2Cache) {
// Load Customer via Query, L2Cache on/off
CICustomer customerReloaded =
DB.find(CICustomer.class)
.where().eq("id", customer.getId())
.setUseCache(l2Cache)
.findOne();
//Access Street by lazy Loading
Class streetClassLazyLoaded = customerReloaded.getAddress().getStreet().getClass();
//Show Cache Hits
System.out.println("Class of Street (Cache on: " + l2Cache + "): " + streetClassLazyLoaded + " | Cache Hit: " + DB.getDefault().getServerCacheManager().getQueryCache(streetClassLazyLoaded).getStatistics(false).getHitCount());
return streetClassLazyLoaded;
}
}
@@ -49,8 +49,7 @@ public class TestInheritanceRefCache extends BaseTestCase {
assertThat(gotRef).isInstanceOf(CInhRef.class);
assertThat(gotRef.getRef()).isInstanceOf(CInhOne.class);
sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertSql(sql.get(0)).contains("from cinh_root");
assertThat(sql).hasSize(0);
// fetch again - both from cache