diff --git a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java index 60833d743..9ab3bface 100644 --- a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java @@ -182,6 +182,13 @@ public final class EntityBeanIntercept implements Serializable { public Object getEmbeddedOwner() { return embeddedOwner; } + + /** + * Return the property index (for the parent) of this embedded bean. + */ + public int getEmbeddedOwnerIndex() { + return embeddedOwnerIndex; + } /** * Special case for a OneToOne, Set the parent bean (by relationship). This is diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java index 37dc2e208..e21842c8f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java @@ -15,10 +15,20 @@ public class CachedBeanDataFromBean { Object[] data = new Object[desc.getPropertyCount()]; boolean[] loaded = new boolean[desc.getPropertyCount()]; + BeanProperty idProperty = desc.getIdProperty(); + if (idProperty != null) { + int propertyIndex = idProperty.getPropertyIndex(); + if (ebi.isLoadedProperty(propertyIndex)) { + // extract the id property value + data[propertyIndex] = idProperty.getCacheDataValue(bean); + loaded[propertyIndex] = true; + } + } BeanProperty[] props = desc.propertiesNonMany(); Object naturalKey = null; + // extract all the non-many properties for (int i = 0; i < props.length; i++) { BeanProperty prop = props[i]; if (ebi.isLoadedProperty(prop.getPropertyIndex())) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java index 1c05d4e18..4bbd3e212 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java @@ -13,19 +13,17 @@ public class CachedBeanDataToBean { EntityBeanIntercept ebi = bean._ebean_getIntercept(); + + BeanProperty idProperty = desc.getIdProperty(); + if (idProperty != null) { + // load the id property + loadProperty(bean, cacheBeanData, ebi, idProperty); + } + + // load the non-many properties BeanProperty[] props = desc.propertiesNonMany(); for (int i = 0; i < props.length; i++) { - - BeanProperty prop = props[i]; - int propertyIndex = prop.getPropertyIndex(); - if (cacheBeanData.isLoaded(propertyIndex)) { - if (ebi.isLoadedProperty(propertyIndex)) { - // already loaded (lazy load on partially loaded bean) - } else { - Object data = cacheBeanData.getData(propertyIndex); - prop.setCacheDataValue(bean, data); - } - } + loadProperty(bean, cacheBeanData, ebi, props[i]); } BeanPropertyAssocMany[] manys = desc.propertiesMany(); @@ -38,4 +36,17 @@ public class CachedBeanDataToBean { return true; } + private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop) { + + int propertyIndex = prop.getPropertyIndex(); + if (cacheBeanData.isLoaded(propertyIndex)) { + if (ebi.isLoadedProperty(propertyIndex)) { + // already loaded (lazy load on partially loaded bean) + } else { + Object data = cacheBeanData.getData(propertyIndex); + prop.setCacheDataValue(bean, data); + } + } + } + } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java index 6c12c50e0..43f5bbc33 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java @@ -23,6 +23,7 @@ import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiQuery.Mode; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType; import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; /** @@ -324,19 +325,27 @@ public class DefaultBeanLoader { } public void refresh(EntityBean bean) { - refreshBeanInternal(bean, SpiQuery.Mode.REFRESH_BEAN); + refreshBeanInternal(bean, SpiQuery.Mode.REFRESH_BEAN, -1); } public void loadBean(EntityBeanIntercept ebi) { - refreshBeanInternal(ebi.getOwner(), SpiQuery.Mode.LAZYLOAD_BEAN); + refreshBeanInternal(ebi.getOwner(), SpiQuery.Mode.LAZYLOAD_BEAN, -1); } - private void refreshBeanInternal(EntityBean bean, SpiQuery.Mode mode) { + private void refreshBeanInternal(EntityBean bean, SpiQuery.Mode mode, int embeddedOwnerIndex) { EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept();; PersistenceContext pc = ebi.getPersistenceContext(); BeanDescriptor desc = server.getBeanDescriptor(bean.getClass()); + if (EntityType.EMBEDDED == desc.getEntityType()) { + // lazy loading on an embedded bean property + EntityBean embeddedOwner = (EntityBean)ebi.getEmbeddedOwner(); + int ownerIndex = ebi.getEmbeddedOwnerIndex(); + + refreshBeanInternal(embeddedOwner, mode, ownerIndex); + } + Object id = desc.getId(bean); if (pc == null) { @@ -348,7 +357,7 @@ public class DefaultBeanLoader { } } - if (ebi != null) { + if (embeddedOwnerIndex == -1) { if (SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) { // lazy loading and the bean cache is active if (desc.cacheBeanLoad((EntityBean)bean, ebi, id)) { @@ -365,6 +374,11 @@ public class DefaultBeanLoader { query.setLazyLoadProperty(ebi.getLazyLoadProperty()); } + if (embeddedOwnerIndex > -1) { + String embeddedBeanPropertyName = ebi.getProperty(embeddedOwnerIndex); + query.select("id,"+embeddedBeanPropertyName); + } + // don't collect autoFetch usage profiling information // as we just copy the data out of these fetched beans // and put the data into the original bean @@ -373,12 +387,13 @@ public class DefaultBeanLoader { query.setMode(mode); query.setId(id); - // make sure the query doesn't use the cache - if (mode.equals(SpiQuery.Mode.REFRESH_BEAN)) { + + if (embeddedOwnerIndex > -1 || mode.equals(SpiQuery.Mode.REFRESH_BEAN)) { + // make sure the query doesn't use the cache query.setUseCache(false); } - if (ebi != null && ebi.isReadOnly()) { + if (ebi.isReadOnly()) { query.setReadOnly(true); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index d90d6fc4f..1ad02cab9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -842,6 +842,20 @@ public class BeanDescriptor implements MetaBeanInfo { cacheBeanPutData((EntityBean)bean); } + /** + * Extract the raw cache data from the bean. + */ + public CachedBeanData cacheBeanExtractData(EntityBean bean) { + return cacheHelp.beanExtractData(bean); + } + + /** + * Load the raw cache data into the bean. + */ + public void cacheBeanLoadData(EntityBean bean, CachedBeanData data) { + cacheHelp.beanLoadData(bean, data); + } + /** * Put a bean into the bean cache. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java index eaac6c5b9..9f56a3e61 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java @@ -360,12 +360,20 @@ public final class BeanDescriptorCacheHelp { } } + public CachedBeanData beanExtractData(EntityBean bean) { + return CachedBeanDataFromBean.extract(desc, bean); + } + + public void beanLoadData(EntityBean bean, CachedBeanData data) { + CachedBeanDataToBean.load(desc, bean, data); + } + /** * Put a bean into the bean cache. */ public void beanCachePut(EntityBean bean) { - CachedBeanData beanData = CachedBeanDataFromBean.extract(desc, bean); + CachedBeanData beanData = beanExtractData(bean); Object id = desc.getId(bean); if (beanLog.isDebugEnabled()) { @@ -402,15 +410,15 @@ public final class BeanDescriptorCacheHelp { @SuppressWarnings("unchecked") private T beanCacheGetInternal(Object id, Boolean readOnly) { - CachedBeanData d = (CachedBeanData) getBeanCache().get(id); - if (d == null) { + CachedBeanData data = (CachedBeanData) getBeanCache().get(id); + if (data == null) { if (beanLog.isTraceEnabled()) { beanLog.trace(" GET {}({}) - cache miss", cacheName, id); } return null; } if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) { - Object bean = d.getSharableBean(); + Object bean = data.getSharableBean(); if (bean != null) { if (beanLog.isTraceEnabled()) { beanLog.trace(" GET {}({}) - hit shared bean", cacheName, id); @@ -425,7 +433,7 @@ public final class BeanDescriptorCacheHelp { bean._ebean_getIntercept().setReadOnly(true); } - CachedBeanDataToBean.load(desc, bean, d); + beanLoadData(bean, data); if (beanLog.isTraceEnabled()) { beanLog.trace(" GET {}({}) - hit", cacheName, id); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 7c6f3695b..3ca52206c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -14,6 +14,7 @@ import com.avaje.ebean.Transaction; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.cache.CachedBeanData; import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; import com.avaje.ebeaninternal.server.deploy.id.IdBinder; import com.avaje.ebeaninternal.server.deploy.id.ImportedId; @@ -323,15 +324,15 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } public Object getCacheDataValue(EntityBean bean){ - if (embedded) { - throw new RuntimeException(); + Object ap = getValue(bean); + if (ap == null){ + return null; + } + if (embedded) { + return targetDescriptor.cacheBeanExtractData((EntityBean) ap); + } else { - Object ap = getValue(bean); - if (ap == null){ - return null; - } else { - return targetDescriptor.getId((EntityBean)ap); - } + return targetDescriptor.getId((EntityBean)ap); } } @@ -339,7 +340,10 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { public void setCacheDataValue(EntityBean bean, Object cacheData){ if (cacheData != null) { if (embedded){ - throw new RuntimeException(); + EntityBean embeddedBean = targetDescriptor.createEntityBean(); + targetDescriptor.cacheBeanLoadData(embeddedBean, (CachedBeanData) cacheData); + setValue(bean, embeddedBean); + } else { T ref = targetDescriptor.createReference(Boolean.FALSE, cacheData); setValue(bean, ref); diff --git a/src/test/java/com/avaje/ebeaninternal/server/cache/TestCacheBeanData.java b/src/test/java/com/avaje/ebeaninternal/server/cache/TestCacheBeanData.java index 17a693e87..c23152f5c 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/cache/TestCacheBeanData.java +++ b/src/test/java/com/avaje/ebeaninternal/server/cache/TestCacheBeanData.java @@ -2,8 +2,7 @@ package com.avaje.ebeaninternal.server.cache; import java.sql.Timestamp; -import junit.framework.Assert; - +import org.junit.Assert; import org.junit.Test; import com.avaje.ebean.BaseTestCase; @@ -11,10 +10,13 @@ import com.avaje.ebean.Ebean; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; import com.avaje.tests.model.basic.Address; import com.avaje.tests.model.basic.Country; import com.avaje.tests.model.basic.Customer; import com.avaje.tests.model.basic.Customer.Status; +import com.avaje.tests.model.embedded.EAddress; +import com.avaje.tests.model.embedded.EPerson; public class TestCacheBeanData extends BaseTestCase { @@ -68,4 +70,54 @@ public class TestCacheBeanData extends BaseTestCase { Assert.assertNotNull(newCustomer.getBillingAddress().getId()); } + + + @Test + public void testCacheBeanExtractAndLoadWithEmbdedded() { + + SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null); + BeanDescriptor desc = server.getBeanDescriptor(EPerson.class); + BeanPropertyAssocOne addressBeanProperty = (BeanPropertyAssocOne)desc.getBeanProperty("address"); + + EAddress address = new EAddress(); + address.setStreet("92 Someplace Else"); + address.setSuburb("Sandringham"); + address.setCity("Auckland"); + + EPerson person = new EPerson(); + person.setId(98989L); + person.setName("Rob"); + person.setAddress(address); + + CachedBeanData addressCacheData = (CachedBeanData)addressBeanProperty.getCacheDataValue((EntityBean) person); + + EPerson newPersonCheck = new EPerson(); + newPersonCheck.setId(98989L); + addressBeanProperty.setCacheDataValue((EntityBean) newPersonCheck, addressCacheData); + + EAddress newAddress = newPersonCheck.getAddress(); + Assert.assertEquals(address.getStreet(), newAddress.getStreet()); + Assert.assertEquals(address.getCity(), newAddress.getCity()); + Assert.assertEquals(address.getSuburb(), newAddress.getSuburb()); + + + + + CachedBeanData cacheData = desc.cacheBeanExtractData((EntityBean)person); + + Assert.assertNotNull(cacheData); + + EPerson newPerson = new EPerson(); + desc.cacheBeanLoadData((EntityBean)newPerson, cacheData); + + Assert.assertNotNull(newPerson.getId()); + Assert.assertNotNull(newPerson.getName()); + Assert.assertNotNull(newPerson.getAddress()); + + Assert.assertEquals(person.getId(), newPerson.getId()); + Assert.assertEquals(person.getName(), newPerson.getName()); + Assert.assertEquals(person.getAddress().getStreet(), newPerson.getAddress().getStreet()); + Assert.assertEquals(person.getAddress().getCity(), newPerson.getAddress().getCity()); + + } } diff --git a/src/test/java/com/avaje/tests/model/embedded/EAddress.java b/src/test/java/com/avaje/tests/model/embedded/EAddress.java new file mode 100644 index 000000000..b857fb5b6 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/embedded/EAddress.java @@ -0,0 +1,38 @@ +package com.avaje.tests.model.embedded; + +import javax.persistence.Embeddable; + +@Embeddable +public class EAddress { + + String street; + + String suburb; + + String city; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getSuburb() { + return suburb; + } + + public void setSuburb(String suburb) { + this.suburb = suburb; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + +} diff --git a/src/test/java/com/avaje/tests/model/embedded/EPerson.java b/src/test/java/com/avaje/tests/model/embedded/EPerson.java new file mode 100644 index 000000000..c7c496b38 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/embedded/EPerson.java @@ -0,0 +1,64 @@ +package com.avaje.tests.model.embedded; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +@Entity +public class EPerson { + + @Id + Long id; + + @Version + Long version; + + String name; + + String notes; + + @Embedded + EAddress address; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public EAddress getAddress() { + return address; + } + + public void setAddress(EAddress address) { + this.address = address; + } + +} diff --git a/src/test/java/com/avaje/tests/model/embedded/Eembeddable.java b/src/test/java/com/avaje/tests/model/embedded/Eembeddable.java index 9c94a2e17..225478d49 100644 --- a/src/test/java/com/avaje/tests/model/embedded/Eembeddable.java +++ b/src/test/java/com/avaje/tests/model/embedded/Eembeddable.java @@ -3,16 +3,16 @@ package com.avaje.tests.model.embedded; import javax.persistence.Embeddable; @Embeddable -public class Eembeddable -{ - String description; +public class Eembeddable { + + String description; - public String getDescription() { - return description; - } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } - public void setDescription(String description) { - this.description = description; - } - }