diff --git a/documentation/src/main/docbook/devguide/en-US/Envers.xml b/documentation/src/main/docbook/devguide/en-US/Envers.xml
index fe981b5223..86651fb8f8 100644
--- a/documentation/src/main/docbook/devguide/en-US/Envers.xml
+++ b/documentation/src/main/docbook/devguide/en-US/Envers.xml
@@ -326,6 +326,18 @@
Name of column used for storing ordinal of the change in sets of embeddable elements.
+
+
+ org.hibernate.envers.cascade_delete_revision
+
+
+ false
+
+
+ While deleting revision entry, remove data of associated audited entities.
+ Requires database support for cascade row removal.
+
+
org.hibernate.envers.allow_identifier_reuse
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java
index 0cea5956ae..64166cd818 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java
@@ -2322,6 +2322,7 @@ public final class HbmBinder {
if ( propertyRef != null ) {
mappings.addUniquePropertyReference( toOne.getReferencedEntityName(), propertyRef );
}
+ toOne.setCascadeDeleteEnabled( "cascade".equals( subnode.attributeValue( "on-delete" ) ) );
}
else if ( value instanceof Collection ) {
Collection coll = (Collection) value;
diff --git a/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd b/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd
index 530bff1d22..ae67440487 100644
--- a/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd
+++ b/hibernate-core/src/main/resources/org/hibernate/hibernate-mapping-4.0.xsd
@@ -889,6 +889,14 @@ arbitrary number of queries, and import declarations of arbitrary classes.
+
+
+
+
+
+
+
+
diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/GlobalConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/GlobalConfiguration.java
index 1bcf57eb30..61516f9b71 100644
--- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/GlobalConfiguration.java
+++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/GlobalConfiguration.java
@@ -74,6 +74,9 @@ public class GlobalConfiguration {
// Use revision entity with native id generator
private final boolean useRevisionEntityWithNativeId;
+
+ // While deleting revision entry, remove data of associated audited entities
+ private final boolean cascadeDeleteRevision;
// Support reused identifiers of previously deleted entities
private final boolean allowIdentifierReuse;
@@ -106,6 +109,9 @@ public class GlobalConfiguration {
trackEntitiesChangedInRevision = ConfigurationHelper.getBoolean(
EnversSettings.TRACK_ENTITIES_CHANGED_IN_REVISION, properties, false
);
+
+ cascadeDeleteRevision = ConfigurationHelper.getBoolean(
+ "org.hibernate.envers.cascade_delete_revision", properties, false );
useRevisionEntityWithNativeId = ConfigurationHelper.getBoolean(
EnversSettings.USE_REVISION_ENTITY_WITH_NATIVE_ID, properties, true
@@ -191,6 +197,10 @@ public class GlobalConfiguration {
public boolean isUseRevisionEntityWithNativeId() {
return useRevisionEntityWithNativeId;
}
+
+ public boolean isCascadeDeleteRevision() {
+ return cascadeDeleteRevision;
+ }
public boolean isAllowIdentifierReuse() {
return allowIdentifierReuse;
diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AuditMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AuditMetadataGenerator.java
index a117e85c96..926a81ac26 100644
--- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AuditMetadataGenerator.java
+++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/AuditMetadataGenerator.java
@@ -139,6 +139,9 @@ public final class AuditMetadataGenerator {
private Element cloneAndSetupRevisionInfoRelationMapping() {
final Element revMapping = (Element) revisionInfoRelationMapping.clone();
revMapping.addAttribute( "name", verEntCfg.getRevisionFieldName() );
+ if ( globalCfg.isCascadeDeleteRevision() ) {
+ revMapping.addAttribute( "on-delete", "cascade" );
+ }
MetadataTools.addOrModifyColumn( revMapping, verEntCfg.getRevisionFieldName() );
diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/AbstractRevisionEntityRemovalTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/AbstractRevisionEntityRemovalTest.java
new file mode 100644
index 0000000000..2b8b5b48a0
--- /dev/null
+++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/AbstractRevisionEntityRemovalTest.java
@@ -0,0 +1,108 @@
+package org.hibernate.envers.test.integration.reventity.removal;
+
+import java.util.ArrayList;
+import java.util.Map;
+import javax.persistence.EntityManager;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
+import org.hibernate.envers.test.Priority;
+import org.hibernate.envers.test.entities.StrTestEntity;
+import org.hibernate.envers.test.entities.manytomany.ListOwnedEntity;
+import org.hibernate.envers.test.entities.manytomany.ListOwningEntity;
+import org.hibernate.testing.DialectChecks;
+import org.hibernate.testing.RequiresDialectFeature;
+import org.hibernate.testing.TestForIssue;
+
+/**
+ * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
+ */
+@TestForIssue( jiraKey = "HHH-7807" )
+@RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class)
+public abstract class AbstractRevisionEntityRemovalTest extends BaseEnversJPAFunctionalTestCase {
+ @Override
+ protected void addConfigOptions(Map options) {
+ options.put( "org.hibernate.envers.cascade_delete_revision", "true" );
+ }
+
+ @Override
+ protected Class>[] getAnnotatedClasses() {
+ return new Class>[] {
+ StrTestEntity.class, ListOwnedEntity.class, ListOwningEntity.class,
+ getRevisionEntityClass()
+ };
+ }
+
+ @Test
+ @Priority(10)
+ public void initData() {
+ EntityManager em = getEntityManager();
+
+ // Revision 1 - simple entity
+ em.getTransaction().begin();
+ em.persist( new StrTestEntity( "data" ) );
+ em.getTransaction().commit();
+
+ // Revision 2 - many-to-many relation
+ em.getTransaction().begin();
+ ListOwnedEntity owned = new ListOwnedEntity( 1, "data" );
+ ListOwningEntity owning = new ListOwningEntity( 1, "data" );
+ owned.setReferencing( new ArrayList() );
+ owning.setReferences( new ArrayList() );
+ owned.getReferencing().add( owning );
+ owning.getReferences().add( owned );
+ em.persist( owned );
+ em.persist( owning );
+ em.getTransaction().commit();
+
+ em.getTransaction().begin();
+ Assert.assertEquals( 1, countRecords( em, "STR_TEST_AUD" ) );
+ Assert.assertEquals( 1, countRecords( em, "ListOwned_AUD" ) );
+ Assert.assertEquals( 1, countRecords( em, "ListOwning_AUD" ) );
+ Assert.assertEquals( 1, countRecords( em, "ListOwning_ListOwned_AUD" ) );
+ em.getTransaction().commit();
+
+ em.close();
+ }
+
+ @Test
+ @Priority(9)
+ public void testRemoveExistingRevisions() {
+ EntityManager em = getEntityManager();
+ removeRevision( em, 1 );
+ removeRevision( em, 2 );
+ em.close();
+ }
+
+ @Test
+ @Priority(8)
+ public void testEmptyAuditTables() {
+ EntityManager em = getEntityManager();
+ em.getTransaction().begin();
+
+ Assert.assertEquals( 0, countRecords( em, "STR_TEST_AUD" ) );
+ Assert.assertEquals( 0, countRecords( em, "ListOwned_AUD" ) );
+ Assert.assertEquals( 0, countRecords( em, "ListOwning_AUD" ) );
+ Assert.assertEquals( 0, countRecords( em, "ListOwning_ListOwned_AUD" ) );
+
+ em.getTransaction().commit();
+ em.close();
+ }
+
+ private int countRecords(EntityManager em, String tableName) {
+ return ( (Number) em.createNativeQuery( "SELECT COUNT(*) FROM " + tableName ).getSingleResult() ).intValue();
+ }
+
+ private void removeRevision(EntityManager em, Number number) {
+ em.getTransaction().begin();
+ Object entity = em.find( getRevisionEntityClass(), number );
+ Assert.assertNotNull( entity );
+ em.remove( entity );
+ em.getTransaction().commit();
+ Assert.assertNull( em.find( getRevisionEntityClass(), number ) );
+ }
+
+ protected abstract Class> getRevisionEntityClass();
+}
diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/RemoveDefaultRevisionEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/RemoveDefaultRevisionEntity.java
new file mode 100644
index 0000000000..af2cf50ad6
--- /dev/null
+++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/RemoveDefaultRevisionEntity.java
@@ -0,0 +1,13 @@
+package org.hibernate.envers.test.integration.reventity.removal;
+
+import org.hibernate.envers.enhanced.SequenceIdRevisionEntity;
+
+/**
+ * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
+ */
+public class RemoveDefaultRevisionEntity extends AbstractRevisionEntityRemovalTest {
+ @Override
+ protected Class> getRevisionEntityClass() {
+ return SequenceIdRevisionEntity.class;
+ }
+}
diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/RemoveTrackingRevisionEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/RemoveTrackingRevisionEntity.java
new file mode 100644
index 0000000000..05f4a1bf9a
--- /dev/null
+++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/reventity/removal/RemoveTrackingRevisionEntity.java
@@ -0,0 +1,21 @@
+package org.hibernate.envers.test.integration.reventity.removal;
+
+import java.util.Map;
+
+import org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionEntity;
+
+/**
+ * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
+ */
+public class RemoveTrackingRevisionEntity extends AbstractRevisionEntityRemovalTest {
+ @Override
+ public void addConfigOptions(Map configuration) {
+ super.addConfigOptions( configuration );
+ configuration.put("org.hibernate.envers.track_entities_changed_in_revision", "true");
+ }
+
+ @Override
+ protected Class> getRevisionEntityClass() {
+ return SequenceIdTrackingModifiedEntitiesRevisionEntity.class;
+ }
+}
diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java
index f9e29799d8..3616d96d18 100644
--- a/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java
+++ b/hibernate-testing/src/main/java/org/hibernate/testing/DialectChecks.java
@@ -80,6 +80,12 @@ abstract public class DialectChecks {
}
}
+ public static class SupportsCascadeDeleteCheck implements DialectCheck {
+ public boolean isMatch(Dialect dialect) {
+ return dialect.supportsCascadeDelete();
+ }
+ }
+
public static class SupportsCircularCascadeDeleteCheck implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsCircularCascadeDeleteConstraints();