mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
252 lines
7.4 KiB
Java
252 lines
7.4 KiB
Java
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 {
|
|
|
|
final PersistRequestBean<?> request;
|
|
private final boolean insertedParent;
|
|
final BeanPropertyAssocMany<?> many;
|
|
final EntityBean parentBean;
|
|
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;
|
|
|
|
Collection<?> collection;
|
|
DefaultPersister persister;
|
|
private boolean deleteMissing;
|
|
private int sortOrder;
|
|
|
|
SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
|
|
this.request = 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;
|
|
}
|
|
|
|
public int getFlags() {
|
|
return request.getFlags();
|
|
}
|
|
|
|
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) {
|
|
|
|
this.persister = persister;
|
|
this.deleteMissing = deleteMissing;
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
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.hasJoinTable()) {
|
|
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, request.getFlags());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|