#1142 - Mapping - Add support for @OrderColumn (Ebean automatically maintains an order column)

This commit is contained in:
Rob Bygrave
2018-02-15 22:31:41 +13:00
parent 31cac60393
commit 628af31ddd
23 changed files with 746 additions and 209 deletions
@@ -227,6 +227,11 @@ public interface BeanCollection<E> extends Serializable {
*/
void modifyReset();
/**
* Has been modified by an addition or removal.
*/
boolean wasTouched();
/**
* Return a shallow copy of this collection that is modifiable.
*/
@@ -90,6 +90,7 @@ public final class EntityBeanIntercept implements Serializable {
private int lazyLoadProperty = -1;
private Object ownerId;
private int sortOrder;
/**
* Create a intercept with a given entity.
@@ -693,6 +694,9 @@ public final class EntityBeanIntercept implements Serializable {
* Add and return a dirty property hash recursing into embedded beans.
*/
private void addDirtyPropertyKey(StringBuilder sb) {
if (sortOrder > 0) {
sb.append("s,");
}
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
@@ -1050,4 +1054,18 @@ public final class EntityBeanIntercept implements Serializable {
public void setOldValue(int propertyIndex, Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
}
/**
* Return the sort order value for an order column.
*/
public int getSortOrder() {
return sortOrder;
}
/**
* Set the sort order value for an order column.
*/
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
}
@@ -213,7 +213,12 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
@Override
public boolean wasTouched() {
return modifyHolder != null && modifyHolder.wasTouched();
}
/**
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
* @param other
@@ -28,7 +28,10 @@ class ModifyHolder<E> implements Serializable {
*/
private Set<E> modifyAdditions = new LinkedHashSet<>();
private boolean touched;
void reset() {
touched = false;
modifyDeletions = new LinkedHashSet<>();
modifyAdditions = new LinkedHashSet<>();
}
@@ -50,6 +53,7 @@ class ModifyHolder<E> implements Serializable {
void modifyAddition(E bean) {
if (bean != null) {
touched = true;
// If it is to delete then just remove the deletion
if (!undoDeletion(bean)) {
// Insert
@@ -65,6 +69,7 @@ class ModifyHolder<E> implements Serializable {
@SuppressWarnings("unchecked")
void modifyRemoval(Object bean) {
if (bean != null) {
touched = true;
// If it is to be added then just remove the addition
if (!undoAddition(bean)) {
modifyDeletions.add((E) bean);
@@ -80,6 +85,14 @@ class ModifyHolder<E> implements Serializable {
return modifyDeletions;
}
/**
* Return true if the collection was touched in some way. This is still true even if
* a bean was removed and then added (which is not held as a modification).
*/
boolean wasTouched() {
return touched;
}
/**
* Return true if there additions or removals.
*/
@@ -308,6 +308,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
private final BeanPropertyAssocOne<?> unidirectional;
private final BeanProperty orderColumn;
/**
* list of properties that are Lists/Sets/Maps (Derived).
@@ -480,6 +481,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
this.propertiesLocal = listHelper.getLocal();
this.propertiesMutable = listHelper.getMutable();
this.unidirectional = listHelper.getUnidirectional();
this.orderColumn = listHelper.getOrderColumn();
this.propertiesOne = listHelper.getOnes();
this.propertiesOneExportedSave = listHelper.getOneExportedSave();
this.propertiesOneExportedDelete = listHelper.getOneExportedDelete();
@@ -1925,6 +1927,13 @@ public class BeanDescriptor<T> implements BeanType<T> {
return owner.getBeanDescriptor(otherType);
}
/**
* Return the order column property.
*/
public BeanProperty getOrderColumn() {
return orderColumn;
}
/**
* Return the "shadow" property to support unidirectional relationships.
* <p>
@@ -38,6 +38,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployBeanTable;
import io.ebeaninternal.server.deploy.meta.DeployOrderColumn;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
@@ -50,6 +51,7 @@ import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.type.ScalarTypeInteger;
import io.ebeaninternal.xmlmapping.XmlMappingReader;
import io.ebeaninternal.xmlmapping.model.XmAliasMapping;
import io.ebeaninternal.xmlmapping.model.XmColumnMapping;
@@ -979,6 +981,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
throw new PersistenceException(msg);
}
private void makeOrderColumn(DeployBeanPropertyAssocMany<?> oneToMany) {
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(oneToMany);
DeployOrderColumn orderColumn = oneToMany.getOrderColumn();
DeployBeanProperty orderProperty = new DeployBeanProperty(targetDesc, Integer.class, ScalarTypeInteger.INSTANCE, null);
orderProperty.setName(DeployOrderColumn.LOGICAL_NAME);
orderProperty.setDbColumn(orderColumn.getName());
orderProperty.setNullable(orderColumn.isNullable());
orderProperty.setDbInsertable(orderColumn.isInsertable());
orderProperty.setDbUpdateable(orderColumn.isUpdatable());
orderProperty.setDbRead(true);
targetDesc.setOrderColumn(orderProperty);
}
/**
* A OneToMany with no matching mappedBy property in the target so must be
* unidirectional.
@@ -1112,6 +1131,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
prop.getCascadeInfo().setSaveDelete(true, true);
}
if (prop.hasOrderColumn()) {
makeOrderColumn(prop);
}
if (prop.getMappedBy() == null) {
// if we are doc store only we are done
// this allowes the use of @OneToMany in @DocStore - Entities
@@ -58,6 +58,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
private final boolean unidirectional;
/**
* Flag to indicate that the target has a order column to auto populate.
*/
private final boolean hasOrderColumn;
/**
* Flag to indicate manyToMany relationship.
*/
@@ -109,6 +114,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public BeanPropertyAssocMany(BeanDescriptor<?> descriptor, DeployBeanPropertyAssocMany<T> deploy) {
super(descriptor, deploy);
this.unidirectional = deploy.isUnidirectional();
this.hasOrderColumn = deploy.hasOrderColumn();
this.manyToMany = deploy.isManyToMany();
this.manyType = deploy.getManyType();
this.mapKey = deploy.getMapKey();
@@ -534,6 +540,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return true;
}
public boolean hasOrderColumn() {
return hasOrderColumn;
}
@Override
public boolean isAssocMany() {
return true;
@@ -0,0 +1,36 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Bean property for synthetic sort order value / order column.
*
* The value of which is held on the entity bean intercept.
*/
public class BeanPropertyOrderColumn extends BeanProperty {
public BeanPropertyOrderColumn(BeanDescriptor<?> descriptor, DeployBeanProperty deploy) {
super(descriptor, deploy);
}
@Override
public Object getValue(EntityBean bean) {
return bean._ebean_getIntercept().getSortOrder();
}
@Override
public Object getValueIntercept(EntityBean bean) {
return bean._ebean_getIntercept().getSortOrder();
}
@Override
public void setValue(EntityBean bean, Object value) {
bean._ebean_getIntercept().setSortOrder((int)value);
}
@Override
public void setValueIntercept(EntityBean bean, Object value) {
bean._ebean_getIntercept().setSortOrder((int)value);
}
}
@@ -81,6 +81,8 @@ public class DeployBeanDescriptor<T> {
private DeployBeanPropertyAssocOne<?> unidirectional;
private DeployBeanProperty orderColumn;
/**
* Type of Identity generation strategy used.
*/
@@ -446,6 +448,14 @@ public class DeployBeanDescriptor<T> {
this.unidirectional = unidirectional;
}
public void setOrderColumn(DeployBeanProperty orderColumn) {
this.orderColumn = orderColumn;
}
public DeployBeanProperty getOrderColumn() {
return orderColumn;
}
/**
* Return the concurrency mode used for beans of this type.
*/
@@ -12,35 +12,37 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
/**
* The type of the many, set, list or map.
*/
final ManyType manyType;
private final ManyType manyType;
ModifyListenMode modifyListenMode = ModifyListenMode.NONE;
/**
* Flag to indicate manyToMany relationship.
*/
boolean manyToMany;
private boolean manyToMany;
/**
* Flag to indicate this is a unidirectional relationship.
*/
boolean unidirectional;
private boolean unidirectional;
/**
* Join for manyToMany intersection table.
*/
DeployTableJoin intersectionJoin;
private DeployTableJoin intersectionJoin;
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
DeployTableJoin inverseJoin;
private DeployTableJoin inverseJoin;
String fetchOrderBy;
private String fetchOrderBy;
String mapKey;
private String mapKey;
String intersectionDraftTable;
private String intersectionDraftTable;
private DeployOrderColumn orderColumn;
/**
* Create this property.
@@ -206,4 +208,16 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
public void setIntersectionDraftTable() {
this.intersectionDraftTable = intersectionJoin.getTable() + "_draft";
}
public void setOrderColumn(DeployOrderColumn orderColumn) {
this.orderColumn = orderColumn;
}
public DeployOrderColumn getOrderColumn() {
return orderColumn;
}
public boolean hasOrderColumn() {
return orderColumn != null;
}
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptorMap;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.BeanPropertyOrderColumn;
import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
@@ -64,6 +65,7 @@ public class DeployBeanPropertyLists {
private final TableJoin[] tableJoins;
private final BeanPropertyAssocOne<?> unidirectional;
private final BeanProperty orderColumn;
@SuppressWarnings({"unchecked", "rawtypes"})
public DeployBeanPropertyLists(BeanDescriptorMap owner, BeanDescriptor<?> desc, DeployBeanDescriptor<?> deploy) {
@@ -71,12 +73,11 @@ public class DeployBeanPropertyLists {
setImportedPrimaryKeys(deploy);
DeployBeanProperty deployOrderColumn = deploy.getOrderColumn();
this.orderColumn = deployOrderColumn != null ? new BeanPropertyOrderColumn(desc, deployOrderColumn) : null;
DeployBeanPropertyAssocOne<?> deployUnidirectional = deploy.getUnidirectional();
if (deployUnidirectional == null) {
unidirectional = null;
} else {
unidirectional = new BeanPropertyAssocOne(owner, desc, deployUnidirectional);
}
this.unidirectional = deployUnidirectional == null ? null : new BeanPropertyAssocOne(owner, desc, deployUnidirectional);
this.propertyMap = new LinkedHashMap<>();
@@ -89,7 +90,7 @@ public class DeployBeanPropertyLists {
// Create a BeanProperty for the discriminator column to support
// using RawSql queries with inheritance
discriminatorColumn = inheritInfo.getDiscriminatorColumn();
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, ScalarTypeString.INSTANCE, null);
discDeployProp.setDiscriminator();
discDeployProp.setName(discriminatorColumn);
discDeployProp.setDbColumn(discriminatorColumn);
@@ -115,6 +116,12 @@ public class DeployBeanPropertyLists {
allocateToList(prop);
}
if (orderColumn != null) {
orderColumn.setDeployOrder(order++);
allocateToList(orderColumn);
propertyMap.put(orderColumn.getName(), orderColumn);
}
if (discProperty != null) {
// put the discriminator property into the property map only
// (after the real properties have been organised into their lists)
@@ -160,6 +167,13 @@ public class DeployBeanPropertyLists {
return unidirectional;
}
/**
* Return the order column property.
*/
public BeanProperty getOrderColumn() {
return orderColumn;
}
/**
* Allocate the property to a list.
*/
@@ -0,0 +1,39 @@
package io.ebeaninternal.server.deploy.meta;
import javax.persistence.OrderColumn;
public class DeployOrderColumn {
/**
* Logical property name used for order by and available for expression language.
*/
public static final String LOGICAL_NAME = "orderColumn";
private final String name;
private final boolean insertable;
private final boolean updatable;
private final boolean nullable;
public DeployOrderColumn(OrderColumn orderColumn) {
this.name = orderColumn.name();
this.insertable = orderColumn.insertable();
this.updatable = orderColumn.updatable();
this.nullable = orderColumn.nullable();
}
public String getName() {
return name;
}
public boolean isInsertable() {
return insertable;
}
public boolean isUpdatable() {
return updatable;
}
public boolean isNullable() {
return nullable;
}
}
@@ -13,16 +13,19 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanTable;
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 javax.persistence.CascadeType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
import java.util.Set;
/**
@@ -66,6 +69,14 @@ class AnnotationAssocManys extends AnnotationParser {
prop.setModifyListenMode(ModifyListenMode.REMOVALS);
prop.getCascadeInfo().setDelete(privateOwned.cascadeRemove());
}
OrderColumn orderColumn = get(prop, OrderColumn.class);
if (orderColumn != null) {
// need to cascade as we set the order on cascade
prop.setOrderColumn(new DeployOrderColumn(orderColumn));
prop.setFetchOrderBy(DeployOrderColumn.LOGICAL_NAME);
prop.getCascadeInfo().setType(CascadeType.ALL);
prop.setModifyListenMode(ModifyListenMode.ALL);
}
}
ManyToMany manyToMany = get(prop, ManyToMany.class);
if (manyToMany != null) {
@@ -8,7 +8,6 @@ import io.ebean.Update;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
@@ -21,7 +20,6 @@ import io.ebeaninternal.server.core.PersistRequestCallableSql;
import io.ebeaninternal.server.core.PersistRequestOrmUpdate;
import io.ebeaninternal.server.core.PersistRequestUpdateSql;
import io.ebeaninternal.server.core.Persister;
import io.ebeaninternal.server.deploy.BeanCollectionUtil;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanManager;
@@ -29,7 +27,6 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.deploy.ManyType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -413,7 +410,7 @@ public final class DefaultPersister implements Persister {
}
}
private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) {
void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) {
// determine insert or update taking into account stateless updates
PersistRequestBean<?> request = createRequestRecurse(bean, t, parentBean, insertMode, publish);
@@ -826,86 +823,6 @@ public final class DefaultPersister implements Persister {
}
}
/**
* Helper to wrap the details when saving a OneToMany or ManyToMany
* relationship.
*/
private static class SaveManyPropRequest {
private final boolean insertedParent;
private final BeanPropertyAssocMany<?> many;
private final EntityBean parentBean;
private final SpiTransaction transaction;
private final boolean cascade;
private final boolean deleteMissingChildren;
private final boolean publish;
private SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
this.insertedParent = insertedParent;
this.many = many;
this.cascade = many.getCascadeInfo().isSave();
this.parentBean = parentBean;
this.transaction = request.getTransaction();
this.deleteMissingChildren = request.isDeleteMissingChildren();
this.publish = request.isPublish();
}
public boolean isSaveIntersection() {
return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().getName());
}
private Object getValue() {
return many.getValue(parentBean);
}
private boolean isModifyListenMode() {
return ModifyListenMode.REMOVALS == many.getModifyListenMode();
}
private boolean isDeleteMissingChildren() {
return deleteMissingChildren;
}
private boolean isInsertedParent() {
return insertedParent;
}
private BeanPropertyAssocMany<?> getMany() {
return many;
}
private EntityBean getParentBean() {
return parentBean;
}
private SpiTransaction getTransaction() {
return transaction;
}
private boolean isCascade() {
return cascade;
}
public void modifyListenReset(BeanCollection<?> c) {
if (insertedParent) {
// after insert set the modify listening mode
// for private owned etc
c.setModifyListening(many.getModifyListenMode());
}
c.modifyReset();
}
public boolean isPublish() {
return publish;
}
private void resetModifyState() {
Object details = getValue();
if (details instanceof BeanCollection<?>) {
modifyListenReset((BeanCollection<?>) details);
}
}
}
private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) {
if (saveMany.getMany().isManyToMany()) {
@@ -967,107 +884,7 @@ public final class DefaultPersister implements Persister {
*/
private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren, boolean insertMode) {
BeanPropertyAssocMany<?> prop = saveMany.getMany();
Object details = saveMany.getValue();
// check that the list is not null and if it is a BeanCollection
// check that is has been populated (don't trigger lazy loading)
// For a Map this is a collection of Map.Entry objects and not beans
Collection<?> collection = BeanCollectionUtil.getActualEntries(details);
if (collection == null) {
// nothing to do here
return;
}
BeanDescriptor<?> targetDescriptor = prop.getTargetDescriptor();
if (saveMany.isInsertedParent()) {
// performance optimisation for large collections
targetDescriptor.preAllocateIds(collection.size());
}
SpiTransaction t = saveMany.getTransaction();
boolean isMap = ManyType.MAP == prop.getManyType();
EntityBean parentBean = saveMany.getParentBean();
if (deleteMissingChildren) {
// collect the Id's (to exclude from deleteManyDetails)
List<Object> detailIds = collectIds(collection, targetDescriptor, isMap);
// deleting missing children - children not in our collected detailIds
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds, false);
}
// increase depth for batching order
t.depth(+1);
// if a map, then we get the key value and
// set it to the appropriate property on the
// detail bean before we save it
Object mapKeyValue = null;
boolean saveSkippable = prop.isSaveRecurseSkippable();
boolean skipSavingThisBean;
for (Object detailBean : collection) {
if (isMap) {
// its a map so need the key and value
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) detailBean;
mapKeyValue = entry.getKey();
detailBean = entry.getValue();
}
if (detailBean instanceof EntityBean) {
EntityBean detail = (EntityBean) detailBean;
EntityBeanIntercept ebi = detail._ebean_getIntercept();
if (prop.isManyToMany()) {
skipSavingThisBean = targetDescriptor.isReference(ebi);
} else {
if (targetDescriptor.isReference(ebi)) {
// we can skip this one
skipSavingThisBean = true;
} else if (ebi.isNewOrDirty()) {
skipSavingThisBean = false;
// set the parent bean to detailBean
prop.setJoinValuesToChild(parentBean, detail, mapKeyValue);
} else {
// unmodified so skip depending on prop.isSaveRecurseSkippable();
skipSavingThisBean = saveSkippable;
}
}
if (!skipSavingThisBean) {
saveRecurse(detail, t, parentBean, insertMode, saveMany.isPublish());
}
}
}
t.depth(-1);
}
/**
* Collect the Id values of the details to remove 'missing children' for stateless updates.
*/
private List<Object> collectIds(Collection<?> collection, BeanDescriptor<?> targetDescriptor, boolean isMap) {
List<Object> detailIds = new ArrayList<>();
// stateless update with deleteMissingChildren so first
// collect the Id values to remove the 'missing children'
for (Object detailBean : collection) {
if (isMap) {
detailBean = ((Map.Entry<?, ?>) detailBean).getValue();
}
if (detailBean instanceof EntityBean) {
Object id = targetDescriptor.getId((EntityBean) detailBean);
if (!DmlUtil.isNullOrZero(id)) {
// remember the Id (other details not in the collection) will be removed
detailIds.add(id);
}
}
}
return detailIds;
saveMany.saveDetails(this, deleteMissingChildren, insertMode);
}
/**
@@ -1266,7 +1083,7 @@ public final class DefaultPersister implements Persister {
* collection (and should not be deleted).
* </p>
*/
private void deleteManyDetails(SpiTransaction t, BeanDescriptor<?> desc, EntityBean parentBean,
void deleteManyDetails(SpiTransaction t, BeanDescriptor<?> desc, EntityBean parentBean,
BeanPropertyAssocMany<?> many, List<Object> excludeDetailIds, boolean softDelete) {
if (many.getCascadeInfo().isDelete()) {
@@ -0,0 +1,247 @@
package io.ebeaninternal.server.persist;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanCollectionUtil;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.ManyType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Helper to wrap the details when saving a OneToMany or ManyToMany relationship.
*/
class SaveManyPropRequest {
private final boolean insertedParent;
private final BeanPropertyAssocMany<?> many;
private final EntityBean parentBean;
private final SpiTransaction transaction;
private final boolean cascade;
private final boolean deleteMissingChildren;
private final boolean publish;
private final Object value;
private final BeanDescriptor<?> targetDescriptor;
private final boolean isMap;
private final boolean saveRecurseSkippable;
private Collection<?> collection;
private DefaultPersister persister;
private boolean deleteMissing;
private boolean insertMode;
private int sortOrder;
SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
this.insertedParent = insertedParent;
this.many = many;
this.cascade = many.getCascadeInfo().isSave();
this.parentBean = parentBean;
this.transaction = request.getTransaction();
this.deleteMissingChildren = request.isDeleteMissingChildren();
this.publish = request.isPublish();
this.value = many.getValue(parentBean);
this.targetDescriptor = many.getTargetDescriptor();
this.isMap = ManyType.MAP == many.getManyType();
this.saveRecurseSkippable = many.isSaveRecurseSkippable();
}
public boolean isSaveIntersection() {
return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().getName());
}
Object getValue() {
return value;
}
boolean isModifyListenMode() {
return BeanCollection.ModifyListenMode.REMOVALS == many.getModifyListenMode();
}
boolean isDeleteMissingChildren() {
return deleteMissingChildren;
}
boolean isInsertedParent() {
return insertedParent;
}
BeanPropertyAssocMany<?> getMany() {
return many;
}
EntityBean getParentBean() {
return parentBean;
}
SpiTransaction getTransaction() {
return transaction;
}
boolean isCascade() {
return cascade;
}
boolean isPublish() {
return publish;
}
void modifyListenReset(BeanCollection<?> c) {
if (insertedParent) {
// after insert set the modify listening mode for private owned etc
c.setModifyListening(many.getModifyListenMode());
}
c.modifyReset();
}
void resetModifyState() {
if (value instanceof BeanCollection<?>) {
modifyListenReset((BeanCollection<?>) value);
}
}
void saveDetails(DefaultPersister persister, boolean deleteMissing, boolean insertMode) {
this.persister = persister;
this.deleteMissing = deleteMissing;
this.insertMode = insertMode;
// check that the list is not null and if it is a BeanCollection
// check that is has been populated (don't trigger lazy loading)
// For a Map this is a collection of Map.Entry objects and not beans
collection = BeanCollectionUtil.getActualEntries(value);
if (collection != null) {
processDetails();
}
}
private void processDetails() {
BeanProperty orderColumn = null;
boolean hasOrderColumn = many.hasOrderColumn();
if (hasOrderColumn) {
if (!insertedParent && canSkipForOrderColumn()) {
return;
}
orderColumn = targetDescriptor.getOrderColumn();
}
if (insertedParent) {
// performance optimisation for large collections
targetDescriptor.preAllocateIds(collection.size());
}
if (deleteMissing) {
// collect the Id's (to exclude from deleteManyDetails)
List<Object> detailIds = collectIds(collection, targetDescriptor, isMap);
// deleting missing children - children not in our collected detailIds
persister.deleteManyDetails(transaction, many.getBeanDescriptor(), parentBean, many, detailIds, false);
}
transaction.depth(+1);
saveAllBeans(orderColumn);
if (hasOrderColumn) {
resetModifyState();
}
transaction.depth(-1);
}
private void saveAllBeans(BeanProperty orderColumn) {
// if a map, then we get the key value and
// set it to the appropriate property on the
// detail bean before we save it
Object mapKeyValue = null;
boolean skipSavingThisBean;
for (Object detailBean : collection) {
sortOrder++;
if (isMap) {
// its a map so need the key and value
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) detailBean;
mapKeyValue = entry.getKey();
detailBean = entry.getValue();
}
if (detailBean instanceof EntityBean) {
EntityBean detail = (EntityBean) detailBean;
EntityBeanIntercept ebi = detail._ebean_getIntercept();
if (many.isManyToMany()) {
skipSavingThisBean = targetDescriptor.isReference(ebi);
} else {
if (orderColumn != null) {
orderColumn.setValue(detail, sortOrder);
ebi.setDirty(true);
}
if (targetDescriptor.isReference(ebi)) {
// we can skip this one
skipSavingThisBean = true;
} else if (ebi.isNewOrDirty()) {
skipSavingThisBean = false;
// set the parent bean to detailBean
many.setJoinValuesToChild(parentBean, detail, mapKeyValue);
} else {
// unmodified so skip depending on prop.isSaveRecurseSkippable();
skipSavingThisBean = saveRecurseSkippable;
}
}
if (!skipSavingThisBean) {
persister.saveRecurse(detail, transaction, parentBean, insertMode, publish);
}
}
}
}
/**
* Return true if we can skip based on .. no modifications to the collection and no beans are dirty.
*/
private boolean canSkipForOrderColumn() {
return value instanceof BeanCollection
&& !((BeanCollection<?>) value).wasTouched()
&& noDirtyBeans();
}
private boolean noDirtyBeans() {
for (Object bean : collection) {
if (bean instanceof EntityBean && ((EntityBean) bean)._ebean_getIntercept().isDirty()) {
return false;
}
}
return true;
}
/**
* Collect the Id values of the details to remove 'missing children' for stateless updates.
*/
private List<Object> collectIds(Collection<?> collection, BeanDescriptor<?> targetDescriptor, boolean isMap) {
List<Object> detailIds = new ArrayList<>();
// stateless update with deleteMissingChildren so first
// collect the Id values to remove the 'missing children'
for (Object detailBean : collection) {
if (isMap) {
detailBean = ((Map.Entry<?, ?>) detailBean).getValue();
}
if (detailBean instanceof EntityBean) {
Object id = targetDescriptor.getId((EntityBean) detailBean);
if (!DmlUtil.isNullOrZero(id)) {
// remember the Id (other details not in the collection) will be removed
detailIds.add(id);
}
}
}
return detailIds;
}
}
@@ -3,10 +3,12 @@ package io.ebeaninternal.server.persist.dml;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbEncrypt;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.persist.dmlbind.Bindable;
import io.ebeaninternal.server.persist.dmlbind.BindableId;
import io.ebeaninternal.server.persist.dmlbind.BindableList;
import io.ebeaninternal.server.persist.dmlbind.BindableOrderColumn;
import io.ebeaninternal.server.persist.dmlbind.BindableUnidirectional;
import io.ebeaninternal.server.persist.dmlbind.FactoryAssocOnes;
import io.ebeaninternal.server.persist.dmlbind.FactoryBaseProperties;
@@ -62,6 +64,11 @@ public class MetaFactory {
embeddedFact.create(setList, desc, DmlMode.UPDATE, includeLobs);
assocOneFact.create(setList, desc, DmlMode.UPDATE);
BeanProperty orderColumn = desc.getOrderColumn();
if (orderColumn != null) {
setList.add(new BindableOrderColumn(orderColumn));
}
BindableId id = idFact.createId(desc);
Bindable version = versionFact.create(desc);
Bindable tenantId = versionFact.createTenantId(desc);
@@ -0,0 +1,37 @@
package io.ebeaninternal.server.persist.dmlbind;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanProperty;
import java.sql.SQLException;
import java.util.List;
/**
* Bindable for the synthetic order column.
*/
public class BindableOrderColumn extends BindableProperty {
public BindableOrderColumn(BeanProperty prop) {
super(prop);
}
@Override
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
int sortOrder = request.getEntityBeanIntercept().getSortOrder();
if (sortOrder > 0) {
list.add(this);
}
}
/**
* Normal binding of a property value from the bean.
*/
@Override
public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException {
int sortOrder = bean._ebean_getIntercept().getSortOrder();
request.bind(sortOrder, prop);
}
}
@@ -109,7 +109,7 @@ public final class DefaultTypeManager implements TypeManager {
private final ScalarType<?> shortType = new ScalarTypeShort();
private final ScalarType<?> integerType = new ScalarTypeInteger();
private final ScalarType<?> integerType = ScalarTypeInteger.INSTANCE;
private final ScalarType<?> longType = new ScalarTypeLong();
@@ -130,7 +130,7 @@ public final class DefaultTypeManager implements TypeManager {
private final ScalarType<?> currencyType = new ScalarTypeCurrency();
private final ScalarType<?> timeZoneType = new ScalarTypeTimeZone();
private final ScalarType<?> stringType = new ScalarTypeString();
private final ScalarType<?> stringType = ScalarTypeString.INSTANCE;
private final ScalarType<?> classType = new ScalarTypeClass();
@@ -1,10 +1,10 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.text.TextException;
import io.ebeaninternal.server.core.BasicTypeConverter;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import java.io.DataInput;
import java.io.DataOutput;
@@ -17,7 +17,9 @@ import java.sql.Types;
*/
public class ScalarTypeInteger extends ScalarTypeBase<Integer> {
public ScalarTypeInteger() {
public static ScalarTypeInteger INSTANCE = new ScalarTypeInteger();
private ScalarTypeInteger() {
super(Integer.class, true, Types.INTEGER);
}
@@ -1,9 +1,9 @@
package io.ebeaninternal.server.type;
import io.ebeaninternal.server.core.BasicTypeConverter;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebeaninternal.server.core.BasicTypeConverter;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import java.io.DataInput;
import java.io.DataOutput;
@@ -16,7 +16,9 @@ import java.sql.Types;
*/
public class ScalarTypeString extends ScalarTypeBase<String> {
public ScalarTypeString() {
public static final ScalarTypeString INSTANCE = new ScalarTypeString();
private ScalarTypeString() {
super(String.class, true, Types.VARCHAR);
}