All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.ebeaninternal.server.core.PersistRequestBean Maven / Gradle / Ivy

There is a newer version: 15.8.0
Show newest version
package io.ebeaninternal.server.core;

import io.ebean.InsertOptions;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PreGetterCallback;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
import io.ebean.event.BeanPersistRequest;
import io.ebean.event.changelog.BeanChange;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.deploy.*;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.*;
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
import io.ebeanservice.docstore.api.DocStoreUpdate;
import io.ebeanservice.docstore.api.DocStoreUpdateContext;
import io.ebeanservice.docstore.api.DocStoreUpdates;

import javax.persistence.EntityNotFoundException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Statement;
import java.util.*;

/**
 * PersistRequest for insert update or delete of a bean.
 */
public final class PersistRequestBean extends PersistRequest implements BeanPersistRequest, DocStoreUpdate, PreGetterCallback, SpiProfileTransactionEvent {

  private final BeanManager beanManager;
  private final BeanDescriptor beanDescriptor;
  private final BeanPersistListener beanPersistListener;
  private final BeanPersistController controller;
  private final T bean;
  private final EntityBean entityBean;
  private final EntityBeanIntercept intercept;
  /**
   * The parent bean for unidirectional save.
   */
  private final Object parentBean;
  private final boolean dirty;
  private final boolean publish;
  private int flags;
  private boolean saveRecurse;
  private DocStoreMode docStoreMode;
  private final ConcurrencyMode concurrencyMode;
  /**
   * The unique id used for logging summary.
   */
  private Object idValue;
  /**
   * Hash value used to handle cascade delete both ways in a relationship.
   */
  private Integer beanHash;
  private boolean statelessUpdate;
  private boolean notifyCache;
  /**
   * Flag used to detect when only many properties where updated via a cascade. Used to ensure
   * appropriate caches are updated in that case.
   */
  private boolean updatedManysOnly;
  /**
   * Element collection change as part of bean cache.
   */
  private Map collectionChanges;
  /**
   * Set true when the request includes cascade save to a many.
   */
  private boolean updatedMany;
  /**
   * Many properties that were cascade saved (and hence might need caches updated later).
   */
  private List> updatedManys;
  /**
   * Store the updated properties to notify persist listener.
   */
  private Set updatedProperties;
  /**
   * Flags indicating the dirty properties on the bean.
   */
  private boolean[] dirtyProperties;
  /**
   * Imported OneToOne orphan that needs to be deleted.
   */
  private EntityBean orphanBean;
  /**
   * Flag set when request is added to JDBC batch.
   */
  private boolean batched;
  /**
   * Flag set when batchOnCascade to avoid using batch on the top bean.
   */
  private boolean skipBatchForTopLevel;
  /**
   * Flag set when batch mode is turned on for a persist cascade.
   */
  private boolean batchOnCascadeSet;
  /**
   * Set for updates to determine if all loaded properties are included in the update.
   */
  private boolean requestUpdateAllLoadedProps;
  private long version;
  private long now;
  private long profileOffset;
  /**
   * Flag set when request is added to JDBC batch registered as a "getter callback" to automatically flush batch.
   */
  private boolean getterCallback;
  private boolean pendingPostUpdateNotify;
  /**
   * Set to true when post execute has occurred (so includes batch flush).
   */
  private boolean postExecute;
  /**
   * Set to true after many properties have been persisted (so includes element collections).
   */
  private boolean complete;
  /**
   * Many-to-many intersection table changes that are held for later batch processing.
   */
  private List saveMany;
  private InsertOptions insertOptions;

