mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-23 01:07:49 +00:00
#1973 - L2Cache, Inheritance and Lazy-Loading loads the parent Class not the Child
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user