#115 - Mapping - Add support for @ElementCollection enhancement

Initial support - simple List.
This commit is contained in:
Rob Bygrave
2018-03-20 14:22:29 +13:00
parent c44931ea25
commit 74a9019535
79 changed files with 1891 additions and 805 deletions
@@ -1,6 +1,5 @@
package io.ebeaninternal.api;
import io.ebean.EbeanServer;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.util.StringHelper;
@@ -124,11 +123,11 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBeanProperty();
}
public SpiQuery<?> createQuery(EbeanServer server, int batchSize) {
public SpiQuery<?> createQuery(SpiEbeanServer server, int batchSize) {
BeanPropertyAssocMany<?> many = getMany();
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
SpiQuery<?> query = many.newQuery(server);
String orderBy = many.getLazyFetchOrderBy();
if (orderBy != null) {
query.orderBy(orderBy);
@@ -4,13 +4,18 @@ import io.ebean.config.DbConstraintNaming;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbPlatformTypeMapping;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.dbmigration.model.ModelContainer;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.TableJoinColumn;
import io.ebeaninternal.server.type.ScalarType;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The context used during DDL generation.
@@ -187,4 +192,49 @@ public class ModelBuildContext {
addTable(draftTable);
}
/**
* Return a builder to add foreign keys.
*/
public FkeyBuilder fkeyBuilder(MTable destTable) {
return new FkeyBuilder(this, destTable);
}
public static class FkeyBuilder {
private final AtomicInteger count = new AtomicInteger();
private final ModelBuildContext ctx;
private final MTable destTable;
private final String tableName;
FkeyBuilder(ModelBuildContext ctx, MTable destTable) {
this.ctx = ctx;
this.destTable = destTable;
this.tableName = destTable.getName();
}
/**
* Add a foreign key based on the table join.
*/
public FkeyBuilder addForeignKey(BeanDescriptor<?> desc, TableJoin tableJoin, boolean direction) {
String baseTable = ctx.normaliseTable(desc.getBaseTable());
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, count.incrementAndGet());
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, count.get());
MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex);
for (TableJoinColumn column : tableJoin.columns()) {
String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
foreignKey.addColumnPair(localCol, refCol);
}
destTable.addForeignKey(foreignKey);
return this;
}
}
}
@@ -0,0 +1,34 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanTable;
/**
* Add the element collection table to the model.
*/
public class ModelBuildElementTable {
/**
* Build and add the MTable model for the ElementCollection property.
*/
public static void build(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
BeanTable beanTable = manyProp.getBeanTable();
BeanDescriptor<?> targetDescriptor = manyProp.getTargetDescriptor();
MTable table = new MTable(beanTable.getBaseTable());
VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor));
ctx.fkeyBuilder(table)
.addForeignKey(manyProp.getBeanDescriptor(), manyProp.getTableJoin(), true);
ctx.addTable(table);
}
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -12,7 +11,7 @@ import io.ebeaninternal.server.deploy.TableJoinColumn;
/**
* Add the intersection table to the model.
*/
public class ModelBuildIntersectionTable {
class ModelBuildIntersectionTable {
private final ModelBuildContext ctx;
@@ -22,9 +21,7 @@ public class ModelBuildIntersectionTable {
private MTable intersectionTable;
private int countForeignKey;
public ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
this.ctx = ctx;
this.manyProp = manyProp;
this.intersectionTableJoin = manyProp.getIntersectionTableJoin();
@@ -51,33 +48,13 @@ public class ModelBuildIntersectionTable {
private void buildFkConstraints() {
BeanDescriptor<?> localDesc = manyProp.getBeanDescriptor();
buildFkConstraints(localDesc, intersectionTableJoin.columns(), true);
BeanDescriptor<?> targetDesc = manyProp.getTargetDescriptor();
buildFkConstraints(targetDesc, tableJoin.columns(), false);
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
intersectionTable.checkDuplicateForeignKeys();
}
private void buildFkConstraints(BeanDescriptor<?> desc, TableJoinColumn[] columns, boolean direction) {
String tableName = intersectionTableJoin.getTable();
String baseTable = ctx.normaliseTable(desc.getBaseTable());
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, ++countForeignKey);
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, countForeignKey);
MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex);
intersectionTable.addForeignKey(foreignKey);
for (TableJoinColumn column : columns) {
String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
foreignKey.addColumnPair(localCol, refCol);
}
}
private MTable createTable() {
BeanDescriptor<?> localDesc = manyProp.getBeanDescriptor();
@@ -123,27 +123,24 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
@Override
public void visitMany(BeanPropertyAssocMany<?> p) {
if (p.hasJoinTable()) {
if (p.getMappedBy() == null) {
// only create on other 'owning' side
if (p.hasJoinTable() && p.getMappedBy() == null) {
// only create on other 'owning' side
//TableJoin intersectionTableJoin = p.getIntersectionTableJoin();
// check if the intersection table has already been created
// build the create table and fkey constraints
// putting the DDL into ctx for later output as we are
// in the middle of rendering the create table DDL
MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build();
if (p.isO2mJoinTable()) {
intersectionTable.clearForeignKeyIndexes();
Collection<MColumn> cols = intersectionTable.allColumns();
if (cols.size() == 2) {
// always the second column that we put the unique constraint on
MColumn col = new ArrayList<>(cols).get(1);
col.setUnique(determineUniqueConstraintName(col.getName()));
}
// build the create table and fkey constraints
// putting the DDL into ctx for later output as we are
// in the middle of rendering the create table DDL
MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build();
if (p.isO2mJoinTable()) {
intersectionTable.clearForeignKeyIndexes();
Collection<MColumn> cols = intersectionTable.allColumns();
if (cols.size() == 2) {
// always the second column that we put the unique constraint on
MColumn col = new ArrayList<>(cols).get(1);
col.setUnique(determineUniqueConstraintName(col.getName()));
}
}
} else if (p.isElementCollection()) {
ModelBuildElementTable.build(ctx, p);
}
}
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebean.util.SplitName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,7 +56,7 @@ public class ProfileOriginNodeUsage {
}
for (String propName : aggregateUsed) {
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
BeanProperty beanProp = desc.findPropertyFromPath(propName);
if (beanProp == null) {
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
@@ -79,6 +79,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private List<T> cacheBeans;
private BeanPropertyAssocMany<?> manyProperty;
/**
* Create the InternalQueryRequest.
*/
@@ -496,11 +498,18 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
/**
* Return the many property that is fetched in the query or null if there is
* not one.
* Determine and return the ToMany property that is included in the query.
*/
public BeanPropertyAssocMany<?> determineMany() {
manyProperty = beanDescriptor.getManyProperty(query);
return manyProperty;
}
/**
* Return the many property that is fetched in the query or null if there is not one.
*/
public BeanPropertyAssocMany<?> getManyProperty() {
return beanDescriptor.getManyProperty(query);
return manyProperty;
}
/**
@@ -1,11 +1,47 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collection;
abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
final BeanPropertyAssocMany<T> many;
final BeanDescriptor<T> targetDescriptor;
final String propertyName;
BeanCollectionLoader loader;
BaseCollectionHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
}
BaseCollectionHelp() {
this.many = null;
this.targetDescriptor = null;
this.propertyName = null;
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
public Collection underlying(Object value) {
if (value instanceof BeanCollection) {
@@ -14,4 +50,18 @@ abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
return (Collection)value;
}
}
void jsonWriteCollection(SpiJsonWriter ctx, String name, Collection<?> list) throws IOException {
if (!list.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Object bean : list) {
jsonWriteElement(ctx, bean);
}
ctx.endAssocMany();
}
}
void jsonWriteElement(SpiJsonWriter ctx, Object bean) throws IOException {
targetDescriptor.jsonWrite(ctx, (EntityBean) bean);
}
}
@@ -7,6 +7,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.query.CQueryCollectionAdd;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.IOException;
@@ -15,7 +16,7 @@ import java.util.Collection;
/**
* Helper functions for performing tasks on Lists Sets or Maps.
*/
public interface BeanCollectionHelp<T> {
public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
/**
* Set the EbeanServer that owns the configuration.
@@ -20,10 +20,12 @@ public class BeanCollectionHelpFactory {
*/
public static <T> BeanCollectionHelp<T> create(BeanPropertyAssocMany<T> manyProperty) {
boolean elementCollection = manyProperty.isElementCollection();
ManyType manyType = manyProperty.getManyType();
switch (manyType) {
case LIST:
return new BeanListHelp<>(manyProperty);
return elementCollection ? new BeanListHelpElement<>(manyProperty) : new BeanListHelp<>(manyProperty);
case SET:
return new BeanSetHelp<>(manyProperty);
case MAP:
@@ -35,9 +37,7 @@ public class BeanCollectionHelpFactory {
}
@SuppressWarnings("unchecked")
public static <T> BeanCollectionHelp<T> create(OrmQueryRequest<T> request) {
SpiQuery.Type manyType = request.getQuery().getType();
public static <T> BeanCollectionHelp<T> create(SpiQuery.Type manyType, OrmQueryRequest<T> request) {
if (manyType == SpiQuery.Type.LIST) {
return LIST_HELP;
@@ -62,7 +62,13 @@ import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.DmlUtil;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.query.ExtraJoin;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.STreePropertyAssoc;
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.OrmQueryDetail;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.text.json.ReadJson;
@@ -100,7 +106,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Describes Beans including their deployment information.
*/
public class BeanDescriptor<T> implements BeanType<T> {
public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
@@ -114,7 +120,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, SqlTreeProperty> dynamicProperty = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, STreeProperty> dynamicProperty = new ConcurrentHashMap<>();
private final Map<String, SpiRawSql> namedRawSql;
@@ -239,7 +245,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
private final BeanDescriptorMap owner;
private final String[] properties;
final String[] properties;
/**
* Intercept pre post on insert,update, and delete .
@@ -601,7 +607,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
/**
* Create an entity bean that is used as a prototype/factory to create new instances.
*/
private EntityBean createPrototypeEntityBean(Class<T> beanType) {
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
if (Modifier.isAbstract(beanType.getModifiers())) {
return null;
}
@@ -662,21 +668,19 @@ public class BeanDescriptor<T> implements BeanType<T> {
* These properties need to be initialised prior to the association properties
* as they are used to get the imported and exported properties.
* </p>
*
* @param withHistoryTables map populated if @History is supported on this entity bean
*/
public void initialiseId(Map<String, String> withHistoryTables, Map<String, String> draftTables) {
public void initialiseId(BeanDescriptorInitContext initContext) {
if (logger.isTraceEnabled()) {
logger.trace("BeanDescriptor initialise " + fullName);
}
if (draftable) {
draftTables.put(baseTable, draftTable);
initContext.addDraft(baseTable, draftTable);
}
if (historySupport) {
// add mapping (used to swap out baseTable for asOf queries)
withHistoryTables.put(baseTable, baseTableAsOf);
initContext.addHistory(baseTable, baseTableAsOf);
}
if (inheritInfo != null) {
@@ -686,28 +690,24 @@ public class BeanDescriptor<T> implements BeanType<T> {
if (isEmbedded()) {
// initialise all the properties
for (BeanProperty prop : propertiesAll()) {
prop.initialise();
prop.initialise(initContext);
}
} else {
// initialise just the Id properties
if (idProperty != null) {
idProperty.initialise();
idProperty.initialise(initContext);
}
}
}
/**
* Initialise the exported and imported parts for associated properties.
*
* @param asOfTableMap the map of base tables to associated 'with history' tables
* @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name
* @param draftTableMap the map of base tables to associated 'draft' tables.
*/
public void initialiseOther(Map<String, String> asOfTableMap, String asOfViewSuffix, Map<String, String> draftTableMap) {
public void initialiseOther(BeanDescriptorInitContext initContext) {
for (BeanPropertyAssocMany<?> aPropertiesManyToMany1 : propertiesManyToMany) {
for (BeanPropertyAssocMany<?> many : propertiesManyToMany) {
// register associated draft table for M2M intersection
aPropertiesManyToMany1.registerDraftIntersectionTable(draftTableMap);
many.registerDraftIntersectionTable(initContext);
}
if (historySupport) {
@@ -717,8 +717,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
// register associated history table for M2M intersection
if (!aPropertiesManyToMany.isExcludedFromHistory()) {
TableJoin intersectionTableJoin = aPropertiesManyToMany.getIntersectionTableJoin();
String intersectionTableName = intersectionTableJoin.getTable();
asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
initContext.addHistoryIntersection(intersectionTableJoin.getTable());
}
}
}
@@ -727,14 +726,14 @@ public class BeanDescriptor<T> implements BeanType<T> {
// initialise all the non-id properties
for (BeanProperty prop : propertiesAll()) {
if (!prop.isId()) {
prop.initialise();
prop.initialise(initContext);
}
prop.registerColumn(this, null);
}
}
if (unidirectional != null) {
unidirectional.initialise();
unidirectional.initialise(initContext);
}
idBinder.initialise();
@@ -799,7 +798,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
if (propName == null) {
return;
}
props[i] = findBeanProperty(propName);
props[i] = findProperty(propName);
}
if (props.length == 1) {
for (BeanProperty[] inserted : propertiesUnique) {
@@ -1639,6 +1638,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
/**
* Execute the postLoad if a BeanPostLoad exists for this bean.
*/
@Override
public void postLoad(Object bean) {
if (beanPostLoad != null) {
beanPostLoad.postLoad(bean);
@@ -1787,6 +1787,11 @@ public class BeanDescriptor<T> implements BeanType<T> {
return idBinder instanceof IdBinderSimple;
}
@Override
public boolean hasId() {
return idProperty != null;
}
/**
* Return true if this type has a simple Id and the platform supports mutli-value binding.
*/
@@ -1951,7 +1956,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
* Return the bean property traversing the object graph and taking into
* account inheritance.
*/
public BeanProperty getBeanPropertyFromPath(String path) {
@Override
public BeanProperty findPropertyFromPath(String path) {
BeanDescriptor<?> other = this;
while (true) {
@@ -1983,7 +1989,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
}
String[] splitBegin = SplitName.splitBegin(path);
BeanProperty beanProperty = result.findBeanProperty(splitBegin[0]);
BeanProperty beanProperty = result.findProperty(splitBegin[0]);
if (beanProperty instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
path = splitBegin[1];
@@ -2224,7 +2230,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
@Override
public Property getProperty(String propName) {
return findBeanProperty(propName);
return findProperty(propName);
}
/**
@@ -2458,7 +2464,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
/**
* Return a 'dynamic property' used to read a formula.
*/
private SqlTreeProperty findSqlTreeFormula(String formulaExpression) {
private STreeProperty findSqlTreeFormula(String formulaExpression) {
return dynamicProperty.computeIfAbsent(formulaExpression, (formula) -> new FormulaPropertyPath(this, formula).build());
}
@@ -2468,7 +2474,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
*
* The property can be a dynamic formula or a well known bean property.
*/
public SqlTreeProperty findSqlTreeProperty(String propName) {
@Override
public STreeProperty findPropertyWithDynamic(String propName) {
if (propName.indexOf('(') > -1) {
return findSqlTreeFormula(propName);
}
@@ -2482,7 +2489,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
* inheritance tree (not up).
* </p>
*/
public BeanProperty findBeanProperty(String propName) {
@Override
public BeanProperty findProperty(String propName) {
int basePos = propName.indexOf('.');
if (basePos > -1) {
// embedded property
@@ -2540,6 +2548,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
return autoTunable;
}
public boolean isElementType() {
return false;
}
/**
* Returns the Inheritance mapping information. This will be null if this type
* of bean is not involved in any ORM inheritance mapping.
@@ -2809,6 +2821,41 @@ public class BeanDescriptor<T> implements BeanType<T> {
return draftableElement;
}
@Override
public boolean isEmbeddedPath(String propertyPath) {
ElPropertyDeploy elProp = getElPropertyDeploy(propertyPath);
if (elProp == null) {
throw new PersistenceException("Invalid path " + propertyPath + " from " + getFullName());
}
return elProp.getBeanProperty().isEmbedded();
}
@Override
public ExtraJoin extraJoin(String propertyPath) {
ElPropertyValue elGetValue = getElGetValue(propertyPath);
if (elGetValue != null) {
BeanProperty beanProperty = elGetValue.getBeanProperty();
if (beanProperty instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
if (!assocProp.isEmbedded()) {
return new ExtraJoin(assocProp, elGetValue.containsMany());
}
}
}
return null;
}
@Override
public void inheritanceLoad(SqlBeanLoad sqlBeanLoad, STreeProperty property, DbReadContext ctx) {
BeanProperty p = getBeanProperty(property.getName());
if (p != null) {
p.load(sqlBeanLoad);
} else {
property.loadIgnore(ctx);
}
}
public void setUnmappedJson(EntityBean bean, Map<String, Object> unmappedProperties) {
if (unmappedJson != null) {
unmappedJson.setValueIntercept(bean, unmappedProperties);
@@ -2933,16 +2980,6 @@ public class BeanDescriptor<T> implements BeanType<T> {
return selectLastInsertedId;
}
/**
* Return the TableJoins.
* <p>
* For properties mapped to secondary tables rather than the base table.
* </p>
*/
public TableJoin[] tableJoins() {
return derivedTableJoins;
}
@Override
public Collection<? extends Property> allProperties() {
return propertiesAll();
@@ -3131,6 +3168,26 @@ public class BeanDescriptor<T> implements BeanType<T> {
}
}
@Override
public STreeProperty[] propsBaseScalar() {
return propertiesBaseScalar;
}
@Override
public STreePropertyAssoc[] propsEmbedded() {
return propertiesEmbedded;
}
@Override
public STreePropertyAssocOne[] propsOne() {
return propertiesOne;
}
@Override
public STreePropertyAssocMany[] propsMany() {
return propertiesMany;
}
/**
* All the BeanPropertyAssocOne that are not embedded. These are effectively
* joined beans. For ManyToOne and OneToOne associations.
@@ -0,0 +1,24 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
/**
* Bean descriptor used with ElementCollection (where we don't have a mapped type/class).
*/
public class BeanDescriptorElement<T> extends BeanDescriptor<T> {
public BeanDescriptorElement(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy) {
super(owner, deploy);
}
public boolean isElementType() {
return true;
}
@Override
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
return new ElementEntityBean(properties);
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.deploy;
import java.util.Map;
class BeanDescriptorInitContext {
private final Map<String, String> withHistoryTables;
private final Map<String, String> draftTables;
private final String asOfViewSuffix;
BeanDescriptorInitContext(Map<String, String> withHistoryTables, Map<String, String> draftTables, String asOfViewSuffix) {
this.withHistoryTables = withHistoryTables;
this.draftTables = draftTables;
this.asOfViewSuffix = asOfViewSuffix;
}
void addDraft(String baseTable, String draftTable) {
draftTables.put(baseTable, draftTable);
}
void addHistory(String baseTable, String baseTableAsOf) {
withHistoryTables.put(baseTable, baseTableAsOf);
}
void addHistoryIntersection(String intersectionTableName) {
withHistoryTables.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
}
void addDraftIntersection(String intersectionPublishTable, String intersectionDraftTable) {
draftTables.put(intersectionPublishTable, intersectionDraftTable);
}
}
@@ -184,8 +184,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final BeanLifecycleAdapterFactory beanLifecycleAdapterFactory;
private final boolean eagerFetchLobs;
private final String asOfViewSuffix;
/**
@@ -215,12 +213,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.databasePlatform = serverConfig.getDatabasePlatform();
this.multiValueBind = config.getMultiValueBind();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm(), multiValueBind);
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
this.queryPlanTTLSeconds = serverConfig.getQueryPlanTTLSeconds();
this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
String versionsBetweenSuffix = getVersionsBetweenSuffix(databasePlatform, serverConfig);
this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig.isDisableL2Cache());
this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig);
this.bootupClasses = config.getBootupClasses();
this.createProperties = config.getDeployCreateProperties();
this.namingConvention = serverConfig.getNamingConvention();
@@ -575,12 +572,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// we can initialise them which sorts out circular
// dependencies for OneToMany and ManyToOne etc
BeanDescriptorInitContext initContext = new BeanDescriptorInitContext(asOfTableMap, draftTableMap, asOfViewSuffix);
// PASS 1:
// initialise the ID properties of all the beans
// first (as they are needed to initialise the
// associated properties in the second pass).
for (BeanDescriptor<?> d : descMap.values()) {
d.initialiseId(asOfTableMap, draftTableMap);
d.initialiseId(initContext);
}
// PASS 2:
@@ -595,7 +594,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// also look for intersection tables with
// associated history support and register them
// into the asOfTableMap
d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap);
d.initialiseOther(initContext);
}
// PASS 4:
@@ -656,6 +655,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return beanTableMap.get(type);
}
/**
* Return a BeanTable for an ElementCollection.
*/
public BeanTable getCollectionBeanTable(String fullTableName, Class<?> targetType) {
return new BeanTable(this, fullTableName, targetType);
}
@SuppressWarnings("unchecked")
public <T> BeanManager<T> getBeanManager(Class<T> entityType) {
@@ -1025,7 +1031,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* </p>
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void makeUnidirectional(DeployBeanInfo<?> info, DeployBeanPropertyAssocMany<?> oneToMany) {
private void makeUnidirectional(DeployBeanPropertyAssocMany<?> oneToMany) {
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(oneToMany);
@@ -1045,34 +1051,36 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// mark this property as unidirectional
oneToMany.setUnidirectional();
// create the 'shadow' unidirectional property
// which is put on the target descriptor
DeployBeanPropertyAssocOne<?> unidirectional = new DeployBeanPropertyAssocOne(targetDesc, owningType);
unidirectional.setUndirectionalShadow();
unidirectional.setNullable(false);
unidirectional.setDbRead(true);
unidirectional.setDbInsertable(true);
unidirectional.setDbUpdateable(false);
targetDesc.setUnidirectional(unidirectional);
// specify table and table alias...
BeanTable beanTable = getBeanTable(owningType);
unidirectional.setBeanTable(beanTable);
unidirectional.setName(beanTable.getBaseTable());
info.setBeanJoinType(unidirectional, true);
// define the TableJoin
DeployTableJoin oneToManyJoin = oneToMany.getTableJoin();
if (!oneToManyJoin.hasJoinColumns()) {
throw new RuntimeException("No join columns");
}
createUnidirectional(targetDesc, owningType, beanTable, oneToManyJoin);
}
// inverse of the oneToManyJoin
DeployTableJoin unidirectionalJoin = unidirectional.getTableJoin();
unidirectionalJoin.setColumns(oneToManyJoin.columns(), true);
/**
* Create and add a Unidirectional property (for ElementCollection) which maps to the foreign key.
*/
public <A> void createUnidirectional(DeployBeanDescriptor<?> targetDesc, Class<A> targetType, BeanTable beanTable, DeployTableJoin oneToManyJoin) {
// create the 'shadow' unidirectional property
// which is put on the target descriptor
DeployBeanPropertyAssocOne<A> unidirectional = new DeployBeanPropertyAssocOne<>(targetDesc, targetType);
unidirectional.setUndirectionalShadow();
unidirectional.setNullable(false);
unidirectional.setDbRead(true);
unidirectional.setDbInsertable(true);
unidirectional.setDbUpdateable(false);
unidirectional.setBeanTable(beanTable);
unidirectional.setName(beanTable.getBaseTable());
unidirectional.setJoinType(true);
unidirectional.setJoinColumns(oneToManyJoin.columns(), true);
targetDesc.setUnidirectional(unidirectional);
}
private void checkMappedByOneToOne(DeployBeanPropertyAssocOne<?> prop) {
@@ -1137,6 +1145,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private void checkMappedByOneToMany(DeployBeanInfo<?> info, DeployBeanPropertyAssocMany<?> prop) {
if (prop.isElementCollection()) {
// skip mapping check
return;
}
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(prop);
if (targetDesc.isDraftableElement()) {
@@ -1159,7 +1171,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
if (!findMappedBy(prop)) {
if (!prop.isO2mJoinTable()) {
makeUnidirectional(info, prop);
makeUnidirectional(prop);
}
return;
}
@@ -1311,7 +1323,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
DeployBeanInfo<T> info = new DeployBeanInfo<>(deployUtil, desc);
readAnnotations.readInitial(info, eagerFetchLobs);
readAnnotations.readInitial(info);
return info;
}
@@ -1636,6 +1648,20 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
target.setPrimaryKeyJoin(inverseJoin);
}
/**
* Create a DeployBeanDescriptor for an ElementCollection target.
*/
public <A> DeployBeanDescriptor<A> createDeployDescriptor(Class<A> targetType) {
return new DeployBeanDescriptor<>(this, targetType, serverConfig);
}
/**
* Create a BeanDescriptor for an ElementCollection target.
*/
public <A> BeanDescriptor<A> createElementDescriptor(DeployBeanDescriptor<A> elementDescriptor) {
return new BeanDescriptorElement<>(this, elementDescriptor);
}
public void visitMetrics(MetricVisitor visitor) {
for (BeanDescriptor<?> desc : immutableDescriptorList) {
desc.visitMetrics(visitor);
@@ -5,7 +5,6 @@ import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanList;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
@@ -17,48 +16,20 @@ import java.util.List;
/**
* Helper object for dealing with Lists.
*/
public final class BeanListHelp<T> extends BaseCollectionHelp<T> {
public class BeanListHelp<T> extends BaseCollectionHelp<T> {
private final BeanPropertyAssocMany<T> many;
private final BeanDescriptor<T> targetDescriptor;
private final String propertyName;
private BeanCollectionLoader loader;
public BeanListHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
BeanListHelp(BeanPropertyAssocMany<T> many) {
super(many);
}
public BeanListHelp() {
this.many = null;
this.targetDescriptor = null;
this.propertyName = null;
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
}
/**
* Internal add bypassing any modify listening.
*/
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
BeanListHelp() {
super();
}
@Override
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanList<?>) {
BeanList<?> bl = (BeanList<?>) bc;
if (bl.getActualList() == null) {
bl.setActualList(new ArrayList<>());
@@ -144,13 +115,7 @@ public final class BeanListHelp<T> extends BaseCollectionHelp<T> {
list = (List<?>) collection;
}
if (!list.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Object aList : list) {
targetDescriptor.jsonWrite(ctx, (EntityBean) aList);
}
ctx.endAssocMany();
}
jsonWriteCollection(ctx, name, list);
}
}
@@ -0,0 +1,37 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
/**
* Helper for element collection List.
*/
public class BeanListHelpElement<T> extends BeanListHelp<T> {
BeanListHelpElement(BeanPropertyAssocMany<T> many) {
super(many);
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
Object elementValue = bean._ebean_getField(0);
if (withCheck) {
collection.internalAddWithCheck(elementValue);
} else {
collection.internalAdd(elementValue);
}
}
@Override
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
return super.getBeanCollectionAdd(bc, mapKey);
}
@Override
void jsonWriteElement(SpiJsonWriter ctx, Object element) {
many.jsonWriteValue(ctx, element);
//targetDescriptor.jsonWrite(ctx, (EntityBean) element);
}
}
@@ -5,7 +5,6 @@ import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
@@ -24,12 +23,11 @@ public final class BeanMapHelp<T> extends BaseCollectionHelp<T> {
private final BeanDescriptor<T> targetDescriptor;
private final String propertyName;
private final BeanProperty beanProperty;
private BeanCollectionLoader loader;
/**
* When created for a given query that will return a map.
*/
public BeanMapHelp(BeanDescriptor<T> targetDescriptor, String mapKey) {
BeanMapHelp(BeanDescriptor<T> targetDescriptor, String mapKey) {
this.targetDescriptor = targetDescriptor;
this.beanProperty = targetDescriptor.getBeanProperty(mapKey);
this.many = null;
@@ -39,18 +37,13 @@ public final class BeanMapHelp<T> extends BaseCollectionHelp<T> {
/**
* When help is attached to a specific many property.
*/
public BeanMapHelp(BeanPropertyAssocMany<T> many) {
BeanMapHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
this.beanProperty = targetDescriptor.getBeanProperty(many.getMapKey());
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
}
@Override
@SuppressWarnings("unchecked")
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
@@ -22,9 +22,9 @@ import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.properties.BeanPropertyGetter;
import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
@@ -56,7 +56,7 @@ import java.util.Set;
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
*/
public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty {
public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
@@ -455,11 +455,10 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
* initialise variables that can't be done in construction due to recursive
* issues.
*/
public void initialise() {
public void initialise(BeanDescriptorInitContext initContext) {
// do nothing for normal BeanProperty
if (!isTransient && scalarType == null) {
String msg = "No ScalarType assigned to " + descriptor.getFullName() + "." + getName();
throw new RuntimeException(msg);
throw new RuntimeException("No ScalarType assigned to " + descriptor.getFullName() + "." + getName());
}
}
@@ -505,6 +504,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
/**
* Return true if this property is based on a formula.
*/
@Override
public boolean isFormula() {
return formula;
}
@@ -530,6 +530,11 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
return descriptor.getEncryptKey(this);
}
@Override
public String getEncryptKeyAsString() {
return getEncryptKey().getStringValue();
}
public String getDecryptProperty(String propertyName) {
return dbEncryptFunction.getDecryptSql(propertyName);
}
@@ -552,6 +557,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
* Add any extra joins required to support this property. Generally a no
* operation except for a OneToOne exported.
*/
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (formula && sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, joinType);
@@ -576,6 +582,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
return aggregation != null;
}
@Override
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
if (aggregation != null) {
@@ -586,8 +593,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
} else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) {
if (secondaryTableJoin != null) {
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
ctx.pushTableAlias(relativePrefix);
ctx.pushTableAlias(ctx.getRelativePrefix(secondaryTableJoinPrefix));
}
if (dbEncrypted) {
@@ -614,14 +620,17 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
return owningType.isAssignableFrom(type);
}
@Override
public void loadIgnore(DbReadContext ctx) {
scalarType.loadIgnore(ctx.getDataReader());
}
@Override
public void load(SqlBeanLoad sqlBeanLoad) {
sqlBeanLoad.load(this);
}
@Override
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
if (prefix == null) {
selectChain.add(name);
@@ -808,13 +817,6 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
setValue(bean, cacheData);
}
/**
* Get the property value from a compound value type.
*/
public Object getValueObject(Object bean) {
throw new RuntimeException("Expected to be called only on BeanPropertyCompoundScalar");
}
@Override
public Object getVal(Object bean) {
return getValue((EntityBean) bean);
@@ -1368,6 +1370,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
/**
* Return true if this is included in the unique id.
*/
@Override
public boolean isId() {
return id;
}
@@ -1376,6 +1379,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
* Return true if this is an Embedded property. In this case it shares the
* table and primary key of its owner object.
*/
@Override
public boolean isEmbedded() {
return embedded;
}
@@ -1,10 +1,11 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.id.IdBinder;
@@ -15,7 +16,10 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.query.STreePropertyAssoc;
import io.ebeaninternal.server.query.STreeType;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import io.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
@@ -30,7 +34,7 @@ import java.util.List;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
*/
public abstract class BeanPropertyAssoc<T> extends BeanProperty {
public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STreePropertyAssoc {
private static final Logger logger = LoggerFactory.getLogger(BeanPropertyAssoc.class);
@@ -101,15 +105,18 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
* Initialise post construction.
*/
@Override
public void initialise() {
public void initialise(BeanDescriptorInitContext initContext) {
// this *MUST* execute after the BeanDescriptor is
// put into the map to stop infinite recursion
initialiseTargetDescriptor(initContext);
}
void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) {
targetDescriptor = descriptor.getBeanDescriptor(targetType);
if (!isTransient) {
targetIdBinder = targetDescriptor.getIdBinder();
targetInheritInfo = targetDescriptor.getInheritInfo();
saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable();
if (!targetIdBinder.isComplexId()) {
targetIdProperty = targetIdBinder.getIdProperty();
}
@@ -142,6 +149,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
/**
* Add table join with table alias based on prefix.
*/
@Override
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
return tableJoin.addJoin(joinType, prefix, ctx);
}
@@ -149,6 +157,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
/**
* Add table join with explicit table alias.
*/
@Override
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
return tableJoin.addJoin(joinType, a1, a2, ctx);
}
@@ -186,6 +195,29 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
return targetDescriptor;
}
SpiEbeanServer server() {
return descriptor.getEbeanServer();
}
/**
* Create a new query for the target type.
*
* We use target descriptor rather than target property type to support ElementCollection.
*/
public SpiQuery<T> newQuery(SpiEbeanServer server) {
return new DefaultOrmQuery<>(targetDescriptor, server, server.getExpressionFactory());
}
@Override
public IdBinder getIdBinder() {
return descriptor.getIdBinder();
}
@Override
public STreeType target() {
return targetDescriptor;
}
/**
* Return true if the target side has soft delete.
*/
@@ -325,6 +357,13 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isInsertable();
}
/**
* Return the underlying BeanTable for this property.
*/
public BeanTable getBeanTable() {
return beanTable;
}
/**
* return the join to use for the bean.
*/
@@ -440,10 +479,6 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
}
}
EbeanServer server() {
return getBeanDescriptor().getEbeanServer();
}
void bindParentIds(DefaultSqlUpdate delete, List<Object> parentIds) {
if (isExportedSimple()) {
@@ -492,4 +527,26 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
private boolean isExportedSimple() {
return exportedProperties.length == 1;
}
/**
* Find and return the exported property matching to this property.
*/
ExportedProperty findMatch(boolean embedded, BeanProperty prop, String matchColumn, TableJoin tableJoin) {
String searchTable = tableJoin.getTable();
for (TableJoinColumn column : tableJoin.columns()) {
String matchTo = column.getLocalDbColumn();
if (matchColumn.equalsIgnoreCase(matchTo)) {
String foreignCol = column.getForeignDbColumn();
return new ExportedProperty(embedded, foreignCol, prop);
}
}
String msg = "Error with the Join on [" + getFullBeanName()
+ "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?"
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?";
throw new PersistenceException(msg);
}
}
@@ -1,7 +1,5 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
@@ -12,11 +10,11 @@ import io.ebean.bean.EntityBean;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.STreePropertyAssocMany;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
@@ -35,7 +33,7 @@ import java.util.Map;
/**
* Property mapped to a List Set or Map.
*/
public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements STreePropertyAssocMany {
private static final Logger logger = LoggerFactory.getLogger(BeanPropertyAssocMany.class);
@@ -51,7 +49,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
private final TableJoin inverseJoin;
final TableJoin inverseJoin;
/**
* Flag to indicate that this is a unidirectional relationship.
@@ -70,6 +68,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
private final boolean manyToMany;
private final boolean elementCollection;
/**
* Order by used when fetch joining the associated many.
*/
@@ -91,8 +91,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private BeanProperty mapKeyProperty;
private String exportedPropertyBindProto = "?";
/**
* Property on the 'child' bean that links back to the 'master'.
*/
@@ -106,9 +104,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private ImportedId importedId;
private String deleteByParentIdSql;
private String deleteByParentIdInSql;
private BeanPropertyAssocManySqlHelp<T> sqlHelp;
/**
* Create this property.
@@ -119,6 +115,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
this.o2mJoinTable = deploy.isO2mJoinTable();
this.hasOrderColumn = deploy.hasOrderColumn();
this.manyToMany = deploy.isManyToMany();
this.elementCollection = deploy.isElementCollection();
this.manyType = deploy.getManyType();
this.mapKey = deploy.getMapKey();
this.fetchOrderBy = deploy.getFetchOrderBy();
@@ -136,13 +133,15 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
@Override
public void initialise() {
super.initialise();
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
initialiseAssocMany();
}
private void initialiseAssocMany() {
if (!isTransient) {
this.help = BeanCollectionHelpFactory.create(this);
if (hasJoinTable()) {
if (hasJoinTable() || elementCollection) {
importedId = createImportedId(this, targetDescriptor, tableJoin);
} else {
@@ -159,37 +158,21 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
exportedProperties = createExported();
this.sqlHelp = new BeanPropertyAssocManySqlHelp<>(this, exportedProperties);
if (exportedProperties.length > 0) {
embeddedExportedProperties = exportedProperties[0].isEmbedded();
exportedPropertyBindProto = deriveExportedPropertyBindProto();
if (fetchOrderBy != null) {
// derive lazyFetchOrderBy
StringBuilder sb = new StringBuilder(50);
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(", ");
}
// these fk columns are either on the intersection (int_) or base table (t0)
String fkTableAlias = hasJoinTable() ? "int_" : "t0";
sb.append(fkTableAlias).append(".").append(exportedProperties[i].getForeignDbColumn());
}
sb.append(", ").append(fetchOrderBy);
lazyFetchOrderBy = sb.toString().trim();
lazyFetchOrderBy = sqlHelp.lazyFetchOrderBy(fetchOrderBy);
}
}
String delStmt;
if (hasJoinTable()) {
delStmt = "delete from " + inverseJoin.getTable() + " where ";
} else {
delStmt = "delete from " + targetDescriptor.getBaseTable() + " where ";
}
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false, "");
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true, "");
}
}
String targetTable() {
return targetDescriptor.getBaseTable();
}
/**
* Initialise after the target bean descriptors have been all set.
*/
@@ -295,44 +278,23 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public SqlUpdate deleteByParentId(Object parentId, List<Object> parentIdist) {
if (parentId != null) {
return deleteByParentId(parentId);
return sqlHelp.deleteByParentId(parentId);
} else {
return deleteByParentIdList(parentIdist);
return sqlHelp.deleteByParentIdList(parentIdist);
}
}
private SqlUpdate deleteByParentId(Object parentId) {
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql);
bindParentId(sqlDelete, parentId);
return sqlDelete;
}
/**
* Find the Id's of detail beans given a parent Id or list of parent Id's.
*/
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
if (parentId != null) {
return findIdsByParentId(parentId, t, excludeDetailIds);
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds);
} else {
return findIdsByParentIdList(parentIdList, t, excludeDetailIds);
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds);
}
}
private List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(false, "");
EbeanServer server = server();
Query<?> q = server.find(getPropertyType());
bindParentIdEq(rawWhere, parentId, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
/**
* Exclude many properties from bean cache data.
*/
@@ -359,88 +321,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
// assumes the ManyToOne property is included
query.where().in(childMasterIdProperty, parentIds);
} else {
addWhereParentIdIn(query, parentIds);
sqlHelp.addWhereParentIdIn(query, parentIds);
}
}
/**
* Add a where clause to the query for a given list of parent Id's.
*/
private void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = hasJoinTable() ? "int_." : "t0.";
if (hasJoinTable()) {
query.setM2MIncludeJoin(inverseJoin);
}
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere);
bindParentIdsIn(expr, parentIds, query);
}
private List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
String expr = rawWhere + inClause;
EbeanServer server = descriptor.getEbeanServer();
Query<?> q = server.find(propertyType);
bindParentIdsIn(expr, parentIds, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
private SqlUpdate deleteByParentIdList(List<Object> parentIds) {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
sb.append(buildInClauseBinding(parentIds.size(), exportedPropertyBindProto));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
bindParentIds(delete, parentIds);
return delete;
}
private String deriveExportedPropertyBindProto() {
if (exportedProperties.length == 1) {
return "?";
}
StringBuilder sb = new StringBuilder();
sb.append("(");
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append("?");
}
sb.append(")");
return sb.toString();
}
private String buildInClauseBinding(int size, String bindProto) {
if (descriptor.isSimpleId()) {
return descriptor.getIdBinder().getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1)));
sb.append(" in");
sb.append(" (");
for (int i = 0; i < size; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(bindProto);
}
sb.append(") ");
return sb.toString();
}
/**
* Set the lazy load server to help create reference collections (that lazy
* load on demand).
@@ -476,7 +360,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
@Override
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
public Object readSet(DbReadContext ctx, EntityBean bean) {
return null;
}
@@ -616,6 +500,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return manyToMany;
}
public boolean isElementCollection() {
return elementCollection;
}
/**
* ManyToMany only, join from local table to intersection table.
*/
@@ -705,30 +593,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
}
private String deriveWhereParentIdSql(boolean inClause, String tableAlias) {
StringBuilder sb = new StringBuilder();
if (inClause) {
sb.append("(");
}
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
String s = inClause ? "," : " and ";
sb.append(s);
}
sb.append(tableAlias).append(fkColumn);
if (!inClause) {
sb.append("=? ");
}
}
if (inClause) {
sb.append(")");
}
return sb.toString();
}
/**
* Create the array of ExportedProperty used to build reference objects.
*/
@@ -741,12 +605,9 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
if (idProp != null && idProp.isEmbedded()) {
BeanPropertyAssocOne<?> one = (BeanPropertyAssocOne<?>) idProp;
BeanDescriptor<?> targetDesc = one.getTargetDescriptor();
BeanProperty[] emIds = targetDesc.propertiesBaseScalar();
try {
for (BeanProperty emId : emIds) {
ExportedProperty expProp = findMatch(true, emId);
list.add(expProp);
for (BeanProperty emId : one.getTargetDescriptor().propertiesBaseScalar()) {
list.add(findMatch(true, emId));
}
} catch (PersistenceException e) {
// not found as individual scalar properties
@@ -755,8 +616,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
} else {
if (idProp != null) {
ExportedProperty expProp = findMatch(false, idProp);
list.add(expProp);
list.add(findMatch(false, idProp));
}
}
@@ -768,32 +628,12 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
private ExportedProperty findMatch(boolean embedded, BeanProperty prop) {
String matchColumn = prop.getDbColumn();
String searchTable;
TableJoinColumn[] columns;
if (hasJoinTable()) {
// look for column going to intersection
columns = intersectionJoin.columns();
searchTable = intersectionJoin.getTable();
return findMatch(embedded, prop, prop.getDbColumn(), intersectionJoin);
} else {
columns = tableJoin.columns();
searchTable = tableJoin.getTable();
return findMatch(embedded, prop, prop.getDbColumn(), tableJoin);
}
for (TableJoinColumn column : columns) {
String matchTo = column.getLocalDbColumn();
if (matchColumn.equalsIgnoreCase(matchTo)) {
String foreignCol = column.getForeignDbColumn();
return new ExportedProperty(embedded, foreignCol, prop);
}
}
String msg = "Error with the Join on [" + getFullBeanName()
+ "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?"
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?";
throw new PersistenceException(msg);
}
/**
@@ -813,8 +653,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
Class<?> beanType = descriptor.getBeanType();
BeanDescriptor<?> targetDesc = getTargetDescriptor();
BeanPropertyAssocOne<?>[] ones = targetDesc.propertiesOne();
for (BeanPropertyAssocOne<?> prop : ones) {
for (BeanPropertyAssocOne<?> prop : targetDesc.propertiesOne()) {
if (mappedBy != null) {
// match using mappedBy as property name
if (mappedBy.equalsIgnoreCase(prop.getName())) {
@@ -880,9 +719,9 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
/**
* Register the mapping of intersection table to associated draft table.
*/
public void registerDraftIntersectionTable(Map<String, String> draftTableMap) {
public void registerDraftIntersectionTable(BeanDescriptorInitContext initContext) {
if (hasDraftIntersection()) {
draftTableMap.put(intersectionPublishTable, intersectionDraftTable);
initContext.addDraftIntersection(intersectionPublishTable, intersectionDraftTable);
}
}
@@ -928,7 +767,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
* Skip JSON write value for ToMany property.
*/
@Override
public void jsonWriteValue(SpiJsonWriter writeJson, Object value) throws IOException {
public void jsonWriteValue(SpiJsonWriter writeJson, Object value) {
// do nothing, exclude ToMany properties
}
@@ -1030,4 +869,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public boolean isIncludeCascadeDelete() {
return cascadeInfo.isDelete() || o2mJoinTable || ModifyListenMode.REMOVALS == modifyListenMode;
}
public String insertElementCollection() {
return sqlHelp.insertElementCollection();
}
public boolean isTargetDocStoreMapped() {
return targetDescriptor.isDocStoreMapped();
}
public BeanCollectionHelp<T> getHelp() {
return help;
}
}
@@ -13,7 +13,7 @@ import java.io.IOException;
/**
* Help BeanPropertyAssocMany with JSON processing.
*/
public class BeanPropertyAssocManyJsonHelp {
class BeanPropertyAssocManyJsonHelp {
/**
* The associated many property.
@@ -28,7 +28,7 @@ public class BeanPropertyAssocManyJsonHelp {
/**
* Construct for the owning many property.
*/
public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
this.many = many;
boolean objectMapperPresent = many.getBeanDescriptor().getServerConfig().getClassLoadConfig().isJacksonObjectMapperPresent();
this.jsonTransient = !objectMapperPresent ? null : new BeanPropertyAssocManyJsonTransient();
@@ -14,12 +14,12 @@ import java.util.LinkedHashMap;
/**
* Helper used to read transient many properties using Jackson ObjectMapper.
*/
public class BeanPropertyAssocManyJsonTransient {
class BeanPropertyAssocManyJsonTransient {
/**
* Use Jackson ObjectMapper to read the transient 'many' property.
*/
public void jsonReadUsingObjectMapper(BeanPropertyAssocMany<?> many, ReadJson readJson, EntityBean parentBean) throws IOException {
void jsonReadUsingObjectMapper(BeanPropertyAssocMany<?> many, ReadJson readJson, EntityBean parentBean) throws IOException {
ObjectMapper mapper = readJson.getObjectMapper();
@@ -0,0 +1,258 @@
package io.ebeaninternal.server.deploy;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.dbmigration.model.visitor.BaseTablePropertyVisitor;
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import java.util.List;
class BeanPropertyAssocManySqlHelp<T> {
private final BeanPropertyAssocMany<T> many;
private final ExportedProperty[] exportedProperties;
private final boolean hasJoinTable;
private final BeanDescriptor<?> descriptor;
private final String exportedPropertyBindProto;
private final String deleteByParentIdSql;
private final String deleteByParentIdInSql;
private final String elementCollectionInsertSql;
BeanPropertyAssocManySqlHelp(BeanPropertyAssocMany<T> many, ExportedProperty[] exportedProperties) {
this.many = many;
this.exportedProperties = exportedProperties;
this.hasJoinTable = many.hasJoinTable();
this.descriptor = many.getBeanDescriptor();
this.exportedPropertyBindProto = deriveExportedPropertyBindProto();
String delStmt;
if (hasJoinTable) {
delStmt = "delete from " + many.inverseJoin.getTable() + " where ";
} else {
delStmt = "delete from " + many.targetTable() + " where ";
}
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false, "");
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true, "");
if (many.isElementCollection()) {
elementCollectionInsertSql = elementCollectionInsert();
} else {
elementCollectionInsertSql = null;
}
}
private String elementCollectionInsert() {
StringBuilder sb = new StringBuilder(200);
sb.append("insert into ").append(many.targetTable()).append(" (");
append(sb, "", ",", "");
Cols cols = new Cols(sb);
VisitAllUsing.visitOne(many.targetDescriptor, cols);
sb.append(") values (");
appendBind(sb, exportedProperties.length, true);
appendBind(sb, cols.colCount, false);
sb.append(")");
return sb.toString();
}
public String insertElementCollection() {
return elementCollectionInsertSql;
}
private static class Cols extends BaseTablePropertyVisitor {
int colCount;
private final StringBuilder sb;
private Cols(StringBuilder sb) {
this.sb = sb;
}
@Override
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
sb.append(",").append(p.getDbColumn());
colCount++;
}
@Override
public void visitOneImported(BeanPropertyAssocOne<?> p) {
}
@Override
public void visitScalar(BeanProperty p) {
sb.append(",").append(p.getDbColumn());
colCount++;
}
@Override
public void visitEnd() {
}
}
String lazyFetchOrderBy(String fetchOrderBy) {
// derive lazyFetchOrderBy
StringBuilder sb = new StringBuilder(50);
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(", ");
}
// these fk columns are either on the intersection (int_) or base table (t0)
String fkTableAlias = hasJoinTable ? "int_" : "t0";
sb.append(fkTableAlias).append(".").append(exportedProperties[i].getForeignDbColumn());
}
sb.append(", ").append(fetchOrderBy);
return sb.toString().trim();
}
/**
* Add a where clause to the query for a given list of parent Id's.
*/
void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = hasJoinTable ? "int_." : "t0.";
if (hasJoinTable) {
query.setM2MIncludeJoin(many.inverseJoin);
}
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere);
many.bindParentIdsIn(expr, parentIds, query);
}
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(false, "");
SpiEbeanServer server = descriptor.getEbeanServer();
SpiQuery<?> q = many.newQuery(server);
many.bindParentIdEq(rawWhere, parentId, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
String expr = rawWhere + inClause;
SpiEbeanServer server = descriptor.getEbeanServer();
SpiQuery<?> q = many.newQuery(server);
//Query<?> q = server.find(propertyType);
many.bindParentIdsIn(expr, parentIds, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
SqlUpdate deleteByParentId(Object parentId) {
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql);
many.bindParentId(sqlDelete, parentId);
return sqlDelete;
}
SqlUpdate deleteByParentIdList(List<Object> parentIds) {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
sb.append(buildInClauseBinding(parentIds.size(), exportedPropertyBindProto));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
many.bindParentIds(delete, parentIds);
return delete;
}
private void appendBind(StringBuilder sb, int count, boolean skipComma) {
for (int i = 0; i < count; i++) {
if (!skipComma || i > 0) {
sb.append(",");
}
sb.append("?");
}
}
private void append(StringBuilder sb, String tableAlias, String prefix, String suffix) {
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
sb.append(prefix);
}
sb.append(tableAlias).append(fkColumn);
sb.append(suffix);
}
}
private String deriveWhereParentIdSql(boolean inClause, String tableAlias) {
StringBuilder sb = new StringBuilder();
if (inClause) {
sb.append("(");
}
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
String s = inClause ? "," : " and ";
sb.append(s);
}
sb.append(tableAlias).append(fkColumn);
if (!inClause) {
sb.append("=? ");
}
}
if (inClause) {
sb.append(")");
}
return sb.toString();
}
private String buildInClauseBinding(int size, String bindProto) {
if (descriptor.isSimpleId()) {
return descriptor.getIdBinder().getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1)));
sb.append(" in");
sb.append(" (");
for (int i = 0; i < size; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(bindProto);
}
sb.append(") ");
return sb.toString();
}
private String deriveExportedPropertyBindProto() {
if (exportedProperties.length == 1) {
return "?";
}
StringBuilder sb = new StringBuilder();
sb.append("(");
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append("?");
}
sb.append(")");
return sb.toString();
}
}
@@ -16,10 +16,12 @@ import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.STreePropertyAssocOne;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.ScalarType;
import javax.persistence.PersistenceException;
import java.io.IOException;
@@ -32,7 +34,7 @@ import java.util.Map;
/**
* Property mapped to a joined bean.
*/
public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STreePropertyAssocOne {
private final boolean oneToOne;
@@ -94,8 +96,12 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
public void initialise() {
super.initialise();
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
initialiseAssocOne();
}
private void initialiseAssocOne() {
localHelp = createHelp(embedded, oneToOneExported);
if (!isTransient) {
@@ -417,6 +423,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
}
@Override
public ScalarType<?> getIdScalarType() {
return targetDescriptor.getIdProperty().getScalarType();
}
/**
* Return the Id values from the given bean.
*/
@@ -539,24 +550,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
*/
private ExportedProperty findMatch(boolean embeddedProp, BeanProperty prop) {
String matchColumn = prop.getDbColumn();
String searchTable = tableJoin.getTable();
TableJoinColumn[] columns = tableJoin.columns();
for (TableJoinColumn column : columns) {
String matchTo = column.getLocalDbColumn();
if (matchColumn.equalsIgnoreCase(matchTo)) {
String foreignCol = column.getForeignDbColumn();
return new ExportedProperty(embeddedProp, foreignCol, prop);
}
}
String msg = "Error with the Join on [" + getFullBeanName()
+ "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?"
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?";
throw new PersistenceException(msg);
return findMatch(embeddedProp, prop, prop.getDbColumn(), tableJoin);
}
@@ -4,8 +4,27 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
public class BeanPropertySimpleCollection<T> extends BeanPropertyAssocMany<T> {
private BeanDescriptor<T> elementDescriptor;
public BeanPropertySimpleCollection(BeanDescriptor<?> descriptor, DeployBeanPropertySimpleCollection<T> deploy) {
super(descriptor, deploy);
this.elementDescriptor = deploy.getElementDescriptor();
}
@Override
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
if (isElementCollection()) {
// initialise all non-id properties (we don't have an Id property)
elementDescriptor.initialiseOther(initContext);
}
}
void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) {
if (isElementCollection()) {
targetDescriptor = elementDescriptor;
} else {
targetDescriptor = descriptor.getBeanDescriptor(targetType);
}
}
}
@@ -5,7 +5,6 @@ import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanSet;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
@@ -19,32 +18,18 @@ import java.util.Set;
*/
public final class BeanSetHelp<T> extends BaseCollectionHelp<T> {
private final BeanPropertyAssocMany<T> many;
private final BeanDescriptor<T> targetDescriptor;
private final String propertyName;
private BeanCollectionLoader loader;
/**
* When attached to a specific many property.
*/
public BeanSetHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
BeanSetHelp(BeanPropertyAssocMany<T> many) {
super(many);
}
/**
* For a query that returns a set.
*/
public BeanSetHelp() {
this.many = null;
this.targetDescriptor = null;
this.propertyName = null;
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
BeanSetHelp() {
super();
}
@Override
@@ -61,15 +46,6 @@ public final class BeanSetHelp<T> extends BaseCollectionHelp<T> {
}
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
public BeanCollection<T> createEmptyNoParent() {
return new BeanSet<>();
@@ -142,13 +118,6 @@ public final class BeanSetHelp<T> extends BaseCollectionHelp<T> {
} else {
set = (Set<?>) collection;
}
if (!set.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Object bean : set) {
targetDescriptor.jsonWrite(ctx, (EntityBean) bean);
}
ctx.endAssocMany();
}
jsonWriteCollection(ctx, name, set);
}
}
@@ -42,6 +42,16 @@ public class BeanTable {
this.idProperty = mutable.createIdProperty(owner);
}
/**
* Construct for element collection.
*/
public BeanTable(BeanDescriptorMap owner, String tableName, Class<?> beanType) {
this.owner = owner;
this.beanType = beanType;
this.baseTable = tableName;
this.idProperty = null;
}
@Override
public String toString() {
return baseTable;
@@ -5,6 +5,7 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.query.STreePropertyAssocMany;
import io.ebeaninternal.server.type.DataReader;
import java.util.Map;
@@ -68,7 +69,7 @@ public interface DbReadContext {
* Return the property that is associated with the many. There can only be
* one. This can be null.
*/
BeanPropertyAssocMany<?> getManyProperty();
STreePropertyAssocMany getManyProperty();
/**
* Set back the bean that has just been loaded with its id.
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.type.ScalarType;
import java.util.List;
@@ -9,14 +9,14 @@ import java.util.List;
/**
* Abstract base for dynamic properties.
*/
abstract class DynamicPropertyBase implements SqlTreeProperty {
abstract class DynamicPropertyBase implements STreeProperty {
final String name;
final String fullName;
final String elPrefix;
final ScalarType<?> scalarType;
public DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType<?> scalarType) {
DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType<?> scalarType) {
this.name = name;
this.fullName = fullName;
this.elPrefix = elPrefix;
@@ -43,6 +43,11 @@ abstract class DynamicPropertyBase implements SqlTreeProperty {
return false;
}
@Override
public boolean isFormula() {
return false;
}
@Override
public String getElPrefix() {
return elPrefix;
@@ -67,4 +72,9 @@ abstract class DynamicPropertyBase implements SqlTreeProperty {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
// do not add to from usually
}
@Override
public String getEncryptKeyAsString() {
return null;
}
}
@@ -0,0 +1,81 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
class ElementEntityBean implements EntityBean {
private final String[] properties;
private Object[] data;
private EntityBeanIntercept intercept;
ElementEntityBean(String[] properties) {
this.properties = properties;
this.intercept = new EntityBeanIntercept(this);
}
@Override
public String[] _ebean_getPropertyNames() {
return properties;
}
@Override
public String _ebean_getPropertyName(int pos) {
return properties[pos];
}
@Override
public String _ebean_getMarker() {
return null;
}
@Override
public Object _ebean_newInstance() {
return new ElementEntityBean(properties);
}
@Override
public void _ebean_setEmbeddedLoaded() {
}
@Override
public boolean _ebean_isEmbeddedNewOrDirty() {
return false;
}
@Override
public EntityBeanIntercept _ebean_getIntercept() {
return intercept;
}
@Override
public EntityBeanIntercept _ebean_intercept() {
return intercept;
}
@Override
public void _ebean_setField(int fieldIndex, Object value) {
if (data == null) {
data = new Object[properties.length];
}
data[fieldIndex] = value;
}
@Override
public void _ebean_setFieldIntercept(int fieldIndex, Object value) {
_ebean_setField(fieldIndex, value);
}
@Override
public Object _ebean_getField(int fieldIndex) {
return data == null ? null : data[fieldIndex];
}
@Override
public Object _ebean_getFieldIntercept(int fieldIndex) {
return _ebean_getField(fieldIndex);
}
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.Types;
@@ -89,7 +89,7 @@ class FormulaPropertyPath {
return alias;
}
SqlTreeProperty build() {
STreeProperty build() {
DeployPropertyParser parser = descriptor.parser().setCatchFirst(true);
@@ -184,7 +184,7 @@ public class InheritInfo {
for (InheritInfo childInfo : children) {
// recursively search this child bean descriptor
prop = childInfo.desc().findBeanProperty(propertyName);
prop = childInfo.desc().findProperty(propertyName);
if (prop != null) {
return prop;
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.type.DataBind;
import java.io.DataInput;
@@ -52,7 +53,7 @@ public interface IdBinder {
/**
* Return the Id BeanProperty.
*/
BeanProperty getBeanProperty();
STreeProperty getBeanProperty();
/**
* Find a BeanProperty that is mapped to the database column.
@@ -0,0 +1,26 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.properties.BeanPropertySetter;
/**
* Setter used for "element beans" with ElementCollection.
*/
class BeanPropertyElementSetter implements BeanPropertySetter {
private final int pos;
BeanPropertyElementSetter(int pos) {
this.pos = pos;
}
@Override
public void set(EntityBean bean, Object value) {
bean._ebean_setField(pos, value);
}
@Override
public void setIntercept(EntityBean bean, Object value) {
set(bean, value);
}
}
@@ -210,6 +210,8 @@ public class DeployBeanProperty {
private boolean undirectionalShadow;
private boolean elementProperty;
private int sortOrder;
private boolean excludedFromHistory;
@@ -444,7 +446,11 @@ public class DeployBeanProperty {
}
public BeanPropertySetter getSetter() {
return setter;
if (elementProperty) {
return new BeanPropertyElementSetter(sortOrder);
} else {
return setter;
}
}
/**
@@ -1081,4 +1087,10 @@ public class DeployBeanProperty {
return dbMigrationInfos;
}
/**
* Set when this property is part of a 'element bean' used with ElementCollection.
*/
public void setElementProperty() {
this.elementProperty = true;
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.ManyType;
import io.ebeaninternal.server.deploy.TableJoin;
@@ -23,6 +24,8 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
private boolean o2mJoinTable;
private boolean elementCollection;
/**
* Flag to indicate this is a unidirectional relationship.
*/
@@ -46,6 +49,11 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
private DeployOrderColumn orderColumn;
/**
* Effectively the dynamically created target descriptor (that doesn't have a mapped type/class per say).
*/
private BeanDescriptor<?> elementDescriptor;
/**
* Create this property.
*/
@@ -231,5 +239,24 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
this.o2mJoinTable = true;
setModifyListenMode(ModifyListenMode.ALL);
}
public void setElementCollection() {
elementCollection = true;
cascadeInfo.setSaveDelete(true, true);
setModifyListenMode(ModifyListenMode.ALL);
}
public boolean isElementCollection() {
return elementCollection;
}
public void setElementDescriptor(BeanDescriptor<?> elementDescriptor) {
this.elementDescriptor = elementDescriptor;
}
@SuppressWarnings("unchecked")
public <A> BeanDescriptor<A> getElementDescriptor() {
return (BeanDescriptor<A>)elementDescriptor;
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.query.SqlJoinType;
import javax.persistence.CascadeType;
@@ -162,4 +163,12 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
public boolean isOrphanRemoval() {
return orphanRemoval;
}
public void setJoinType(boolean outerJoin) {
tableJoin.setType(outerJoin ? SqlJoinType.OUTER : SqlJoinType.INNER);
}
public void setJoinColumns(DeployTableJoinColumn[] columns, boolean reverse) {
tableJoin.setColumns(columns, reverse);
}
}
@@ -7,18 +7,24 @@ import io.ebean.annotation.Where;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.config.NamingConvention;
import io.ebean.config.TableName;
import io.ebean.util.CamelCaseHelper;
import io.ebean.util.StringHelper;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployOrderColumn;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.type.ScalarType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
@@ -38,8 +44,8 @@ class AnnotationAssocManys extends AnnotationParser {
/**
* Create with the DeployInfo.
*/
AnnotationAssocManys(DeployBeanInfo<?> info, boolean javaxValidationAnnotations, BeanDescriptorManager factory) {
super(info, javaxValidationAnnotations);
AnnotationAssocManys(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) {
super(info, readConfig);
this.factory = factory;
}
@@ -82,6 +88,10 @@ class AnnotationAssocManys extends AnnotationParser {
if (manyToMany != null) {
readToMany(manyToMany, prop);
}
ElementCollection elementCollection = get(prop, ElementCollection.class);
if (elementCollection != null) {
readElementCollection(prop, elementCollection);
}
if (get(prop, HistoryExclude.class) != null) {
prop.setExcludedFromHistory();
@@ -160,6 +170,59 @@ class AnnotationAssocManys extends AnnotationParser {
}
}
private void readElementCollection(DeployBeanPropertyAssocMany<?> prop, ElementCollection elementCollection) {
prop.setElementCollection();
if (!elementCollection.targetClass().equals(void.class)) {
prop.setTargetType(elementCollection.targetClass());
}
Column column = get(prop, Column.class);
if (column != null) {
prop.setDbColumn(column.name());
prop.setDbLength(column.length());
prop.setDbScale(column.scale());
}
CollectionTable collectionTable = get(prop, CollectionTable.class);
String fullTableName = getFullTableName(collectionTable);
if (fullTableName == null) {
fullTableName = descriptor.getBaseTable()+"_"+ CamelCaseHelper.toUnderscoreFromCamel(prop.getName());
}
//namingConvention.
BeanTable beanTable = factory.getCollectionBeanTable(fullTableName, prop.getTargetType());
prop.setBeanTable(beanTable);
if (collectionTable != null) {
prop.getTableJoin().addJoinColumn(true, collectionTable.joinColumns(), beanTable);
}
Class<?> elementType = prop.getTargetType();
DeployBeanDescriptor<?> elementDescriptor = factory.createDeployDescriptor(elementType);
elementDescriptor.setBaseTable(new TableName(fullTableName), readConfig.getAsOfViewSuffix(), readConfig.getVersionsBetweenSuffix());
ScalarType<?> scalarType = util.getTypeManager().getScalarType(elementType);
DeployBeanProperty elementProp = new DeployBeanProperty(elementDescriptor, elementType, scalarType, null);
elementProp.setName("value");
elementProp.setDbColumn(prop.getDbColumn());
elementProp.setNullable(false);
elementProp.setDbInsertable(true);
elementProp.setDbUpdateable(true);
elementProp.setDbRead(true);
elementProp.setElementProperty();
elementDescriptor.addBeanProperty(elementProp);
elementDescriptor.setProperties(new String[]{"value"});
elementDescriptor.setName(prop.getFullBeanName());
Class<?> owningType = prop.getOwningType();
factory.createUnidirectional(elementDescriptor, owningType, beanTable, prop.getTableJoin());
prop.setElementDescriptor(factory.createElementDescriptor(elementDescriptor));
}
/**
* Define the joins for a ManyToMany relationship.
* <p>
@@ -204,6 +267,24 @@ class AnnotationAssocManys extends AnnotationParser {
return sb.toString();
}
/**
* Return the full table name
*/
private String getFullTableName(CollectionTable collectionTable) {
if (collectionTable == null) {
return null;
}
StringBuilder sb = new StringBuilder();
if (!StringHelper.isNull(collectionTable.catalog())) {
sb.append(collectionTable.catalog()).append(".");
}
if (!StringHelper.isNull(collectionTable.schema())) {
sb.append(collectionTable.schema()).append(".");
}
sb.append(collectionTable.name());
return sb.toString();
}
/**
* Define intersection table and foreign key columns for ManyToMany.
* <p>
@@ -41,8 +41,8 @@ public class AnnotationAssocOnes extends AnnotationParser {
/**
* Create with the deploy Info.
*/
AnnotationAssocOnes(DeployBeanInfo<?> info, boolean javaxValidationAnnotations, BeanDescriptorManager factory) {
super(info, javaxValidationAnnotations);
AnnotationAssocOnes(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) {
super(info, readConfig);
this.factory = factory;
}
@@ -152,7 +152,7 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
}
info.setBeanJoinType(prop, prop.isNullable());
prop.setJoinType(prop.isNullable());
if (!prop.getTableJoin().hasJoinColumns() && beanTable != null) {
@@ -42,25 +42,15 @@ public class AnnotationClass extends AnnotationParser {
private final boolean disableL2Cache;
/**
* Create for normal early parse of class level annotations.
*/
public AnnotationClass(DeployBeanInfo<?> info, boolean validationAnnotations, String asOfViewSuffix, String versionsBetweenSuffix, boolean disableL2Cache) {
super(info, validationAnnotations);
this.asOfViewSuffix = asOfViewSuffix;
this.versionsBetweenSuffix = versionsBetweenSuffix;
this.disableL2Cache = disableL2Cache;
}
/**
* Create to parse AttributeOverride annotations which is run last
* after all the properties/fields have been parsed fully.
*/
public AnnotationClass(DeployBeanInfo<?> info) {
super(info, false);
this.asOfViewSuffix = null;
this.versionsBetweenSuffix = null;
this.disableL2Cache = false;
public AnnotationClass(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info, readConfig);
this.asOfViewSuffix = readConfig.getAsOfViewSuffix();
this.versionsBetweenSuffix = readConfig.getVersionsBetweenSuffix();
this.disableL2Cache = readConfig.isDisableL2Cache();
}
/**
@@ -91,14 +91,11 @@ public class AnnotationFields extends AnnotationParser {
*/
private FetchType defaultLobFetchType = FetchType.LAZY;
AnnotationFields(GeneratedPropertyFactory generatedPropFactory, DeployBeanInfo<?> info,
boolean javaxValidationAnnotations, boolean jacksonAnnotationsPresent, boolean eagerFetchLobs) {
super(info, javaxValidationAnnotations);
this.jacksonAnnotationsPresent = jacksonAnnotationsPresent;
this.generatedPropFactory = generatedPropFactory;
if (eagerFetchLobs) {
AnnotationFields(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info, readConfig);
this.jacksonAnnotationsPresent = readConfig.isJacksonAnnotations();
this.generatedPropFactory = readConfig.getGeneratedPropFactory();
if (readConfig.isEagerFetchLobs()) {
defaultLobFetchType = FetchType.EAGER;
}
}
@@ -27,9 +27,12 @@ public abstract class AnnotationParser extends AnnotationBase {
protected final boolean validationAnnotations;
public AnnotationParser(DeployBeanInfo<?> info, boolean validationAnnotations) {
protected final ReadAnnotationConfig readConfig;
public AnnotationParser(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info.getUtil());
this.validationAnnotations = validationAnnotations;
this.readConfig = readConfig;
this.validationAnnotations = readConfig.isJavaxValidationAnnotations();
this.info = info;
this.beanType = info.getDescriptor().getBeanType();
this.descriptor = info.getDescriptor();
@@ -7,10 +7,10 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Read the class level deployment annotations.
*/
public class AnnotationSql extends AnnotationParser {
class AnnotationSql extends AnnotationParser {
public AnnotationSql(DeployBeanInfo<?> info, boolean javaxValidationAnnotations) {
super(info, javaxValidationAnnotations);
AnnotationSql(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info, readConfig);
}
@Override
@@ -3,7 +3,6 @@ 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.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -70,15 +69,6 @@ public class DeployBeanInfo<T> {
return tableJoin;
}
/**
* Set a the join alias for a assoc one property.
*/
public void setBeanJoinType(DeployBeanPropertyAssocOne<?> beanProp, boolean outerJoin) {
DeployTableJoin tableJoin = beanProp.getTableJoin();
tableJoin.setType(outerJoin ? SqlJoinType.OUTER : SqlJoinType.INNER);
}
/**
* Add named RawSql from ebean.xml.
*/
@@ -0,0 +1,58 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
/**
* Configuration used when reading the deployment annotations.
*/
class ReadAnnotationConfig {
private final GeneratedPropertyFactory generatedPropFactory;
private final String asOfViewSuffix;
private final String versionsBetweenSuffix;
private final boolean disableL2Cache;
private final boolean eagerFetchLobs;
private final boolean javaxValidationAnnotations;
private final boolean jacksonAnnotations;
ReadAnnotationConfig(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, ServerConfig serverConfig) {
this.generatedPropFactory = generatedPropFactory;
this.asOfViewSuffix = asOfViewSuffix;
this.versionsBetweenSuffix = versionsBetweenSuffix;
this.disableL2Cache = serverConfig.isDisableL2Cache();
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
}
GeneratedPropertyFactory getGeneratedPropFactory() {
return generatedPropFactory;
}
String getAsOfViewSuffix() {
return asOfViewSuffix;
}
String getVersionsBetweenSuffix() {
return versionsBetweenSuffix;
}
boolean isDisableL2Cache() {
return disableL2Cache;
}
boolean isEagerFetchLobs() {
return eagerFetchLobs;
}
boolean isJavaxValidationAnnotations() {
return javaxValidationAnnotations;
}
boolean isJacksonAnnotations() {
return jacksonAnnotations;
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
@@ -9,38 +10,10 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory
*/
public class ReadAnnotations {
/**
* Creates appropriate generated properties - WhenXXX, WhoXXX, Version etc.
*/
private final GeneratedPropertyFactory generatedPropFactory;
private final ReadAnnotationConfig readConfig;
/**
* Typically _with_history and when appended to the base table derives the name of
* the view that unions the base table with the history table to support asOf queries.
*/
private final String asOfViewSuffix;
private final String versionsBetweenSuffix;
/**
* True if the javax validation annotations are present in the classpath.
*/
private final boolean javaxValidationAnnotations;
/**
* True if the jackson annotations are present in the classpath.
*/
private final boolean jacksonAnnotations;
private final boolean disableL2Cache;
public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, boolean disableL2Cache) {
this.generatedPropFactory = generatedPropFactory;
this.asOfViewSuffix = asOfViewSuffix;
this.versionsBetweenSuffix = versionsBetweenSuffix;
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
this.disableL2Cache = disableL2Cache;
public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, ServerConfig serverConfig) {
this.readConfig = new ReadAnnotationConfig(generatedPropFactory, asOfViewSuffix, versionsBetweenSuffix, serverConfig);
}
/**
@@ -50,12 +23,10 @@ public class ReadAnnotations {
* to resolve the relationships etc.
* </p>
*/
public void readInitial(DeployBeanInfo<?> info, boolean eagerFetchLobs) {
public void readInitial(DeployBeanInfo<?> info) {
try {
new AnnotationClass(info, javaxValidationAnnotations, asOfViewSuffix, versionsBetweenSuffix, disableL2Cache).parse();
new AnnotationFields(generatedPropFactory, info, javaxValidationAnnotations, jacksonAnnotations, eagerFetchLobs).parse();
new AnnotationClass(info, readConfig).parse();
new AnnotationFields(info, readConfig).parse();
} catch (RuntimeException e) {
throw new RuntimeException("Error reading annotations for " + info, e);
}
@@ -75,14 +46,14 @@ public class ReadAnnotations {
try {
new AnnotationAssocOnes(info, javaxValidationAnnotations, factory).parse();
new AnnotationAssocManys(info, javaxValidationAnnotations, factory).parse();
new AnnotationAssocOnes(info, readConfig, factory).parse();
new AnnotationAssocManys(info, readConfig, factory).parse();
// read the Sql annotations last because they may be
// dependent on field level annotations
new AnnotationSql(info, javaxValidationAnnotations).parse();
new AnnotationSql(info, readConfig).parse();
new AnnotationClass(info).parseAttributeOverride();
new AnnotationClass(info, readConfig).parseAttributeOverride();
info.getDescriptor().postAnnotations();
} catch (RuntimeException e) {
@@ -5,8 +5,6 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import java.util.List;
/**
* Mark transient properties.
*/
@@ -20,29 +18,22 @@ public class TransientProperties {
*/
public void process(DeployBeanDescriptor<?> desc) {
List<DeployBeanProperty> props = desc.propertiesBase();
for (DeployBeanProperty prop : props) {
for (DeployBeanProperty prop : desc.propertiesBase()) {
if (!prop.isDbRead() && !prop.isDbInsertable() && !prop.isDbUpdateable()) {
// non-transient...
prop.setTransient();
}
}
List<DeployBeanPropertyAssocOne<?>> ones = desc.propertiesAssocOne();
for (DeployBeanPropertyAssocOne<?> prop : ones) {
if (prop.getBeanTable() == null) {
if (!prop.isEmbedded()) {
prop.setTransient();
}
}
}
List<DeployBeanPropertyAssocMany<?>> manys = desc.propertiesAssocMany();
for (DeployBeanPropertyAssocMany<?> prop : manys) {
if (prop.getBeanTable() == null) {
for (DeployBeanPropertyAssocOne<?> prop : desc.propertiesAssocOne()) {
if (prop.getBeanTable() == null && !prop.isEmbedded()) {
prop.setTransient();
}
}
for (DeployBeanPropertyAssocMany<?> prop : desc.propertiesAssocMany()) {
if (prop.getBeanTable() == null) {
prop.setTransient();
}
}
}
}
@@ -327,7 +327,7 @@ public class DLoadContext implements LoadContext {
}
private BeanProperty getBeanProperty(BeanDescriptor<?> desc, String path) {
return desc.getBeanPropertyFromPath(path);
return desc.findPropertyFromPath(path);
}
/**
@@ -33,7 +33,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps);
this.property = property;
this.docStoreMapped = property.getTargetDescriptor().isDocStoreMapped();
this.docStoreMapped = property.isTargetDocStoreMapped();
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(firstBatchSize);
@@ -29,6 +29,7 @@ import io.ebeaninternal.server.deploy.BeanManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection;
import io.ebeaninternal.server.deploy.IntersectionRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -869,8 +870,7 @@ public final class DefaultPersister implements Persister {
SpiTransaction t = request.getTransaction();
// exported ones with cascade save
BeanPropertyAssocOne<?>[] expOnes = desc.propertiesOneExportedSave();
for (BeanPropertyAssocOne<?> prop : expOnes) {
for (BeanPropertyAssocOne<?> prop : desc.propertiesOneExportedSave()) {
// check for partial beans
if (request.isLoadedProperty(prop)) {
EntityBean detailBean = prop.getValueAsEntityBean(parentBean);
@@ -886,12 +886,11 @@ public final class DefaultPersister implements Persister {
}
// many's with cascade save
BeanPropertyAssocMany<?>[] manys = desc.propertiesManySave();
boolean insertedParent = request.isInsertedParent();
for (BeanPropertyAssocMany<?> many : manys) {
for (BeanPropertyAssocMany<?> many : desc.propertiesManySave()) {
// check that property is loaded and collection should be cascaded to
if (request.isLoadedProperty(many) && !many.isSkipSaveBeanCollection(parentBean, insertedParent)) {
saveMany(new SaveManyPropRequest(insertedParent, many, parentBean, request));
saveMany2(insertedParent, many, parentBean, request);
if (!insertedParent) {
request.addUpdatedManyProperty(many);
}
@@ -899,6 +898,18 @@ public final class DefaultPersister implements Persister {
}
}
private void saveMany2(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
saveMany(saveManyRequest(insertedParent, many, parentBean, request));
}
private SaveManyPropRequest saveManyRequest(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
if (many instanceof BeanPropertySimpleCollection) {
return new SaveManySimpleCollection(insertedParent, many, parentBean, request);
} else {
return new SaveManyPropRequest(insertedParent, many, parentBean, request);
}
}
private void saveMany(SaveManyPropRequest saveMany) {
if (saveMany.getMany().hasJoinTable()) {
@@ -1131,8 +1142,7 @@ public final class DefaultPersister implements Persister {
}
// Many's with delete cascade
BeanPropertyAssocMany<?>[] manys = desc.propertiesManyDelete();
for (BeanPropertyAssocMany<?> many : manys) {
for (BeanPropertyAssocMany<?> many : desc.propertiesManyDelete()) {
if (many.hasJoinTable()) {
if (!softDelete) {
// delete associated rows from intersection table (but not during soft delete)
@@ -21,11 +21,11 @@ import java.util.Map;
*/
class SaveManyPropRequest {
private final PersistRequestBean<?> request;
final PersistRequestBean<?> request;
private final boolean insertedParent;
private final BeanPropertyAssocMany<?> many;
private final EntityBean parentBean;
private final SpiTransaction transaction;
final BeanPropertyAssocMany<?> many;
final EntityBean parentBean;
final SpiTransaction transaction;
private final boolean cascade;
private final boolean deleteMissingChildren;
private final boolean publish;
@@ -35,8 +35,8 @@ class SaveManyPropRequest {
private final boolean isMap;
private final boolean saveRecurseSkippable;
private Collection<?> collection;
private DefaultPersister persister;
Collection<?> collection;
DefaultPersister persister;
private boolean deleteMissing;
private int sortOrder;
@@ -127,7 +127,7 @@ class SaveManyPropRequest {
}
}
private void processDetails() {
void processDetails() {
BeanProperty orderColumn = null;
boolean hasOrderColumn = many.hasOrderColumn();
@@ -0,0 +1,39 @@
package io.ebeaninternal.server.persist;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
class SaveManySimpleCollection extends SaveManyPropRequest {
SaveManySimpleCollection(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
super(insertedParent, many, parentBean, request);
}
@Override
void processDetails() {
Object parentId = request.getBeanId();
SqlUpdate sqlDelete = many.deleteByParentId(parentId, null);
SpiEbeanServer server = request.getServer();
server.execute(sqlDelete, transaction);
transaction.depth(+1);
String insert = many.insertElementCollection();
SqlUpdate sqlInsert = server.createSqlUpdate(insert);
for (Object value : collection) {
sqlInsert.setParameter(1, parentId);
sqlInsert.setParameter(2, value);
server.execute(sqlInsert, transaction);
}
transaction.depth(-1);
}
}
@@ -35,7 +35,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -56,6 +55,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
private static final Logger logger = LoggerFactory.getLogger(CQuery.class);
private static final CQueryCollectionAddNoop NOOP_ADD = new CQueryCollectionAddNoop();
/**
* The resultSet rows read.
*/
@@ -99,7 +100,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
/**
* The help for the 'master' collection.
*/
private final BeanCollectionHelp<T> help;
private final CQueryCollectionAdd help;
/**
* The overall find request wrapper object.
@@ -147,7 +148,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
/**
* For master detail query.
*/
private final BeanPropertyAssocMany<?> manyProperty;
private final STreePropertyAssocMany manyProperty;
private DataReader dataReader;
@@ -180,20 +181,26 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
private long executionTimeMicros;
/**
* Flag set when findIterate is being read audited.
* Flag set when read auditing.
*/
private boolean audit;
/**
* Flag set when findIterate is being read audited meaning we log in batches.
*/
private boolean auditFindIterate;
/**
* A buffer of Ids collected for findIterate auditing.
*/
private List<Object> auditFindIterateIds;
private List<Object> auditIds;
/**
* Create the Sql select based on the request.
*/
public CQuery(OrmQueryRequest<T> request, CQueryPredicates predicates, CQueryPlan queryPlan) {
this.request = request;
this.audit = request.isAuditReads();
this.queryPlan = queryPlan;
this.query = request.getQuery();
this.queryMode = query.getMode();
@@ -220,7 +227,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
this.logWhereSql = queryPlan.getLogWhereSql();
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
this.help = createHelp(request);
if (lazyLoadManyProperty != null) {
this.help = NOOP_ADD;
} else {
this.help = createHelp(request);
}
this.collection = (help != null ? help.createEmptyNoParent() : null);
}
@@ -233,7 +244,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
// subQuery compiled for InQueryExpression
return null;
}
return BeanCollectionHelpFactory.create(request);
return BeanCollectionHelpFactory.create(manyType, request);
}
}
@@ -374,7 +385,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
*/
public void close() {
try {
if (auditFindIterateIds != null && !auditFindIterateIds.isEmpty()) {
if (auditFindIterate && auditIds != null && !auditIds.isEmpty()) {
auditIterateLogMessage();
}
} catch (Throwable e) {
@@ -510,8 +521,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
protected EntityBean next() {
if (auditFindIterate) {
auditIterateNextBean();
if (audit) {
auditNextBean();
}
hasNextCache = false;
if (nextBean == null) {
@@ -656,7 +667,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* per SqlSelect. This can be null.
*/
@Override
public BeanPropertyAssocMany<?> getManyProperty() {
public STreePropertyAssocMany getManyProperty() {
return manyProperty;
}
@@ -747,30 +758,25 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
*/
void auditFindMany() {
if (!collection.isEmpty()) {
if (auditIds != null && !auditIds.isEmpty()) {
// get the id values of the underlying collection
List<Object> ids = new ArrayList<>(collection.size());
Collection<T> underlyingBeans = collection.getActualDetails();
for (T underlyingBean : underlyingBeans) {
ids.add(desc.getIdForJson(underlyingBean));
}
ReadEvent futureReadEvent = query.getFutureFetchAudit();
if (futureReadEvent == null) {
// normal query execution
desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, ids);
desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, auditIds);
} else {
// this query was executed via findFutureList() and the prepare()
// has already been called so set the details and log
futureReadEvent.setQueryKey(queryPlan.getAuditQueryKey());
futureReadEvent.setBindLog(bindLog);
futureReadEvent.setIds(ids);
futureReadEvent.setIds(auditIds);
desc.readAuditFutureMany(futureReadEvent);
}
}
}
/**
* Indicate that read auditing is occurring on a this findIterate query.
* Indicate that read auditing is occurring on this findIterate query.
*/
void auditFindIterate() {
auditFindIterate = true;
@@ -780,21 +786,21 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Send the current buffer of findIterate collected ids to the audit log.
*/
private void auditIterateLogMessage() {
desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, auditFindIterateIds);
desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, auditIds);
// create a new list on demand with the next bean/id
auditFindIterateIds = null;
auditIds = null;
}
/**
* Add the id to the audit id buffer and flush if needed in batches of 100.
*/
private void auditIterateNextBean() {
private void auditNextBean() {
if (auditFindIterateIds == null) {
auditFindIterateIds = new ArrayList<>(100);
if (auditIds == null) {
auditIds = new ArrayList<>(100);
}
auditFindIterateIds.add(desc.getIdForJson(nextBean));
if (auditFindIterateIds.size() >= 100) {
auditIds.add(desc.getIdForJson(nextBean));
if (auditFindIterate && auditIds.size() >= 100) {
auditIterateLogMessage();
}
}
@@ -18,7 +18,6 @@ import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.Binder;
@@ -524,20 +523,15 @@ class CQueryBuilder {
return rawSqlHandler.buildSql(request, predicates, query.getRawSql().getSql());
}
BeanPropertyAssocMany<?> manyProp = select.getManyProperty();
boolean useSqlLimiter = false;
StringBuilder sb = new StringBuilder(500);
String dbOrderBy = predicates.getDbOrderBy();
if (selectClause != null) {
sb.append(selectClause);
} else {
useSqlLimiter = (query.hasMaxRowsOrFirstRow() && manyProp == null);
useSqlLimiter = (query.hasMaxRowsOrFirstRow() && select.getManyProperty() == null);
if (!useSqlLimiter) {
sb.append("select ");
@@ -686,7 +680,7 @@ class CQueryBuilder {
throw new IllegalArgumentException("Illegal enum: "+ orderBy);
}
}
/**
* Append where or and based on the hasWhere flag.
*/
@@ -0,0 +1,21 @@
package io.ebeaninternal.server.query;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
/**
* Defines interface for adding beans to the collection which might be a List, Set or Map.
*/
public interface CQueryCollectionAdd<T> {
/**
* Create an empty collection.
*/
BeanCollection<T> createEmptyNoParent();
/**
* Add a bean to the List Set or Map.
*/
void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck);
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.query;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
/**
* A NOOP based CQueryCollectionAdd for use with lazy loading many queries where the
* beans loaded into the collection are added to the collection(s) of the parent(s).
*/
class CQueryCollectionAddNoop<T> implements CQueryCollectionAdd<T> {
/**
* Return null as we are not collecting the beans.
*/
@Override
public BeanCollection<T> createEmptyNoParent() {
return null;
}
/**
* Do nothing for this case.
*/
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
// do nothing
}
}
@@ -11,7 +11,6 @@ import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.DataReader;
@@ -74,7 +73,7 @@ public class CQueryPlan {
/**
* Encrypted properties required additional binding.
*/
private final BeanProperty[] encryptedProps;
private final STreeProperty[] encryptedProps;
private final CQueryPlanStats stats;
@@ -177,9 +176,8 @@ public class CQueryPlan {
DataBind bindEncryptedProperties(PreparedStatement stmt, Connection conn) throws SQLException {
DataBind dataBind = new DataBind(dataTimeZone, stmt, conn);
if (encryptedProps != null) {
for (BeanProperty encryptedProp : encryptedProps) {
String key = encryptedProp.getEncryptKey().getStringValue();
dataBind.setString(key);
for (STreeProperty encryptedProp : encryptedProps) {
dataBind.setString(encryptedProp.getEncryptKeyAsString());
}
}
return dataBind;
@@ -231,7 +231,7 @@ public class CQueryPredicates {
}
}
BeanPropertyAssocMany<?> manyProperty = request.getManyProperty();
BeanPropertyAssocMany<?> manyProperty = request.determineMany();
if (manyProperty != null) {
OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false);
SpiExpressionList<?> filterManyExpr = chunk.getFilterMany();
@@ -0,0 +1,20 @@
package io.ebeaninternal.server.query;
public class ExtraJoin {
private final STreePropertyAssoc property;
private final boolean containsMany;
public ExtraJoin(STreePropertyAssoc property, boolean containsMany) {
this.property = property;
this.containsMany = containsMany;
}
public STreePropertyAssoc getProperty() {
return property;
}
public boolean isContainsMany() {
return containsMany;
}
}
@@ -8,10 +8,10 @@ import java.util.List;
/**
* A property in the SQL Tree.
*
* <p>
* A BeanProperty or a dynamically created property based on formula.
*/
public interface SqlTreeProperty {
public interface STreeProperty {
/**
* Return the property name.
@@ -38,6 +38,16 @@ public interface SqlTreeProperty {
*/
boolean isAggregation();
/**
* Return true if the property is a formula.
*/
boolean isFormula();
/**
* Return the encryption key as a string value (when the property is encrypted).
*/
String getEncryptKeyAsString();
/**
* Return the Expression language prefix (join path).
*/
@@ -0,0 +1,39 @@
package io.ebeaninternal.server.query;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.id.IdBinder;
public interface STreePropertyAssoc extends STreeProperty {
/**
* Return the extra where clause if set.
*/
String getExtraWhere();
/**
* Return the type of the target (other side).
*/
STreeType target();
/**
* Return the IdBinder of the underlying type.
*/
IdBinder getIdBinder();
/**
* Add a Join with the given alias.
*/
SqlJoinType addJoin(SqlJoinType joinType, String alias2, String alias, DbSqlContext ctx);
/**
* Add a Join with the given prefix (determining the alias).
*/
SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx);
/**
* Add a bean to the parent.
*/
void setValue(EntityBean parentBean, Object contextBean);
}
@@ -0,0 +1,45 @@
package io.ebeaninternal.server.query;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
public interface STreePropertyAssocMany extends STreePropertyAssoc {
/**
* Append exported columns to the select.
*/
void addSelectExported(DbSqlContext ctx, String prefix);
/**
* Return true if this is a ManyToMany with history.
*/
boolean isManyToManyWithHistory();
/**
* Return a reference collection.
*/
BeanCollection<?> createReferenceIfNull(EntityBean localBean);
/**
* Return true if the property has a join table.
*/
boolean hasJoinTable();
/**
* Return the intersection table join.
*/
TableJoin getIntersectionTableJoin();
/**
* Add a bean to the collection.
*/
void addBeanToCollectionWithCreate(EntityBean contextParent, EntityBean detailBean, boolean withCheck);
/**
* Return true if the property is excluded from history.
*/
boolean isExcludedFromHistory();
}
@@ -0,0 +1,16 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.server.type.ScalarType;
public interface STreePropertyAssocOne extends STreePropertyAssoc {
/**
* Return true if the property is an Id.
*/
boolean isAssocId();
/**
* Return the scalar type of the associated id property.
*/
ScalarType<?> getIdScalarType();
}
@@ -0,0 +1,136 @@
package io.ebeaninternal.server.query;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.id.IdBinder;
/**
* Bean type interface for Sql query tree.
*/
public interface STreeType {
/**
* Return the bean short name.
*/
String getName();
/**
* Return true if the underlying type has an Id property.
*/
boolean hasId();
/**
* Return true if the type is for ElementCollection (not mapped to an entity type/class).
*/
boolean isElementType();
/**
* Return true if the type uses soft delete.
*/
boolean isSoftDelete();
/**
* Return true if the type uses history.
*/
boolean isHistorySupport();
/**
* Return true if the type is RawSql based.
*/
boolean isRawSqlBased();
/**
* Return the soft delete predicate using the given table alias.
*/
String getSoftDeletePredicate(String baseTableAlias);
/**
* Return the scalar properties.
*/
STreeProperty[] propsBaseScalar();
/**
* Return the embedded bean properties.
*/
STreePropertyAssoc[] propsEmbedded();
/**
* Return the associated one properties.
*/
STreePropertyAssocOne[] propsOne();
/**
* Return the associated many properties.
*/
STreePropertyAssocMany[] propsMany();
/**
* Return the inheritance information for this type.
*/
InheritInfo getInheritInfo();
/**
* Return the IdBinder for this type.
*/
IdBinder getIdBinder();
/**
* Create a new entity bean instance.
*/
EntityBean createEntityBean();
/**
* Put the entity bean into the persistence context.
*/
Object contextPutIfAbsent(PersistenceContext persistenceContext, Object id, EntityBean localBean);
/**
* Set draft status on the entity bean.
*/
void setDraft(EntityBean localBean);
/**
* Invoke any post load listeners.
*/
void postLoad(Object localBean);
/**
* Return the base table to use given the temporalMode.
*/
String getBaseTable(SpiQuery.TemporalMode temporalMode);
/**
* Return true if the given path is an embedded bean.
*/
boolean isEmbeddedPath(String propertyPath);
/**
* Return the bean property traversing the object graph and taking into account inheritance.
*/
STreeProperty findPropertyFromPath(String property);
/**
* Find a known property.
*/
STreeProperty findProperty(String propName);
/**
* Find and return property allowing for dynamic formula properties.
*/
STreeProperty findPropertyWithDynamic(String baseName);
/**
* Return an extra join if the property path requires it.
*/
ExtraJoin extraJoin(String propertyPath);
/**
* Load the property taking into account inheritance.
*/
void inheritanceLoad(SqlBeanLoad sqlBeanLoad, STreeProperty property, DbReadContext ctx);
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import java.util.ArrayList;
import java.util.List;
@@ -18,7 +16,7 @@ class SqlTree {
/**
* Property if resultSet contains master and detail rows.
*/
private final BeanPropertyAssocMany<?> manyProperty;
private final STreePropertyAssocMany manyProperty;
private final Set<String> includes;
@@ -38,7 +36,7 @@ class SqlTree {
/**
* Encrypted Properties require additional binding.
*/
private final BeanProperty[] encryptedProps;
private final STreeProperty[] encryptedProps;
/**
* Where clause for inheritance.
@@ -51,7 +49,7 @@ class SqlTree {
* Create the SqlSelectClause.
*/
SqlTree(String summary, SqlTreeNode rootNode, String distinctOn, String selectSql, String fromSql, String groupBy, String inheritanceWhereSql,
BeanProperty[] encryptedProps, BeanPropertyAssocMany<?> manyProperty, Set<String> includes, boolean includeJoins) {
STreeProperty[] encryptedProps, STreePropertyAssocMany manyProperty, Set<String> includes, boolean includeJoins) {
this.summary = summary;
this.rootNode = rootNode;
@@ -147,11 +145,11 @@ class SqlTree {
* Return the property that is associated with the many. There can only be one
* per SqlSelect. This can be null.
*/
BeanPropertyAssocMany<?> getManyProperty() {
STreePropertyAssocMany getManyProperty() {
return manyProperty;
}
BeanProperty[] getEncryptedProps() {
STreeProperty[] getEncryptedProps() {
return encryptedProps;
}
@@ -1,17 +1,14 @@
package io.ebeaninternal.server.query;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.PersistenceException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Special Map of the logical property joins to table alias.
@@ -61,16 +58,11 @@ class SqlTreeAlias {
/**
* Add joins.
*/
public void addJoin(Set<String> propJoins, BeanDescriptor<?> desc) {
public void addJoin(Set<String> propJoins, STreeType desc) {
if (propJoins != null) {
for (String propJoin : propJoins) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propJoin);
if (elProp == null) {
throw new PersistenceException("Invalid path " + propJoin + " from " + desc.getFullName());
} else if (elProp.getBeanProperty().isEmbedded()) {
if (desc.isEmbeddedPath(propJoin)) {
addEmbeddedPropertyJoin(propJoin);
} else {
addPropertyJoin(propJoin, joinProps);
}
@@ -6,14 +6,8 @@ import io.ebeaninternal.api.PropertyJoin;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import org.slf4j.Logger;
@@ -37,7 +31,7 @@ public final class SqlTreeBuilder {
private final SpiQuery<?> query;
private final BeanDescriptor<?> desc;
private final STreeType desc;
private final OrmQueryDetail queryDetail;
@@ -51,7 +45,7 @@ public final class SqlTreeBuilder {
/**
* Property if resultSet contains master and detail rows.
*/
private BeanPropertyAssocMany<?> manyProperty;
private STreePropertyAssocMany manyProperty;
private final SqlTreeAlias alias;
@@ -141,7 +135,7 @@ public final class SqlTreeBuilder {
String fromSql = null;
String inheritanceWhereSql = null;
String groupBy = null;
BeanProperty[] encryptedProps = null;
STreeProperty[] encryptedProps = null;
if (!rawSql) {
selectSql = buildSelectClause();
fromSql = buildFromClause();
@@ -233,7 +227,7 @@ public final class SqlTreeBuilder {
return ctx.getContent();
}
private void buildRoot(BeanDescriptor<?> desc) {
private void buildRoot(STreeType desc) {
rootNode = buildSelectChain(null, null, desc, null);
@@ -253,26 +247,24 @@ public final class SqlTreeBuilder {
* Recursively build the query tree depending on what leaves in the tree
* should be included.
*/
private SqlTreeNode buildSelectChain(String prefix, BeanPropertyAssoc<?> prop,
BeanDescriptor<?> desc, List<SqlTreeNode> joinList) {
private SqlTreeNode buildSelectChain(String prefix, STreePropertyAssoc prop,
STreeType desc, List<SqlTreeNode> joinList) {
List<SqlTreeNode> myJoinList = new ArrayList<>();
BeanPropertyAssocOne<?>[] ones = desc.propertiesOne();
for (BeanPropertyAssocOne<?> one : ones) {
for (STreePropertyAssocOne one : desc.propsOne()) {
String propPrefix = SplitName.add(prefix, one.getName());
if (isIncludeBean(propPrefix)) {
selectIncludes.add(propPrefix);
buildSelectChain(propPrefix, one, one.getTargetDescriptor(), myJoinList);
buildSelectChain(propPrefix, one, one.target(), myJoinList);
}
}
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
for (BeanPropertyAssocMany<?> many : manys) {
for (STreePropertyAssocMany many : desc.propsMany()) {
String propPrefix = SplitName.add(prefix, many.getName());
if (isIncludeMany(propPrefix, many)) {
selectIncludes.add(propPrefix);
buildSelectChain(propPrefix, many, many.getTargetDescriptor(), myJoinList);
buildSelectChain(propPrefix, many, many.target(), myJoinList);
}
}
@@ -304,24 +296,24 @@ public final class SqlTreeBuilder {
Collection<PropertyJoin> includes = manyWhereJoins.getPropertyJoins();
for (PropertyJoin joinProp : includes) {
BeanPropertyAssoc<?> beanProperty = (BeanPropertyAssoc<?>) desc.getBeanPropertyFromPath(joinProp.getProperty());
STreePropertyAssoc beanProperty = (STreePropertyAssoc) desc.findPropertyFromPath(joinProp.getProperty());
SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType());
myJoinList.add(nodeJoin);
}
}
private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc<?> prop, BeanDescriptor<?> desc, List<SqlTreeNode> myList, SqlTreeProperties props) {
private SqlTreeNode buildNode(String prefix, STreePropertyAssoc prop, STreeType desc, List<SqlTreeNode> myList, SqlTreeProperties props) {
if (prefix == null) {
buildExtraJoins(desc, myList);
// Optional many property for lazy loading query
BeanPropertyAssocMany<?> lazyLoadMany = (query == null) ? null : query.getLazyLoadMany();
STreePropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadMany();
boolean withId = !rawNoId && !subQuery && (query == null || query.isWithId());
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, temporalMode, disableLazyLoad);
} else if (prop instanceof BeanPropertyAssocMany<?>) {
return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany<?>) prop, props, myList, temporalMode, disableLazyLoad);
} else if (prop instanceof STreePropertyAssocMany) {
return new SqlTreeNodeManyRoot(prefix, (STreePropertyAssocMany) prop, props, myList, temporalMode, disableLazyLoad);
} else {
// do not read Id on child beans (e.g. when used with fetch())
@@ -334,7 +326,7 @@ public final class SqlTreeBuilder {
* Build extra joins to support properties used in where clause but not
* already in select clause.
*/
private void buildExtraJoins(BeanDescriptor<?> desc, List<SqlTreeNode> myList) {
private void buildExtraJoins(STreeType desc, List<SqlTreeNode> myList) {
if (rawSql) {
return;
@@ -383,25 +375,25 @@ public final class SqlTreeBuilder {
* This means it can included individual properties of an embedded bean.
* </p>
*/
private void addPropertyToSubQuery(SqlTreeProperties selectProps, BeanDescriptor<?> desc, String propName) {
private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName) {
BeanProperty p = desc.findBeanProperty(propName);
STreeProperty p = desc.findProperty(propName);
if (p == null) {
logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it.");
} else if (p instanceof BeanPropertyAssoc<?> && p.isEmbedded()) {
} else if (p instanceof STreePropertyAssoc && p.isEmbedded()) {
// if the property is embedded we need to lookup the real column name
int pos = propName.indexOf('.');
if (pos > -1) {
String name = propName.substring(pos + 1);
p = ((BeanPropertyAssoc<?>) p).getTargetDescriptor().findBeanProperty(name);
p = ((STreePropertyAssoc) p).target().findProperty(name);
}
}
selectProps.add(p);
}
private void addProperty(SqlTreeProperties selectProps, BeanDescriptor<?> desc,
private void addProperty(SqlTreeProperties selectProps, STreeType desc,
OrmQueryProperties queryProps, String propName) {
if (subQuery) {
@@ -418,7 +410,7 @@ public final class SqlTreeBuilder {
// make sure we only included the base/embedded bean once
if (!selectProps.containsProperty(baseName)) {
SqlTreeProperty p = desc.findSqlTreeProperty(baseName);
STreeProperty p = desc.findPropertyWithDynamic(baseName);
if (p == null) {
logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it.");
@@ -436,17 +428,17 @@ public final class SqlTreeBuilder {
} else {
// find the property including searching the
// sub class hierarchy if required
SqlTreeProperty p = desc.findSqlTreeProperty(propName);
STreeProperty p = desc.findPropertyWithDynamic(propName);
if (p == null) {
logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it.");
p = desc.findBeanProperty("id");
p = desc.findProperty("id");
selectProps.add(p);
} else if (p.isId() && excludeIdProperty()) {
// do not bother to include id for normal queries as the
// id is always added (except for subQueries)
} else if (p instanceof BeanPropertyAssoc<?>) {
} else if (p instanceof STreePropertyAssoc) {
// need to check if this property should be
// excluded. This occurs when this property is
// included as a bean join. With a bean join
@@ -463,7 +455,7 @@ public final class SqlTreeBuilder {
}
}
private SqlTreeProperties getBaseSelectPartial(BeanDescriptor<?> desc, OrmQueryProperties queryProps) {
private SqlTreeProperties getBaseSelectPartial(STreeType desc, OrmQueryProperties queryProps) {
SqlTreeProperties selectProps = new SqlTreeProperties();
selectProps.setReadOnly(queryProps.isReadOnly());
@@ -484,7 +476,7 @@ public final class SqlTreeBuilder {
return selectProps;
}
private SqlTreeProperties getBaseSelect(BeanDescriptor<?> desc, OrmQueryProperties queryProps) {
private SqlTreeProperties getBaseSelect(STreeType desc, OrmQueryProperties queryProps) {
boolean partial = queryProps != null && !queryProps.allProperties();
if (partial) {
@@ -495,17 +487,16 @@ public final class SqlTreeBuilder {
selectProps.setAllProperties();
// normal simple properties of the bean
selectProps.add(desc.propertiesBaseScalar());
selectProps.add(desc.propertiesEmbedded());
selectProps.add(desc.propsBaseScalar());
selectProps.add(desc.propsEmbedded());
BeanPropertyAssocOne<?>[] propertiesOne = desc.propertiesOne();
for (BeanPropertyAssocOne<?> aPropertiesOne : propertiesOne) {
for (STreePropertyAssocOne propertyAssocOne : desc.propsOne()) {
//noinspection StatementWithEmptyBody
if (queryProps != null && queryProps.isIncludedBeanJoin(aPropertiesOne.getName())) {
if (queryProps != null && queryProps.isIncludedBeanJoin(propertyAssocOne.getName())) {
// if it is a joined bean... then don't add the property
// as it will have its own entire Node in the SqlTree
} else {
selectProps.add(aPropertiesOne);
selectProps.add(propertyAssocOne);
}
}
@@ -521,7 +512,7 @@ public final class SqlTreeBuilder {
/**
* Return true if this many node should be included in the query.
*/
private boolean isIncludeMany(String propName, BeanPropertyAssocMany<?> manyProp) {
private boolean isIncludeMany(String propName, STreePropertyAssocMany manyProp) {
if (queryDetail.isJoinsEmpty()) {
return false;
@@ -588,9 +579,9 @@ public final class SqlTreeBuilder {
*/
private final Map<String, SqlTreeNodeExtraJoin> rootRegister = new HashMap<>();
private final BeanDescriptor<?> desc;
private final STreeType desc;
private IncludesDistiller(BeanDescriptor<?> desc, Set<String> selectIncludes,
private IncludesDistiller(STreeType desc, Set<String> selectIncludes,
Set<String> predicateIncludes) {
this.desc = desc;
this.selectIncludes = selectIncludes;
@@ -645,25 +636,14 @@ public final class SqlTreeBuilder {
*/
private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) {
ElPropertyValue elGetValue = desc.getElGetValue(propertyName);
if (elGetValue == null) {
// this can occur for master detail queries
// with concatenated keys (so not an error now)
ExtraJoin extra = desc.extraJoin(propertyName);
if (extra == null) {
return null;
}
BeanProperty beanProperty = elGetValue.getBeanProperty();
if (beanProperty instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
if (assocProp.isEmbedded()) {
// no extra join required for embedded beans
return null;
}
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp, elGetValue.containsMany());
} else {
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.getProperty(), extra.isContainsMany());
joinRegister.put(propertyName, extraJoin);
return extraJoin;
}
return null;
}
/**
@@ -674,10 +654,8 @@ public final class SqlTreeBuilder {
* not specified and is implicitly created.
* </p>
*/
private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp,
SqlTreeNodeExtraJoin childJoin) {
private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp, SqlTreeNodeExtraJoin childJoin) {
while (true) {
int dotPos = includeProp.lastIndexOf('.');
if (dotPos == -1) {
// no parent possible(parent is root)
@@ -9,11 +9,6 @@ import io.ebean.util.SplitName;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Mode;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.InheritInfo;
@@ -34,7 +29,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0];
protected final BeanDescriptor<?> desc;
protected final STreeType desc;
protected final IdBinder idBinder;
@@ -48,14 +43,14 @@ class SqlTreeNodeBean implements SqlTreeNode {
*/
private final boolean partialObject;
protected final SqlTreeProperty[] properties;
protected final STreeProperty[] properties;
/**
* Extra where clause added by Where annotation on associated many.
*/
private final String extraWhere;
private final BeanPropertyAssoc<?> nodeBeanProp;
private final STreePropertyAssoc nodeBeanProp;
/**
* False if report bean and has no id property.
@@ -70,7 +65,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
private final Map<String, String> pathMap;
final BeanPropertyAssocMany<?> lazyLoadParent;
final STreePropertyAssocMany lazyLoadParent;
final SpiQuery.TemporalMode temporalMode;
@@ -92,29 +87,29 @@ class SqlTreeNodeBean implements SqlTreeNode {
/**
* Construct for leaf node.
*/
SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null, temporalMode, disableLazyLoad);
this(prefix, beanProp, beanProp.target(), props, myChildren, withId, null, temporalMode, disableLazyLoad);
}
/**
* Construct for root node.
*/
SqlTreeNodeBean(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
BeanPropertyAssocMany<?> many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
SqlTreeNodeBean(STreeType desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this(null, null, desc, props, myList, withId, many, temporalMode, disableLazyLoad);
}
/**
* Create with the appropriate node.
*/
private SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, BeanDescriptor<?> desc, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, BeanPropertyAssocMany<?> lazyLoadParent,
private SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, STreeType desc, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, STreePropertyAssocMany lazyLoadParent,
SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this.lazyLoadParent = lazyLoadParent;
this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getBeanDescriptor().getIdBinder();
this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getIdBinder();
this.prefix = prefix;
this.desc = desc;
this.inheritInfo = desc.getInheritInfo();
@@ -129,7 +124,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
this.aggregationRoot = props.isAggregationRoot();
// the bean has an Id property and we want to use it
this.readId = !aggregationRoot && withId && (desc.getIdProperty() != null);
this.readId = !aggregationRoot && withId && desc.hasId();
this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions;
this.partialObject = props.isPartialObject();
@@ -150,25 +145,22 @@ class SqlTreeNodeBean implements SqlTreeNode {
// if we have also no children, NPE happens anyway.
return children[0].getSingleAttributeScalarType();
}
if (properties[0] instanceof BeanPropertyAssocOne<?>) {
BeanPropertyAssocOne<?> assocOne = (BeanPropertyAssocOne<?>)properties[0];
if (properties[0] instanceof STreePropertyAssocOne) {
STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0];
if (assocOne.isAssocId()) {
return assocOne.getTargetDescriptor().getIdProperty().getScalarType();
return assocOne.getIdScalarType();
}
}
return properties[0].getScalarType();
}
private Map<String, String> createPathMap(String prefix, BeanDescriptor<?> desc) {
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
private Map<String, String> createPathMap(String prefix, STreeType desc) {
HashMap<String, String> m = new HashMap<>();
for (BeanPropertyAssocMany<?> many : manys) {
for (STreePropertyAssocMany many : desc.propsMany()) {
String name = many.getName();
m.put(name, getPath(prefix, name));
}
return m;
}
@@ -189,7 +181,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
idBinder.buildRawSqlSelectChain(prefix, selectChain);
}
for (SqlTreeProperty property : properties) {
for (STreeProperty property : properties) {
property.buildRawSqlSelectChain(prefix, selectChain);
}
// recursively continue reading...
@@ -228,7 +220,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
Class<?> localType;
BeanDescriptor<?> localDesc;
STreeType localDesc;
IdBinder localIdBinder;
EntityBean localBean;
@@ -292,21 +284,16 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (inheritInfo == null) {
// normal behavior with no inheritance
for (SqlTreeProperty property : properties) {
for (STreeProperty property : properties) {
property.load(sqlBeanLoad);
}
} else {
// take account of inheritance and due to subclassing approach
// need to get a 'local' version of the property
for (SqlTreeProperty property : properties) {
for (STreeProperty property : properties) {
// get a local version of the BeanProperty
BeanProperty p = localDesc.getBeanProperty(property.getName());
if (p != null) {
p.load(sqlBeanLoad);
} else {
property.loadIgnore(ctx);
}
localDesc.inheritanceLoad(sqlBeanLoad, property, ctx);
}
}
@@ -374,6 +361,9 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (!readId || temporalVersions) {
// a bean with no Id (never found in context)
if (lazyLoadParentId != null && desc.isElementType()) {
ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId);
}
return localBean;
} else {
@@ -388,13 +378,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
* Create lazy loading proxies for the Many's except for the one that is
* included in the actual query.
*/
private void createListProxies(BeanDescriptor<?> localDesc, DbReadContext ctx, EntityBean localBean, boolean disableLazyLoad) {
private void createListProxies(STreeType localDesc, DbReadContext ctx, EntityBean localBean, boolean disableLazyLoad) {
BeanPropertyAssocMany<?> fetchedMany = ctx.getManyProperty();
STreePropertyAssocMany fetchedMany = ctx.getManyProperty();
// load the List/Set/Map proxy objects (deferred fetching of lists)
BeanPropertyAssocMany<?>[] manys = localDesc.propertiesMany();
for (BeanPropertyAssocMany<?> many : manys) {
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (fetchedMany == null || !fetchedMany.equals(many)) {
// create a proxy for the many (deferred fetching)
@@ -419,7 +408,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (readId) {
appendSelectId(ctx, idBinder.getBeanProperty());
}
for (SqlTreeProperty property : properties) {
for (STreeProperty property : properties) {
if (!property.isAggregation()) {
property.appendSelect(ctx, subQuery);
}
@@ -486,14 +475,13 @@ class SqlTreeNodeBean implements SqlTreeNode {
/**
* Append the properties to the buffer.
*/
private void appendSelect(DbSqlContext ctx, boolean subQuery, SqlTreeProperty[] props) {
for (SqlTreeProperty prop : props) {
private void appendSelect(DbSqlContext ctx, boolean subQuery, STreeProperty[] props) {
for (STreeProperty prop : props) {
prop.appendSelect(ctx, subQuery);
}
}
protected void appendSelectId(DbSqlContext ctx, BeanProperty prop) {
protected void appendSelectId(DbSqlContext ctx, STreeProperty prop) {
if (prop != null) {
prop.appendSelect(ctx, false);
}
@@ -546,7 +534,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
// join and return SqlJoinType to use for child joins
joinType = appendFromBaseTable(ctx, joinType);
for (SqlTreeProperty property : properties) {
for (STreeProperty property : properties) {
// usually nothing... except for 1-1 Exported
property.appendFrom(ctx, joinType);
}
@@ -601,8 +589,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
private SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) {
if (nodeBeanProp instanceof BeanPropertyAssocMany<?>) {
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) nodeBeanProp;
if (nodeBeanProp instanceof STreePropertyAssocMany) {
STreePropertyAssocMany manyProp = (STreePropertyAssocMany) nodeBeanProp;
if (manyProp.hasJoinTable()) {
String alias = ctx.getTableAlias(prefix);
@@ -619,7 +607,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
return nodeBeanProp.addJoin(joinType, alias2, alias, ctx);
}
}
return nodeBeanProp.addJoin(joinType, prefix, ctx);
@@ -4,14 +4,11 @@ import io.ebean.Version;
import io.ebean.bean.EntityBean;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@@ -25,7 +22,7 @@ import java.util.List;
*/
class SqlTreeNodeExtraJoin implements SqlTreeNode {
private final BeanPropertyAssoc<?> assocBeanProperty;
private final STreePropertyAssoc assocBeanProperty;
private final String prefix;
@@ -35,11 +32,11 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
private List<SqlTreeNodeExtraJoin> children;
SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc<?> assocBeanProperty, boolean pathContainsMany) {
SqlTreeNodeExtraJoin(String prefix, STreePropertyAssoc assocBeanProperty, boolean pathContainsMany) {
this.prefix = prefix;
this.assocBeanProperty = assocBeanProperty;
this.pathContainsMany = pathContainsMany;
this.manyJoin = assocBeanProperty instanceof BeanPropertyAssocMany<?>;
this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany;
}
@Override
@@ -103,8 +100,8 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
boolean manyToMany = false;
if (assocBeanProperty instanceof BeanPropertyAssocMany<?>) {
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) assocBeanProperty;
if (assocBeanProperty instanceof STreePropertyAssocMany) {
STreePropertyAssocMany manyProp = (STreePropertyAssocMany) assocBeanProperty;
if (manyProp.hasJoinTable()) {
manyToMany = true;
@@ -127,7 +124,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
}
if (!manyToMany) {
if (assocBeanProperty.isFormula()) {
// add joins for formula beans
// add joins for formula beans
assocBeanProperty.appendFrom(ctx, joinType);
}
joinType = assocBeanProperty.addJoin(joinType, prefix, ctx);
@@ -164,7 +161,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
* Does nothing.
*/
@Override
public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) throws SQLException {
public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) {
return null;
}
@@ -172,7 +169,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
* Does nothing.
*/
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
public <T> Version<T> loadVersion(DbReadContext ctx) {
return null;
}
@@ -2,7 +2,6 @@ package io.ebeaninternal.server.query;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
@@ -11,9 +10,9 @@ import java.util.List;
final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
private final BeanPropertyAssocMany<?> manyProp;
private final STreePropertyAssocMany manyProp;
SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany<?> prop, SqlTreeProperties props, List<SqlTreeNode> myList,
SqlTreeNodeManyRoot(String prefix, STreePropertyAssocMany prop, SqlTreeProperties props, List<SqlTreeNode> myList,
SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
super(prefix, prop, props, myList, true, temporalMode, disableLazyLoad);
this.manyProp = prop;
@@ -4,15 +4,11 @@ import io.ebean.Version;
import io.ebean.bean.EntityBean;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.SQLException;
import java.util.List;
/**
@@ -24,15 +20,14 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
private final String prefix;
private final BeanPropertyAssoc<?> nodeBeanProp;
private final STreePropertyAssoc nodeBeanProp;
/**
* The many where join which is either INNER or OUTER.
*/
private final SqlJoinType manyJoinType;
SqlTreeNodeManyWhereJoin(String prefix, BeanPropertyAssoc<?> prop, SqlJoinType manyJoinType) {
SqlTreeNodeManyWhereJoin(String prefix, STreePropertyAssoc prop, SqlJoinType manyJoinType) {
this.nodeBeanProp = prop;
this.prefix = prefix;
this.manyJoinType = manyJoinType;
@@ -86,16 +81,16 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
* Join to base table for this node. This includes a join to the
* intersection table if this is a ManyToMany node.
*/
void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
private void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
String alias = ctx.getTableAliasManyWhere(prefix);
String parentAlias = ctx.getTableAliasManyWhere(parentPrefix);
if (nodeBeanProp instanceof BeanPropertyAssocOne<?>) {
if (nodeBeanProp instanceof STreePropertyAssocOne) {
nodeBeanProp.addJoin(joinType, parentAlias, alias, ctx);
} else {
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) nodeBeanProp;
STreePropertyAssocMany manyProp = (STreePropertyAssocMany) nodeBeanProp;
if (!manyProp.hasJoinTable()) {
manyProp.addJoin(joinType, parentAlias, alias, ctx);
@@ -125,13 +120,13 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
}
@Override
public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) throws SQLException {
public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) {
// nothing to do here
return null;
}
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
public <T> Version<T> loadVersion(DbReadContext ctx) {
// nothing to do here
return null;
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
@@ -18,8 +16,8 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
/**
* Specify for SqlSelect to include an Id property or not.
*/
SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
TableJoin includeJoin, BeanPropertyAssocMany<?> many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
SqlTreeNodeRoot(STreeType desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
TableJoin includeJoin, STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
super(desc, props, myList, withId, many, temporalMode, disableLazyLoad);
this.includeJoin = includeJoin;
@@ -20,7 +20,7 @@ public class SqlTreeProperties {
/**
* The bean properties in order.
*/
private final List<SqlTreeProperty> propsList = new ArrayList<>();
private final List<STreeProperty> propsList = new ArrayList<>();
/**
* Maintain a list of property names to detect embedded bean additions.
@@ -40,17 +40,17 @@ public class SqlTreeProperties {
return propNames.contains(propName);
}
public void add(SqlTreeProperty[] props) {
public void add(STreeProperty[] props) {
propsList.addAll(Arrays.asList(props));
}
public void add(SqlTreeProperty prop) {
public void add(STreeProperty prop) {
propsList.add(prop);
propNames.add(prop.getName());
}
public SqlTreeProperty[] getProps() {
return propsList.toArray(new SqlTreeProperty[propsList.size()]);
public STreeProperty[] getProps() {
return propsList.toArray(new STreeProperty[propsList.size()]);
}
boolean isPartialObject() {
@@ -97,7 +97,7 @@ public class SqlTreeProperties {
*/
private String aggregationJoin() {
if (!allProperties) {
for (SqlTreeProperty beanProperty : propsList) {
for (STreeProperty beanProperty : propsList) {
if (beanProperty.isAggregation()) {
aggregation = true;
aggregationPath = beanProperty.getElPrefix();
@@ -1,11 +1,11 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.FetchConfig;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebean.util.SplitName;
import javax.persistence.PersistenceException;
import java.io.Serializable;
@@ -427,7 +427,7 @@ public class OrmQueryDetail implements Serializable {
for (OrmQueryProperties joinProps : fetchPaths.values()) {
if (!joinProps.hasSelectClause()) {
BeanDescriptor<?> assocDesc = desc.getBeanDescriptor(joinProps.getPath());
if (assocDesc.hasDefaultSelectClause()) {
if (assocDesc != null && assocDesc.hasDefaultSelectClause()) {
fetch(joinProps.getPath(), assocDesc.getDefaultSelectClause(), joinProps.getFetchConfig());
}
}
@@ -72,7 +72,7 @@ public class DocStructure {
public <T> void prepareMany(BeanDescriptor<T> desc) {
Set<String> strings = embedded.keySet();
for (String prop : strings) {
BeanPropertyAssoc<?> embProp = (BeanPropertyAssoc<?>) desc.findBeanProperty(prop);
BeanPropertyAssoc<?> embProp = (BeanPropertyAssoc<?>) desc.findProperty(prop);
if (embProp.isMany()) {
prepare(prop, embProp);
}