  public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, SpiTransaction t,
                            PersistExecute persistExecute, PersistRequest.Type type, int flags) {
    super(server, t, persistExecute);
    this.entityBean = (EntityBean) bean;
    this.intercept = entityBean._ebean_getIntercept();
    this.beanManager = mgr;
    this.beanDescriptor = mgr.getBeanDescriptor();
    this.beanPersistListener = beanDescriptor.persistListener();
    this.bean = bean;
    this.parentBean = parentBean;
    this.controller = beanDescriptor.persistController();
    this.type = type;
    this.docStoreMode = calcDocStoreMode(transaction, type);
    this.flags = flags;
    if (Flags.isRecurse(flags)) {
      this.persistCascade = t.isPersistCascade();
    }
    if (this.type == Type.UPDATE) {
      if (intercept.isNew()) {
        // 'stateless update' - set loaded properties as dirty
        intercept.setNewBeanForUpdate();
        statelessUpdate = true;
      } else if (!intercept.isDirty()) {
        // check if any mutable scalar properties are dirty
        beanDescriptor.checkAnyMutableProperties(intercept);
      }
    }
    this.concurrencyMode = beanDescriptor.concurrencyMode(intercept);
    this.publish = Flags.isPublish(flags);
    if (isMarkDraftDirty(publish)) {
      beanDescriptor.setDraftDirty(entityBean, true);
    }
    this.dirty = intercept.isDirty();
  }

  /**
   * Init generated properties for soft delete (as it's an update).
   */
  public void initForSoftDelete() {
    initGeneratedProperties();
  }

  @Override
  public void addTimingBatch(long startNanos, int batch) {
    beanDescriptor.metricPersistBatch(type, startNanos, batch);
  }

  @Override
  public void addTimingNoBatch(long startNanos) {
    beanDescriptor.metricPersistNoBatch(type, startNanos);
  }

  /**
   * Add to profile as batched bean insert, update or delete.
   */
  @Override
  public void profile(long offset, int flushCount) {
    profileBase(type.profileEventId, offset, beanDescriptor.name(), flushCount);
  }

  /**
   * Return the document store event that should be used for this request.
   * 

* Used to check if the Transaction has set the mode to IGNORE when doing large batch inserts that we * don't want to send to the doc store. */ private DocStoreMode calcDocStoreMode(SpiTransaction txn, Type type) { DocStoreMode txnMode = (txn == null) ? null : txn.docStoreMode(); return beanDescriptor.docStoreMode(type, txnMode); } @Override public boolean isCascade() { return Flags.isRecurse(flags); } /** * Return true if the draftDirty property should be set to true for this request. */ private boolean isMarkDraftDirty(boolean publish) { return !publish && type != Type.DELETE && beanDescriptor.isDraftable(); } /** * Set the transaction from prior persist request. * Only used when hard deleting draft & associated live beans. */ public void setTrans(SpiTransaction transaction) { this.transaction = transaction; this.createdTransaction = false; this.persistCascade = transaction.isPersistCascade(); } /** * Init the transaction and also check for batch on cascade escalation. */ public void initTransIfRequiredWithBatchCascade() { if (createImplicitTransIfRequired()) { docStoreMode = calcDocStoreMode(transaction, type); } checkBatchEscalationOnCascade(); } /** * Check for batch escalation on cascade. */ public void checkBatchEscalationOnCascade() { if (type == Type.UPDATE || beanDescriptor.isBatchEscalateOnCascade(type)) { if (transaction.checkBatchEscalationOnCascade(this)) { // we escalated to use batch mode so flush when done // but if createdTransaction then commit will flush it batchOnCascadeSet = !createdTransaction; } } persistCascade = transaction.isPersistCascade(); } private void initGeneratedProperties() { switch (type) { case INSERT: onInsertGeneratedProperties(); break; case UPDATE: if (!beanDescriptor.isReference(intercept) && (dirty || statelessUpdate)) { onUpdateGeneratedProperties(); } break; case DELETE_SOFT: onUpdateGeneratedProperties(); break; } } private void onUpdateGeneratedProperties() { for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) { GeneratedProperty generatedProperty = prop.generatedProperty(); if (prop.isVersion()) { if (isLoadedProperty(prop)) { // @Version property must be loaded to be involved Object value = generatedProperty.getUpdateValue(prop, entityBean, now()); Object oldVal = prop.getValue(entityBean); setVersionValue(value); intercept.setOldValue(prop.propertyIndex(), oldVal); } } else { // @WhenModified set without invoking interception Object oldVal = prop.getValue(entityBean); Object value = generatedProperty.getUpdateValue(prop, entityBean, now()); prop.setValueChanged(entityBean, value); intercept.setOldValue(prop.propertyIndex(), oldVal); } } } private void onFailedUpdateUndoGeneratedProperties() { for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) { Object oldVal = intercept.origValue(prop.propertyIndex()); prop.setValue(entityBean, oldVal); } } private void onInsertGeneratedProperties() { for (BeanProperty prop : beanDescriptor.propertiesGenInsert()) { Object value = prop.generatedProperty().getInsertValue(prop, entityBean, now()); prop.setValueChanged(entityBean, value); } } /** * If using batch on cascade flush if required. */ public void flushBatchOnCascade() { if (batchOnCascadeSet) { // we escalated to batch mode for request so flush transaction.flushBatchOnCascade(); batchOnCascadeSet = false; } } @Override public void rollbackTransIfRequired() { if (batchOnCascadeSet) { transaction.flushBatchOnRollback(); batchOnCascadeSet = false; } super.rollbackTransIfRequired(); } /** * Return true is this request was added to the JDBC batch. */ public boolean isBatched() { return batched; } /** * Set when request is added to the JDBC batch. */ public void setBatched() { batched = true; if (type == Type.INSERT || type == Type.UPDATE) { if (beanDescriptor.hasSingleIdProperty()) { // used to trigger automatic jdbc batch flush intercept.registerGetterCallback(this); getterCallback = true; } } } @Override public void preGetterTrigger(int propertyIndex) { if (flushBatchOnGetter(propertyIndex)) { transaction.flush(); } } private boolean flushBatchOnGetter(int propertyIndex) { // propertyIndex of -1 the Id property, no flush for get Id on UPDATE if (propertyIndex == -1) { if (beanDescriptor.isIdLoaded(intercept)) { return false; } else { return type == Type.INSERT; } } else { return beanDescriptor.isGeneratedProperty(propertyIndex); } } public void setSkipBatchForTopLevel() { skipBatchForTopLevel = true; } @Override public boolean isBatchThisRequest() { return !skipBatchForTopLevel && super.isBatchThisRequest(); } /** * Return true if this is an insert request. */ public boolean isInsert() { return Type.INSERT == type; } @Override public Set loadedProperties() { return intercept.loadedPropertyNames(); } @Override public Set updatedProperties() { return intercept.dirtyPropertyNames(); } public boolean isChangedProperty(int propertyIndex) { if (dirtyProperties == null) { return intercept.isChangedProperty(propertyIndex); } else { return dirtyProperties[propertyIndex]; } } /** * Return the dirty properties on this request. */ @Override public boolean[] dirtyProperties() { return dirtyProperties; } /** * Return true if any of the given property names are dirty. */ @Override public boolean hasDirtyProperty(Set propertyNames) { return intercept.hasDirtyProperty(propertyNames); } /** * Return true if any of the given properties are dirty. */ public boolean hasDirtyProperty(int[] propertyPositions) { for (int propertyPosition : propertyPositions) { if (dirtyProperties[propertyPosition]) { return true; } } return false; } @Override public Map updatedValues() { return intercept.dirtyValues(); } /** * Set the cache notify status. */ private void setNotifyCache() { this.notifyCache = beanDescriptor.isCacheNotify(type, publish); } /** * Return true if this change should notify persist listener or doc store (and keep the request). */ private boolean isNotifyListeners() { return isNotifyPersistListener() || isDocStoreNotify(); } /** * Return true if this request should update the document store. */ private boolean isDocStoreNotify() { return docStoreMode != DocStoreMode.IGNORE; } private boolean isNotifyPersistListener() { return beanPersistListener != null; } /** * Collect L2 cache changes to be applied after the transaction has successfully committed. */ private void notifyCache(CacheChangeSet changeSet) { if (notifyCache) { switch (type) { case INSERT: beanDescriptor.cachePersistInsert(this, changeSet); break; case UPDATE: beanDescriptor.cachePersistUpdate(idValue, this, changeSet); break; case DELETE: case DELETE_SOFT: beanDescriptor.cachePersistDelete(idValue, this, changeSet); break; default: throw new IllegalStateException("Invalid type " + type); } } } /** * Process the persist request updating the document store. */ @Override public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException { switch (type) { case INSERT: beanDescriptor.docStoreInsert(idValue, this, txn); break; case UPDATE: case DELETE_SOFT: beanDescriptor.docStoreUpdate(idValue, this, txn); break; case DELETE: beanDescriptor.docStoreDeleteById(idValue, txn); break; default: throw new IllegalStateException("Invalid type " + type); } } /** * Add this event to the queue entries in IndexUpdates. */ @Override public void addToQueue(DocStoreUpdates docStoreUpdates) { switch (type) { case INSERT: case UPDATE: case DELETE_SOFT: docStoreUpdates.queueIndex(beanDescriptor.docStoreQueueId(), idValue); break; case DELETE: docStoreUpdates.queueDelete(beanDescriptor.docStoreQueueId(), idValue); break; default: throw new IllegalStateException("Invalid type " + type); } } public void addToPersistMap(BeanPersistIdMap beanPersistMap) { beanPersistMap.add(beanDescriptor, type, idValue); } public void notifyLocalPersistListener() { if (beanPersistListener != null) { switch (type) { case INSERT: beanPersistListener.inserted(bean); break; case UPDATE: beanPersistListener.updated(bean, updatedProperties); break; case DELETE: beanPersistListener.deleted(bean); break; case DELETE_SOFT: beanPersistListener.softDeleted(bean); break; default: } } } public boolean isParent(Object o) { return o == parentBean; } /** * Return true if this bean has been already been persisted (inserted or updated) in this transaction. */ public boolean isRegisteredBean() { return transaction.isRegisteredBean(bean); } public void unRegisterBean() { if (!saveRecurse) { // only clear all persisted beans when persisting at the top level transaction.unregisterBeans(); } } /** * The hash used to register the bean with the transaction. *

* Takes into account the class type and id value. */ private Integer beanHash() { if (beanHash == null) { Object id = beanDescriptor.getId(entityBean); int hc = 92821 * bean.getClass().getName().hashCode(); if (id != null) { hc += id.hashCode(); } beanHash = hc; } return beanHash; } public void registerDeleteBean() { Integer hash = beanHash(); transaction.registerDeleteBean(hash); } public boolean isRegisteredForDeleteBean() { if (transaction == null) { return false; } else { Integer hash = beanHash(); return transaction.isRegisteredDeleteBean(hash); } } /** * Return the BeanDescriptor for the associated bean. */ public BeanDescriptor descriptor() { return beanDescriptor; } /** * Prepare the update after potential modifications in a BeanPersistController. */ private void postControllerPrepareUpdate() { if (statelessUpdate && controller != null) { // 'stateless update' - set dirty properties modified in controller preUpdate intercept.setNewBeanForUpdate(); } } /** * Used to skip updates if we know the bean is not dirty. This is the case for EntityBeans that * have not been modified. */ public boolean isDirty() { return dirty; } /** * Return the concurrency mode used for this persist. */ public ConcurrencyMode concurrencyMode() { return concurrencyMode; } /** * Returns a description of the request. This is typically the bean class name or the base table * for MapBeans. *

* Used to determine common persist requests for queueing and statement batching. *

*/ public String fullName() { return beanDescriptor.fullName(); } /** * Return the bean associated with this request. */ @Override public T bean() { return bean; } public EntityBean entityBean() { return entityBean; } /** * Return the Id value for the bean. */ public Object beanId() { return beanDescriptor.getId(entityBean); } /** * Create and return a new reference bean matching this beans Id value. */ public T createReference() { return beanDescriptor.createRef(beanId(), null); } /** * Return true if the bean type is a Draftable. */ public boolean isDraftable() { return beanDescriptor.isDraftable(); } /** * Return true if this request is a hard delete of a draftable bean. * If this is true Ebean is expected to auto-publish and delete the associated live bean. */ public boolean isHardDeleteDraft() { if (type == Type.DELETE && beanDescriptor.isDraftable() && !beanDescriptor.isDraftableElement()) { // deleting a top level draftable bean if (beanDescriptor.isLiveInstance(entityBean)) { throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans"); } return true; } return false; } /** * Return true if this was a hard/permanent delete request (and should cascade as such). */ public boolean isHardDeleteCascade() { return (type == Type.DELETE && beanDescriptor.isSoftDelete()); } /** * Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'. * Save or Update is not allowed to execute using 'live' beans - must use publish(). */ public void checkDraft() { if (beanDescriptor.isDraftable() && beanDescriptor.isLiveInstance(entityBean)) { throw new PersistenceException("Save or update is not allowed on a 'live' bean - only draft beans"); } } /** * Return the parent bean for cascading save with unidirectional relationship. */ public Object parentBean() { return parentBean; } /** * Return the intercept if there is one. */ public EntityBeanIntercept intercept() { return intercept; } /** * Return true if this property is loaded (full bean or included in partial bean). */ public boolean isLoadedProperty(BeanProperty prop) { return intercept.isLoadedProperty(prop.propertyIndex()); } /** * Return true if the property is dirty. */ public boolean isDirtyProperty(BeanProperty prop) { return intercept.isDirtyProperty(prop.propertyIndex()); } /** * Return the original / old value for the given property. */ public Object origValue(BeanProperty prop) { return intercept.origValue(prop.propertyIndex()); } @Override public int executeNow() { if (getterCallback) { intercept.clearGetterCallback(); } switch (type) { case INSERT: executeInsert(); return -1; case UPDATE: executeUpdate(); return -1; case DELETE_SOFT: prepareForSoftDelete(); executeSoftDelete(); return -1; case DELETE: return executeDelete(); default: throw new RuntimeException("Invalid type " + type); } } /** * Soft delete is executed as update so we want to set deleted=true property. */ private void prepareForSoftDelete() { beanDescriptor.softDeleteValue(entityBean); } @Override public int executeOrQueue() { boolean batch = isBatchThisRequest(); try { BatchControl control = transaction.batchControl(); if (control != null) { return control.executeOrQueue(this, batch); } if (batch) { control = persistExecute.createBatchControl(transaction); return control.executeOrQueue(this, true); } else { return executeNoBatch(); } } catch (BatchedSqlException e) { throw transaction.translate(e.getMessage(), e.getCause()); } } private int executeNoBatch() { profileOffset = transaction.profileOffset(); int result = executeNow(); transaction.profileEvent(this); return result; } /** * Set the generated key back to the bean. Only used for inserts with getGeneratedKeys. */ @Override public void setGeneratedKey(Object idValue) { if (idValue != null) { // remember it for logging summary this.idValue = beanDescriptor.convertSetId(idValue, entityBean); } } /** * Set the Id value that was bound. Used for the purposes of logging summary information on this * request. */ public void setBoundId(Object idValue) { this.idValue = idValue; } /** * Check for optimistic concurrency exception. */ @Override public void checkRowCount(int rowCount) { if (rowCount != 1 && rowCount != Statement.SUCCESS_NO_INFO) { if (ConcurrencyMode.VERSION == concurrencyMode) { onFailedUpdateUndoGeneratedProperties(); throw new OptimisticLockException("Data has changed. updated row count " + rowCount, null, bean); } else if (rowCount == 0 && type == Type.UPDATE) { throw new EntityNotFoundException("No rows updated"); } } switch (type) { case DELETE: case DELETE_SOFT: postDelete(); break; case UPDATE: postUpdate(); break; default: // do nothing } } /** * Clear the bean from the PersistenceContext (L1 cache) for stateless updates. */ private void postUpdate() { if (statelessUpdate) { beanDescriptor.contextClear(transaction.persistenceContext(), idValue); } } private void postUpdateNotify() { if (pendingPostUpdateNotify) { controller.postUpdate(this); } } /** * Remove deleted beans from the persistence context early. */ public void removeFromPersistenceContext() { idValue = beanDescriptor.getId(entityBean); beanDescriptor.contextDeleted(transaction.persistenceContext(), idValue); } /** * Aggressive L1 and L2 cache cleanup for deletes. */ private void postDelete() { beanDescriptor.contextClear(transaction.persistenceContext(), idValue); } private void changeLog() { BeanChange changeLogBean = beanDescriptor.changeLogBean(this); if (changeLogBean != null) { transaction.addBeanChange(changeLogBean); } } /** * Post processing. */ @Override public void postExecute() { saveQueuedMany(); postExecute = true; if (controller != null) { controllerPost(); } setNotifyCache(); boolean isChangeLog = beanDescriptor.isChangeLog(); if (type == Type.UPDATE) { // get the dirty properties for notify cache & orphanRemoval of vanilla collection detection dirtyProperties = intercept.dirtyProperties(); } if (isChangeLog) { changeLog(); } // if bean persisted again then should result in an update intercept.setLoaded(); if (isInsert()) { postInsert(); } addPostCommitListeners(); notifyCacheOnPostExecute(); if (logSummary()) { logSummaryMessage(); } } private void saveQueuedMany() { if (saveMany != null) { saveMany.forEach(SaveMany::saveBatch); } } /** * Ensure the preUpdate event fires (for case where only element collection has changed). */ public void preElementCollectionUpdate() { if (controller != null && !dirty) { // fire preUpdate notification when only element collection updated controller.preUpdate(this); pendingPostUpdateNotify = true; } if (!dirty) { setNotifyCache(); } } public boolean isNotifyCache() { return notifyCache; } private void controllerPost() { boolean old = transaction.isFlushOnQuery(); transaction.setFlushOnQuery(false); try { switch (type) { case INSERT: controller.postInsert(this); break; case UPDATE: controller.postUpdate(this); break; case DELETE_SOFT: controller.postSoftDelete(this); break; case DELETE: controller.postDelete(this); break; default: break; } } finally { transaction.setFlushOnQuery(old); } } private void logSummaryMessage() { String draft = (beanDescriptor.isDraftable() && !publish) ? " draft[true]" : ""; String name = beanDescriptor.name(); switch (type) { case INSERT: transaction.logSummary("Inserted [{0}] [{1}]{2}", name, (idValue == null ? "" : idValue), draft); break; case UPDATE: transaction.logSummary("Updated [{0}] [{1}]{2}", name, idValue , draft); break; case DELETE: transaction.logSummary("Deleted [{0}] [{1}]{2}", name, idValue , draft); break; case DELETE_SOFT: transaction.logSummary("SoftDelete [{0}] [{1}]{2}", name, idValue , draft); break; default: break; } } /** * Add the request to TransactionEvent if there are post commit listeners. */ private void addPostCommitListeners() { TransactionEvent event = transaction.event(); if (event != null && isNotifyListeners()) { event.addListenerNotify(this); } } /** * Return true if the property should be included in the update. */ public boolean isAddToUpdate(BeanProperty prop) { if (requestUpdateAllLoadedProps) { return intercept.isLoadedProperty(prop.propertyIndex()); } else { return intercept.isDirtyProperty(prop.propertyIndex()); } } /** * Register the derived relationships to get executed later (on JDBC batch flush or commit). */ public void deferredRelationship(EntityBean assocBean, ImportedId importedId, EntityBean bean) { transaction.registerDeferred(new PersistDeferredRelationship(server, beanDescriptor, assocBean, importedId, bean)); } private void postInsert() { // mark all properties as loaded after an insert to support immediate update beanDescriptor.setAllLoaded(entityBean); if (!publish) { beanDescriptor.setDraft(entityBean); } if (transaction.isAutoPersistUpdates() && idValue != null) { // with getGeneratedKeys off we will not have a idValue beanDescriptor.contextPut(transaction.persistenceContext(), idValue, entityBean); } } /** * Return if persist can be skipped on the reference only bean. */ public boolean isSkipReference() { return intercept.isReference() || (Flags.isRecurse(flags) && beanDescriptor.referenceIdPropertyOnly(intercept)); } public boolean isReference() { return beanDescriptor.isReference(intercept); } public void setUpdatedMany() { this.updatedMany = true; } /** * This many property has potential L2 cache update for many Ids. */ public void addUpdatedManyForL2Cache(BeanPropertyAssocMany many) { if (updatedManys == null) { updatedManys = new ArrayList<>(5); } updatedManys.add(many); } /** * Return the list of updated many properties for L2 cache update (can be null). */ public List> updatedManyForL2Cache() { return updatedManys; } /** * Completed insert or delete request. Do cache notify in non-batched case. */ public void complete() { notifyCacheOnComplete(); } /** * Completed update request handling cases for element collection and where ONLY * many properties were updated. */ public void completeUpdate() { if (!dirty && updatedMany) { // set the flag and register for post commit processing if there // is caching or registered listeners if (idValue == null) { this.idValue = beanDescriptor.getId(entityBean); } // not dirty so postExecute() will never be called // set true to trigger cache notify if needed postExecute = true; updatedManysOnly = true; setNotifyCache(); addPostCommitListeners(); saveQueuedMany(); } notifyCacheOnComplete(); postUpdateNotify(); } /** * Notify cache when using batched persist. */ private void notifyCacheOnPostExecute() { postExecute = true; if (notifyCache && complete) { // add cache notification (on batch persist) TransactionEvent event = transaction.event(); if (event != null) { notifyCache(event.obtainCacheChangeSet()); } } } /** * Notify cache when not use batched persist. */ private void notifyCacheOnComplete() { complete = true; if (notifyCache && postExecute) { // add cache notification (on non-batch persist) TransactionEvent event = transaction.event(); if (event != null) { notifyCache(event.obtainCacheChangeSet()); } } } /** * For requests that update document store add this event to either the list * of queue events or list of update events. */ public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) { if (type == Type.UPDATE) { beanDescriptor.docStoreUpdateEmbedded(this, docStoreUpdates); } switch (docStoreMode) { case UPDATE: { docStoreUpdates.addPersist(this); return; } case QUEUE: { if (type == Type.DELETE) { docStoreUpdates.queueDelete(beanDescriptor.docStoreQueueId(), idValue); } else { docStoreUpdates.queueIndex(beanDescriptor.docStoreQueueId(), idValue); } } break; default: break; } } /** * Determine if all loaded properties should be used for an update. *

* Takes into account transaction setting and JDBC batch. *

*/ private boolean determineUpdateAllLoadedProperties() { Boolean txnUpdateAll = transaction.isUpdateAllLoadedProperties(); if (txnUpdateAll != null) { // use the setting explicitly set on the transaction requestUpdateAllLoadedProps = txnUpdateAll; } else { // if using batch use the server default setting requestUpdateAllLoadedProps = isBatchThisRequest() && server.isUpdateAllPropertiesInBatch(); } return requestUpdateAllLoadedProps; } /** * Return the flags set on this persist request. */ public int flags() { return flags; } /** * Return true if this request is a 'publish' action. */ public boolean isPublish() { return publish; } /** * Return the key for an update persist request. */ public String updatePlanHash() { StringBuilder key; if (determineUpdateAllLoadedProperties()) { key = intercept.loadedPropertyKey(); } else { key = intercept.dirtyPropertyKey(); } BeanProperty versionProperty = beanDescriptor.versionProperty(); if (versionProperty != null) { if (intercept.isLoadedProperty(versionProperty.propertyIndex())) { key.append('v'); } } if (publish) { key.append('p'); } return key.toString(); } /** * Return the table to update depending if the request is a 'publish' one or normal. */ public String updateTable() { return publish ? beanDescriptor.baseTable() : beanDescriptor.draftTable(); } /** * Return the delete mode - Soft or Hard. */ public DeleteMode deleteMode() { return Type.DELETE_SOFT == type ? DeleteMode.SOFT : DeleteMode.HARD; } /** * Set the value of the Version property on the bean. */ private void setVersionValue(Object versionValue) { version = beanDescriptor.setVersion(entityBean, versionValue); } /** * Return the version in long form (if set). */ public long version() { return version; } private void setTenantId() { Object tenantId = transaction.tenantId(); if (tenantId != null) { beanDescriptor.setTenantId(entityBean, tenantId); } } private void executeInsert() { setGeneratedId(); setTenantId(); if (controller == null || controller.preInsert(this)) { beanManager.getBeanPersister().insert(this); } } private void executeUpdate() { setTenantId(); if (controller == null || controller.preUpdate(this)) { // check dirty state for all mutable scalar properties (like DbJson, Hstore) beanDescriptor.checkAllMutableProperties(intercept); if (beanPersistListener != null) { updatedProperties = updatedProperties(); } postControllerPrepareUpdate(); beanManager.getBeanPersister().update(this); } } private void executeSoftDelete() { setTenantId(); if (controller == null || controller.preSoftDelete(this)) { postControllerPrepareUpdate(); beanManager.getBeanPersister().update(this); } } private int executeDelete() { setTenantId(); if (controller == null || controller.preDelete(this)) { return beanManager.getBeanPersister().delete(this); } // delete handled by the BeanController so return 0 return 0; } /** * Persist to the document store now (via buffer, not post commit). */ public void docStorePersist() { idValue = beanDescriptor.getId(entityBean); if (type == Type.UPDATE) { dirtyProperties = intercept.dirtyProperties(); } // processing now so set IGNORE (unlike DB + DocStore processing with post-commit) docStoreMode = DocStoreMode.IGNORE; try { docStoreUpdate(transaction.docStoreTransaction().obtain()); postExecute(); if (type == Type.UPDATE && beanDescriptor.isDocStoreEmbeddedInvalidation() && transaction.isPersistCascade()) { // queue embedded/nested updates for later processing beanDescriptor.docStoreUpdateEmbedded(this, transaction.docStoreTransaction().queue()); } } catch (IOException e) { throw new PersistenceException("Error persisting doc store bean", e); } } /** * Use a common 'now' value across both when created and when updated etc. */ public long now() { if (now == 0) { now = server.clockNow(); } return now; } /** * Return true if this is a stateless update request (in which case it doesn't really have 'old values'). */ public boolean isStatelessUpdate() { return statelessUpdate; } /** * Add to profile as single bean insert, update or delete (not batched). */ @Override public void profile() { profileBase(type.profileEventId, profileOffset, beanDescriptor.name(), 1); } /** * Set the request flags indicating this is an insert. */ public void flagInsert() { initGeneratedProperties(); if (intercept.isNew()) { flags = Flags.setInsertNormal(flags); } else { flags = Flags.setInsert(flags); } } /** * Unset the request insert flag indicating this is an update. */ public void flagUpdate() { initGeneratedProperties(); if (intercept.isLoaded()) { flags = Flags.setUpdateNormal(flags); } else { flags = Flags.setUpdate(flags); } } /** * Return true if this request is an insert. */ public boolean isInsertedParent() { return Flags.isInsert(flags); } /** * Add an element collection change to L2 bean cache update. */ public void addCollectionChange(String name, Object value) { if (collectionChanges == null) { collectionChanges = new LinkedHashMap<>(); } collectionChanges.put(name, value); } /** * Build the bean update for the L2 cache. */ public void addBeanUpdate(CacheChangeSet changeSet) { if (!updatedManysOnly || collectionChanges != null) { boolean updateNaturalKey = false; String key = beanDescriptor.cacheKey(idValue); Map changes = new LinkedHashMap<>(); EntityBean bean = entityBean(); boolean[] dirtyProperties = dirtyProperties(); if (dirtyProperties != null) { for (int i = 0; i < dirtyProperties.length; i++) { if (dirtyProperties[i]) { BeanProperty property = beanDescriptor.propertyByIndex(i); if (property.isCacheDataInclude()) { Object val = property.getCacheDataValue(bean); changes.put(property.name(), val); if (property.isNaturalKey()) { updateNaturalKey = true; String valStr = (val == null) ? null : val.toString(); changeSet.addNaturalKeyPut(beanDescriptor, key, valStr); } } } } } if (collectionChanges != null) { // add element collection update changes.putAll(collectionChanges); } changeSet.addBeanUpdate(beanDescriptor, key, changes, updateNaturalKey, version()); } } /** * Set an orphan bean that needs to be deleted AFTER the request has persisted. */ public void setImportedOrphanForRemoval(BeanPropertyAssocOne prop) { Object orphan = origValue(prop); if (orphan instanceof EntityBean) { orphanBean = (EntityBean) orphan; } } public EntityBean importedOrphanForRemoval() { return orphanBean; } /** * Return the SQL used to fetch the last inserted id value. */ public String selectLastInsertedId() { return beanDescriptor.selectLastInsertedId(publish); } /** * Return true if the intersection table updates or element collection updates should be queued. */ public boolean isQueueSaveMany() { return !postExecute; } /** * The intersection table updates or element collection to the batch executed later on postExecute. */ public void addSaveMany(SaveMany saveManyRequest) { if (this.saveMany == null) { this.saveMany = new ArrayList<>(); } this.saveMany.add(saveManyRequest); } public boolean isForcedUpdate() { return Flags.isUpdateForce(flags); } /** * Set when this request is from cascading persist. */ public void setSaveRecurse() { saveRecurse = true; } private void setGeneratedId() { beanDescriptor.setGeneratedId(entityBean, transaction); } public void setInsertOptions(InsertOptions insertOptions) { this.insertOptions = insertOptions; } public InsertOptions insertOptions() { return insertOptions; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy