public void checkVersionAndRaiseSOSE( Serializable id, Object oldVersion, SessionImplementor session, Tuple resultset) { final Object resultSetVersion = gridVersionType.nullSafeGet(resultset, getVersionColumnName(), session, null); final SessionFactoryImplementor factory = getFactory(); if (!gridVersionType.isEqual(oldVersion, resultSetVersion, factory)) { if (factory.getStatistics().isStatisticsEnabled()) { factory.getStatisticsImplementor().optimisticFailure(getEntityName()); } throw new StaleObjectStateException(getEntityName(), id); } }
private Tuple createNewResultSetIfNull( EntityKey key, Tuple resultset, Serializable id, SessionImplementor session) { if (resultset == null) { resultset = gridDialect.createTuple(key, getTupleContext()); gridIdentifierType.nullSafeSet(resultset, id, getIdentifierColumnNames(), session); } return resultset; }
/** Retrieve the version number */ @Override public Object getCurrentVersion(Serializable id, SessionImplementor session) throws HibernateException { if (log.isTraceEnabled()) { log.trace("Getting version: " + MessageHelper.infoString(this, id, getFactory())); } final Tuple resultset = getResultsetById(id, session); if (resultset == null) { return null; } else { return gridVersionType.nullSafeGet(resultset, getVersionColumnName(), session, null); } }
@Override public Object forceVersionIncrement( Serializable id, Object currentVersion, SessionImplementor session) { if (!isVersioned()) { throw new AssertionFailure("cannot force version increment on non-versioned entity"); } if (isVersionPropertyGenerated()) { // the difficulty here is exactly what do we update in order to // force the version to be incremented in the db... throw new HibernateException( "LockMode.FORCE is currently not supported for generated version properties"); } Object nextVersion = getVersionType().next(currentVersion, session); if (log.isTraceEnabled()) { log.trace( "Forcing version increment [" + MessageHelper.infoString(this, id, getFactory()) + "; " + getVersionType().toLoggableString(currentVersion, getFactory()) + " -> " + getVersionType().toLoggableString(nextVersion, getFactory()) + "]"); } /* * We get the value from the grid and compare the version values before putting the next version in * Contrary to the database version, there is * TODO should we use cache.replace() it seems more expensive to pass the resultset around "just" the atomicity of the operation */ final EntityKey key = EntityKeyBuilder.fromPersister(this, id, session); final Tuple resultset = gridDialect.getTuple(key, getTupleContext()); checkVersionAndRaiseSOSE(id, currentVersion, session, resultset); gridVersionType.nullSafeSet( resultset, nextVersion, new String[] {getVersionColumnName()}, session); gridDialect.updateTuple(resultset, key, getTupleContext()); return nextVersion; }
@Override public void delete(Serializable id, Object version, Object object, SessionImplementor session) throws HibernateException { final int span = getTableSpan(); if (span > 1) { throw new HibernateException( "Hibernate OGM does not yet support entities spanning multiple tables"); } final EntityMetamodel entityMetamodel = getEntityMetamodel(); boolean isImpliedOptimisticLocking = !entityMetamodel.isVersioned() && isAllOrDirtyOptLocking(); Object[] loadedState = null; if (isImpliedOptimisticLocking) { // need to treat this as if it where optimistic-lock="all" (dirty does *not* make sense); // first we need to locate the "loaded" state // // Note, it potentially could be a proxy, so doAfterTransactionCompletion the location the // safe way... org.hibernate.engine.spi.EntityKey key = session.generateEntityKey(id, this); Object entity = session.getPersistenceContext().getEntity(key); if (entity != null) { EntityEntry entry = session.getPersistenceContext().getEntry(entity); loadedState = entry.getLoadedState(); } } final EntityKey key = EntityKeyBuilder.fromPersister(this, id, session); final Tuple resultset = gridDialect.getTuple(key, this.getTupleContext()); final SessionFactoryImplementor factory = getFactory(); if (isImpliedOptimisticLocking && loadedState != null) { // we need to utilize dynamic delete statements for (int j = span - 1; j >= 0; j--) { boolean[] versionability = getPropertyVersionability(); // TODO do a diff on the properties value from resultset GridType[] types = gridPropertyTypes; for (int i = 0; i < entityMetamodel.getPropertySpan(); i++) { boolean include = isPropertyOfTable(i, j) && versionability[i]; if (include) { final GridType type = types[i]; final Object snapshotValue = type.nullSafeGet(resultset, getPropertyColumnNames(i), session, object); // TODO support other entity modes if (!type.isEqual(loadedState[i], snapshotValue, factory)) { if (factory.getStatistics().isStatisticsEnabled()) { factory.getStatisticsImplementor().optimisticFailure(getEntityName()); } throw new StaleObjectStateException(getEntityName(), id); } } } } } else { if (entityMetamodel.isVersioned()) { checkVersionAndRaiseSOSE(id, version, session, resultset); } } for (int j = span - 1; j >= 0; j--) { if (isInverseTable(j)) { return; } if (log.isTraceEnabled()) { log.trace("Deleting entity: " + MessageHelper.infoString(this, id, factory)); if (j == 0 && isVersioned()) { log.trace("Version: " + version); } } // delete association information // needs to be executed before the tuple removal because the AtomicMap in ISPN is cleared upon // removal new EntityDehydrator() .gridDialect(gridDialect) .gridPropertyTypes(gridPropertyTypes) .gridIdentifierType(gridIdentifierType) .id(id) .persister(this) .resultset(resultset) .session(session) .tableIndex(j) .onlyRemovePropertyMetadata() .dehydrate(); gridDialect.removeTuple(key, getTupleContext()); } }
/** Update an object */ @Override public void update( final Serializable id, final Object[] fields, final int[] dirtyFields, final boolean hasDirtyCollection, final Object[] oldFields, final Object oldVersion, final Object object, final Object rowId, final SessionImplementor session) throws HibernateException { // note: dirtyFields==null means we had no snapshot, and we couldn't get one using // select-before-update // oldFields==null just means we had no snapshot to begin with (we might have used // select-before-update to get the dirtyFields) // TODO support "multi table" entities final boolean[] tableUpdateNeeded = getTableUpdateNeeded(dirtyFields, hasDirtyCollection); final int span = getTableSpan(); final boolean[] propsToUpdate; EntityEntry entry = session.getPersistenceContext().getEntry(object); // Ensure that an immutable or non-modifiable entity is not being updated unless it is // in the process of being deleted. if (entry == null && !isMutable()) { throw new IllegalStateException("Updating immutable entity that is not in session yet!"); } // we always use a dynamicUpdate model for Infinispan if (( // getEntityMetamodel().isDynamicUpdate() && dirtyFields != null)) { propsToUpdate = getPropertiesToUpdate(dirtyFields, hasDirtyCollection); // don't need to check laziness (dirty checking algorithm handles that) } else if (!isModifiableEntity(entry)) { // TODO does that apply to OGM? // We need to generate UPDATE SQL when a non-modifiable entity (e.g., read-only or immutable) // needs: // - to have references to transient entities set to null before being deleted // - to have version incremented do to a "dirty" association // If dirtyFields == null, then that means that there are no dirty properties to // to be updated; an empty array for the dirty fields needs to be passed to // getPropertiesToUpdate() instead of null. propsToUpdate = getPropertiesToUpdate( (dirtyFields == null ? ArrayHelper.EMPTY_INT_ARRAY : dirtyFields), hasDirtyCollection); // don't need to check laziness (dirty checking algorithm handles that) } else { // For the case of dynamic-update="false", or no snapshot, we update all properties // TODO handle lazy propsToUpdate = getPropertyUpdateability(object); } final SessionFactoryImplementor factory = getFactory(); if (log.isTraceEnabled()) { log.trace("Updating entity: " + MessageHelper.infoString(this, id, factory)); if (isVersioned()) { log.trace( "Existing version: " + oldVersion + " -> New version: " + fields[getVersionProperty()]); } } for (int j = 0; j < span; j++) { // Now update only the tables with dirty properties (and the table with the version number) if (tableUpdateNeeded[j]) { final EntityKey key = EntityKeyBuilder.fromPersister(this, id, session); Tuple resultset = gridDialect.getTuple(key, this.getTupleContext()); final boolean useVersion = j == 0 && isVersioned(); resultset = createNewResultSetIfNull(key, resultset, id, session); final EntityMetamodel entityMetamodel = getEntityMetamodel(); // Write any appropriate versioning conditional parameters if (useVersion && entityMetamodel.getOptimisticLockStyle() == OptimisticLockStyle.VERSION) { if (checkVersion(propsToUpdate)) { checkVersionAndRaiseSOSE(id, oldVersion, session, resultset); } } else if (isAllOrDirtyOptLocking() && oldFields != null) { boolean[] versionability = getPropertyVersionability(); // TODO: is this really necessary???? boolean[] includeOldField = entityMetamodel.getOptimisticLockStyle() == OptimisticLockStyle.ALL ? getPropertyUpdateability() : propsToUpdate; // TODO do a diff on the properties value from resultset and the dirty value GridType[] types = gridPropertyTypes; for (int i = 0; i < entityMetamodel.getPropertySpan(); i++) { boolean include = includeOldField[i] && isPropertyOfTable(i, j) && versionability[i]; // TODO: is this really necessary???? if (include) { final GridType type = types[i]; // FIXME what do do with settable? boolean[] settable = type.toColumnNullness(oldFields[i], factory); final Object snapshotValue = type.nullSafeGet(resultset, getPropertyColumnNames(i), session, object); comparePropertyAndRaiseSOSE( id, oldFields[i], factory, !type.isEqual(oldFields, snapshotValue, factory)); } } } // dehydrate dehydrate(resultset, fields, propsToUpdate, getPropertyColumnUpdateable(), j, id, session); gridDialect.updateTuple(resultset, key, getTupleContext()); } } }