HHH-7093 - ValidityAuditStrategy behaviour depending on row value constractor feature
This commit is contained in:
parent
7ed87233a9
commit
af554fe59b
|
@ -131,6 +131,10 @@ public class Oracle9iDialect extends Oracle8iDialect {
|
|||
return true;
|
||||
}
|
||||
|
||||
public boolean supportsRowValueConstructorSyntax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean supportsTupleDistinctCounts() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -459,4 +459,7 @@ public class PostgreSQL81Dialect extends Dialect {
|
|||
return " for share";
|
||||
}
|
||||
|
||||
public boolean supportsRowValueConstructorSyntax() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -500,7 +500,7 @@ public final class AuditMetadataGenerator {
|
|||
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,10 +7,13 @@ import java.util.Map;
|
|||
|
||||
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;
|
||||
|
@ -18,6 +21,7 @@ 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 @@ import static org.hibernate.envers.entities.mapper.relation.query.QueryConstants
|
|||
*
|
||||
* @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,28 +63,54 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
|||
|
||||
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
|
||||
*/
|
||||
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());
|
||||
|
||||
QueryBuilder qb = new QueryBuilder(auditedEntityName, MIDDLE_ENTITY_ALIAS);
|
||||
@SuppressWarnings({"unchecked"})
|
||||
List<Object> l = qb.toQuery(session).setLockOptions(LockOptions.UPGRADE).list();
|
||||
|
||||
// e.id = :id
|
||||
IdMapper idMapper = auditCfg.getEntCfg().get(entityName).getIdMapper();
|
||||
idMapper.addIdEqualsToQuery(qb.getRootParameters(), id, auditCfg.getAuditEntCfg().getOriginalIdPropName(), true);
|
||||
updateLastRevision(session, auditCfg, l, id, auditedEntityName, revision);
|
||||
} else {
|
||||
// Save the audit data
|
||||
session.save(auditedEntityName, data);
|
||||
sessionCacheCleaner.scheduleAuditDataRemoval(session, data);
|
||||
|
||||
addEndRevisionNullRestriction(auditCfg, qb);
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
List<Object> l = qb.toQuery(session).setLockOptions(LockOptions.UPGRADE).list();
|
||||
|
||||
updateLastRevision(session, auditCfg, l, id, auditedEntityName, revision);
|
||||
// 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
|
||||
|
@ -87,10 +118,22 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
|||
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 class ValidityAuditStrategy implements AuditStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
addEndRevisionNullRestriction(auditCfg, qb);
|
||||
addEndRevisionNullRestriction(auditCfg, qb.getRootParameters());
|
||||
|
||||
final List<Object> l = qb.toQuery(session).setLockOptions(LockOptions.UPGRADE).list();
|
||||
|
||||
|
@ -120,9 +163,8 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
|||
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 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
|||
|
||||
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 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
|||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue