Support @ManyToOne inside @Embeddables - fix for #52 (#1608)

* #52 - @ManyToOne support inside @Embeddables

Initial test and work

* #52 - @ManyToOne support inside @Embeddables

Initial test and work

* #52 - @ManyToOne support inside @Embeddables

Initial test and work

* #52 - @ManyToOne support inside @Embeddables

Initial test and work

* #52 - @ManyToOne support inside @Embeddables

Initial test and work

* #52 - @ManyToOne support inside @Embeddables

Initial test and work

* #52 - @ManyToOne support inside @Embeddables

Update test with sql capture

* #52 - @ManyToOne support inside @Embeddables

Update test with cache clearing before hand (such that we hit DB on lazy load)
This commit is contained in:
Rob Bygrave
2019-01-10 03:40:02 +13:00
committed by GitHub
parent 2b0669c2ae
commit 6b27a9d280
20 changed files with 356 additions and 69 deletions
@@ -147,7 +147,11 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
@Override
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
visitScalar(p);
if (p instanceof BeanPropertyAssocOne) {
visitOneImported((BeanPropertyAssocOne)p);
} else {
visitScalar(p);
}
if (embedded.isId()) {
// compound primary key
lastColumn.setPrimaryKey(true);
@@ -17,9 +17,16 @@ abstract class AssocOneHelp {
protected final BeanDescriptor<?> target;
private final String path;
AssocOneHelp(BeanPropertyAssocOne<?> property) {
this(property, null);
}
AssocOneHelp(BeanPropertyAssocOne<?> property, String embeddedPrefix) {
this.property = property;
this.target = property.targetDescriptor;
this.path = (embeddedPrefix == null) ? property.name : embeddedPrefix + "." + property.name;
}
/**
@@ -67,7 +74,7 @@ abstract class AssocOneHelp {
boolean disableLazyLoading = ctx.isDisableLazyLoading();
Object ref = target.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
if (!disableLazyLoading) {
ctx.register(property.name, ((EntityBean) ref)._ebean_getIntercept());
ctx.register(path, ((EntityBean) ref)._ebean_getIntercept());
}
return ref;
}
@@ -5,8 +5,8 @@ package io.ebeaninternal.server.deploy;
*/
class AssocOneHelpRefSimple extends AssocOneHelp {
AssocOneHelpRefSimple(BeanPropertyAssocOne<?> property) {
super(property);
AssocOneHelpRefSimple(BeanPropertyAssocOne<?> property, String embeddedPrefix) {
super(property, embeddedPrefix);
}
/**
@@ -724,16 +724,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
inheritInfo.setDescriptor(this);
}
if (isEmbedded()) {
// initialise all the properties
for (BeanProperty prop : propertiesAll()) {
prop.initialise(initContext);
}
} else {
// initialise just the Id properties
if (idProperty != null) {
idProperty.initialise(initContext);
}
// initialise just the Id property only
if (idProperty != null) {
idProperty.initialise(initContext);
}
}
@@ -759,14 +752,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
if (!isEmbedded()) {
// initialise all the non-id properties
for (BeanProperty prop : propertiesAll()) {
if (!prop.isId()) {
prop.initialise(initContext);
}
prop.registerColumn(this, null);
// initialise all the non-id properties
for (BeanProperty prop : propertiesAll()) {
if (!prop.isId()) {
prop.initialise(initContext);
}
prop.registerColumn(this, null);
}
if (unidirectional != null) {
@@ -8,6 +8,8 @@ class BeanDescriptorInitContext {
private final Map<String, String> draftTables;
private final String asOfViewSuffix;
private String embeddedPrefix;
BeanDescriptorInitContext(Map<String, String> withHistoryTables, Map<String, String> draftTables, String asOfViewSuffix) {
this.withHistoryTables = withHistoryTables;
this.draftTables = draftTables;
@@ -29,4 +31,12 @@ class BeanDescriptorInitContext {
void addDraftIntersection(String intersectionPublishTable, String intersectionDraftTable) {
draftTables.put(intersectionPublishTable, intersectionDraftTable);
}
public void setEmbeddedPrefix(String embeddedPrefix) {
this.embeddedPrefix = embeddedPrefix;
}
public String getEmbeddedPrefix() {
return embeddedPrefix;
}
}
@@ -150,8 +150,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final String serverName;
private Map<Class<?>, DeployBeanInfo<?>> deployInfoMap = new HashMap<>();
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<>();
private final Map<String, BeanDescriptor<?>> descMap = new HashMap<>();
@@ -196,6 +194,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final int queryPlanTTLSeconds;
// temporary collections used during startup and then cleared
private Map<Class<?>, DeployBeanInfo<?>> deployInfoMap = new HashMap<>();
private Set<Class<?>> embeddedIdTypes = new HashSet<>();
private List<DeployBeanInfo<?>> embeddedBeans = new ArrayList<>();
/**
* Create for a given database dbConfig.
*/
@@ -366,7 +370,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
createListeners();
readEntityDeploymentInitial();
readXmlMapping(mappings);
readEmbeddedDeployment();
readEntityBeanTable();
readEntityDeploymentAssociations();
readInheritedIdGenerators();
@@ -385,7 +388,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
logStatus();
deployInfoMap.clear();
// clear collections we no longer need
embeddedIdTypes = null;
embeddedBeans = null;
deployInfoMap = null;
return asOfTableMap;
@@ -592,8 +597,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
for (BeanDescriptor<?> d : descMap.values()) {
d.initLast();
if (!d.isEmbedded()) {
BeanManager<?> m = beanManagerFactory.create(d);
beanManagerMap.put(d.getFullName(), m);
beanManagerMap.put(d.getFullName(), beanManagerFactory.create(d));
checkForValidEmbeddedId(d);
}
}
@@ -676,11 +680,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
logger.debug("Entities[{}]", entityBeanCount);
}
private <T> BeanDescriptor<T> createEmbedded(Class<T> beanClass) {
DeployBeanInfo<T> info = getDeploy(beanClass);
return new BeanDescriptor<>(this, info.getDescriptor());
}
/**
* Return the bean deploy info for the given class.
*/
@@ -689,24 +688,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return (DeployBeanInfo<T>) deployInfoMap.get(cls);
}
private void registerBeanDescriptor(BeanDescriptor<?> desc) {
private void registerBeanDescriptor(DeployBeanInfo<?> info) {
BeanDescriptor desc = new BeanDescriptor<>(this, info.getDescriptor());
descMap.put(desc.getBeanType().getName(), desc);
if (desc.isDocStoreMapped()) {
descQueueMap.put(desc.getDocStoreQueueId(), desc);
}
}
/**
* Read deployment information for all the embedded beans.
*/
private void readEmbeddedDeployment() {
List<Class<?>> embeddedClasses = bootupClasses.getEmbeddables();
for (Class<?> embeddedClass : embeddedClasses) {
registerBeanDescriptor(createEmbedded(embeddedClass));
}
}
/**
* Read the initial deployment information for the entities.
* <p>
@@ -719,14 +708,31 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
for (Class<?> entityClass : bootupClasses.getEntities()) {
DeployBeanInfo<?> info = createDeployBeanInfo(entityClass);
deployInfoMap.put(entityClass, info);
Class<?> embeddedIdType = info.getEmbeddedIdType();
if (embeddedIdType != null){
embeddedIdTypes.add(embeddedIdType);
}
}
for (Class<?> entityClass : bootupClasses.getEmbeddables()) {
DeployBeanInfo<?> info = createDeployBeanInfo(entityClass);
readDeployAssociations(info);
deployInfoMap.put(entityClass, info);
if (embeddedIdTypes.contains(entityClass)) {
// register embeddedId types early - scalar properties only
// and needed for creating BeanTables (id properties)
registerEmbeddedBean(info);
} else {
// delay register of other embedded beans until after
// the BeanTables have been created to support ManyToOne
embeddedBeans.add(info);
}
}
}
private void registerEmbeddedBean(DeployBeanInfo<?> info) {
readDeployAssociations(info);
registerBeanDescriptor(info);
}
/**
* Create the BeanTable information which has the base table and id.
* <p>
@@ -739,6 +745,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
BeanTable beanTable = createBeanTable(info);
beanTableMap.put(beanTable.getBeanType(), beanTable);
}
// register non-id embedded beans (after bean tables are created)
for (DeployBeanInfo<?> info : embeddedBeans) {
registerEmbeddedBean(info);
}
}
/**
@@ -816,13 +827,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
secondaryPropsJoins(info);
}
// Set inheritance info
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
setInheritanceInfo(info);
}
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
registerBeanDescriptor(new BeanDescriptor(this, info.getDescriptor()));
if (!info.isEmbedded()) {
registerBeanDescriptor(info);
}
}
}
@@ -30,7 +30,7 @@ public class BeanEmbeddedMetaFactory {
String columnPrefix = prop.getColumnPrefix();
Map<String, String> propColMap = prop.getDeployEmbedded().getPropertyColumnMap();
BeanProperty[] sourceProperties = targetDesc.propertiesBaseScalar();
BeanProperty[] sourceProperties = targetDesc.propertiesNonTransient();
BeanProperty[] embeddedProperties = new BeanProperty[sourceProperties.length];
for (int i = 0; i < sourceProperties.length; i++) {
@@ -45,7 +45,11 @@ public class BeanEmbeddedMetaFactory {
}
BeanPropertyOverride overrides = new BeanPropertyOverride(dbColumn);
embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides);
if (sourceProperties[i] instanceof BeanPropertyAssocOne) {
embeddedProperties[i] = new BeanPropertyAssocOne((BeanPropertyAssocOne)sourceProperties[i], overrides);
} else {
embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides);
}
}
return new BeanEmbeddedMeta(embeddedProperties);
@@ -104,6 +104,22 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
this.fetchPreference = deploy.getFetchPreference();
}
/**
* Copy constructor for ManyToOne inside Embeddable.
*/
public BeanPropertyAssoc(BeanPropertyAssoc source, BeanPropertyOverride override) {
super(source, override);
foreignKey = source.foreignKey;
extraWhere = source.extraWhere;
beanTable = source.beanTable;
mappedBy = source.mappedBy;
docStoreDoc = source.docStoreDoc;
targetType = source.targetType;
cascadeInfo = source.cascadeInfo;
fetchPreference = source.fetchPreference;
tableJoin = source.tableJoin.withOverrideColumn(override.getDbColumn());
}
/**
* Initialise post construction.
*/
@@ -93,14 +93,35 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
/**
* Copy constructor for ManyToOne inside Embeddable.
*/
public BeanPropertyAssocOne(BeanPropertyAssocOne source, BeanPropertyOverride override) {
super(source, override);
primaryKeyExport = source.primaryKeyExport;
oneToOne = source.oneToOne;
oneToOneExported = source.oneToOneExported;
orphanRemoval = source.orphanRemoval;
embeddedProps = null;
embeddedPropsMap = null;
}
@Override
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
initialiseAssocOne();
initialiseAssocOne(initContext.getEmbeddedPrefix());
if (embedded) {
// initialise ManyToOne importedId
initContext.setEmbeddedPrefix(name);
for (BeanProperty embeddedProp : embeddedProps) {
embeddedProp.initialise(initContext);
}
initContext.setEmbeddedPrefix(null);
}
}
private void initialiseAssocOne() {
localHelp = createHelp(embedded, oneToOneExported);
private void initialiseAssocOne(String embeddedPrefix) {
localHelp = createHelp(embedded, oneToOneExported, embeddedPrefix);
if (!isTransient) {
//noinspection StatementWithEmptyBody
@@ -668,7 +689,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported) {
private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported, String embeddedPrefix) {
if (embedded) {
return new AssocOneHelpEmbedded(this);
} else if (oneToOneExported) {
@@ -677,7 +698,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
if (targetInheritInfo != null) {
return new AssocOneHelpRefInherit(this);
} else {
return new AssocOneHelpRefSimple(this);
return new AssocOneHelpRefSimple(this, embeddedPrefix);
}
}
}
@@ -51,6 +51,15 @@ public final class TableJoin {
this.queryHash = calcQueryHash();
}
private TableJoin(TableJoin source, String overrideColumn) {
this.table = source.table;
this.type = source.type;
this.inheritInfo = source.inheritInfo;
this.columns = new TableJoinColumn[1];
this.columns[0] = source.columns[0].withOverrideColumn(overrideColumn);
this.queryHash = calcQueryHash();
}
/**
* Calculate a hash value for adding to a query plan.
*/
@@ -166,4 +175,11 @@ public final class TableJoin {
sb.append(a2).append(".").append(pair.getForeignDbColumn());
}
}
TableJoin withOverrideColumn(String overrideColumn) {
if (columns.length == 1 && overrideColumn != null && !overrideColumn.equals(columns[0].getLocalDbColumn())) {
return new TableJoin(this, overrideColumn);
}
return this;
}
}
@@ -46,6 +46,16 @@ public class TableJoinColumn {
this.queryHash = hash();
}
private TableJoinColumn(TableJoinColumn source, String overrideColumn) {
this.localDbColumn = InternString.intern(overrideColumn);
this.foreignDbColumn = source.foreignDbColumn;
this.localSqlFormula = null;
this.foreignSqlFormula = null;
this.insertable = source.isInsertable();
this.updateable = source.isUpdateable();
this.queryHash = hash();
}
int hash() {
int result = localDbColumn != null ? localDbColumn.hashCode() : 0;
result = 92821 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0);
@@ -123,4 +133,7 @@ public class TableJoinColumn {
return foreignSqlFormula;
}
TableJoinColumn withOverrideColumn(String overrideColumn) {
return new TableJoinColumn(this, overrideColumn);
}
}
@@ -132,6 +132,7 @@ public class AnnotationFields extends AnnotationParser {
prop.setId();
prop.setNullable(false);
prop.setEmbedded();
info.setEmbeddedId(prop);
}
DocEmbedded docEmbedded = get(prop, DocEmbedded.class);
@@ -54,6 +54,7 @@ public abstract class AnnotationParser extends AnnotationBase {
} else {
prop.setId();
prop.setEmbedded();
info.setEmbeddedId(prop);
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy.parse;
import io.ebean.RawSql;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -23,6 +24,8 @@ public class DeployBeanInfo<T> {
private final DeployBeanDescriptor<T> descriptor;
private DeployBeanPropertyAssoc<?> embeddedId;
/**
* Create with a DeployUtil and BeanDescriptor.
*/
@@ -89,4 +92,19 @@ public class DeployBeanInfo<T> {
public void setPrimaryKeyJoin(TableJoin join) {
descriptor.setPrimaryKeyJoin(join);
}
/**
* This bean type has an embedded Id property.
*/
public void setEmbeddedId(DeployBeanPropertyAssoc<?> embeddedId) {
this.embeddedId = embeddedId;
}
public Class<?> getEmbeddedIdType() {
return (embeddedId == null) ? null : embeddedId.getTargetType();
}
public boolean isEmbedded() {
return descriptor.isEmbedded();
}
}
@@ -28,7 +28,7 @@ public class FactoryBaseProperties {
for (BeanProperty prop : desc.propertiesBaseScalar()) {
if (!prop.isImportedPrimaryKey()) {
Bindable item = factoryProperty.create(prop, mode, withLobs);
Bindable item = factoryProperty.create(prop, mode, withLobs, false);
if (item != null) {
list.add(item);
}
@@ -24,13 +24,11 @@ public class FactoryEmbedded {
*/
public void create(List<Bindable> list, BeanDescriptor<?> desc, DmlMode mode, boolean withLobs) {
BeanPropertyAssocOne<?>[] embedded = desc.propertiesEmbedded();
for (BeanPropertyAssocOne<?> anEmbedded : embedded) {
for (BeanPropertyAssocOne<?> anEmbedded : desc.propertiesEmbedded()) {
BeanProperty[] props = anEmbedded.getProperties();
List<Bindable> bindList = new ArrayList<>(props.length);
for (BeanProperty prop : props) {
Bindable item = factoryProperty.create(prop, mode, withLobs);
Bindable item = factoryProperty.create(prop, mode, withLobs, true);
if (item != null) {
bindList.add(item);
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.persist.dmlbind;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.persist.dml.DmlMode;
/**
@@ -14,14 +15,14 @@ public class FactoryProperty {
private final boolean bindEncryptDataFirst;
public FactoryProperty(boolean bindEncryptDataFirst) {
FactoryProperty(boolean bindEncryptDataFirst) {
this.bindEncryptDataFirst = bindEncryptDataFirst;
}
/**
* Create a Bindable for the property given the mode and withLobs flag.
*/
public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs) {
public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs, boolean allowManyToOne) {
if (DmlMode.INSERT == mode && !prop.isDbInsertable()) {
return null;
@@ -30,15 +31,18 @@ public class FactoryProperty {
return null;
}
if (prop.isLob()) {
if (!withLobs) {
// Lob exclusion
return null;
} else {
return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop);
}
if (prop.isLob() && !withLobs) {
// Lob exclusion
return null;
}
if (prop.isDbEncrypted()){
return new BindableEncryptedProperty(prop, bindEncryptDataFirst);
}
return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop);
if (allowManyToOne && prop instanceof BeanPropertyAssocOne) {
return new BindableAssocOne((BeanPropertyAssocOne<?>)prop);
}
return new BindableProperty(prop);
}
}