HHH-7807 - Cascade delete revisions
Conflicts: documentation/src/main/docbook/devguide/en-US/Envers.xml hibernate-envers/src/main/java/org/hibernate/envers/configuration/GlobalConfiguration.java hibernate-envers/src/main/java/org/hibernate/envers/configuration/metadata/AuditMetadataGenerator.java
This commit is contained in:
parent
5255c5074f
commit
456dfd83f9
|
@ -326,6 +326,18 @@
|
|||
Name of column used for storing ordinal of the change in sets of embeddable elements.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<property>org.hibernate.envers.cascade_delete_revision</property>
|
||||
</entry>
|
||||
<entry>
|
||||
false
|
||||
</entry>
|
||||
<entry>
|
||||
While deleting revision entry, remove data of associated audited entities.
|
||||
Requires database support for cascade row removal.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<property>org.hibernate.envers.allow_identifier_reuse</property>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -889,6 +889,14 @@ arbitrary number of queries, and import declarations of arbitrary classes.
|
|||
<xs:attribute name="foreign-key" type="xs:string"/>
|
||||
<xs:attribute name="lazy" type="lazy-attribute"/>
|
||||
<xs:attribute name="name" use="required" type="xs:string"/>
|
||||
<xs:attribute name="on-delete" default="noaction">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="cascade"/>
|
||||
<xs:enumeration value="noaction"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- A property embedded in a composite identifier or map index (always not-null). -->
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() );
|
||||
|
||||
|
|
|
@ -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<ListOwningEntity>() );
|
||||
owning.setReferences( new ArrayList<ListOwnedEntity>() );
|
||||
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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue