HHH-9216 - Add documentation about merging multiple detached representations of the same entity

This commit is contained in:
Vlad Mihalcea 2016-10-26 14:24:27 +03:00
parent bf2bc323f6
commit e469a99c30
4 changed files with 108 additions and 3 deletions

View File

@ -773,6 +773,23 @@ false:: does not allow
|`hibernate.collection_join_subquery`| `true` (default value) or `false` | Setting which indicates whether or not the new JOINS over collection tables should be rewritten to subqueries.
|`hibernate.allow_refresh_detached_entity`| `true` (default value when using Hibernate native bootstrapping) or `false` (default value when using JPA bootstrapping) | Setting that allows to call `javax.persistence.EntityManager#refresh()` or `org.hibernate.Session#refresh()` on a detached instance even when the `org.hibernate.Session` is obtained from a JPA `javax.persistence.EntityManager`.
|`hibernate.event.merge.entity_copy_observer`| `disallow` (default value), `allow`, `log` (testing purpose only) or fully-qualified class name a|
Setting that specifies how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging.
The possible values are:
disallow (the default):: throws `IllegalStateException` if an entity copy is detected
allow:: performs the merge operation on each entity copy that is detected
log:: (provided for testing only) performs the merge operation on each entity copy that is detected and logs information about the entity copies.
This setting requires DEBUG logging be enabled for `org.hibernate.event.internal.EntityCopyAllowedLoggedObserver`.
In addition, the application may customize the behavior by providing an implementation of `org.hibernate.event.spi.EntityCopyObserver` and setting `hibernate.event.merge.entity_copy_observer` to the class name.
When this property is set to `allow` or `log`, Hibernate will merge each entity copy detected while cascading the merge operation.
In the process of merging each entity copy, Hibernate will cascade the merge operation from each entity copy to its associations with `cascade=CascadeType.MERGE` or `CascadeType.ALL`.
The entity state resulting from merging an entity copy will be overwritten when another entity copy is merged.
For more details, check out the <<chapters/pc/PersistenceContext.adoc#pc-merge-gotchas,Merge gotchas>> section.
|=====================================================================================================================================================================================================================================================

View File

@ -384,6 +384,65 @@ include::{sourcedir}/PersistenceContextTest.java[tags=pc-merge-native-example]
----
====
[[pc-merge-gotchas]]
===== Merging gotchas
For example, Hibernate throws `IllegalStateException` when merging a parent entity which has references to 2 detached child entities `child1` and `child2` (obtained from different sessions), and `child1` and `child2` represent the same persistent entity, `Child`.
A new configuration property, `hibernate.event.merge.entity_copy_observer`, controls how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging.
The possible values are:
disallow (the default):: throws `IllegalStateException` if an entity copy is detected
allow:: performs the merge operation on each entity copy that is detected
log:: (provided for testing only) performs the merge operation on each entity copy that is detected and logs information about the entity copies.
This setting requires DEBUG logging be enabled for `org.hibernate.event.internal.EntityCopyAllowedLoggedObserver`.
In addition, the application may customize the behavior by providing an implementation of `org.hibernate.event.spi.EntityCopyObserver` and setting `hibernate.event.merge.entity_copy_observer` to the class name.
When this property is set to `allow` or `log`, Hibernate will merge each entity copy detected while cascading the merge operation.
In the process of merging each entity copy, Hibernate will cascade the merge operation from each entity copy to its associations with `cascade=CascadeType.MERGE` or `CascadeType.ALL`.
The entity state resulting from merging an entity copy will be overwritten when another entity copy is merged.
[WARNING]
====
Because cascade order is undefined, the order in which the entity copies are merged is undefined.
As a result, if property values in the entity copies are not consistent, the resulting entity state will be indeterminate, and data will be lost from all entity copies except for the last one merged.
Therefore, the *last writer wins*.
If an entity copy cascades the merge operation to an association that is (or contains) a new entity, that new entity will be merged (i.e., persisted and the merge operation will be cascaded to its associations according to its mapping),
even if that same association is ultimately overwritten when Hibernate merges a different representation having a different value for its association.
If the association is mapped with `orphanRemoval = true`, the new entity will not be deleted because the semantics of orphanRemoval do not apply if the entity being orphaned is a new entity.
There are known issues when representations of the same persistent entity have different values for a collection.
See https://hibernate.atlassian.net/browse/HHH-9239[HHH-9239] and https://hibernate.atlassian.net/browse/HHH-9240[HHH-9240] for more details.
These issues can cause data loss or corruption.
By setting `hibernate.event.merge.entity_copy_observer` configuration property to `allow` or `log`,
Hibernate will allow entity copies of any type of entity to be merged.
The only way to exclude particular entity classes or associations that contain critical data is to provide a custom implementation of `org.hibernate.event.spi.EntityCopyObserver` with the desired behavior,
and setting `hibernate.event.merge.entity_copy_observer` to the class name.
====
[TIP]
====
Hibernate provides limited DEBUG logging capabilities that can help determine the entity classes for which entity copies were found.
By setting `hibernate.event.merge.entity_copy_observer` to `log` and enabling DEBUG logging for `org.hibernate.event.internal.EntityCopyAllowedLoggedObserver`,
the following will be logged each time an application calls `EntityManager.merge( entity )` or +
`Session.merge( entity )`:
- number of times multiple representations of the same persistent entity was detected summarized by entity name;
- details by entity name and ID, including output from calling toString() on each representation being merged as well as the merge result.
The log should be reviewed to determine if multiple representations of entities containing critical data are detected.
If so, the application should be modified so there is only one representation, and a custom implementation of `org.hibernate.event.spi.EntityCopyObserver` should be provided to disallow entity copies for entities with critical data.
Using optimistic locking is recommended to detect if different representations are from different versions of the same persistent entity.
If they are not from the same version, Hibernate will throw either the JPA `OptimisticLockException` or the native `StaleObjectStateException` depending on your bootstrapping strategy.
====
[[pc-contains]]
=== Checking persistent state

