From 0a49fc9b6299545947b759e67fc23973d541dc64 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Mon, 14 May 2018 11:12:42 +0200 Subject: [PATCH] FIX: NPE when fetching inherited beans from cache (#1373) --- .../server/deploy/BeanDescriptor.java | 11 ++++ .../cache/TestInheritanceRefCache.java | 64 +++++++++++++++++++ .../org/tests/model/basic/cache/CInhRef.java | 25 ++++++++ 3 files changed, 100 insertions(+) create mode 100644 src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java create mode 100644 src/test/java/org/tests/model/basic/cache/CInhRef.java diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index e34020a10..0a75df76c 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -71,6 +71,7 @@ import io.ebeaninternal.server.query.STreePropertyAssocMany; import io.ebeaninternal.server.query.STreePropertyAssocOne; import io.ebeaninternal.server.query.STreeType; import io.ebeaninternal.server.query.SqlBeanLoad; +import io.ebeaninternal.server.querydefn.DefaultOrmQuery; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.type.DataBind; @@ -1878,6 +1879,9 @@ public class BeanDescriptor implements BeanType, STreeType { * on first access (lazy load) or immediately (eager load) */ public EntityBean createEntityBean(boolean isNew) { + if (prototypeEntityBean == null) { + throw new UnsupportedOperationException("cannot create entity bean for abstract entity " + getName()); + } try { EntityBean bean = (EntityBean) prototypeEntityBean._ebean_newInstance(); @@ -1969,6 +1973,13 @@ public class BeanDescriptor implements BeanType, STreeType { public T createReference(Object id, PersistenceContext pc) { try { + if (inheritInfo != null && !inheritInfo.isConcrete()) { + // we actually need to do a query because we don't know the type without the discriminator + // value, just select the id property and discriminator column (auto added) + DefaultOrmQuery query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory()); + return query.select(getIdProperty().getName()).setId(id).findOne(); + } + EntityBean eb = createEntityBean(); id = convertSetId(id, eb); EntityBeanIntercept ebi = eb._ebean_getIntercept(); diff --git a/src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java b/src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java new file mode 100644 index 000000000..1f45212eb --- /dev/null +++ b/src/test/java/org/tests/inheritance/cache/TestInheritanceRefCache.java @@ -0,0 +1,64 @@ +package org.tests.inheritance.cache; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.tests.model.basic.cache.CInhOne; +import org.tests.model.basic.cache.CInhRef; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.ebeantest.LoggedSqlCollector; + +public class TestInheritanceRefCache extends BaseTestCase { + + @Test + public void test() { + + CInhOne one = new CInhOne(); + one.setLicenseNumber("O12"); + one.setDriver("Jimmy"); + one.setNotes("Hello"); + + Ebean.save(one); + + CInhRef ref = new CInhRef(); + ref.setRef(one); + + Ebean.save(ref); + + Integer id = ref.getId(); + + LoggedSqlCollector.start(); + CInhRef gotRef = Ebean.find(CInhRef.class).setId(id).findOne(); + + assertThat(gotRef).isInstanceOf(CInhRef.class); + assertThat(gotRef.getRef()).isInstanceOf(CInhOne.class); + List sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("from cinh_ref").contains("left join cinh_root"); + + // fetch again - from cache (but fetch second bean from cache) + LoggedSqlCollector.start(); + gotRef = Ebean.find(CInhRef.class).setId(id).findOne(); + + assertThat(gotRef).isInstanceOf(CInhRef.class); + assertThat(gotRef.getRef()).isInstanceOf(CInhOne.class); + sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("from cinh_root"); + + + // fetch again - both from cache + LoggedSqlCollector.start(); + gotRef = Ebean.find(CInhRef.class).setId(id).findOne(); + + assertThat(gotRef).isInstanceOf(CInhRef.class); + assertThat(gotRef.getRef()).isInstanceOf(CInhOne.class); + sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(0); + + } +} diff --git a/src/test/java/org/tests/model/basic/cache/CInhRef.java b/src/test/java/org/tests/model/basic/cache/CInhRef.java new file mode 100644 index 000000000..63e918724 --- /dev/null +++ b/src/test/java/org/tests/model/basic/cache/CInhRef.java @@ -0,0 +1,25 @@ +package org.tests.model.basic.cache; + +import org.tests.model.basic.BasicDomain; + +import io.ebean.annotation.Cache; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +@Entity +@Cache(enableQueryCache = true) +public class CInhRef extends BasicDomain { + private static final long serialVersionUID = -4673953370819311120L; + + @ManyToOne(cascade = {}) + private CInhRoot ref; + + public CInhRoot getRef() { + return ref; + } + + public void setRef(CInhRoot ref) { + this.ref = ref; + } +}