Merge remote-tracking branch 'upstream5/master' into wip/6.0_merged_4

This commit is contained in:
Andrea Boriero 2019-10-02 10:24:59 +01:00
commit 4b6777cde2
28 changed files with 947 additions and 127 deletions

View File

@ -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-queries>>.
[[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

View File

@ -285,21 +285,21 @@ public class DefaultLoadEventListener implements LoadEventListener {
LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
if ( li.isUnwrap() || event.getShouldUnwrapProxy() ) {
if ( li.isUnwrap() ) {
if ( entityMetamodel.hasSubclasses() ) {
LOG.debug( "Ignoring NO_PROXY for to-one association with subclasses to honor laziness" );
}
else {
return li.getImplementation();
}
}
return persistenceContext.narrowProxy( proxy, persister, keyToLoad, null );
}
// specialized handling for entities with subclasses with a HibernateProxy factory
if ( entityMetamodel.hasSubclasses() ) {
// entities with subclasses that define a ProxyFactory can create
// a HibernateProxy so long as NO_PROXY was not specified.
if ( event.getShouldUnwrapProxy() != null && event.getShouldUnwrapProxy() ) {
LOG.debug( "Ignoring NO_PROXY for to-one association with subclasses to honor laziness" );
}
// entities with subclasses that define a ProxyFactory can create a HibernateProxy
return createProxy( event, persister, keyToLoad, persistenceContext );
}
}

View File

@ -47,8 +47,6 @@ public class LoadEvent extends AbstractEvent {
private Object result;
private PostLoadEvent postLoadEvent;
private Boolean shouldUnwrapProxy;
public LoadEvent(Serializable entityId, Object instanceToLoad, EventSource source) {
this( entityId, null, instanceToLoad, DEFAULT_LOCK_OPTIONS, false, source );
}
@ -192,19 +190,4 @@ public class LoadEvent extends AbstractEvent {
public void setPostLoadEvent(PostLoadEvent postLoadEvent) {
this.postLoadEvent = postLoadEvent;
}
public Boolean getShouldUnwrapProxy() {
if ( shouldUnwrapProxy == null ) {
final boolean enabled = getSession().getFactory()
.getSessionFactoryOptions()
.isEnhancementAsProxyEnabled();
return enabled;
}
return shouldUnwrapProxy;
}
public void setShouldUnwrapProxy(Boolean shouldUnwrapProxy) {
this.shouldUnwrapProxy = shouldUnwrapProxy;
}
}

View File

@ -1014,7 +1014,6 @@ public final class SessionImpl
loadEvent = null;
event = recycleEventInstance( event, id, entityName );
event.setShouldUnwrapProxy( unwrapProxy );
fireLoadNoChecks( event, type );
@ -1054,7 +1053,6 @@ public final class SessionImpl
event.setLockMode( LoadEvent.DEFAULT_LOCK_MODE );
event.setLockScope( LoadEvent.DEFAULT_LOCK_OPTIONS.getScope() );
event.setLockTimeout( LoadEvent.DEFAULT_LOCK_OPTIONS.getTimeOut() );
event.setShouldUnwrapProxy( null );
return event;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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 http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.bootstrap;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.testing.env.ConnectionProviderBuilder;
/**
* @author Steve Ebersole
*/
public class DataSourceStub implements DataSource {
private final String id;
private final DriverManagerConnectionProviderImpl connectionProvider;
private PrintWriter printWriter;
DataSourceStub(String id) {
this.id = id;
connectionProvider = new DriverManagerConnectionProviderImpl();
connectionProvider.configure( ConnectionProviderBuilder.getConnectionProviderProperties() );
printWriter = null;
}
public String getId() {
return id;
}
@Override
public Connection getConnection() throws SQLException {
return connectionProvider.getConnection();
}
@Override
public Connection getConnection(String username, String password) {
throw new UnsupportedOperationException();
}
@Override
public PrintWriter getLogWriter() {
return printWriter;
}
@Override
public void setLogWriter(PrintWriter out) {
this.printWriter = out;
}
@Override
public void setLoginTimeout(int seconds) {
}
@Override
public int getLoginTimeout() {
return -1;
}
@Override
public Logger getParentLogger() {
return Logger.getGlobal();
}
@Override
public <T> T unwrap(Class<T> iface) {
//noinspection unchecked
return (T) this;
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return iface.isAssignableFrom( getClass() );
}
@Override
public String toString() {
return "DataSourceImpl(" + id + ")";
}
}

View File

@ -0,0 +1,54 @@
/*
* 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 http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.bootstrap;
import java.util.Collections;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceProvider;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.testing.util.jpa.PersistenceUnitInfoAdapter;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* @author Steve Ebersole
*/
public class PersistenceUnitInfoTests extends BaseUnitTestCase {
@Test
@TestForIssue( jiraKey = "HHH-13432" )
@FailureExpected( jiraKey = "HHH-13432" )
public void testJtaDataExposedAsProperty() {
final DataSource puDataSource = new DataSourceStub( "puDataSource" );
final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() {
@Override
public DataSource getNonJtaDataSource() {
return puDataSource;
}
};
final PersistenceProvider provider = new HibernatePersistenceProvider();
final EntityManagerFactory emf = provider.createContainerEntityManagerFactory(
info,
Collections.emptyMap()
);
final Map<String, Object> properties = emf.getProperties();
final Object o = properties.get( AvailableSettings.JPA_JTA_DATASOURCE );
assertEquals( o, puDataSource );
}
}

View File

@ -6,13 +6,9 @@
*/
package org.hibernate.orm.test.bootstrap;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.PersistenceProvider;
import javax.persistence.spi.PersistenceUnitInfo;
@ -100,7 +96,7 @@ public class PersistenceUnitOverridesTests extends BaseUnitTestCase {
final Properties puProperties;
{
puDataSource = new DataSourceImpl( "puDataSource" );
puDataSource = new DataSourceStub( "puDataSource" );
puProperties = new Properties();
puProperties.putAll( info.getProperties() );
@ -161,7 +157,7 @@ public class PersistenceUnitOverridesTests extends BaseUnitTestCase {
final DataSource puDataSource;
{
puDataSource = new DataSourceImpl( "puDataSource" );
puDataSource = new DataSourceStub( "puDataSource" );
}
@Override
@ -209,8 +205,8 @@ public class PersistenceUnitOverridesTests extends BaseUnitTestCase {
"have precedence over integration settings, which is also incorrect"
)
public void testPassingIntegrationJpaDataSourceOverrideForJtaDataSourceElement() {
final DataSource puDataSource = new DataSourceImpl( "puDataSource" );
final DataSource integrationDataSource = new DataSourceImpl( "integrationDataSource" );
final DataSource puDataSource = new DataSourceStub( "puDataSource" );
final DataSource integrationDataSource = new DataSourceStub( "integrationDataSource" );
PersistenceProvider provider = new HibernatePersistenceProvider() {
@Override
@ -263,7 +259,7 @@ public class PersistenceUnitOverridesTests extends BaseUnitTestCase {
public void testIntegrationOverridesOfPersistenceXmlDataSource() {
// mimics a DataSource defined in the persistence.xml
final DataSourceImpl dataSource = new DataSourceImpl( "puDataSource" );
final DataSourceStub dataSource = new DataSourceStub( "puDataSource" );
final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() {
@Override
@ -274,7 +270,7 @@ public class PersistenceUnitOverridesTests extends BaseUnitTestCase {
// Now create "integration Map" that overrides the DataSource to use
final DataSource override = new DataSourceImpl( "integrationDataSource" );
final DataSource override = new DataSourceStub( "integrationDataSource" );
final Map<String,Object> integrationSettings = new HashMap<>();
integrationSettings.put( AvailableSettings.JPA_NON_JTA_DATASOURCE, override );
@ -308,7 +304,7 @@ public class PersistenceUnitOverridesTests extends BaseUnitTestCase {
public void testIntegrationOverridesOfPersistenceXmlDataSourceWithDriverManagerInfo() {
// mimics a DataSource defined in the persistence.xml
final DataSourceImpl dataSource = new DataSourceImpl( "puDataSource" );
final DataSourceStub dataSource = new DataSourceStub( "puDataSource" );
final PersistenceUnitInfoAdapter info = new PersistenceUnitInfoAdapter() {
@Override
@ -335,71 +331,4 @@ public class PersistenceUnitOverridesTests extends BaseUnitTestCase {
assertThat( connectionProvider, instanceOf( DriverManagerConnectionProviderImpl.class ) );
}
private static class DataSourceImpl implements DataSource {
private final String id;
private final DriverManagerConnectionProviderImpl connectionProvider;
private PrintWriter printWriter;
DataSourceImpl(String id) {
this.id = id;
connectionProvider = new DriverManagerConnectionProviderImpl();
connectionProvider.configure( ConnectionProviderBuilder.getConnectionProviderProperties() );
printWriter = null;
}
public String getId() {
return id;
}
@Override
public Connection getConnection() throws SQLException {
return connectionProvider.getConnection();
}
@Override
public Connection getConnection(String username, String password) {
throw new UnsupportedOperationException();
}
@Override
public PrintWriter getLogWriter() {
return printWriter;
}
@Override
public void setLogWriter(PrintWriter out) {
this.printWriter = out;
}
@Override
public void setLoginTimeout(int seconds) {
}
@Override
public int getLoginTimeout() {
return -1;
}
@Override
public Logger getParentLogger() {
return Logger.getGlobal();
}
@Override
public <T> T unwrap(Class<T> iface) {
//noinspection unchecked
return (T) this;
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return iface.isAssignableFrom( getClass() );
}
@Override
public String toString() {
return "DataSourceImpl(" + id + ")";
}
}
}

View File

@ -21,6 +21,7 @@ import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
@ -63,7 +64,6 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction
}
@Test
@FailureExpected( jiraKey = "HHH-13640")
public void testNewProxyAssociation() {
inTransaction(
session -> {
@ -80,6 +80,7 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction
final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" );
assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) );
assertFalse( Hibernate.isInitialized( otherEntity.animal ) );
assertTrue( HibernateProxy.class.isInstance( otherEntity.animal ) );
Animal animal = session.load( Animal.class, "A Human" );
assertFalse( Hibernate.isInitialized( animal ) );
}
@ -87,8 +88,7 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction
}
@Test
@FailureExpected( jiraKey = "HHH-13640")
public void testReusedProxyAssociation() {
public void testExistingProxyAssociation() {
inTransaction(
session -> {
Human human = new Human( "A Human" );
@ -105,8 +105,43 @@ public class LazyToOnesProxyWithSubclassesTest extends BaseNonConfigCoreFunction
final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" );
assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) );
assertFalse( Hibernate.isInitialized( otherEntity.animal ) );
assertTrue( HibernateProxy.class.isInstance( otherEntity.animal ) );
assertTrue( Hibernate.isPropertyInitialized( otherEntity, "primate" ) );
assertFalse( Hibernate.isInitialized( otherEntity.primate ) );
assertTrue( HibernateProxy.class.isInstance( otherEntity.primate ) );
}
);
}
@Test
@FailureExpected( jiraKey = "HHH-13640" )
public void testExistingProxyAssociationLeafSubclass() {
inTransaction(
session -> {
Human human = new Human( "A Human" );
OtherEntity otherEntity = new OtherEntity( "test1" );
otherEntity.animal = human;
otherEntity.primate = human;
otherEntity.human = human;
session.persist( human );
session.persist( otherEntity );
}
);
inSession(
session -> {
final OtherEntity otherEntity = session.get( OtherEntity.class, "test1" );
assertTrue( Hibernate.isPropertyInitialized( otherEntity, "animal" ) );
assertFalse( Hibernate.isInitialized( otherEntity.animal ) );
assertTrue( HibernateProxy.class.isInstance( otherEntity.animal ) );
assertTrue( Hibernate.isPropertyInitialized( otherEntity, "primate" ) );
assertFalse( Hibernate.isInitialized( otherEntity.primate ) );
assertTrue( HibernateProxy.class.isInstance( otherEntity.primate ) );
assertTrue( Hibernate.isPropertyInitialized( otherEntity, "human" ) );
assertFalse( Hibernate.isInitialized( otherEntity.human ) );
// TODO: Should otherEntity.human be a narrowed HibernateProxy or
// an uninitialized non-HibernateProxy proxy?
}
);
}

View File

@ -0,0 +1,74 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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.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:
* <ul>
* <li>For basic types, prioritizes audit annotation naming followed by physical column name appended with suffix.</li>
* <li>For associations with single column foreign keys, behaves like basic types.</li>
* <li>For associations with multiple column foreign keys, prioritizes audit annotation naming followed by using property name.</li>
* <li>For embeddables, behaves like associations with multiple column foreign keys</li>
* </ul>
*
* @author Chris Cranford
* @since 5.4.7
*/
public class ImprovedModifiedColumnNamingStrategy extends LegacyModifiedColumnNamingStrategy {
@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
final String columnName;
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
super.addModifiedColumns( globalCfg, value, parent, propertyAuditingData );
}
}

View File

@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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:
* <ul>
* <li>If an audit annotation modified column name is supplied, use it directly with no suffix.</li>
* <li>If no audit annotation modified column name is present, use the property name appended with suffix.</li>
* </ul>
*
* This is the default Envers modified column naming behavior.
*
* @author Chris Cranford
* @since 5.4.7
*/
public class LegacyModifiedColumnNamingStrategy implements ModifiedColumnNamingStrategy {
@Override
public void addModifiedColumns(
GlobalConfiguration globalCfg,
Value value,
Element parent,
PropertyAuditingData propertyAuditingData) {
final String columnName;
if ( propertyAuditingData.isModifiedFlagNameExplicitlySpecified() ) {
columnName = propertyAuditingData.getExplicitModifiedFlagName();
}
else {
columnName = propertyAuditingData.getModifiedFlagName();
}
MetadataTools.addModifiedFlagProperty(
parent,
propertyAuditingData.getName(),
globalCfg.getModifiedFlagSuffix(),
columnName
);
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<StrategyRegistration> getStrategyRegistrations() {
final List<StrategyRegistration> registrations = new ArrayList<>();
registrations.add(
new SimpleStrategyRegistrationImpl(
ModifiedColumnNamingStrategy.class,
LegacyModifiedColumnNamingStrategy.class,
"default", "legacy"
)
);
registrations.add(
new SimpleStrategyRegistrationImpl(
ModifiedColumnNamingStrategy.class,
ImprovedModifiedColumnNamingStrategy.class,
"improved"
)
);
return registrations;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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.7
*/
@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);
}

View File

@ -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.7
*/
String MODIFIED_COLUMN_NAMING_STRATEGY = "org.hibernate.envers.modified_column_naming_strategy";
}

View File

@ -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<ModifiedColumnNamingStrategy>() {
@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;
}
}

View File

@ -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
);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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();

View File

@ -0,0 +1,13 @@
#
# 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
#
#
# 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
#
org.hibernate.envers.boot.internal.ModifiedColumnNamingStrategyRegistrationProvider

View File

@ -22,4 +22,7 @@
<bean id="additionalJaxbMappingProducer" class="org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl"/>
<service ref="additionalJaxbMappingProducer" interface="org.hibernate.boot.spi.AdditionalJaxbMappingProducer"/>
<bean id="modifiedColumnNamingStrategyRegistrationProducer" class="org.hibernate.envers.boot.internal.ModifiedColumnNamingStrategyRegistrationProvider"/>
<service ref="modifiedColumnNamingStrategyRegistrationProducer" interface="org.hibernate.boot.registry.selector.StrategyRegistrationProvider"/>
</blueprint>

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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" ) ) );
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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" ) ) );
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}