diff --git a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java index e1d1c87f8..a49f76fb7 100644 --- a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java +++ b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java @@ -12,7 +12,7 @@ public interface PersistenceContext { /** * Put the entity bean into the PersistenceContext. */ - void put(Object id, Object bean); + void put(Class rootType, Object id, Object bean); /** * Put the entity bean into the PersistenceContext if one is not already @@ -22,7 +22,7 @@ public interface PersistenceContext { * returns null. *

*/ - Object putIfAbsent(Object id, Object bean); + Object putIfAbsent(Class rootType, Object id, Object bean); /** * Return an object given its type and unique id. diff --git a/src/main/java/com/avaje/ebean/bean/PersistenceContextUtil.java b/src/main/java/com/avaje/ebean/bean/PersistenceContextUtil.java new file mode 100644 index 000000000..d3f2402fb --- /dev/null +++ b/src/main/java/com/avaje/ebean/bean/PersistenceContextUtil.java @@ -0,0 +1,21 @@ +package com.avaje.ebean.bean; + +import javax.persistence.Entity; + +/** + * Utility to find the root bean type. + */ +public class PersistenceContextUtil { + + /** + * Find and return the root bean type for the given class. + */ + public static Class root(Class beanType) { + Class parent = beanType.getSuperclass(); + while (parent != null && parent.isAnnotationPresent(Entity.class)) { + beanType = parent; + parent = parent.getSuperclass(); + } + return beanType; + } +} 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 705e18867..a2b13a15d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java @@ -126,7 +126,7 @@ public class DefaultBeanLoader { if (pc == null) { pc = new DefaultPersistenceContext(); - pc.put(parentId, parentBean); + parentDesc.contextPut(pc, parentId, parentBean); } boolean useManyIdCache = beanCollection != null && parentDesc.isManyPropCaching(); @@ -263,7 +263,7 @@ public class DefaultBeanLoader { if (pc == null) { // a reference with no existing persistenceContext pc = new DefaultPersistenceContext(); - pc.put(id, bean); + desc.contextPut(pc, id, bean); ebi.setPersistenceContext(pc); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 1068509b1..c580f0bb7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -616,9 +616,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { ref = desc.createReference(null, id); } - if (ctx != null && (ref instanceof EntityBean)) { - // Not putting a vanilla reference in the persistence context - ctx.put(id, ref); + if (ctx != null) { + desc.contextPut(ctx, id, ref); } } return (T) ref; diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index 2c890081a..b70c635c5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -189,7 +189,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe */ public void persistenceContextAdd(EntityBean bean) { Object id = beanDescriptor.getId(bean); - persistenceContext.put(id, bean); + beanDescriptor.contextPut(persistenceContext, id, bean); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelp.java new file mode 100644 index 000000000..3fe90131a --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelp.java @@ -0,0 +1,39 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.query.SqlJoinType; + +import java.sql.SQLException; + +/** + * Local interface to handle Embedded, Reference and Reference Exported + * cases. + */ +abstract class AssocOneHelp { + + /** + * Effectively skip reading (the jdbc resultSet as already in the persistence context etc). + */ + abstract void loadIgnore(DbReadContext ctx); + + /** + * Read and return the bean. + */ + abstract Object read(DbReadContext ctx) throws SQLException; + + /** + * Read setting values into the bean. + */ + abstract Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException; + + /** + * Append to the select clause. + */ + abstract void appendSelect(DbSqlContext ctx, boolean subQuery); + + /** + * Append to the from clause. + */ + abstract void appendFrom(DbSqlContext ctx, SqlJoinType joinType); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpEmbedded.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpEmbedded.java new file mode 100644 index 000000000..58db38f97 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpEmbedded.java @@ -0,0 +1,69 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.query.SqlJoinType; + +import java.sql.SQLException; + +/** + * Helper for Embedded BeanPropertyAssocOne. + */ +final class AssocOneHelpEmbedded extends AssocOneHelp { + + private BeanPropertyAssocOne beanPropertyAssocOne; + + public AssocOneHelpEmbedded(BeanPropertyAssocOne beanPropertyAssocOne) { + this.beanPropertyAssocOne = beanPropertyAssocOne; + } + + void loadIgnore(DbReadContext ctx) { + for (int i = 0; i < beanPropertyAssocOne.embeddedProps.length; i++) { + beanPropertyAssocOne.embeddedProps[i].loadIgnore(ctx); + } + } + + @Override + Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { + Object dbVal = read(ctx); + if (bean != null) { + // set back to the parent bean + beanPropertyAssocOne.setValue(bean, dbVal); + ctx.propagateState(dbVal); + return dbVal; + + } else { + return null; + } + } + + @Override + Object read(DbReadContext ctx) throws SQLException { + + EntityBean embeddedBean = beanPropertyAssocOne.targetDescriptor.createEntityBean(); + + boolean notNull = false; + for (int i = 0; i < beanPropertyAssocOne.embeddedProps.length; i++) { + Object value = beanPropertyAssocOne.embeddedProps[i].readSet(ctx, embeddedBean); + if (value != null) { + notNull = true; + } + } + if (notNull) { + ctx.propagateState(embeddedBean); + return embeddedBean; + } else { + return null; + } + } + + @Override + void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { + } + + @Override + void appendSelect(DbSqlContext ctx, boolean subQuery) { + for (int i = 0; i < beanPropertyAssocOne.embeddedProps.length; i++) { + beanPropertyAssocOne.embeddedProps[i].appendSelect(ctx, subQuery); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpReference.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpReference.java new file mode 100644 index 000000000..e37edc241 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpReference.java @@ -0,0 +1,120 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContextUtil; +import com.avaje.ebeaninternal.server.query.SqlJoinType; + +import java.sql.SQLException; + +/** + * Helper for BeanPropertyAssocOne imported reference - this is the common case. + */ +final class AssocOneHelpReference extends AssocOneHelp { + + private BeanPropertyAssocOne beanPropertyAssocOne; + + AssocOneHelpReference(BeanPropertyAssocOne beanPropertyAssocOne) { + this.beanPropertyAssocOne = beanPropertyAssocOne; + } + + @Override + void loadIgnore(DbReadContext ctx) { + beanPropertyAssocOne.targetIdBinder.loadIgnore(ctx); + if (beanPropertyAssocOne.targetInheritInfo != null) { + ctx.getDataReader().incrementPos(1); + } + } + + @Override + Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { + Object val = read(ctx); + if (bean != null) { + beanPropertyAssocOne.setValue(bean, val); + ctx.propagateState(val); + } + return val; + } + + /** + * Read and set a Reference bean. + */ + @Override + Object read(DbReadContext ctx) throws SQLException { + + BeanDescriptor rowDescriptor = null; + Class rowType = beanPropertyAssocOne.targetType; + if (beanPropertyAssocOne.targetInheritInfo != null) { + // read discriminator to determine the type + InheritInfo rowInheritInfo = beanPropertyAssocOne.targetInheritInfo.readType(ctx); + if (rowInheritInfo != null) { + rowType = rowInheritInfo.getType(); + rowDescriptor = rowInheritInfo.getBeanDescriptor(); + } + } + + // read the foreign key column(s) + Object id = beanPropertyAssocOne.targetIdBinder.read(ctx); + if (id == null) { + return null; + } + + // check transaction context to see if it already exists + Object existing = ctx.getPersistenceContext().get(rowType, id); + + if (existing != null) { + return existing; + } + + Boolean readOnly = ctx.isReadOnly(); + Object ref; + if (beanPropertyAssocOne.targetInheritInfo != null) { + // for inheritance hierarchy create the correct type for this row... + ref = rowDescriptor.createReference(readOnly, id); + } else { + ref = beanPropertyAssocOne.targetDescriptor.createReference(readOnly, id); + } + + Class rootType = PersistenceContextUtil.root(ref.getClass()); + Object existingBean = ctx.getPersistenceContext().putIfAbsent(rootType, id, ref); + if (existingBean != null) { + // advanced case when we use multiple concurrent threads to + // build a single object graph, and another thread has since + // loaded a matching bean so we will use that instead. + ref = existingBean; + + } else { + EntityBeanIntercept ebi = ((EntityBean) ref)._ebean_getIntercept(); + if (Boolean.TRUE.equals(ctx.isReadOnly())) { + ebi.setReadOnly(true); + } + ctx.register(beanPropertyAssocOne.name, ebi); + } + + return ref; + } + + @Override + void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { + if (beanPropertyAssocOne.targetInheritInfo != null) { + // add join to support the discriminator column + String relativePrefix = ctx.getRelativePrefix(beanPropertyAssocOne.name); + beanPropertyAssocOne.tableJoin.addJoin(joinType, relativePrefix, ctx); + } + } + + /** + * Append columns for foreign key columns. + */ + @Override + void appendSelect(DbSqlContext ctx, boolean subQuery) { + + if (!subQuery && beanPropertyAssocOne.targetInheritInfo != null) { + // add discriminator column + String relativePrefix = ctx.getRelativePrefix(beanPropertyAssocOne.getName()); + String tableAlias = ctx.getTableAlias(relativePrefix); + ctx.appendColumn(tableAlias, beanPropertyAssocOne.targetInheritInfo.getDiscriminatorColumn()); + } + beanPropertyAssocOne.importedId.sqlAppend(ctx); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpReferenceExported.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpReferenceExported.java new file mode 100644 index 000000000..198fce397 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/AssocOneHelpReferenceExported.java @@ -0,0 +1,91 @@ +package com.avaje.ebeaninternal.server.deploy; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.deploy.id.IdBinder; +import com.avaje.ebeaninternal.server.query.SqlJoinType; + +import java.sql.SQLException; + +/** + * Helper for BeanPropertyAssocOne for OneToOne exported reference - not so common. + */ +final class AssocOneHelpReferenceExported extends AssocOneHelp { + + private BeanPropertyAssocOne beanPropertyAssocOne; + + public AssocOneHelpReferenceExported(BeanPropertyAssocOne beanPropertyAssocOne) { + this.beanPropertyAssocOne = beanPropertyAssocOne; + } + + @Override + void loadIgnore(DbReadContext ctx) { + beanPropertyAssocOne.targetDescriptor.getIdBinder().loadIgnore(ctx); + } + + /** + * Read and set a Reference bean. + */ + @Override + Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { + + Object dbVal = read(ctx); + if (bean != null) { + beanPropertyAssocOne.setValue(bean, dbVal); + ctx.propagateState(dbVal); + } + return dbVal; + } + + @Override + Object read(DbReadContext ctx) throws SQLException { + + // TODO: Support for Inheritance hierarchy on exported OneToOne ? + IdBinder idBinder = beanPropertyAssocOne.targetDescriptor.getIdBinder(); + Object id = idBinder.read(ctx); + if (id == null) { + return null; + } + + PersistenceContext persistCtx = ctx.getPersistenceContext(); + Object existing = persistCtx.get(beanPropertyAssocOne.targetType, id); + + if (existing != null) { + return existing; + } + Object ref = beanPropertyAssocOne.targetDescriptor.createReference(ctx.isReadOnly(), id); + + EntityBeanIntercept ebi = ((EntityBean) ref)._ebean_getIntercept(); + if (Boolean.TRUE.equals(ctx.isReadOnly())) { + ebi.setReadOnly(true); + } + beanPropertyAssocOne.targetDescriptor.contextPut(persistCtx, id, ref); + ctx.register(beanPropertyAssocOne.name, ebi); + return ref; + } + + /** + * Append columns for foreign key columns. + */ + @Override + void appendSelect(DbSqlContext ctx, boolean subQuery) { + + // set appropriate tableAlias for the exported id columns + + String relativePrefix = ctx.getRelativePrefix(beanPropertyAssocOne.getName()); + ctx.pushTableAlias(relativePrefix); + + IdBinder idBinder = beanPropertyAssocOne.targetDescriptor.getIdBinder(); + idBinder.appendSelect(ctx, subQuery); + + ctx.popTableAlias(); + } + + @Override + void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { + + String relativePrefix = ctx.getRelativePrefix(beanPropertyAssocOne.getName()); + beanPropertyAssocOne.tableJoin.addJoin(joinType, relativePrefix, ctx); + } +} 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 f5a8ee4a0..d34bb65be 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -12,6 +12,7 @@ import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.bean.PersistenceContextUtil; import com.avaje.ebean.config.EncryptKey; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.IdGenerator; @@ -33,7 +34,6 @@ import com.avaje.ebean.plugin.BeanDocType; import com.avaje.ebean.plugin.BeanType; import com.avaje.ebean.plugin.ExpressionPath; import com.avaje.ebean.plugin.Property; -import com.avaje.ebean.text.json.JsonReadOptions; import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.LoadContext; import com.avaje.ebeaninternal.api.SpiEbeanServer; @@ -74,7 +74,6 @@ import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder; import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping; import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping; -import com.fasterxml.jackson.core.JsonParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -195,6 +194,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { * The type of bean this describes. */ private final Class beanType; + private final Class rootBeanType; /** * This is not sent to a remote client. @@ -387,6 +387,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.fullName = InternString.intern(deploy.getFullName()); this.beanType = deploy.getBeanType(); + this.rootBeanType = PersistenceContextUtil.root(beanType); this.prototypeEntityBean = createPrototypeEntityBean(beanType); this.namedQueries = deploy.getNamedQueries(); @@ -1667,6 +1668,20 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return fullName; } + /** + * Put the bean into the persistence context. + */ + public void contextPut(PersistenceContext pc, Object id, Object bean) { + pc.put(rootBeanType, id, bean); + } + + /** + * Put the bean into the persistence context if it is absent. + */ + public Object contextPutIfAbsent(PersistenceContext pc, Object id, EntityBean localBean) { + return pc.putIfAbsent(rootBeanType, id, localBean); + } + /** * Helper method to return the unique property. If only one property makes up * the unique id then it's value is returned. If there is a concatenated 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 bb2c928a8..0e970a1b9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java @@ -245,7 +245,7 @@ public final class BeanDescriptorCacheHelp { EntityBeanIntercept refEbi = ((EntityBean) refBean)._ebean_getIntercept(); many.add(bc, (EntityBean) refBean); - persistenceContext.put(id, refBean); + targetDescriptor.contextPut(persistenceContext, id, refBean); refEbi.setPersistenceContext(persistenceContext); } return true; @@ -343,8 +343,7 @@ public final class BeanDescriptorCacheHelp { EntityBeanIntercept ebi = entityBean._ebean_getIntercept(); ebi.setPersistenceContext(context); Object id = desc.getId(entityBean); - context.put(id, bean); - + desc.contextPut(context, id, bean); } /** 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 e17e0f988..612ce34c4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -6,11 +6,8 @@ import com.avaje.ebean.SqlUpdate; import com.avaje.ebean.Transaction; import com.avaje.ebean.ValuePair; 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; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; @@ -34,28 +31,29 @@ import java.util.Map; */ public class BeanPropertyAssocOne extends BeanPropertyAssoc { - private final boolean oneToOne; + protected final boolean oneToOne; - private final boolean oneToOneExported; + protected final boolean oneToOneExported; - private final boolean importedPrimaryKey; + protected final boolean importedPrimaryKey; - private final LocalHelp localHelp; + protected final AssocOneHelp localHelp; - private final BeanProperty[] embeddedProps; + protected final BeanProperty[] embeddedProps; - private final HashMap embeddedPropsMap; + protected final HashMap embeddedPropsMap; /** * The information for Imported foreign Keys. */ - private ImportedId importedId; + protected ImportedId importedId; - private ExportedProperty[] exportedProperties; + protected ExportedProperty[] exportedProperties; - private String deleteByParentIdSql; - private String deleteByParentIdInSql; - BeanPropertyAssocMany relationshipProperty; + protected String deleteByParentIdSql; + protected String deleteByParentIdInSql; + + protected BeanPropertyAssocMany relationshipProperty; /** * Create based on deploy information of an EmbeddedId. @@ -629,269 +627,13 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } } - private LocalHelp createHelp(boolean embedded, boolean oneToOneExported) { + private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported) { if (embedded) { - return new Embedded(); + return new AssocOneHelpEmbedded(this); } else if (oneToOneExported) { - return new ReferenceExported(); + return new AssocOneHelpReferenceExported(this); } else { - return new Reference(); - } - } - - /** - * Local interface to handle Embedded, Reference and Reference Exported - * cases. - */ - private abstract class LocalHelp { - - abstract void loadIgnore(DbReadContext ctx); - - abstract Object read(DbReadContext ctx) throws SQLException; - - abstract Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException; - - abstract void appendSelect(DbSqlContext ctx, boolean subQuery); - - abstract void appendFrom(DbSqlContext ctx, SqlJoinType joinType); - - } - - private final class Embedded extends LocalHelp { - - void loadIgnore(DbReadContext ctx) { - for (int i = 0; i < embeddedProps.length; i++) { - embeddedProps[i].loadIgnore(ctx); - } - } - - @Override - Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { - Object dbVal = read(ctx); - if (bean != null) { - // set back to the parent bean - setValue(bean, dbVal); - ctx.propagateState(dbVal); - return dbVal; - - } else { - return null; - } - } - - @Override - Object read(DbReadContext ctx) throws SQLException { - - EntityBean embeddedBean = targetDescriptor.createEntityBean(); - - boolean notNull = false; - for (int i = 0; i < embeddedProps.length; i++) { - Object value = embeddedProps[i].readSet(ctx, embeddedBean); - if (value != null) { - notNull = true; - } - } - if (notNull) { - ctx.propagateState(embeddedBean); - return embeddedBean; - } else { - return null; - } - } - - @Override - void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { - } - - @Override - void appendSelect(DbSqlContext ctx, boolean subQuery) { - for (int i = 0; i < embeddedProps.length; i++) { - embeddedProps[i].appendSelect(ctx, subQuery); - } - } - } - - /** - * For imported reference - this is the common case. - */ - private final class Reference extends LocalHelp { - - Reference() { - } - - @Override - void loadIgnore(DbReadContext ctx) { - targetIdBinder.loadIgnore(ctx); - if (targetInheritInfo != null) { - ctx.getDataReader().incrementPos(1); - } - } - - @Override - Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { - Object val = read(ctx); - if (bean != null) { - setValue(bean, val); - ctx.propagateState(val); - } - return val; - } - - /** - * Read and set a Reference bean. - */ - @Override - Object read(DbReadContext ctx) throws SQLException { - - BeanDescriptor rowDescriptor = null; - Class rowType = targetType; - if (targetInheritInfo != null) { - // read discriminator to determine the type - InheritInfo rowInheritInfo = targetInheritInfo.readType(ctx); - if (rowInheritInfo != null) { - rowType = rowInheritInfo.getType(); - rowDescriptor = rowInheritInfo.getBeanDescriptor(); - } - } - - // read the foreign key column(s) - Object id = targetIdBinder.read(ctx); - if (id == null) { - return null; - } - - // check transaction context to see if it already exists - Object existing = ctx.getPersistenceContext().get(rowType, id); - - if (existing != null) { - return existing; - } - - Boolean readOnly = ctx.isReadOnly(); - Object ref; - if (targetInheritInfo != null) { - // for inheritance hierarchy create the correct type for this row... - ref = rowDescriptor.createReference(readOnly, id); - } else { - ref = targetDescriptor.createReference(readOnly, id); - } - - Object existingBean = ctx.getPersistenceContext().putIfAbsent(id, ref); - if (existingBean != null) { - // advanced case when we use multiple concurrent threads to - // build a single object graph, and another thread has since - // loaded a matching bean so we will use that instead. - ref = existingBean; - - } else { - EntityBeanIntercept ebi = ((EntityBean) ref)._ebean_getIntercept(); - if (Boolean.TRUE.equals(ctx.isReadOnly())) { - ebi.setReadOnly(true); - } - ctx.register(name, ebi); - } - - return ref; - } - - @Override - void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { - if (targetInheritInfo != null) { - // add join to support the discriminator column - String relativePrefix = ctx.getRelativePrefix(name); - tableJoin.addJoin(joinType, relativePrefix, ctx); - } - } - - /** - * Append columns for foreign key columns. - */ - @Override - void appendSelect(DbSqlContext ctx, boolean subQuery) { - - if (!subQuery && targetInheritInfo != null) { - // add discriminator column - String relativePrefix = ctx.getRelativePrefix(getName()); - String tableAlias = ctx.getTableAlias(relativePrefix); - ctx.appendColumn(tableAlias, targetInheritInfo.getDiscriminatorColumn()); - } - importedId.sqlAppend(ctx); - } - } - - /** - * For OneToOne exported reference - not so common. - */ - private final class ReferenceExported extends LocalHelp { - - @Override - void loadIgnore(DbReadContext ctx) { - targetDescriptor.getIdBinder().loadIgnore(ctx); - } - - /** - * Read and set a Reference bean. - */ - @Override - Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { - - Object dbVal = read(ctx); - if (bean != null) { - setValue(bean, dbVal); - ctx.propagateState(dbVal); - } - return dbVal; - } - - @Override - Object read(DbReadContext ctx) throws SQLException { - - // TODO: Support for Inheritance hierarchy on exported OneToOne ? - IdBinder idBinder = targetDescriptor.getIdBinder(); - Object id = idBinder.read(ctx); - if (id == null) { - return null; - } - - PersistenceContext persistCtx = ctx.getPersistenceContext(); - Object existing = persistCtx.get(targetType, id); - - if (existing != null) { - return existing; - } - Object ref = targetDescriptor.createReference(ctx.isReadOnly(), id); - - EntityBeanIntercept ebi = ((EntityBean) ref)._ebean_getIntercept(); - if (Boolean.TRUE.equals(ctx.isReadOnly())) { - ebi.setReadOnly(true); - } - persistCtx.put(id, ref); - ctx.register(name, ebi); - return ref; - } - - /** - * Append columns for foreign key columns. - */ - @Override - void appendSelect(DbSqlContext ctx, boolean subQuery) { - - // set appropriate tableAlias for the exported id columns - - String relativePrefix = ctx.getRelativePrefix(getName()); - ctx.pushTableAlias(relativePrefix); - - IdBinder idBinder = targetDescriptor.getIdBinder(); - idBinder.appendSelect(ctx, subQuery); - - ctx.popTableAlias(); - } - - @Override - void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { - - String relativePrefix = ctx.getRelativePrefix(getName()); - tableJoin.addJoin(joinType, relativePrefix, ctx); + return new AssocOneHelpReference(this); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java index b64dacb58..0466085ee 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -239,7 +239,7 @@ public class SqlTreeNodeBean implements SqlTreeNode { localBean = null; } else if (!temporalVersions) { // check the PersistenceContext to see if the bean already exists - contextBean = (EntityBean) persistenceContext.putIfAbsent(id, localBean); + contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean); if (contextBean == null) { // bean just added to the persistenceContext contextBean = localBean; diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java index 8fbfa1035..e2ad41484 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java @@ -120,7 +120,7 @@ public class ReadJson { return null; } - Object contextBean = persistenceContext.putIfAbsent(id, bean); + Object contextBean = beanDesc.contextPutIfAbsent(persistenceContext, id, bean); if (contextBean == null) { if (loadContext != null) { EntityBeanIntercept ebi = bean._ebean_getIntercept(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java index 68c0a0a39..d54659b8e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -1,9 +1,9 @@ package com.avaje.ebeaninternal.server.transaction; import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebean.bean.PersistenceContextUtil; import com.avaje.ebeaninternal.api.Monitor; -import javax.persistence.Entity; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -31,7 +31,7 @@ public final class DefaultPersistenceContext implements PersistenceContext { /** * Map used hold caches. One cache per bean type. */ - private final HashMap typeCache = new HashMap(); + private final HashMap, ClassContext> typeCache = new HashMap, ClassContext>(); private final Monitor monitor = new Monitor(); @@ -44,15 +44,15 @@ public final class DefaultPersistenceContext implements PersistenceContext { /** * Set an object into the PersistenceContext. */ - public void put(Object id, Object bean) { + public void put(Class rootType, Object id, Object bean) { synchronized (monitor) { - getClassContext(bean.getClass()).put(id, bean); + getClassContext(rootType).put(id, bean); } } - public Object putIfAbsent(Object id, Object bean) { + public Object putIfAbsent(Class rootType, Object id, Object bean) { synchronized (monitor) { - return getClassContext(bean.getClass()).putIfAbsent(id, bean); + return getClassContext(rootType).putIfAbsent(id, bean); } } @@ -76,7 +76,7 @@ public final class DefaultPersistenceContext implements PersistenceContext { */ public int size(Class beanType) { synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); + ClassContext classMap = typeCache.get(beanType); return classMap == null ? 0 : classMap.size(); } } @@ -92,7 +92,7 @@ public final class DefaultPersistenceContext implements PersistenceContext { public void clear(Class beanType) { synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); + ClassContext classMap = typeCache.get(beanType); if (classMap != null) { classMap.clear(); } @@ -101,7 +101,7 @@ public final class DefaultPersistenceContext implements PersistenceContext { public void deleted(Class beanType, Object id) { synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); + ClassContext classMap = typeCache.get(beanType); if (classMap != null && id != null) { classMap.deleted(id); } @@ -110,7 +110,7 @@ public final class DefaultPersistenceContext implements PersistenceContext { public void clear(Class beanType, Object id) { synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); + ClassContext classMap = typeCache.get(beanType); if (classMap != null && id != null) { classMap.remove(id); } @@ -125,25 +125,15 @@ public final class DefaultPersistenceContext implements PersistenceContext { private ClassContext getClassContext(Class beanType) { - String clsName = getBeanBaseType(beanType).getName(); - ClassContext classMap = typeCache.get(clsName); + Class rootType = PersistenceContextUtil.root(beanType); + ClassContext classMap = typeCache.get(rootType); if (classMap == null) { classMap = new ClassContext(); - typeCache.put(clsName, classMap); + typeCache.put(rootType, classMap); } return classMap; } - private Class getBeanBaseType(Class beanType) { - Class parent = beanType.getSuperclass(); - - while (parent != null && parent.isAnnotationPresent(Entity.class)) { - beanType = parent; - parent = parent.getSuperclass(); - } - return beanType; - } - private static class ClassContext { private final Map map = new HashMap(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/NoopPersistenceContext.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/NoopPersistenceContext.java index ef2607c5e..503833fd9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/NoopPersistenceContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/NoopPersistenceContext.java @@ -11,12 +11,12 @@ import com.avaje.ebean.bean.PersistenceContext; public class NoopPersistenceContext implements PersistenceContext { @Override - public void put(Object id, Object bean) { + public void put(Class rootType, Object id, Object bean) { // do nothing } @Override - public Object putIfAbsent(Object id, Object bean) { + public Object putIfAbsent(Class rootType, Object id, Object bean) { // do nothing return null; } diff --git a/src/test/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java b/src/test/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java new file mode 100644 index 000000000..380dcc9d8 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContextTest.java @@ -0,0 +1,157 @@ +package com.avaje.ebeaninternal.server.transaction; + +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.tests.model.basic.Car; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Vehicle; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class DefaultPersistenceContextTest { + + Customer customer42; + + Car car1; + + public DefaultPersistenceContextTest() { + customer42 = new Customer(); + customer42.setId(42); + + car1 = new Car(); + car1.setId(1); + } + + PersistenceContext pc() { + return new DefaultPersistenceContext(); + } + + PersistenceContext pcWith42() { + PersistenceContext pc = pc(); + pc.put(Customer.class, 42, customer42); + return pc; + } + + @Test + public void put_get_withInheritance() throws Exception { + + PersistenceContext pc = pc(); + pc.put(Vehicle.class, 1, car1); + + Object found = pc.get(Car.class, 1); + assertThat(found).isSameAs(car1); + } + + @Test + public void put_get() throws Exception { + + PersistenceContext pc = pc(); + pc.put(Customer.class, customer42.getId(), customer42); + + Object found = pc.get(Customer.class, 42); + assertThat(found).isSameAs(customer42); + } + + @Test + public void putIfAbsent_when_absent() throws Exception { + + PersistenceContext pc = pc(); + Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), customer42); + + assertThat(existing).isNull(); + } + + @Test + public void putIfAbsent_when_notAbsent() throws Exception { + + PersistenceContext pc = pcWith42(); + Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), new Customer()); + + assertThat(existing).isSameAs(customer42); + } + + @Test + public void get_when_empty() throws Exception { + PersistenceContext pc = pc(); + Object found = pc.get(Customer.class, 42); + assertThat(found).isNull(); + } + + @Test + public void get_when_there() throws Exception { + PersistenceContext pc = pcWith42(); + Object found = pc.get(Customer.class, 42); + assertThat(found).isSameAs(customer42); + } + + @Test + public void getWithOption_when_empty() throws Exception { + + PersistenceContext pc = pc(); + PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42); + assertThat(withOption).isNull(); + } + + @Test + public void getWithOption_when_there() throws Exception { + + PersistenceContext pc = pcWith42(); + + PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42); + assertThat(withOption.getBean()).isSameAs(customer42); + } + + @Test + public void getWithOption_when_deleted() throws Exception { + + PersistenceContext pc = pcWith42(); + pc.deleted(Customer.class, 42); + + PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42); + assertThat(withOption.isDeleted()).isTrue(); + assertThat(withOption.getBean()).isNull(); + } + + @Test + public void size_when_empty() throws Exception { + + PersistenceContext pc = pc(); + assertThat(pc.size(Customer.class)).isEqualTo(0); + } + + @Test + public void size_when_some() throws Exception { + + PersistenceContext pc = pcWith42(); + assertThat(pc.size(Customer.class)).isEqualTo(1); + } + + @Test + public void clear() throws Exception { + + PersistenceContext pc = pcWith42(); + pc.clear(); + assertThat(pc.size(Customer.class)).isEqualTo(0); + } + + @Test + public void clearClass() throws Exception { + + PersistenceContext pc = pcWith42(); + pc.clear(Customer.class); + assertThat(pc.size(Customer.class)).isEqualTo(0); + } + + @Test + public void clearClassAndId() throws Exception { + + PersistenceContext pc = pcWith42(); + pc.put(Customer.class, 43, new Customer()); + + pc.clear(Customer.class, 42); + assertThat(pc.size(Customer.class)).isEqualTo(1); + + pc.clear(Customer.class, 43); + assertThat(pc.size(Customer.class)).isEqualTo(0); + } +} \ No newline at end of file