From 3ecdd860a376f7bfb4250b10329bbeb25b045e2e Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Tue, 17 Sep 2019 23:31:10 -0400 Subject: [PATCH] HHH-10398 Allow MOD column naming to be driven by a strategy In the past the MOD columns were constructed based on the property name, therefore if users specified a @Column/@JoinColumn like annotation and changed the underlying schema column, the MOD column would continue to be derived based on the property name. This enhancement introduces a new ModifiedColumnNamingStrategy SPI that comes with two implementations, a default/legacy mode that maintains the prior naming model and an improved mode that will derive the MOD name based on the naming strategy ORM used to derive the column name. --- .../userguide/chapters/envers/Envers.adoc | 75 +++++++++++ .../ImprovedModifiedColumnNamingStrategy.java | 80 +++++++++++ .../LegacyModifiedColumnNamingStrategy.java | 47 +++++++ ...umnNamingStrategyRegistrationProvider.java | 45 +++++++ .../spi/ModifiedColumnNamingStrategy.java | 37 +++++ .../envers/configuration/EnversSettings.java | 9 ++ .../internal/GlobalConfiguration.java | 27 ++++ .../metadata/AuditMetadataGenerator.java | 13 +- .../internal/metadata/MetadataTools.java | 20 +++ .../reader/AuditedPropertiesReader.java | 8 +- .../ComponentAuditedPropertiesReader.java | 8 +- .../metadata/reader/PropertyAuditingData.java | 14 ++ ...stry.selector.StrategyRegistrationProvider | 1 + .../ImprovedColumnNamingStrategyTest.java | 52 ++++++++ .../LegacyColumnNamingStrategyTest.java | 42 ++++++ .../modifiedflags/naming/OtherEntity.java | 42 ++++++ .../modifiedflags/naming/OtherEntityId.java | 36 +++++ .../modifiedflags/naming/SingleIdEntity.java | 41 ++++++ .../modifiedflags/naming/TestEmbeddable.java | 34 +++++ .../modifiedflags/naming/TestEntity.java | 126 ++++++++++++++++++ 20 files changed, 739 insertions(+), 18 deletions(-) create mode 100644 hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ImprovedModifiedColumnNamingStrategy.java create mode 100644 hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/LegacyModifiedColumnNamingStrategy.java create mode 100644 hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ModifiedColumnNamingStrategyRegistrationProvider.java create mode 100644 hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/ModifiedColumnNamingStrategy.java create mode 100644 hibernate-envers/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/ImprovedColumnNamingStrategyTest.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/LegacyColumnNamingStrategyTest.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntity.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntityId.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/SingleIdEntity.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEmbeddable.java create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEntity.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc index 95eefaa5a5..77a3d1f868 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/envers/Envers.adoc @@ -286,6 +286,9 @@ The suffix for columns storing "Modified Flags". + For example, a property called "age", will by default get modified flag with column name "age_MOD". +`*org.hibernate.envers.modified_column_naming_strategy*` (default: `org.hibernate.envers.boot.internal.LegacyModifiedColumnNamingStrategy` ):: +The naming strategy to be used for modified flag columns in the audit metadata. + `*org.hibernate.envers.embeddable_set_ordinal_field_name*` (default: `SETORDINAL` ):: Name of column used for storing ordinal of the change in sets of embeddable elements. @@ -314,6 +317,7 @@ The following configuration options have been added recently and should be regar . `org.hibernate.envers.track_entities_changed_in_revision` . `org.hibernate.envers.using_modified_flag` . `org.hibernate.envers.modified_flag_suffix` +. `org.hibernate.envers.modified_column_naming_strategy` . `org.hibernate.envers.original_id_prop_name` . `org.hibernate.envers.find_by_revision_exact_match` ==== @@ -721,6 +725,77 @@ include::{extrasdir}/envers-tracking-properties-changes-example.sql[] To see how "Modified Flags" can be utilized, check out the very simple query API that uses them: <>. +[[envers-tracking-properties-changes-strategy]] +=== Selecting strategy for tracking property level changes + +By default, Envers uses the `legacy` modified column naming strategy. +This strategy is designed to add columns based the following rule-set: + +. If property is annotated with `@Audited` and the _modifiedColumnName_ attribute is specified, the column will directly be based on the supplied name. +. If property is not annotated with `@Audited` or if no _modifiedColumnName_ attribute is given, the column will be named after the java class property, appended with the configured suffix, the default being `_MOD`. + +While this strategy has no performance drawbacks, it does present concerns for users who prefer consistency without verbosity. +Lets take the following entity mapping as an example. + +``` +@Audited(withModifiedFlags = true) +@Entity +public class Customer { + @Id + private Integer id; + @Column(name = "customer_name") + private String name; +} +``` + +This mapping will actually lead to some inconsistent naming between columns, see below with how the model's name will be stored in `customer_name` but the modified column that tracks whether this column changes between revisions is named `name_MOD`. + +``` +CREATE TABLE Customer_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint not null, + customer_name varchar(255), + name_MOD boolean, + primary key(id, REV) +) +``` + +An additional strategy called `improved`, aims to address these consistent column naming concerns. +This strategy uses the following rule-set: + +. Property is a Basic type (Single Column valued property) +.. Use the _modifiedColumnName_ directly if one is supplied on the property mapping +.. Otherwise use the resolved ORM column name appended with the modified flag suffix configured value. +. Property is an Association (to-one mapping) with a Foreign Key using a single column +.. Use the _modifiedColumnName_ directly if one is supplied on the property mapping +.. Otherwise use the resolved ORM column name appended with the modified flag suffix configured value. +. Property is an Association (to-one mapping) with a Foreign Key using multiple columns +.. Use the _modifiedColumnName_ directly if one is supplied on the property mapping +.. Otherwise use the property name appended with the modified flag suffix configured value. +. Property is an Embeddable +.. Use the _modifiedColumnName_ directly if one is supplied on the property mapping +.. Otherwise use the property name appended with the modified flag suffix configured value. + +While using this strategy, the same `Customer` mapping will generate the following table schema: + +``` +CREATE TABLE Customer_AUD ( + id bigint not null, + REV integer not null, + REVTYPE tinyint not null, + customer_name varchar(255), + customer_name_MOD boolean, + primary key(id, REV) +) +``` + +When already using Envers in conjunction with the modified columns flag feature, its advised not to enable the new strategy immediately as schema changes would be required. +You will need to either migrate your existing schema manually to adhere to the rules above or use the explicit _modifiedColumnName_ attribute on the `@Audited` annotation for existing columns that use the feature. + +To configure a custom strategy implementation or use the improved strategy, the configuration option `org.hibernate.envers.modified_column_naming_strategy` will need to be set. +This option can be the fully qualified class name of a `ModifiedColumnNameStrategy` implementation or `legacy` or `improved` for either of the two provided implementations. + [[envers-queries]] === Queries diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ImprovedModifiedColumnNamingStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ImprovedModifiedColumnNamingStrategy.java new file mode 100644 index 0000000000..3fa797cbad --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ImprovedModifiedColumnNamingStrategy.java @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.boot.internal; + +import org.hibernate.envers.boot.spi.ModifiedColumnNamingStrategy; +import org.hibernate.envers.configuration.internal.GlobalConfiguration; +import org.hibernate.envers.configuration.internal.metadata.MetadataTools; +import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData; +import org.hibernate.envers.internal.tools.StringTools; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.Value; +import org.hibernate.type.BasicType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; + +import org.dom4j.Element; + +/** + * A {@link ModifiedColumnNamingStrategy} that adds modified columns with the following rules: + *
    + *
  • For basic types, prioritizes audit annotation naming followed by physical column name appended with suffix.
  • + *
  • For associations with single column foreign keys, behaves like basic types.
  • + *
  • For associations with multiple column foreign keys, prioritizes audit annotation naming followed by using property name.
  • + *
  • For embeddables, behaves like associations with multiple column foreign keys
  • + *
