Permalink
Browse files

HHH-7093 - ValidityAuditStrategy behaviour depending on row value con…

…stractor feature
  • Loading branch information...
1 parent 7ed8723 commit af554fe59b20c8cd5ef5078c212be0fafd88a68e @lukasz-antoniak lukasz-antoniak committed May 23, 2012
@@ -131,6 +131,10 @@ public boolean supportsRowValueConstructorSyntaxInInList() {
return true;
}
+ public boolean supportsRowValueConstructorSyntax() {
+ return true;
+ }
+
public boolean supportsTupleDistinctCounts() {
return false;
}
@@ -459,4 +459,7 @@ public String getReadLockString(int timeout) {
return " for share";
}
+ public boolean supportsRowValueConstructorSyntax() {
+ return true;
+ }
}
@@ -500,7 +500,7 @@ public void generateFirstPass(PersistentClass pc, ClassAuditingData auditingData
addJoins(pc, propertyMapper, auditingData, pc.getEntityName(), xmlMappingData, true);
// Storing the generated configuration
- EntityConfiguration entityCfg = new EntityConfiguration(auditEntityName,pc.getClassName(), idMapper,
+ EntityConfiguration entityCfg = new EntityConfiguration(auditEntityName, pc.getClassName(), idMapper,
propertyMapper, parentEntityName);
entitiesConfigurations.put(pc.getEntityName(), entityCfg);
}
@@ -7,17 +7,21 @@
import org.hibernate.LockOptions;
import org.hibernate.Session;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.configuration.AuditEntitiesConfiguration;
import org.hibernate.envers.configuration.GlobalConfiguration;
+import org.hibernate.envers.entities.EntityConfiguration;
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.entities.mapper.id.IdMapper;
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
import org.hibernate.envers.entities.mapper.relation.MiddleIdData;
import org.hibernate.envers.synchronization.SessionCacheCleaner;
import org.hibernate.envers.tools.query.Parameters;
import org.hibernate.envers.tools.query.QueryBuilder;
+import org.hibernate.envers.tools.query.UpdateBuilder;
import org.hibernate.property.Getter;
import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS;
@@ -44,6 +48,7 @@
*
* @author Stephanie Pau
* @author Adam Warski (adam at warski dot org)
+ * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ValidityAuditStrategy implements AuditStrategy {
@@ -58,39 +63,77 @@ public ValidityAuditStrategy() {
public void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data,
Object revision) {
- AuditEntitiesConfiguration audEntCfg = auditCfg.getAuditEntCfg();
- String auditedEntityName = audEntCfg.getAuditEntityName(entityName);
+ final AuditEntitiesConfiguration audEntitiesCfg = auditCfg.getAuditEntCfg();
+ final String auditedEntityName = audEntitiesCfg.getAuditEntityName(entityName);
+ final Dialect dialect = ((SessionImplementor)session).getFactory().getDialect();
+ final EntityConfiguration auditEntityCfg = auditCfg.getEntCfg().get(entityName);
+ final IdMapper idMapper = auditEntityCfg.getIdMapper();
// Update the end date of the previous row if this operation is expected to have a previous row
if (getRevisionType(auditCfg, data) != RevisionType.ADD) {
- /*
- Constructing a query:
- select e from audited_ent e where e.end_rev is null and e.id = :id
- */
-
- QueryBuilder qb = new QueryBuilder(auditedEntityName, MIDDLE_ENTITY_ALIAS);
-
- // e.id = :id
- IdMapper idMapper = auditCfg.getEntCfg().get(entityName).getIdMapper();
- idMapper.addIdEqualsToQuery(qb.getRootParameters(), id, auditCfg.getAuditEntCfg().getOriginalIdPropName(), true);
-
- addEndRevisionNullRestriction(auditCfg, qb);
-
- @SuppressWarnings({"unchecked"})
- List<Object> l = qb.toQuery(session).setLockOptions(LockOptions.UPGRADE).list();
-
- updateLastRevision(session, auditCfg, l, id, auditedEntityName, revision);
+ if (shallSelectAndUpdate(dialect, auditEntityCfg)) {
+ // Constructing a query:
+ // select e from audited_ent e where e.end_rev is null and e.id = :id
+ QueryBuilder qb = new QueryBuilder(auditedEntityName, MIDDLE_ENTITY_ALIAS);
+ // e.id = :id
+ idMapper.addIdEqualsToQuery(qb.getRootParameters(), id, auditCfg.getAuditEntCfg().getOriginalIdPropName(), true);
+ // e.end_rev is null
+ addEndRevisionNullRestriction(auditCfg, qb.getRootParameters());
+
+ @SuppressWarnings({"unchecked"})
+ List<Object> l = qb.toQuery(session).setLockOptions(LockOptions.UPGRADE).list();
+
+ updateLastRevision(session, auditCfg, l, id, auditedEntityName, revision);
+ } else {
+ // Save the audit data
+ session.save(auditedEntityName, data);
+ sessionCacheCleaner.scheduleAuditDataRemoval(session, data);
+
+ // Workaround for HHH-3298 and FooBarTest#supportsLockingNullableSideOfJoin(Dialect).
+ // Constructing a statement:
+ // update e from audit_ent e where e.end_rev is null and e.id = :id and e.rev <> :rev
+ final UpdateBuilder ub = new UpdateBuilder(auditedEntityName, MIDDLE_ENTITY_ALIAS);
+ final Number revisionNumber = auditCfg.getRevisionInfoNumberReader().getRevisionNumber(revision);
+ ub.updateValue(auditCfg.getAuditEntCfg().getRevisionEndFieldName(), revision);
+ if (auditCfg.getAuditEntCfg().isRevisionEndTimestampEnabled()) {
+ Object revEndTimestampObj = revisionTimestampGetter.get(revision);
+ Date revisionEndTimestamp = convertRevEndTimestampToDate(revEndTimestampObj);
+ ub.updateValue(auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName(), revisionEndTimestamp);
+ }
+ // e.id = :id
+ idMapper.addIdEqualsToQuery(ub.getRootParameters(), id, auditCfg.getAuditEntCfg().getOriginalIdPropName(), true);
+ // e.end_rev is null
+ addEndRevisionNullRestriction(auditCfg, ub.getRootParameters());
+ // e.rev <> :rev
+ ub.getRootParameters().addWhereWithParam(auditCfg.getAuditEntCfg().getRevisionNumberPath(), true, "<>", revisionNumber);
+ if (ub.toQuery(session).executeUpdate() != 1) {
+ throw new RuntimeException("Cannot update previous revision for entity " + auditedEntityName + " and id " + id);
+ }
+ return;
+ }
}
// Save the audit data
session.save(auditedEntityName, data);
sessionCacheCleaner.scheduleAuditDataRemoval(session, data);
}
+ protected boolean shallSelectAndUpdate(Dialect dialect, EntityConfiguration auditEntityCfg) {
+ // Hibernate fails to execute multi-table bulk operations if dialect does not support "row value constructor" feature.
+ // In case of inheritance, secondary and join table mappings SQL query looks like:
+ // update ParentEntity_AUD set REVEND=? where (id, REV) IN (select id, REV from HT_ChildEntity_AUD)
+ // because Hibernate utilizes temporary tables.
+ // See: http://in.relation.to/Bloggers/MultitableBulkOperations, https://community.jboss.org/wiki/TemporaryTableUse.
+ // TODO: This might be improved to return false only if Hibernate is supposed to produce query with row value
+ // constructor and the actual dialect does not support required feature. However, Hibernate decides to use temporary
+ // tables while translating HQL to SQL query (QueryTranslatorImpl#buildAppropriateStatementExecutor(HqlSqlWalker)),
+ // and it is difficult to predict here.
+ return !dialect.supportsRowValueConstructorSyntax();
+ }
+
@SuppressWarnings({"unchecked"})
public void performCollectionChange(Session session, AuditConfiguration auditCfg,
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
-
final QueryBuilder qb = new QueryBuilder(persistentCollectionChangeData.getEntityName(), MIDDLE_ENTITY_ALIAS);
// Adding a parameter for each id component, except the rev number
@@ -104,7 +147,7 @@ public void performCollectionChange(Session session, AuditConfiguration auditCfg
}
}
- addEndRevisionNullRestriction(auditCfg, qb);
+ addEndRevisionNullRestriction(auditCfg, qb.getRootParameters());
final List<Object> l = qb.toQuery(session).setLockOptions(LockOptions.UPGRADE).list();
@@ -120,9 +163,8 @@ public void performCollectionChange(Session session, AuditConfiguration auditCfg
sessionCacheCleaner.scheduleAuditDataRemoval(session, persistentCollectionChangeData.getData());
}
- private void addEndRevisionNullRestriction(AuditConfiguration auditCfg, QueryBuilder qb) {
- // e.end_rev is null
- qb.getRootParameters().addWhere(auditCfg.getAuditEntCfg().getRevisionEndFieldName(), true, "is", "null", false);
+ private void addEndRevisionNullRestriction(AuditConfiguration auditCfg, Parameters rootParameters) {
+ rootParameters.addWhere(auditCfg.getAuditEntCfg().getRevisionEndFieldName(), true, "is", "null", false);
}
public void addEntityAtRevisionRestriction(GlobalConfiguration globalCfg, QueryBuilder rootQueryBuilder,
@@ -173,16 +215,9 @@ private void updateLastRevision(Session session, AuditConfiguration auditCfg, Li
if (auditCfg.getAuditEntCfg().isRevisionEndTimestampEnabled()) {
// Determine the value of the revision property annotated with @RevisionTimestamp
- Date revisionEndTimestamp;
String revEndTimestampFieldName = auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName();
Object revEndTimestampObj = this.revisionTimestampGetter.get(revision);
-
- // convert to a java.util.Date
- if (revEndTimestampObj instanceof Date) {
- revisionEndTimestamp = (Date) revEndTimestampObj;
- } else {
- revisionEndTimestamp = new Date((Long) revEndTimestampObj);
- }
+ Date revisionEndTimestamp = convertRevEndTimestampToDate(revEndTimestampObj);
// Setting the end revision timestamp
((Map<String, Object>) previousData).put(revEndTimestampFieldName, revisionEndTimestamp);
@@ -195,5 +230,13 @@ private void updateLastRevision(Session session, AuditConfiguration auditCfg, Li
throw new RuntimeException("Cannot find previous revision for entity " + auditedEntityName + " and id " + id);
}
}
+
+ private Date convertRevEndTimestampToDate(Object revEndTimestampObj) {
+ // convert to a java.util.Date
+ if (revEndTimestampObj instanceof Date) {
+ return (Date) revEndTimestampObj;
+ }
+ return new Date((Long) revEndTimestampObj);
+ }
}
@@ -0,0 +1,80 @@
+package org.hibernate.envers.tools.query;
+
+import org.hibernate.Query;
+import org.hibernate.Session;
+import org.hibernate.envers.tools.MutableInteger;
+import org.hibernate.envers.tools.Pair;
+import org.hibernate.envers.tools.StringTools;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
+ */
+public class UpdateBuilder {
+ private final String entityName;
+ private final String alias;
+ private final MutableInteger paramCounter;
+ private final Parameters rootParameters;
+ private final Map<String, Object> updates;
+
+ public UpdateBuilder(String entityName, String alias) {
+ this(entityName, alias, new MutableInteger());
+ }
+
+ private UpdateBuilder(String entityName, String alias, MutableInteger paramCounter) {
+ this.entityName = entityName;
+ this.alias = alias;
+ this.paramCounter = paramCounter;
+ rootParameters = new Parameters(alias, "and", paramCounter);
+ updates = new HashMap<String, Object>();
+ }
+
+ public Parameters getRootParameters() {
+ return rootParameters;
+ }
+
+ public void updateValue(String propertyName, Object value) {
+ updates.put(propertyName, value);
+ }
+
+ public void build(StringBuilder sb, Map<String, Object> updateParamValues) {
+ sb.append("update ").append(entityName).append(" ").append(alias);
+ sb.append(" set ");
+ int i = 1;
+ for (String property : updates.keySet()) {
+ final String paramName = generateParameterName();
+ sb.append(alias).append(".").append(property).append(" = ").append(":").append(paramName);
+ updateParamValues.put(paramName, updates.get(property));
+ if (i < updates.size()) {
+ sb.append(", ");
+ }
+ ++i;
+ }
+ if (!rootParameters.isEmpty()) {
+ sb.append(" where ");
+ rootParameters.build(sb, updateParamValues);
+ }
+ }
+
+ private String generateParameterName() {
+ return "_u" + paramCounter.getAndIncrease();
+ }
+
+ public Query toQuery(Session session) {
+ StringBuilder querySb = new StringBuilder();
+ Map<String, Object> queryParamValues = new HashMap<String, Object>();
+
+ build(querySb, queryParamValues);
+
+ Query query = session.createQuery(querySb.toString());
+ for (Map.Entry<String, Object> paramValue : queryParamValues.entrySet()) {
+ query.setParameter(paramValue.getKey(), paramValue.getValue());
+ }
+
+ return query;
+ }
+}

0 comments on commit af554fe

Please sign in to comment.