diff --git a/src/main/java/io/ebeaninternal/server/cache/CachedBeanId.java b/src/main/java/io/ebeaninternal/server/cache/CachedBeanId.java new file mode 100644 index 000000000..45eab630c --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/cache/CachedBeanId.java @@ -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. + *

+ * 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; + } + +} diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index 6db27933a..f7637296d 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -699,7 +699,7 @@ public final class PersistRequestBean 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); } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index d2adb35f4..78cf988dd 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -2053,14 +2053,16 @@ public class BeanDescriptor implements BeanType, 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 implements BeanType, STreeType { ebi.setPersistenceContext(pc); } return (T) eb; - } catch (Exception ex) { throw new PersistenceException(ex); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index a9919dce8..d33dd95c2 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -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 extends BeanPropertyAssoc 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 extends BeanPropertyAssoc 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 extends BeanPropertyAssoc 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 diff --git a/src/test/java/org/tests/inheritance/cache/CIAddress.java b/src/test/java/org/tests/inheritance/cache/CIAddress.java new file mode 100644 index 000000000..fea779587 --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/CIAddress.java @@ -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; + } +} diff --git a/src/test/java/org/tests/inheritance/cache/CIBaseModel.java b/src/test/java/org/tests/inheritance/cache/CIBaseModel.java new file mode 100644 index 000000000..64e983fa8 --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/CIBaseModel.java @@ -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; + } +} diff --git a/src/test/java/org/tests/inheritance/cache/CICustomer.java b/src/test/java/org/tests/inheritance/cache/CICustomer.java new file mode 100644 index 000000000..3ac832e3d --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/CICustomer.java @@ -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; + } +} diff --git a/src/test/java/org/tests/inheritance/cache/CICustomerParent.java b/src/test/java/org/tests/inheritance/cache/CICustomerParent.java new file mode 100644 index 000000000..fdcbf0a3f --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/CICustomerParent.java @@ -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; + } +} diff --git a/src/test/java/org/tests/inheritance/cache/CIStreet.java b/src/test/java/org/tests/inheritance/cache/CIStreet.java new file mode 100644 index 000000000..0b4293396 --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/CIStreet.java @@ -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; + } +} diff --git a/src/test/java/org/tests/inheritance/cache/CIStreetParent.java b/src/test/java/org/tests/inheritance/cache/CIStreetParent.java new file mode 100644 index 000000000..9ea1f90a7 --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/CIStreetParent.java @@ -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; + } +} diff --git a/src/test/java/org/tests/inheritance/cache/TestInheritCacheRefLoad.java b/src/test/java/org/tests/inheritance/cache/TestInheritCacheRefLoad.java new file mode 100644 index 000000000..0f79a4206 --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/TestInheritCacheRefLoad.java @@ -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; + } + +} diff --git a/src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java b/src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java index 103563e8f..ad104db21 100644 --- a/src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java +++ b/src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java @@ -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