+ * + * @author Chris Cranford + * @since 5.4.6 + */ +public class ImprovedModifiedColumnNamingStrategy implements ModifiedColumnNamingStrategy { + @Override + public void addModifiedColumns( + GlobalConfiguration globalCfg, + Value value, + Element parent, + PropertyAuditingData propertyAuditingData) { + + boolean basicType = value.getType() instanceof BasicType; + boolean toOneType = value.getType() instanceof ManyToOneType || value.getType() instanceof OneToOneType; + + if ( basicType || toOneType ) { + if ( value.getColumnSpan() == 1 ) { + Selectable selectable = value.getColumnIterator().next(); + if ( selectable instanceof Column ) { + // This should not be applied for formulas + String columnName = propertyAuditingData.getModifiedFlagName(); + if ( !propertyAuditingData.isModifiedFlagNameExplicitlySpecified() ) { + columnName = ( (Column) selectable ).getName() + globalCfg.getModifiedFlagSuffix(); + } + else { + columnName = propertyAuditingData.getExplicitModifiedFlagName(); + } + + MetadataTools.addModifiedFlagPropertyWithColumn( + parent, + propertyAuditingData.getName(), + globalCfg.getModifiedFlagSuffix(), + propertyAuditingData.getModifiedFlagName(), + columnName + ); + + return; + } + } + } + + // Default legacy behavior + MetadataTools.addModifiedFlagProperty( + parent, + propertyAuditingData.getName(), + globalCfg.getModifiedFlagSuffix(), + propertyAuditingData.getModifiedFlagName() + ); + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/LegacyModifiedColumnNamingStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/LegacyModifiedColumnNamingStrategy.java new file mode 100644 index 0000000000..528aea71e1 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/LegacyModifiedColumnNamingStrategy.java @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.boot.internal; + +import org.hibernate.envers.boot.spi.ModifiedColumnNamingStrategy; +import org.hibernate.envers.configuration.internal.GlobalConfiguration; +import org.hibernate.envers.configuration.internal.metadata.MetadataTools; +import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData; +import org.hibernate.mapping.Value; + +import org.dom4j.Element; + +/** + * A {@link ModifiedColumnNamingStrategy} that adds modified columns with the following rules: + *
    + *
  • If an audit annotation modified column name is supplied, use it directly with no suffix.
  • + *
  • If no audit annotation modified column name is present, use the property name appended with suffix.
  • + *
+ * + * This is the default Envers modified column naming behavior. + * + * @author Chris Cranford + * @since 5.4.6 + */ +public class LegacyModifiedColumnNamingStrategy implements ModifiedColumnNamingStrategy { + @Override + public void addModifiedColumns( + GlobalConfiguration globalCfg, + Value value, + Element parent, + PropertyAuditingData propertyAuditingData) { + String columnName = propertyAuditingData.getModifiedFlagName(); + if ( propertyAuditingData.isModifiedFlagNameExplicitlySpecified() ) { + columnName = propertyAuditingData.getExplicitModifiedFlagName(); + } + MetadataTools.addModifiedFlagProperty( + parent, + propertyAuditingData.getName(), + globalCfg.getModifiedFlagSuffix(), + columnName + ); + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ModifiedColumnNamingStrategyRegistrationProvider.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ModifiedColumnNamingStrategyRegistrationProvider.java new file mode 100644 index 0000000000..889eddb3e4 --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/ModifiedColumnNamingStrategyRegistrationProvider.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.boot.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.boot.registry.selector.SimpleStrategyRegistrationImpl; +import org.hibernate.boot.registry.selector.StrategyRegistration; +import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; +import org.hibernate.envers.boot.spi.ModifiedColumnNamingStrategy; + +/** + * A {@link StrategyRegistrationProvider} for {@link ModifiedColumnNamingStrategy}s. + * + * @author Chris Cranford + */ +public class ModifiedColumnNamingStrategyRegistrationProvider implements StrategyRegistrationProvider { + @Override + public Iterable getStrategyRegistrations() { + final List registrations = new ArrayList<>(); + + registrations.add( + new SimpleStrategyRegistrationImpl( + ModifiedColumnNamingStrategy.class, + LegacyModifiedColumnNamingStrategy.class, + "default", "legacy" + ) + ); + + registrations.add( + new SimpleStrategyRegistrationImpl( + ModifiedColumnNamingStrategy.class, + ImprovedModifiedColumnNamingStrategy.class, + "improved" + ) + ); + + return registrations; + } +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/ModifiedColumnNamingStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/ModifiedColumnNamingStrategy.java new file mode 100644 index 0000000000..9b624affaa --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/spi/ModifiedColumnNamingStrategy.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.boot.spi; + +import org.hibernate.Incubating; +import org.hibernate.envers.configuration.internal.GlobalConfiguration; +import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData; +import org.hibernate.mapping.Value; + +import org.dom4j.Element; + +/** + * Defines a naming strategy for applying modified columns to the audited entity metamodel. + * + * @author Chris Cranford + * @since 5.4.6 + */ +@Incubating +public interface ModifiedColumnNamingStrategy { + /** + * Adds modified columns to the audited entity metamodel. + * + * @param globalCfg the envers global configuration + * @param value the property value + * @param parent the parent audited entity metamodel + * @param propertyAuditingData the property auditing data + */ + void addModifiedColumns( + GlobalConfiguration globalCfg, + Value value, + Element parent, + PropertyAuditingData propertyAuditingData); +} diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/EnversSettings.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/EnversSettings.java index b789041e5a..ecda1490d7 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/EnversSettings.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/EnversSettings.java @@ -133,4 +133,13 @@ public interface EnversSettings { * @since 5.4.4 */ String FIND_BY_REVISION_EXACT_MATCH = "org.hibernate.envers.find_by_revision_exact_match"; + + /** + * Specifies the {@link org.hibernate.envers.boot.spi.ModifiedColumnNamingStrategy} to use + * + * Defaults to {@link org.hibernate.envers.boot.internal.LegacyModifiedColumnNamingStrategy}. + * + * @since 5.4.6 + */ + String MODIFIED_COLUMN_NAMING_STRATEGY = "org.hibernate.envers.modified_column_naming_strategy"; } 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 359a07bfb1..c9f7eb0db5 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 @@ -7,13 +7,17 @@ package org.hibernate.envers.configuration.internal; import java.util.Map; +import java.util.concurrent.Callable; import org.hibernate.MappingException; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.Environment; import org.hibernate.dialect.HSQLDialect; import org.hibernate.envers.RevisionListener; import org.hibernate.envers.boot.internal.EnversService; +import org.hibernate.envers.boot.internal.LegacyModifiedColumnNamingStrategy; +import org.hibernate.envers.boot.spi.ModifiedColumnNamingStrategy; import org.hibernate.envers.configuration.EnversSettings; import org.hibernate.envers.internal.tools.ReflectionTools; import org.hibernate.internal.util.config.ConfigurationHelper; @@ -77,11 +81,15 @@ public class GlobalConfiguration { */ private final String correlatedSubqueryOperator; + private final ModifiedColumnNamingStrategy modifiedColumnNamingStrategy; + public GlobalConfiguration( EnversService enversService, Map properties) { this.enversService = enversService; + final StrategySelector strategySelector = enversService.getServiceRegistry().getService( StrategySelector.class ); + generateRevisionsForCollections = ConfigurationHelper.getBoolean( EnversSettings.REVISION_ON_COLLECTION_CHANGE, properties, @@ -156,6 +164,21 @@ public class GlobalConfiguration { revisionListenerClass = null; } + modifiedColumnNamingStrategy = strategySelector.resolveDefaultableStrategy( + ModifiedColumnNamingStrategy.class, + properties.get( EnversSettings.MODIFIED_COLUMN_NAMING_STRATEGY ), + new Callable() { + @Override + public ModifiedColumnNamingStrategy call() throws Exception { + return strategySelector.resolveDefaultableStrategy( + ModifiedColumnNamingStrategy.class, + "default", + new LegacyModifiedColumnNamingStrategy() + ); + } + } + ); + allowIdentifierReuse = ConfigurationHelper.getBoolean( EnversSettings.ALLOW_IDENTIFIER_REUSE, properties, false ); @@ -232,4 +255,8 @@ public class GlobalConfiguration { public boolean isAuditReaderFindAtRevisionExactMatch() { return findByRevisionExactMatch; } + + public ModifiedColumnNamingStrategy getModifiedColumnNamingStrategy() { + return modifiedColumnNamingStrategy; + } } 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 e5b630d5df..4de1e90b8b 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 @@ -213,7 +213,7 @@ public final class AuditMetadataGenerator { } return; } - addModifiedFlagIfNeeded( parent, propertyAuditingData, processModifiedFlag ); + addModifiedFlagIfNeeded( value, parent, propertyAuditingData, processModifiedFlag ); } private boolean processedInSecondPass(Type type) { @@ -290,19 +290,20 @@ public final class AuditMetadataGenerator { else { return; } - addModifiedFlagIfNeeded( parent, propertyAuditingData, processModifiedFlag ); + addModifiedFlagIfNeeded( value, parent, propertyAuditingData, processModifiedFlag ); } private void addModifiedFlagIfNeeded( + Value value, Element parent, PropertyAuditingData propertyAuditingData, boolean processModifiedFlag) { if ( processModifiedFlag && propertyAuditingData.isUsingModifiedFlag() ) { - MetadataTools.addModifiedFlagProperty( + globalCfg.getModifiedColumnNamingStrategy().addModifiedColumns( + globalCfg, + value, parent, - propertyAuditingData.getName(), - globalCfg.getModifiedFlagSuffix(), - propertyAuditingData.getModifiedFlagName() + propertyAuditingData ); } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MetadataTools.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MetadataTools.java index cf38c7aa46..77f86d6c84 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MetadataTools.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/MetadataTools.java @@ -95,6 +95,26 @@ public final class MetadataTools { ); } + public static Element addModifiedFlagPropertyWithColumn( + Element parent, + String propertyName, + String suffix, + String modifiedFlagName, + String columnName) { + final Element property = addProperty( + parent, + (modifiedFlagName != null) ? modifiedFlagName : getModifiedFlagPropertyName( propertyName, suffix ), + "boolean", + true, + false, + false + ); + + addColumn( property, columnName, null, null, null, null, null, null ); + + return property; + } + public static String getModifiedFlagPropertyName(String propertyName, String suffix) { return propertyName + suffix; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java index 845439ddd4..45823cb63f 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/AuditedPropertiesReader.java @@ -590,13 +590,9 @@ public class AuditedPropertiesReader { propertyData.setStore( aud.modStore() ); propertyData.setRelationTargetAuditMode( aud.targetAuditMode() ); propertyData.setUsingModifiedFlag( checkUsingModifiedFlag( aud ) ); + propertyData.setModifiedFlagName( MetadataTools.getModifiedFlagPropertyName( propertyName, modifiedFlagSuffix ) ); if( aud.modifiedColumnName() != null && !"".equals( aud.modifiedColumnName() ) ) { - propertyData.setModifiedFlagName( aud.modifiedColumnName() ); - } - else { - propertyData.setModifiedFlagName( - MetadataTools.getModifiedFlagPropertyName( propertyName, modifiedFlagSuffix ) - ); + propertyData.setExplicitModifiedFlagName( aud.modifiedColumnName() ); } return true; } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/ComponentAuditedPropertiesReader.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/ComponentAuditedPropertiesReader.java index decae95ab0..bd5c5965bd 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/ComponentAuditedPropertiesReader.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/ComponentAuditedPropertiesReader.java @@ -44,13 +44,9 @@ public class ComponentAuditedPropertiesReader extends AuditedPropertiesReader { propertyData.setStore( aud.modStore() ); propertyData.setRelationTargetAuditMode( aud.targetAuditMode() ); propertyData.setUsingModifiedFlag( checkUsingModifiedFlag( aud ) ); + propertyData.setModifiedFlagName( MetadataTools.getModifiedFlagPropertyName( propertyName, modifiedFlagSuffix ) ); if( aud.modifiedColumnName() != null && !"".equals( aud.modifiedColumnName() ) ) { - propertyData.setModifiedFlagName( aud.modifiedColumnName() ); - } - else { - propertyData.setModifiedFlagName( - MetadataTools.getModifiedFlagPropertyName( propertyName, modifiedFlagSuffix ) - ); + propertyData.setExplicitModifiedFlagName( aud.modifiedColumnName() ); } } else { diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java index 2e717547da..c8274fe142 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/reader/PropertyAuditingData.java @@ -17,6 +17,7 @@ import org.hibernate.envers.AuditOverrides; import org.hibernate.envers.ModificationStore; import org.hibernate.envers.RelationTargetAuditMode; import org.hibernate.envers.internal.entities.PropertyData; +import org.hibernate.envers.internal.tools.StringTools; import org.hibernate.mapping.Value; import org.hibernate.type.Type; @@ -41,6 +42,7 @@ public class PropertyAuditingData { private boolean forceInsertable; private boolean usingModifiedFlag; private String modifiedFlagName; + private String explicitModifiedFlagName; private Value value; // Synthetic properties are ones which are not part of the actual java model. // They're properties used for bookkeeping by Hibernate @@ -237,6 +239,18 @@ public class PropertyAuditingData { this.modifiedFlagName = modifiedFlagName; } + public boolean isModifiedFlagNameExplicitlySpecified() { + return !StringTools.isEmpty( explicitModifiedFlagName ); + } + + public String getExplicitModifiedFlagName() { + return explicitModifiedFlagName; + } + + public void setExplicitModifiedFlagName(String modifiedFlagName) { + this.explicitModifiedFlagName = modifiedFlagName; + } + public void addAuditingOverride(AuditOverride annotation) { if ( annotation != null ) { final String overrideName = annotation.name(); diff --git a/hibernate-envers/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider b/hibernate-envers/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider new file mode 100644 index 0000000000..394e0d7168 --- /dev/null +++ b/hibernate-envers/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider @@ -0,0 +1 @@ +org.hibernate.envers.boot.internal.ModifiedColumnNamingStrategyRegistrationProvider \ No newline at end of file diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/ImprovedColumnNamingStrategyTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/ImprovedColumnNamingStrategyTest.java new file mode 100644 index 0000000000..c385bde606 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/ImprovedColumnNamingStrategyTest.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.modifiedflags.naming; + +import java.util.Map; + +import org.hibernate.envers.configuration.EnversSettings; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * @author Chris Cranford + */ +public class ImprovedColumnNamingStrategyTest extends BaseEnversJPAFunctionalTestCase { + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + + options.put( EnversSettings.MODIFIED_COLUMN_NAMING_STRATEGY, "improved" ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class, OtherEntity.class, SingleIdEntity.class }; + } + + @Test + public void testModifiedColumns() { + final Table table1 = metadata().getEntityBinding( TestEntity.class.getName() + "_AUD" ).getTable(); + assertNotNull( table1.getColumn( new Column( "data1_MOD") ) ); + assertNotNull( table1.getColumn( new Column( "mydata_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "data_3" ) ) ); + assertNotNull( table1.getColumn( new Column( "the_data_mod" ) ) ); + + assertNotNull( table1.getColumn( new Column( "embeddable_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "otherEntity_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "single_id_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "singleIdEntity2_id_MOD" ) ) ); + + final Table table2 = metadata().getEntityBinding( OtherEntity.class.getName() + "_AUD" ).getTable(); + assertNotNull( table2.getColumn( new Column( "d_MOD" ) ) ); + } + +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/LegacyColumnNamingStrategyTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/LegacyColumnNamingStrategyTest.java new file mode 100644 index 0000000000..8c1bcdd171 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/LegacyColumnNamingStrategyTest.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.modifiedflags.naming; + +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.Table; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * @author Chris Cranford + */ +public class LegacyColumnNamingStrategyTest extends BaseEnversJPAFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class, OtherEntity.class, SingleIdEntity.class }; + } + + @Test + public void testModifiedColumns() { + final Table table1 = metadata().getEntityBinding( TestEntity.class.getName() + "_AUD" ).getTable(); + assertNotNull( table1.getColumn( new Column( "data1_MOD") ) ); + assertNotNull( table1.getColumn( new Column( "data2_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "data_3" ) ) ); + assertNotNull( table1.getColumn( new Column( "the_data_mod" ) ) ); + + assertNotNull( table1.getColumn( new Column( "embeddable_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "otherEntity_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "singleIdEntity_MOD" ) ) ); + assertNotNull( table1.getColumn( new Column( "singleIdEntity2_MOD" ) ) ); + + final Table table2 = metadata().getEntityBinding( OtherEntity.class.getName() + "_AUD" ).getTable(); + assertNotNull( table2.getColumn( new Column( "data_MOD" ) ) ); + } + +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntity.java new file mode 100644 index 0000000000..676e397ccf --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntity.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.modifiedflags.naming; + +import javax.persistence.Column; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +import org.hibernate.envers.Audited; + +/** + * @author Chris Cranford + */ +@Entity +@Audited(withModifiedFlag = true) +public class OtherEntity { + @EmbeddedId + private OtherEntityId id; + + @Column(name = "d") + private String data; + + public OtherEntityId getId() { + return id; + } + + public void setId(OtherEntityId id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntityId.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntityId.java new file mode 100644 index 0000000000..5d3db71e52 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/OtherEntityId.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.modifiedflags.naming; + +import java.io.Serializable; + +import javax.persistence.Embeddable; + +/** + * @author Chris Cranford + */ +@Embeddable +public class OtherEntityId implements Serializable { + private Integer id1; + private Integer id2; + + public Integer getId1() { + return id1; + } + + public void setId1(Integer id1) { + this.id1 = id1; + } + + public Integer getId2() { + return id2; + } + + public void setId2(Integer id2) { + this.id2 = id2; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/SingleIdEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/SingleIdEntity.java new file mode 100644 index 0000000000..adeef99c22 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/SingleIdEntity.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.modifiedflags.naming; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.envers.Audited; + +/** + * @author Chris Cranford + */ +@Entity +@Audited(withModifiedFlag = true) +public class SingleIdEntity { + @Id + @GeneratedValue + private Integer id; + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEmbeddable.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEmbeddable.java new file mode 100644 index 0000000000..1ea22bd87e --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEmbeddable.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.modifiedflags.naming; + +import javax.persistence.Embeddable; + +/** + * @author Chris Cranford + */ +@Embeddable +public class TestEmbeddable { + String value1; + String value2; + + public String getValue1() { + return value1; + } + + public void setValue1(String value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEntity.java new file mode 100644 index 0000000000..66afbf257f --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/modifiedflags/naming/TestEntity.java @@ -0,0 +1,126 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.modifiedflags.naming; + +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; + +import org.hibernate.envers.Audited; + +/** + * @author Chris Cranford + */ +@Entity +@Audited(withModifiedFlag = true) +public class TestEntity { + @Id + @GeneratedValue + private Integer id; + private String data1; + @Column(name = "mydata") + private String data2; + @Audited(modifiedColumnName = "data_3", withModifiedFlag = true) + private String data3; + @Column(name = "thedata") + @Audited(modifiedColumnName = "the_data_mod", withModifiedFlag = true) + private String data4; + @Embedded + private TestEmbeddable embeddable; + @ManyToOne + @JoinColumns({ + @JoinColumn(name = "other_entity_id1", nullable = false), + @JoinColumn(name = "other_entity_id2", nullable = false) + }) + private OtherEntity otherEntity; + + @OneToOne + @JoinColumn(name = "single_id") + private SingleIdEntity singleIdEntity; + + @OneToOne + private SingleIdEntity singleIdEntity2; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData1() { + return data1; + } + + public void setData1(String data1) { + this.data1 = data1; + } + + public String getData2() { + return data2; + } + + public void setData2(String data2) { + this.data2 = data2; + } + + public String getData3() { + return data3; + } + + public void setData3(String data3) { + this.data3 = data3; + } + + public String getData4() { + return data4; + } + + public void setData4(String data4) { + this.data4 = data4; + } + + public TestEmbeddable getEmbeddable() { + return embeddable; + } + + public void setEmbeddable(TestEmbeddable embeddable) { + this.embeddable = embeddable; + } + + public OtherEntity getOtherEntity() { + return otherEntity; + } + + public void setOtherEntity(OtherEntity otherEntity) { + this.otherEntity = otherEntity; + } + + public SingleIdEntity getSingleIdEntity() { + return singleIdEntity; + } + + public void setSingleIdEntity(SingleIdEntity singleIdEntity) { + this.singleIdEntity = singleIdEntity; + } + + public SingleIdEntity getSingleIdEntity2() { + return singleIdEntity2; + } + + public void setSingleIdEntity2(SingleIdEntity singleIdEntity2) { + this.singleIdEntity2 = singleIdEntity2; + } +}