View File

@ -1560,8 +1560,8 @@ public interface AvailableSettings {
String COLLECTION_JOIN_SUBQUERY = "hibernate.collection_join_subquery";
/**
* Setting that allows to call {@link javax.persistence.EntityManager#refresh(Object)} *
* (or {@link org.hibernate.Session#refresh(Object)} on a detached entity instance when the {@link org.hibernate.Session} is obtained from
* Setting that allows to call {@link javax.persistence.EntityManager#refresh(Object)}
* or {@link org.hibernate.Session#refresh(Object)} on a detached entity instance when the {@link org.hibernate.Session} is obtained from
* a JPA {@link javax.persistence.EntityManager}).
* <p>
* <p/>
@ -1572,4 +1572,32 @@ public interface AvailableSettings {
* @since 5.2
*/
String ALLOW_REFRESH_DETACHED_ENTITY = "hibernate.allow_refresh_detached_entity";
/**
* Setting that specifies how Hibernate will respond when multiple representations of the same persistent entity ("entity copy") is detected while merging.
* <p/>
* The possible values are:
*
* <ul>
* <li>disallow (the default): throws {@link java.lang.IllegalStateException} if an entity copy is detected</li>
* <li>allow: performs the merge operation on each entity copy that is detected</li>
* <li>log: (provided for testing only) performs the merge operation on each entity copy that is detected and logs information about the entity copies.
* This setting requires DEBUG logging be enabled for {@link org.hibernate.event.internal.EntityCopyAllowedLoggedObserver}.
* </li>
* </ul>
*
* <p/>
* In addition, the application may customize the behavior by providing an implementation of {@link org.hibernate.event.spi.EntityCopyObserver} and setting {@code hibernate.event.merge.entity_copy_observer} to the class name.
* When this property is set to {@code allow} or {@code log}, Hibernate will merge each entity copy detected while cascading the merge operation.
* In the process of merging each entity copy, Hibernate will cascade the merge operation from each entity copy to its associations with {@code CascadeType.MERGE} or {@code CascadeType.ALL}.
* The entity state resulting from merging an entity copy will be overwritten when another entity copy is merged.
*
* @since 4.3
*/
String MERGE_ENTITY_COPY_OBSERVER = "hibernate.event.merge.entity_copy_observer";
}

View File

@ -15,6 +15,7 @@ import org.hibernate.ObjectDeletedException;
import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.internal.Cascade;
import org.hibernate.engine.internal.CascadePoint;
@ -80,7 +81,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
final ConfigurationService configurationService
= serviceRegistry.getService( ConfigurationService.class );
entityCopyObserverStrategy = configurationService.getSetting(
"hibernate.event.merge.entity_copy_observer",
AvailableSettings.MERGE_ENTITY_COPY_OBSERVER,
new ConfigurationService.Converter<String>() {
@Override
public String convert(Object value) {