HHH-16830 Custom exception handling for applyToLoadByKey associations
This commit is contained in:
parent
d22725a678
commit
e2f7d5d516
|
@ -558,11 +558,12 @@ include::{example-dir-pc}/FilterTest.java[tags=pc-filter-resolver-Account-exampl
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
====
|
====
|
||||||
Filters apply to entity queries, but not to direct fetching, unless otherwise configured using the `applyToLoadById` flag
|
Filters apply to entity queries, but not to direct fetching, unless otherwise configured using the `applyToLoadByKey` flag
|
||||||
on the `@FilterDef`, that should be set to `true` in order to activate the filter with direct fetching.
|
on the `@FilterDef`, that should be set to `true` in order to activate the filter with direct fetching.
|
||||||
|
====
|
||||||
|
|
||||||
Therefore, in the following example, the `activeAccount` filter is not taken into consideration when fetching an entity from the Persistence Context.
|
In the following example, the `activeAccount` filter is not taken into consideration when fetching an entity from the Persistence Context.
|
||||||
On the other hand, the `minimumAmount` filter is taken into consideration, because its `applyToLoadById` flag is set to `true`.
|
On the other hand, the `minimumAmount` filter is taken into consideration, because its `applyToLoadByKey` flag is set to `true`.
|
||||||
|
|
||||||
[[pc-filter-entity-example]]
|
[[pc-filter-entity-example]]
|
||||||
.Fetching entities mapped with `@Filter`
|
.Fetching entities mapped with `@Filter`
|
||||||
|
@ -581,9 +582,15 @@ include::{extrasdir}/pc-filter-entity-example.sql[]
|
||||||
include::{extrasdir}/pc-filter-entity-find-example.sql[]
|
include::{extrasdir}/pc-filter-entity-find-example.sql[]
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[WARNING]
|
||||||
|
====
|
||||||
|
Using `@NotFound(action = NotFoundAction.IGNORE)` on associations that are filtered via a `FilterDef` with `applyToLoadByKey` set to `true`
|
||||||
|
is dangerous, because the association will be set to `null` if the filter excludes the target row.
|
||||||
|
On flush, this can lead to the foreign key column be set to `null` and hence lead to data loss.
|
||||||
|
====
|
||||||
|
|
||||||
As you can see from the example above, contrary to an entity query, the `activeAccount` filter does not prevent the entity from being loaded,
|
As you can see from the example above, contrary to an entity query, the `activeAccount` filter does not prevent the entity from being loaded,
|
||||||
but the `minimumAmount` filter limits the results to the ones with an amount that is greater than the specified one.
|
but the `minimumAmount` filter limits the results to the ones with an amount that is greater than the specified one.
|
||||||
====
|
|
||||||
|
|
||||||
Just like with entity queries, collections can be filtered as well, but only if the filter is enabled on the currently running Hibernate `Session`,
|
Just like with entity queries, collections can be filtered as well, but only if the filter is enabled on the currently running Hibernate `Session`,
|
||||||
either if the filter is enabled explicitly or by setting `autoEnabled` to `true`.
|
either if the filter is enabled explicitly or by setting `autoEnabled` to `true`.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.FilterDef;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown if a filter would make a to-one association {@code null},
|
||||||
|
* which could lead to data loss.
|
||||||
|
* Even though filters are applied to load-by-key operations,
|
||||||
|
* a to-one association should never refer to an entity that is filtered.
|
||||||
|
*
|
||||||
|
* @see FilterDef#applyToLoadByKey()
|
||||||
|
*/
|
||||||
|
public class EntityFilterException extends EntityNotFoundException {
|
||||||
|
private final String entityName;
|
||||||
|
private final Object identifier;
|
||||||
|
private final String role;
|
||||||
|
|
||||||
|
public EntityFilterException(String entityName, Object identifier, String role) {
|
||||||
|
super(
|
||||||
|
String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"Entity `%s` with identifier value `%s` is filtered for association `%s`",
|
||||||
|
entityName,
|
||||||
|
identifier,
|
||||||
|
role
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.entityName = entityName;
|
||||||
|
this.identifier = identifier;
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntityName() {
|
||||||
|
return entityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getIdentifier() {
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,10 +97,10 @@ public interface Filter {
|
||||||
boolean isAutoEnabled();
|
boolean isAutoEnabled();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the associated {@link FilterDefinition applyToLoadById} of this
|
* Get the associated {@link FilterDefinition applyToLoadByKey} of this
|
||||||
* named filter.
|
* named filter.
|
||||||
*
|
*
|
||||||
* @return The flag value
|
* @return The flag value
|
||||||
*/
|
*/
|
||||||
boolean isApplyToLoadByKey();
|
boolean isAppliedToLoadByKey();
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class FilterDefinition implements Serializable {
|
||||||
*
|
*
|
||||||
* @return The flag value.
|
* @return The flag value.
|
||||||
*/
|
*/
|
||||||
public boolean isApplyToLoadByKey() {
|
public boolean isAppliedToLoadByKey() {
|
||||||
return applyToLoadByKey;
|
return applyToLoadByKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ public class FilterHelper {
|
||||||
public boolean isAffectedBy(Map<String, Filter> enabledFilters, boolean onlyApplyForLoadByKey) {
|
public boolean isAffectedBy(Map<String, Filter> enabledFilters, boolean onlyApplyForLoadByKey) {
|
||||||
for ( String filterName : filterNames ) {
|
for ( String filterName : filterNames ) {
|
||||||
Filter filter = enabledFilters.get( filterName );
|
Filter filter = enabledFilters.get( filterName );
|
||||||
if ( filter != null && ( !onlyApplyForLoadByKey || filter.isApplyToLoadByKey() ) ) {
|
if ( filter != null && ( !onlyApplyForLoadByKey || filter.isAppliedToLoadByKey() ) ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,7 @@ public class FilterHelper {
|
||||||
for ( int i = 0, max = filterNames.length; i < max; i++ ) {
|
for ( int i = 0, max = filterNames.length; i < max; i++ ) {
|
||||||
final String filterName = filterNames[i];
|
final String filterName = filterNames[i];
|
||||||
final FilterImpl enabledFilter = (FilterImpl) enabledFilters.get( filterName );
|
final FilterImpl enabledFilter = (FilterImpl) enabledFilters.get( filterName );
|
||||||
if ( enabledFilter != null && ( !onlyApplyLoadByKeyFilters || enabledFilter.isApplyToLoadByKey() ) ) {
|
if ( enabledFilter != null && ( !onlyApplyLoadByKeyFilters || enabledFilter.isAppliedToLoadByKey() ) ) {
|
||||||
filterPredicate.applyFragment( render( aliasGenerator, i, tableGroup, creationState ), enabledFilter, parameterNames[i] );
|
filterPredicate.applyFragment( render( aliasGenerator, i, tableGroup, creationState ), enabledFilter, parameterNames[i] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class FilterImpl implements Filter, Serializable {
|
||||||
this.definition = configuration;
|
this.definition = configuration;
|
||||||
filterName = definition.getFilterName();
|
filterName = definition.getFilterName();
|
||||||
this.autoEnabled = definition.isAutoEnabled();
|
this.autoEnabled = definition.isAutoEnabled();
|
||||||
this.applyToLoadByKey = definition.isApplyToLoadByKey();
|
this.applyToLoadByKey = definition.isAppliedToLoadByKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FilterDefinition getFilterDefinition() {
|
public FilterDefinition getFilterDefinition() {
|
||||||
|
@ -80,7 +80,7 @@ public class FilterImpl implements Filter, Serializable {
|
||||||
*
|
*
|
||||||
* @return The flag value.
|
* @return The flag value.
|
||||||
*/
|
*/
|
||||||
public boolean isApplyToLoadByKey() {
|
public boolean isAppliedToLoadByKey() {
|
||||||
return applyToLoadByKey;
|
return applyToLoadByKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.TimeZone;
|
||||||
|
|
||||||
import org.hibernate.CacheMode;
|
import org.hibernate.CacheMode;
|
||||||
import org.hibernate.ConnectionAcquisitionMode;
|
import org.hibernate.ConnectionAcquisitionMode;
|
||||||
|
import org.hibernate.EntityFilterException;
|
||||||
import org.hibernate.FetchNotFoundException;
|
import org.hibernate.FetchNotFoundException;
|
||||||
import org.hibernate.FlushMode;
|
import org.hibernate.FlushMode;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
@ -2465,6 +2466,13 @@ public class SessionImpl
|
||||||
if ( enfe instanceof FetchNotFoundException ) {
|
if ( enfe instanceof FetchNotFoundException ) {
|
||||||
throw enfe;
|
throw enfe;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
This may happen if the entity has an associations which is filtered by a FilterDef
|
||||||
|
and this associated entity is not found.
|
||||||
|
*/
|
||||||
|
if ( enfe instanceof EntityFilterException ) {
|
||||||
|
throw enfe;
|
||||||
|
}
|
||||||
// DefaultLoadEventListener#returnNarrowedProxy() may throw ENFE (see HHH-7861 for details),
|
// DefaultLoadEventListener#returnNarrowedProxy() may throw ENFE (see HHH-7861 for details),
|
||||||
// which find() should not throw. Find() should return null if the entity was not found.
|
// which find() should not throw. Find() should return null if the entity was not found.
|
||||||
if ( log.isDebugEnabled() ) {
|
if ( log.isDebugEnabled() ) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.cache.MutableCacheKeyBuilder;
|
import org.hibernate.cache.MutableCacheKeyBuilder;
|
||||||
import org.hibernate.engine.FetchStyle;
|
import org.hibernate.engine.FetchStyle;
|
||||||
import org.hibernate.engine.FetchTiming;
|
import org.hibernate.engine.FetchTiming;
|
||||||
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.internal.util.IndexedConsumer;
|
import org.hibernate.internal.util.IndexedConsumer;
|
||||||
|
@ -270,7 +271,7 @@ public class ToOneAttributeMapping
|
||||||
);
|
);
|
||||||
if ( bootValue instanceof ManyToOne ) {
|
if ( bootValue instanceof ManyToOne ) {
|
||||||
final ManyToOne manyToOne = (ManyToOne) bootValue;
|
final ManyToOne manyToOne = (ManyToOne) bootValue;
|
||||||
this.notFoundAction = determineNotFoundAction( ( (ManyToOne) bootValue ).getNotFoundAction(), entityMappingType );
|
this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction();
|
||||||
if ( manyToOne.isLogicalOneToOne() ) {
|
if ( manyToOne.isLogicalOneToOne() ) {
|
||||||
cardinality = Cardinality.LOGICAL_ONE_TO_ONE;
|
cardinality = Cardinality.LOGICAL_ONE_TO_ONE;
|
||||||
}
|
}
|
||||||
|
@ -424,7 +425,7 @@ public class ToOneAttributeMapping
|
||||||
else {
|
else {
|
||||||
this.bidirectionalAttributePath = SelectablePath.parse( oneToOne.getMappedByProperty() );
|
this.bidirectionalAttributePath = SelectablePath.parse( oneToOne.getMappedByProperty() );
|
||||||
}
|
}
|
||||||
notFoundAction = determineNotFoundAction( null, entityMappingType );
|
notFoundAction = null;
|
||||||
isKeyTableNullable = isNullable();
|
isKeyTableNullable = isNullable();
|
||||||
isOptional = !bootValue.isConstrained();
|
isOptional = !bootValue.isConstrained();
|
||||||
isInternalLoadNullable = isNullable();
|
isInternalLoadNullable = isNullable();
|
||||||
|
@ -569,16 +570,6 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotFoundAction determineNotFoundAction(NotFoundAction notFoundAction, EntityMappingType entityMappingType) {
|
|
||||||
// When a filter exists that affects a singular association, we have to enable NotFound handling
|
|
||||||
// to force an exception if the filter would result in the entity not being found.
|
|
||||||
// If we silently just read null, this could lead to data loss on flush
|
|
||||||
if ( entityMappingType.getEntityPersister().hasFilterForLoadByKey() && notFoundAction == null ) {
|
|
||||||
return NotFoundAction.EXCEPTION;
|
|
||||||
}
|
|
||||||
return notFoundAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SelectablePath findBidirectionalOneToManyAttributeName(
|
private static SelectablePath findBidirectionalOneToManyAttributeName(
|
||||||
String propertyPath,
|
String propertyPath,
|
||||||
ManagedMappingType declaringType,
|
ManagedMappingType declaringType,
|
||||||
|
@ -657,9 +648,6 @@ public class ToOneAttributeMapping
|
||||||
return FetchTiming.IMMEDIATE;
|
return FetchTiming.IMMEDIATE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( entityMappingType.getEntityPersister().hasFilterForLoadByKey() ) {
|
|
||||||
return FetchTiming.IMMEDIATE;
|
|
||||||
}
|
|
||||||
return mappedFetchTiming;
|
return mappedFetchTiming;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1330,6 +1318,7 @@ public class ToOneAttributeMapping
|
||||||
this,
|
this,
|
||||||
tableGroup,
|
tableGroup,
|
||||||
keyDomainResult,
|
keyDomainResult,
|
||||||
|
false,
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
|
@ -1371,6 +1360,7 @@ public class ToOneAttributeMapping
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
domainResult,
|
domainResult,
|
||||||
isSelectByUniqueKey( sideNature ),
|
isSelectByUniqueKey( sideNature ),
|
||||||
|
false,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1419,6 +1409,7 @@ public class ToOneAttributeMapping
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
boolean selectByUniqueKey,
|
boolean selectByUniqueKey,
|
||||||
|
boolean isAffectedByFilter,
|
||||||
@SuppressWarnings("unused") DomainResultCreationState creationState) {
|
@SuppressWarnings("unused") DomainResultCreationState creationState) {
|
||||||
return new EntityFetchSelectImpl(
|
return new EntityFetchSelectImpl(
|
||||||
fetchParent,
|
fetchParent,
|
||||||
|
@ -1426,6 +1417,7 @@ public class ToOneAttributeMapping
|
||||||
navigablePath,
|
navigablePath,
|
||||||
keyResult,
|
keyResult,
|
||||||
selectByUniqueKey,
|
selectByUniqueKey,
|
||||||
|
isAffectedByFilter,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1438,6 +1430,7 @@ public class ToOneAttributeMapping
|
||||||
ToOneAttributeMapping toOneMapping,
|
ToOneAttributeMapping toOneMapping,
|
||||||
TableGroup tableGroup,
|
TableGroup tableGroup,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean isAffectedByFilter,
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
DomainResultCreationState creationState) {
|
DomainResultCreationState creationState) {
|
||||||
return new EntityFetchJoinedImpl(
|
return new EntityFetchJoinedImpl(
|
||||||
|
@ -1445,6 +1438,7 @@ public class ToOneAttributeMapping
|
||||||
toOneMapping,
|
toOneMapping,
|
||||||
tableGroup,
|
tableGroup,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
isAffectedByFilter,
|
||||||
navigablePath,
|
navigablePath,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
|
@ -1576,11 +1570,15 @@ public class ToOneAttributeMapping
|
||||||
|
|
||||||
return withRegisteredAssociationKeys(
|
return withRegisteredAssociationKeys(
|
||||||
() -> {
|
() -> {
|
||||||
|
// When a filter exists that affects a singular association, we have to enable NotFound handling
|
||||||
|
// to force an exception if the filter would result in the entity not being found.
|
||||||
|
// If we silently just read null, this could lead to data loss on flush
|
||||||
|
final boolean affectedByEnabledFilters = isAffectedByEnabledFilters( creationState );
|
||||||
DomainResult<?> keyResult = null;
|
DomainResult<?> keyResult = null;
|
||||||
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
|
||||||
// If the key side is non-nullable we also need to add the keyResult
|
// If the key side is non-nullable we also need to add the keyResult
|
||||||
// to be able to manually check invalid foreign key references
|
// to be able to manually check invalid foreign key references
|
||||||
if ( hasNotFoundAction() || !isInternalLoadNullable ) {
|
if ( hasNotFoundAction() || !isInternalLoadNullable || affectedByEnabledFilters ) {
|
||||||
keyResult = foreignKeyDescriptor.createKeyDomainResult(
|
keyResult = foreignKeyDescriptor.createKeyDomainResult(
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
tableGroup,
|
tableGroup,
|
||||||
|
@ -1590,7 +1588,8 @@ public class ToOneAttributeMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( hasNotFoundAction()
|
else if ( hasNotFoundAction()
|
||||||
|| getAssociatedEntityMappingType().getSoftDeleteMapping() != null ) {
|
|| getAssociatedEntityMappingType().getSoftDeleteMapping() != null
|
||||||
|
|| affectedByEnabledFilters ) {
|
||||||
// For the target side only add keyResult when a not-found action is present
|
// For the target side only add keyResult when a not-found action is present
|
||||||
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
|
@ -1605,6 +1604,7 @@ public class ToOneAttributeMapping
|
||||||
this,
|
this,
|
||||||
tableGroup,
|
tableGroup,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
affectedByEnabledFilters,
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
|
@ -1679,6 +1679,7 @@ public class ToOneAttributeMapping
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
keyResult,
|
keyResult,
|
||||||
selectByUniqueKey,
|
selectByUniqueKey,
|
||||||
|
isAffectedByEnabledFilters( creationState ),
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1697,6 +1698,12 @@ public class ToOneAttributeMapping
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAffectedByEnabledFilters(DomainResultCreationState creationState) {
|
||||||
|
final LoadQueryInfluencers loadQueryInfluencers = creationState.getSqlAstCreationState()
|
||||||
|
.getLoadQueryInfluencers();
|
||||||
|
return entityMappingType.isAffectedByEnabledFilters( loadQueryInfluencers, true );
|
||||||
|
}
|
||||||
|
|
||||||
private boolean needsImmediateFetch(FetchTiming fetchTiming) {
|
private boolean needsImmediateFetch(FetchTiming fetchTiming) {
|
||||||
if ( fetchTiming == FetchTiming.IMMEDIATE ) {
|
if ( fetchTiming == FetchTiming.IMMEDIATE ) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -72,7 +72,6 @@ import org.hibernate.classic.Lifecycle;
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
import org.hibernate.collection.spi.PersistentCollection;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.dialect.lock.LockingStrategy;
|
import org.hibernate.dialect.lock.LockingStrategy;
|
||||||
import org.hibernate.engine.FetchStyle;
|
|
||||||
import org.hibernate.engine.FetchTiming;
|
import org.hibernate.engine.FetchTiming;
|
||||||
import org.hibernate.engine.OptimisticLockStyle;
|
import org.hibernate.engine.OptimisticLockStyle;
|
||||||
import org.hibernate.engine.internal.CacheHelper;
|
import org.hibernate.engine.internal.CacheHelper;
|
||||||
|
@ -280,7 +279,6 @@ import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
|
||||||
import org.hibernate.sql.results.graph.DomainResult;
|
import org.hibernate.sql.results.graph.DomainResult;
|
||||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||||
import org.hibernate.sql.results.graph.Fetch;
|
import org.hibernate.sql.results.graph.Fetch;
|
||||||
import org.hibernate.sql.results.graph.FetchOptions;
|
|
||||||
import org.hibernate.sql.results.graph.FetchParent;
|
import org.hibernate.sql.results.graph.FetchParent;
|
||||||
import org.hibernate.sql.results.graph.Fetchable;
|
import org.hibernate.sql.results.graph.Fetchable;
|
||||||
import org.hibernate.sql.results.graph.FetchableContainer;
|
import org.hibernate.sql.results.graph.FetchableContainer;
|
||||||
|
@ -1260,7 +1258,7 @@ public abstract class AbstractEntityPersister
|
||||||
public boolean hasFilterForLoadByKey() {
|
public boolean hasFilterForLoadByKey() {
|
||||||
if ( filterHelper != null ) {
|
if ( filterHelper != null ) {
|
||||||
for ( String filterName : filterHelper.getFilterNames() ) {
|
for ( String filterName : filterHelper.getFilterNames() ) {
|
||||||
if ( factory.getFilterDefinition( filterName ).isApplyToLoadByKey() ) {
|
if ( factory.getFilterDefinition( filterName ).isAppliedToLoadByKey() ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,7 @@ public class EntityResultImpl implements EntityResult, InitializerProducer<Entit
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
NotFoundAction.EXCEPTION,
|
NotFoundAction.EXCEPTION,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
creationState
|
creationState
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.sql.results.graph.entity.internal;
|
package org.hibernate.sql.results.graph.entity.internal;
|
||||||
|
|
||||||
|
import org.hibernate.EntityFilterException;
|
||||||
|
import org.hibernate.FetchNotFoundException;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.engine.spi.EntityHolder;
|
import org.hibernate.engine.spi.EntityHolder;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
|
@ -52,8 +55,9 @@ public abstract class AbstractBatchEntitySelectFetchInitializer<Data extends Abs
|
||||||
NavigablePath fetchedNavigable,
|
NavigablePath fetchedNavigable,
|
||||||
EntityPersister concreteDescriptor,
|
EntityPersister concreteDescriptor,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean affectedByFilter,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
super( parent, toOneMapping, fetchedNavigable, concreteDescriptor, keyResult, creationState );
|
super( parent, toOneMapping, fetchedNavigable, concreteDescriptor, keyResult, affectedByFilter, creationState );
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
this.owningEntityInitializer = (EntityInitializer<InitializerData>) Initializer.findOwningEntityInitializer( parent );
|
this.owningEntityInitializer = (EntityInitializer<InitializerData>) Initializer.findOwningEntityInitializer( parent );
|
||||||
assert owningEntityInitializer != null : "This initializer requires an owning parent entity initializer";
|
assert owningEntityInitializer != null : "This initializer requires an owning parent entity initializer";
|
||||||
|
@ -231,14 +235,30 @@ public abstract class AbstractBatchEntitySelectFetchInitializer<Data extends Abs
|
||||||
|
|
||||||
protected static Object loadInstance(
|
protected static Object loadInstance(
|
||||||
EntityKey entityKey,
|
EntityKey entityKey,
|
||||||
ToOneAttributeMapping referencedModelPart,
|
ToOneAttributeMapping toOneMapping,
|
||||||
|
boolean affectedByFilter,
|
||||||
SharedSessionContractImplementor session) {
|
SharedSessionContractImplementor session) {
|
||||||
return session.internalLoad(
|
final Object instance = session.internalLoad(
|
||||||
entityKey.getEntityName(),
|
entityKey.getEntityName(),
|
||||||
entityKey.getIdentifier(),
|
entityKey.getIdentifier(),
|
||||||
true,
|
true,
|
||||||
referencedModelPart.isInternalLoadNullable()
|
toOneMapping.isInternalLoadNullable()
|
||||||
);
|
);
|
||||||
|
if ( instance == null ) {
|
||||||
|
if ( toOneMapping.getNotFoundAction() != NotFoundAction.IGNORE ) {
|
||||||
|
if ( affectedByFilter ) {
|
||||||
|
throw new EntityFilterException(
|
||||||
|
entityKey.getEntityName(),
|
||||||
|
entityKey.getIdentifier(),
|
||||||
|
toOneMapping.getNavigableRole().getFullPath()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) {
|
||||||
|
throw new FetchNotFoundException( entityKey.getEntityName(), entityKey.getIdentifier() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AttributeMapping[] getParentEntityAttributes(String attributeName) {
|
protected AttributeMapping[] getParentEntityAttributes(String attributeName) {
|
||||||
|
|
|
@ -67,8 +67,9 @@ public class BatchEntityInsideEmbeddableSelectFetchInitializer extends AbstractB
|
||||||
NavigablePath fetchedNavigable,
|
NavigablePath fetchedNavigable,
|
||||||
EntityPersister concreteDescriptor,
|
EntityPersister concreteDescriptor,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean affectedByFilter,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
super( parentAccess, referencedModelPart, fetchedNavigable, concreteDescriptor, keyResult, creationState );
|
super( parentAccess, referencedModelPart, fetchedNavigable, concreteDescriptor, keyResult, affectedByFilter, creationState );
|
||||||
|
|
||||||
this.referencedModelPartSetter = referencedModelPart.getAttributeMetadata().getPropertyAccess().getSetter();
|
this.referencedModelPartSetter = referencedModelPart.getAttributeMetadata().getPropertyAccess().getSetter();
|
||||||
final String rootEmbeddablePropertyName = getRootEmbeddablePropertyName(
|
final String rootEmbeddablePropertyName = getRootEmbeddablePropertyName(
|
||||||
|
@ -172,7 +173,7 @@ public class BatchEntityInsideEmbeddableSelectFetchInitializer extends AbstractB
|
||||||
data.toBatchLoad.forEach(
|
data.toBatchLoad.forEach(
|
||||||
(entityKey, parentInfos) -> {
|
(entityKey, parentInfos) -> {
|
||||||
final SharedSessionContractImplementor session = data.getRowProcessingState().getSession();
|
final SharedSessionContractImplementor session = data.getRowProcessingState().getSession();
|
||||||
final Object loadedInstance = loadInstance( entityKey, toOneMapping, session );
|
final Object loadedInstance = loadInstance( entityKey, toOneMapping, affectedByFilter, session );
|
||||||
for ( ParentInfo parentInfo : parentInfos ) {
|
for ( ParentInfo parentInfo : parentInfos ) {
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
final PersistenceContext persistenceContext = session.getPersistenceContext();
|
||||||
final EntityEntry parentEntityEntry = persistenceContext.getEntry( parentInfo.parentEntityInstance );
|
final EntityEntry parentEntityEntry = persistenceContext.getEntry( parentInfo.parentEntityInstance );
|
||||||
|
|
|
@ -46,8 +46,9 @@ public class BatchEntitySelectFetchInitializer extends AbstractBatchEntitySelect
|
||||||
NavigablePath fetchedNavigable,
|
NavigablePath fetchedNavigable,
|
||||||
EntityPersister concreteDescriptor,
|
EntityPersister concreteDescriptor,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean affectedByFilter,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
super( parentAccess, referencedModelPart, fetchedNavigable, concreteDescriptor, keyResult, creationState );
|
super( parentAccess, referencedModelPart, fetchedNavigable, concreteDescriptor, keyResult, affectedByFilter, creationState );
|
||||||
this.parentAttributes = getParentEntityAttributes( referencedModelPart.getAttributeName() );
|
this.parentAttributes = getParentEntityAttributes( referencedModelPart.getAttributeName() );
|
||||||
this.referencedModelPartSetter = referencedModelPart.getPropertyAccess().getSetter();
|
this.referencedModelPartSetter = referencedModelPart.getPropertyAccess().getSetter();
|
||||||
this.referencedModelPartType = referencedModelPart.findContainingEntityMapping().getEntityPersister()
|
this.referencedModelPartType = referencedModelPart.findContainingEntityMapping().getEntityPersister()
|
||||||
|
@ -95,7 +96,7 @@ public class BatchEntitySelectFetchInitializer extends AbstractBatchEntitySelect
|
||||||
data.toBatchLoad.forEach(
|
data.toBatchLoad.forEach(
|
||||||
(entityKey, parentInfos) -> {
|
(entityKey, parentInfos) -> {
|
||||||
final SharedSessionContractImplementor session = data.getRowProcessingState().getSession();
|
final SharedSessionContractImplementor session = data.getRowProcessingState().getSession();
|
||||||
final Object instance = loadInstance( entityKey, toOneMapping, session );
|
final Object instance = loadInstance( entityKey, toOneMapping, affectedByFilter, session );
|
||||||
for ( ParentInfo parentInfo : parentInfos ) {
|
for ( ParentInfo parentInfo : parentInfos ) {
|
||||||
final Object parentInstance = parentInfo.parentInstance;
|
final Object parentInstance = parentInfo.parentInstance;
|
||||||
final EntityEntry entry = session.getPersistenceContext().getEntry( parentInstance );
|
final EntityEntry entry = session.getPersistenceContext().getEntry( parentInstance );
|
||||||
|
|
|
@ -41,8 +41,9 @@ public class BatchInitializeEntitySelectFetchInitializer extends AbstractBatchEn
|
||||||
NavigablePath fetchedNavigable,
|
NavigablePath fetchedNavigable,
|
||||||
EntityPersister concreteDescriptor,
|
EntityPersister concreteDescriptor,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean affectedByFilter,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
super( parent, referencedModelPart, fetchedNavigable, concreteDescriptor, keyResult, creationState );
|
super( parent, referencedModelPart, fetchedNavigable, concreteDescriptor, keyResult, affectedByFilter, creationState );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,7 +74,7 @@ public class BatchInitializeEntitySelectFetchInitializer extends AbstractBatchEn
|
||||||
super.endLoading( data );
|
super.endLoading( data );
|
||||||
final SharedSessionContractImplementor session = data.getRowProcessingState().getSession();
|
final SharedSessionContractImplementor session = data.getRowProcessingState().getSession();
|
||||||
for ( EntityKey key : data.toBatchLoad ) {
|
for ( EntityKey key : data.toBatchLoad ) {
|
||||||
loadInstance( key, toOneMapping, session );
|
loadInstance( key, toOneMapping, affectedByFilter, session );
|
||||||
}
|
}
|
||||||
data.toBatchLoad.clear();
|
data.toBatchLoad.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ public class EntityFetchJoinedImpl implements EntityFetch, FetchParent, Initiali
|
||||||
private final EntityResultImpl entityResult;
|
private final EntityResultImpl entityResult;
|
||||||
private final DomainResult<?> keyResult;
|
private final DomainResult<?> keyResult;
|
||||||
private final NotFoundAction notFoundAction;
|
private final NotFoundAction notFoundAction;
|
||||||
|
private final boolean isAffectedByFilter;
|
||||||
|
|
||||||
private final String sourceAlias;
|
private final String sourceAlias;
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ public class EntityFetchJoinedImpl implements EntityFetch, FetchParent, Initiali
|
||||||
ToOneAttributeMapping toOneMapping,
|
ToOneAttributeMapping toOneMapping,
|
||||||
TableGroup tableGroup,
|
TableGroup tableGroup,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean isAffectedByFilter,
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
DomainResultCreationState creationState) {
|
DomainResultCreationState creationState) {
|
||||||
this.fetchContainer = toOneMapping;
|
this.fetchContainer = toOneMapping;
|
||||||
|
@ -54,7 +56,7 @@ public class EntityFetchJoinedImpl implements EntityFetch, FetchParent, Initiali
|
||||||
this.keyResult = keyResult;
|
this.keyResult = keyResult;
|
||||||
this.notFoundAction = toOneMapping.getNotFoundAction();
|
this.notFoundAction = toOneMapping.getNotFoundAction();
|
||||||
this.sourceAlias = tableGroup.getSourceAlias();
|
this.sourceAlias = tableGroup.getSourceAlias();
|
||||||
|
this.isAffectedByFilter = isAffectedByFilter;
|
||||||
this.entityResult = new EntityResultImpl(
|
this.entityResult = new EntityResultImpl(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
toOneMapping,
|
toOneMapping,
|
||||||
|
@ -76,7 +78,7 @@ public class EntityFetchJoinedImpl implements EntityFetch, FetchParent, Initiali
|
||||||
this.notFoundAction = collectionPart.getNotFoundAction();
|
this.notFoundAction = collectionPart.getNotFoundAction();
|
||||||
this.keyResult = null;
|
this.keyResult = null;
|
||||||
this.sourceAlias = tableGroup.getSourceAlias();
|
this.sourceAlias = tableGroup.getSourceAlias();
|
||||||
|
this.isAffectedByFilter = false;
|
||||||
this.entityResult = new EntityResultImpl(
|
this.entityResult = new EntityResultImpl(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
collectionPart,
|
collectionPart,
|
||||||
|
@ -96,6 +98,7 @@ public class EntityFetchJoinedImpl implements EntityFetch, FetchParent, Initiali
|
||||||
this.entityResult = original.entityResult;
|
this.entityResult = original.entityResult;
|
||||||
this.keyResult = original.keyResult;
|
this.keyResult = original.keyResult;
|
||||||
this.notFoundAction = original.notFoundAction;
|
this.notFoundAction = original.notFoundAction;
|
||||||
|
this.isAffectedByFilter = original.isAffectedByFilter;
|
||||||
this.sourceAlias = original.sourceAlias;
|
this.sourceAlias = original.sourceAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +156,7 @@ public class EntityFetchJoinedImpl implements EntityFetch, FetchParent, Initiali
|
||||||
keyResult,
|
keyResult,
|
||||||
entityResult.getRowIdResult(),
|
entityResult.getRowIdResult(),
|
||||||
notFoundAction,
|
notFoundAction,
|
||||||
|
isAffectedByFilter,
|
||||||
parent,
|
parent,
|
||||||
false,
|
false,
|
||||||
creationState
|
creationState
|
||||||
|
@ -214,6 +218,10 @@ public class EntityFetchJoinedImpl implements EntityFetch, FetchParent, Initiali
|
||||||
return notFoundAction;
|
return notFoundAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean isAffectedByFilter() {
|
||||||
|
return isAffectedByFilter;
|
||||||
|
}
|
||||||
|
|
||||||
protected String getSourceAlias() {
|
protected String getSourceAlias() {
|
||||||
return sourceAlias;
|
return sourceAlias;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,18 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||||
*/
|
*/
|
||||||
public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
||||||
|
|
||||||
|
private final boolean isAffectedByFilter;
|
||||||
|
|
||||||
public EntityFetchSelectImpl(
|
public EntityFetchSelectImpl(
|
||||||
FetchParent fetchParent,
|
FetchParent fetchParent,
|
||||||
ToOneAttributeMapping fetchedAttribute,
|
ToOneAttributeMapping fetchedAttribute,
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
boolean selectByUniqueKey,
|
boolean selectByUniqueKey,
|
||||||
|
boolean isAffectedByFilter,
|
||||||
DomainResultCreationState creationState) {
|
DomainResultCreationState creationState) {
|
||||||
super( navigablePath, fetchedAttribute, fetchParent, keyResult, false, selectByUniqueKey, creationState );
|
super( navigablePath, fetchedAttribute, fetchParent, keyResult, false, selectByUniqueKey, creationState );
|
||||||
|
this.isAffectedByFilter = isAffectedByFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,6 +49,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
||||||
original.getDiscriminatorFetch(),
|
original.getDiscriminatorFetch(),
|
||||||
original.isSelectByUniqueKey()
|
original.isSelectByUniqueKey()
|
||||||
);
|
);
|
||||||
|
this.isAffectedByFilter = original.isAffectedByFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,6 +57,10 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
||||||
return FetchTiming.IMMEDIATE;
|
return FetchTiming.IMMEDIATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAffectedByFilter() {
|
||||||
|
return isAffectedByFilter;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EntityInitializer<?> createInitializer(InitializerParent<?> parent, AssemblerCreationState creationState) {
|
public EntityInitializer<?> createInitializer(InitializerParent<?> parent, AssemblerCreationState creationState) {
|
||||||
return EntitySelectFetchInitializerBuilder.createInitializer(
|
return EntitySelectFetchInitializerBuilder.createInitializer(
|
||||||
|
@ -61,6 +70,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
|
||||||
getKeyResult(),
|
getKeyResult(),
|
||||||
getNavigablePath(),
|
getNavigablePath(),
|
||||||
isSelectByUniqueKey(),
|
isSelectByUniqueKey(),
|
||||||
|
isAffectedByFilter(),
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.sql.results.graph.entity.internal;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.hibernate.EntityFilterException;
|
||||||
import org.hibernate.FetchNotFoundException;
|
import org.hibernate.FetchNotFoundException;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
@ -100,6 +101,7 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
||||||
private final String sourceAlias;
|
private final String sourceAlias;
|
||||||
private final @Nullable InitializerParent<?> parent;
|
private final @Nullable InitializerParent<?> parent;
|
||||||
private final NotFoundAction notFoundAction;
|
private final NotFoundAction notFoundAction;
|
||||||
|
private final boolean affectedByFilter;
|
||||||
private final boolean isPartOfKey;
|
private final boolean isPartOfKey;
|
||||||
private final boolean isResultInitializer;
|
private final boolean isResultInitializer;
|
||||||
private final boolean hasKeyManyToOne;
|
private final boolean hasKeyManyToOne;
|
||||||
|
@ -141,6 +143,7 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
||||||
@Nullable DomainResult<?> keyResult,
|
@Nullable DomainResult<?> keyResult,
|
||||||
@Nullable DomainResult<Object> rowIdResult,
|
@Nullable DomainResult<Object> rowIdResult,
|
||||||
NotFoundAction notFoundAction,
|
NotFoundAction notFoundAction,
|
||||||
|
boolean affectedByFilter,
|
||||||
@Nullable InitializerParent<?> parent,
|
@Nullable InitializerParent<?> parent,
|
||||||
boolean isResultInitializer,
|
boolean isResultInitializer,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
|
@ -249,6 +252,7 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
||||||
this.notFoundAction = notFoundAction;
|
this.notFoundAction = notFoundAction;
|
||||||
|
|
||||||
this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState );
|
this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState );
|
||||||
|
this.affectedByFilter = affectedByFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -446,6 +450,13 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
||||||
final Object fkKeyValue = keyAssembler.assemble( data.getRowProcessingState() );
|
final Object fkKeyValue = keyAssembler.assemble( data.getRowProcessingState() );
|
||||||
if ( fkKeyValue != null ) {
|
if ( fkKeyValue != null ) {
|
||||||
if ( notFoundAction != NotFoundAction.IGNORE ) {
|
if ( notFoundAction != NotFoundAction.IGNORE ) {
|
||||||
|
if ( affectedByFilter ) {
|
||||||
|
throw new EntityFilterException(
|
||||||
|
getEntityDescriptor().getEntityName(),
|
||||||
|
fkKeyValue,
|
||||||
|
referencedModelPart.getNavigableRole().getFullPath()
|
||||||
|
);
|
||||||
|
}
|
||||||
throw new FetchNotFoundException(
|
throw new FetchNotFoundException(
|
||||||
getEntityDescriptor().getEntityName(),
|
getEntityDescriptor().getEntityName(),
|
||||||
fkKeyValue
|
fkKeyValue
|
||||||
|
|
|
@ -105,6 +105,7 @@ public class EntityResultImpl extends AbstractEntityResultGraphNode
|
||||||
null,
|
null,
|
||||||
getRowIdResult(),
|
getRowIdResult(),
|
||||||
NotFoundAction.EXCEPTION,
|
NotFoundAction.EXCEPTION,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
creationState
|
creationState
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.sql.results.graph.entity.internal;
|
package org.hibernate.sql.results.graph.entity.internal;
|
||||||
|
|
||||||
|
import org.hibernate.EntityFilterException;
|
||||||
|
import org.hibernate.FetchNotFoundException;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
@ -29,8 +32,9 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn
|
||||||
NavigablePath fetchedNavigable,
|
NavigablePath fetchedNavigable,
|
||||||
EntityPersister concreteDescriptor,
|
EntityPersister concreteDescriptor,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean affectedByFilter,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
super( parent, fetchedAttribute, fetchedNavigable, concreteDescriptor, keyResult, creationState );
|
super( parent, fetchedAttribute, fetchedNavigable, concreteDescriptor, keyResult, affectedByFilter, creationState );
|
||||||
this.fetchedAttribute = fetchedAttribute;
|
this.fetchedAttribute = fetchedAttribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,17 +55,30 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
data.setInstance( persistenceContext.getEntity( euk ) );
|
data.setInstance( persistenceContext.getEntity( euk ) );
|
||||||
if ( data.getInstance() == null ) {
|
if ( data.getInstance() == null ) {
|
||||||
data.setInstance( concreteDescriptor.loadByUniqueKey(
|
final Object instance = concreteDescriptor.loadByUniqueKey(
|
||||||
uniqueKeyPropertyName,
|
uniqueKeyPropertyName,
|
||||||
data.entityIdentifier,
|
data.entityIdentifier,
|
||||||
session
|
session
|
||||||
) );
|
);
|
||||||
|
data.setInstance( instance );
|
||||||
|
|
||||||
|
if ( instance == null ) {
|
||||||
|
if ( toOneMapping.getNotFoundAction() != NotFoundAction.IGNORE ) {
|
||||||
|
if ( affectedByFilter ) {
|
||||||
|
throw new EntityFilterException(
|
||||||
|
entityName,
|
||||||
|
data.entityIdentifier,
|
||||||
|
toOneMapping.getNavigableRole().getFullPath()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) {
|
||||||
|
throw new FetchNotFoundException( entityName, data.entityIdentifier );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// If the entity was not in the Persistence Context, but was found now,
|
// If the entity was not in the Persistence Context, but was found now,
|
||||||
// add it to the Persistence Context
|
// add it to the Persistence Context
|
||||||
if ( data.getInstance() != null ) {
|
persistenceContext.addEntity( euk, instance );
|
||||||
persistenceContext.addEntity( euk, data.getInstance() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ( data.getInstance() != null ) {
|
if ( data.getInstance() != null ) {
|
||||||
data.setInstance( persistenceContext.proxyFor( data.getInstance() ) );
|
data.setInstance( persistenceContext.proxyFor( data.getInstance() ) );
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.entity.internal;
|
||||||
|
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.hibernate.EntityFilterException;
|
||||||
import org.hibernate.FetchNotFoundException;
|
import org.hibernate.FetchNotFoundException;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
|
@ -53,6 +54,7 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
|
||||||
protected final EntityPersister concreteDescriptor;
|
protected final EntityPersister concreteDescriptor;
|
||||||
protected final DomainResultAssembler<?> keyAssembler;
|
protected final DomainResultAssembler<?> keyAssembler;
|
||||||
protected final ToOneAttributeMapping toOneMapping;
|
protected final ToOneAttributeMapping toOneMapping;
|
||||||
|
protected final boolean affectedByFilter;
|
||||||
|
|
||||||
public static class EntitySelectFetchInitializerData extends InitializerData {
|
public static class EntitySelectFetchInitializerData extends InitializerData {
|
||||||
// per-row state
|
// per-row state
|
||||||
|
@ -70,6 +72,7 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
|
||||||
NavigablePath fetchedNavigable,
|
NavigablePath fetchedNavigable,
|
||||||
EntityPersister concreteDescriptor,
|
EntityPersister concreteDescriptor,
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
|
boolean affectedByFilter,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
super( creationState );
|
super( creationState );
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
|
@ -79,6 +82,7 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
|
||||||
this.concreteDescriptor = concreteDescriptor;
|
this.concreteDescriptor = concreteDescriptor;
|
||||||
this.keyAssembler = keyResult.createResultAssembler( this, creationState );
|
this.keyAssembler = keyResult.createResultAssembler( this, creationState );
|
||||||
this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading();
|
this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading();
|
||||||
|
this.affectedByFilter = affectedByFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -106,8 +110,8 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RowProcessingState rowProcessingState1 = data.getRowProcessingState();
|
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||||
data.entityIdentifier = keyAssembler.assemble( rowProcessingState1 );
|
data.entityIdentifier = keyAssembler.assemble( rowProcessingState );
|
||||||
|
|
||||||
if ( data.entityIdentifier == null ) {
|
if ( data.entityIdentifier == null ) {
|
||||||
data.setState( State.MISSING );
|
data.setState( State.MISSING );
|
||||||
|
@ -192,20 +196,30 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
|
||||||
data.setState( State.INITIALIZED );
|
data.setState( State.INITIALIZED );
|
||||||
final String entityName = concreteDescriptor.getEntityName();
|
final String entityName = concreteDescriptor.getEntityName();
|
||||||
|
|
||||||
data.setInstance( session.internalLoad(
|
final Object instance = session.internalLoad(
|
||||||
entityName,
|
entityName,
|
||||||
data.entityIdentifier,
|
data.entityIdentifier,
|
||||||
true,
|
true,
|
||||||
toOneMapping.isInternalLoadNullable()
|
toOneMapping.isInternalLoadNullable()
|
||||||
) );
|
);
|
||||||
|
data.setInstance( instance );
|
||||||
|
|
||||||
if ( data.getInstance() == null ) {
|
if ( instance == null ) {
|
||||||
|
if ( toOneMapping.getNotFoundAction() != NotFoundAction.IGNORE ) {
|
||||||
|
if ( affectedByFilter ) {
|
||||||
|
throw new EntityFilterException(
|
||||||
|
entityName,
|
||||||
|
data.entityIdentifier,
|
||||||
|
toOneMapping.getNavigableRole().getFullPath()
|
||||||
|
);
|
||||||
|
}
|
||||||
if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) {
|
if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) {
|
||||||
throw new FetchNotFoundException( entityName, data.entityIdentifier );
|
throw new FetchNotFoundException( entityName, data.entityIdentifier );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
rowProcessingState.getSession().getPersistenceContextInternal().claimEntityHolderIfPossible(
|
rowProcessingState.getSession().getPersistenceContextInternal().claimEntityHolderIfPossible(
|
||||||
new EntityKey( data.entityIdentifier, concreteDescriptor ),
|
new EntityKey( data.entityIdentifier, concreteDescriptor ),
|
||||||
data.getInstance(),
|
instance,
|
||||||
rowProcessingState.getJdbcValuesSourceProcessingState(),
|
rowProcessingState.getJdbcValuesSourceProcessingState(),
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,6 +32,7 @@ public class EntitySelectFetchInitializerBuilder {
|
||||||
DomainResult<?> keyResult,
|
DomainResult<?> keyResult,
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
boolean selectByUniqueKey,
|
boolean selectByUniqueKey,
|
||||||
|
boolean affectedByFilter,
|
||||||
AssemblerCreationState creationState) {
|
AssemblerCreationState creationState) {
|
||||||
if ( selectByUniqueKey ) {
|
if ( selectByUniqueKey ) {
|
||||||
return new EntitySelectFetchByUniqueKeyInitializer(
|
return new EntitySelectFetchByUniqueKeyInitializer(
|
||||||
|
@ -40,6 +41,7 @@ public class EntitySelectFetchInitializerBuilder {
|
||||||
navigablePath,
|
navigablePath,
|
||||||
entityPersister,
|
entityPersister,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
affectedByFilter,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +53,7 @@ public class EntitySelectFetchInitializerBuilder {
|
||||||
navigablePath,
|
navigablePath,
|
||||||
entityPersister,
|
entityPersister,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
affectedByFilter,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +66,7 @@ public class EntitySelectFetchInitializerBuilder {
|
||||||
navigablePath,
|
navigablePath,
|
||||||
entityPersister,
|
entityPersister,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
affectedByFilter,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
case BATCH_LOAD:
|
case BATCH_LOAD:
|
||||||
|
@ -73,6 +77,7 @@ public class EntitySelectFetchInitializerBuilder {
|
||||||
navigablePath,
|
navigablePath,
|
||||||
entityPersister,
|
entityPersister,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
affectedByFilter,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +88,7 @@ public class EntitySelectFetchInitializerBuilder {
|
||||||
navigablePath,
|
navigablePath,
|
||||||
entityPersister,
|
entityPersister,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
affectedByFilter,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -93,6 +99,7 @@ public class EntitySelectFetchInitializerBuilder {
|
||||||
navigablePath,
|
navigablePath,
|
||||||
entityPersister,
|
entityPersister,
|
||||||
keyResult,
|
keyResult,
|
||||||
|
affectedByFilter,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,7 @@ public class CircularFetchImpl extends AbstractNonJoinedEntityFetch implements B
|
||||||
keyResult,
|
keyResult,
|
||||||
navigablePath,
|
navigablePath,
|
||||||
selectByUniqueKey,
|
selectByUniqueKey,
|
||||||
|
false,
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import jakarta.persistence.CascadeType;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.EntityGraph;
|
import jakarta.persistence.EntityGraph;
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
import jakarta.persistence.EnumType;
|
import jakarta.persistence.EnumType;
|
||||||
import jakarta.persistence.Enumerated;
|
import jakarta.persistence.Enumerated;
|
||||||
import jakarta.persistence.FetchType;
|
import jakarta.persistence.FetchType;
|
||||||
|
@ -24,10 +25,13 @@ import jakarta.persistence.NoResultException;
|
||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
import org.hibernate.FetchNotFoundException;
|
import org.hibernate.EntityFilterException;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.annotations.Filter;
|
import org.hibernate.annotations.Filter;
|
||||||
import org.hibernate.annotations.FilterDef;
|
import org.hibernate.annotations.FilterDef;
|
||||||
|
import org.hibernate.annotations.NotFound;
|
||||||
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.annotations.ParamDef;
|
import org.hibernate.annotations.ParamDef;
|
||||||
import org.hibernate.jpa.AvailableHints;
|
import org.hibernate.jpa.AvailableHints;
|
||||||
import org.hibernate.metamodel.CollectionClassification;
|
import org.hibernate.metamodel.CollectionClassification;
|
||||||
|
@ -56,7 +60,10 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
protected Class<?>[] getAnnotatedClasses() {
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
return new Class<?>[] {
|
return new Class<?>[] {
|
||||||
Client.class,
|
Client.class,
|
||||||
Account.class
|
Account.class,
|
||||||
|
AccountEager.class,
|
||||||
|
AccountNotFound.class,
|
||||||
|
AccountNotFoundException.class
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +113,21 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
entityManager.persist(client);
|
entityManager.persist(client);
|
||||||
//end::pc-filter-persistence-example[]
|
//end::pc-filter-persistence-example[]
|
||||||
|
entityManager.persist(
|
||||||
|
new AccountEager()
|
||||||
|
.setId(2L)
|
||||||
|
.setParentAccount( account1 )
|
||||||
|
);
|
||||||
|
entityManager.persist(
|
||||||
|
new AccountNotFound()
|
||||||
|
.setId(2L)
|
||||||
|
.setParentAccount( account1 )
|
||||||
|
);
|
||||||
|
entityManager.persist(
|
||||||
|
new AccountNotFoundException()
|
||||||
|
.setId(2L)
|
||||||
|
.setParentAccount( account1 )
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +135,9 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
entityManager.createQuery( "update Account set parentAccount = null" ).executeUpdate();
|
entityManager.createQuery( "update Account set parentAccount = null" ).executeUpdate();
|
||||||
|
entityManager.createQuery( "delete from AccountEager" ).executeUpdate();
|
||||||
|
entityManager.createQuery( "delete from AccountNotFound" ).executeUpdate();
|
||||||
|
entityManager.createQuery( "delete from AccountNotFoundException" ).executeUpdate();
|
||||||
entityManager.createQuery( "delete from Account" ).executeUpdate();
|
entityManager.createQuery( "delete from Account" ).executeUpdate();
|
||||||
entityManager.createQuery( "delete from Client" ).executeUpdate();
|
entityManager.createQuery( "delete from Client" ).executeUpdate();
|
||||||
});
|
});
|
||||||
|
@ -273,31 +298,42 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
@Test
|
@Test
|
||||||
@JiraKey("HHH-16830")
|
@JiraKey("HHH-16830")
|
||||||
public void testApplyToLoadByKeyAssociationFiltering() {
|
public void testApplyToLoadByKeyAssociationFiltering() {
|
||||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
Account account = entityManager.find(Account.class, 2L);
|
Account account = entityManager.find( Account.class, 2L );
|
||||||
assertNotNull( account.getParentAccount() );
|
assertNotNull( account.getParentAccount() );
|
||||||
});
|
} );
|
||||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
}
|
||||||
entityManager.unwrap(Session.class)
|
|
||||||
.enableFilter("accountType")
|
|
||||||
.setParameter("type", "DEBIT");
|
|
||||||
|
|
||||||
FetchNotFoundException exception = assertThrows(
|
@Test
|
||||||
FetchNotFoundException.class,
|
@JiraKey("HHH-16830")
|
||||||
() -> entityManager.find( Account.class, 2L )
|
public void testApplyToLoadByKeyAssociationFilteringLazyInitialization() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap( Session.class )
|
||||||
|
.enableFilter( "accountType" )
|
||||||
|
.setParameter( "type", "DEBIT" );
|
||||||
|
|
||||||
|
Account account = entityManager.find( Account.class, 2L );
|
||||||
|
EntityNotFoundException exception = assertThrows(
|
||||||
|
EntityNotFoundException.class,
|
||||||
|
() -> Hibernate.initialize( account.getParentAccount() )
|
||||||
);
|
);
|
||||||
// Account with id 1 does not exist
|
// Account with id 1 does not exist
|
||||||
assertTrue( exception.getMessage().contains( "`1`" ) );
|
assertTrue( exception.getMessage().endsWith( " 1" ) );
|
||||||
});
|
} );
|
||||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
}
|
||||||
entityManager.unwrap(Session.class)
|
|
||||||
.enableFilter("accountType")
|
@Test
|
||||||
.setParameter("type", "DEBIT");
|
@JiraKey("HHH-16830")
|
||||||
|
public void testApplyToLoadByKeyAssociationFilteringAccountLoadGraphInitializer() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap( Session.class )
|
||||||
|
.enableFilter( "accountType" )
|
||||||
|
.setParameter( "type", "DEBIT" );
|
||||||
EntityGraph<Account> entityGraph = entityManager.createEntityGraph( Account.class );
|
EntityGraph<Account> entityGraph = entityManager.createEntityGraph( Account.class );
|
||||||
entityGraph.addAttributeNodes( "parentAccount" );
|
entityGraph.addAttributeNodes( "parentAccount" );
|
||||||
|
|
||||||
FetchNotFoundException exception = assertThrows(
|
EntityFilterException exception = assertThrows(
|
||||||
FetchNotFoundException.class,
|
EntityFilterException.class,
|
||||||
() -> entityManager.find(
|
() -> entityManager.find(
|
||||||
Account.class,
|
Account.class,
|
||||||
2L,
|
2L,
|
||||||
|
@ -305,22 +341,116 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// Account with id 1 does not exist
|
// Account with id 1 does not exist
|
||||||
assertTrue( exception.getMessage().contains( "`1`" ) );
|
assertTrue( exception.getRole().endsWith( "parentAccount" ) );
|
||||||
});
|
assertEquals( 1L, exception.getIdentifier() );
|
||||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
} );
|
||||||
entityManager.unwrap(Session.class)
|
}
|
||||||
.enableFilter("accountType")
|
|
||||||
.setParameter("type", "DEBIT");
|
|
||||||
|
|
||||||
FetchNotFoundException exception = assertThrows(
|
@Test
|
||||||
FetchNotFoundException.class,
|
@JiraKey("HHH-16830")
|
||||||
|
public void testApplyToLoadByKeyAssociationFilteringAccountJoinInitializer() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap( Session.class )
|
||||||
|
.enableFilter( "accountType" )
|
||||||
|
.setParameter( "type", "DEBIT" );
|
||||||
|
|
||||||
|
EntityFilterException exception = assertThrows(
|
||||||
|
EntityFilterException.class,
|
||||||
() -> entityManager.createQuery(
|
() -> entityManager.createQuery(
|
||||||
"select a from Account a left join fetch a.parentAccount where a.id = 2",
|
"select a from Account a left join fetch a.parentAccount where a.id = 2",
|
||||||
Account.class
|
Account.class
|
||||||
).getResultList()
|
).getResultList()
|
||||||
);
|
);
|
||||||
// Account with id 1 does not exist
|
// Account with id 1 does not exist
|
||||||
assertTrue( exception.getMessage().contains( "`1`" ) );
|
assertTrue( exception.getRole().contains( "parentAccount" ) );
|
||||||
|
assertEquals( 1L, exception.getIdentifier() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@JiraKey("HHH-16830")
|
||||||
|
public void testApplyToLoadByKeyAssociationFilteringAccountSelectInitializer() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap( Session.class )
|
||||||
|
.enableFilter( "accountType" )
|
||||||
|
.setParameter( "type", "DEBIT" );
|
||||||
|
|
||||||
|
EntityFilterException exception = assertThrows(
|
||||||
|
EntityFilterException.class,
|
||||||
|
() -> entityManager.createQuery(
|
||||||
|
"select a from AccountEager a where a.id = 2",
|
||||||
|
AccountEager.class
|
||||||
|
).getResultList()
|
||||||
|
);
|
||||||
|
// Account with id 1 does not exist
|
||||||
|
assertTrue( exception.getRole().contains( "parentAccount" ) );
|
||||||
|
assertEquals( 1L, exception.getIdentifier() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@JiraKey("HHH-16830")
|
||||||
|
public void testApplyToLoadByKeyAssociationFilteringAccountNotFoundException() {
|
||||||
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap(Session.class)
|
||||||
|
.enableFilter("accountType")
|
||||||
|
.setParameter("type", "DEBIT");
|
||||||
|
|
||||||
|
EntityFilterException exception = assertThrows(
|
||||||
|
EntityFilterException.class,
|
||||||
|
() -> entityManager.createQuery(
|
||||||
|
"select a from AccountNotFoundException a where a.id = 2",
|
||||||
|
AccountNotFoundException.class
|
||||||
|
).getSingleResult()
|
||||||
|
);
|
||||||
|
// Account with id 1 does not exist
|
||||||
|
assertTrue( exception.getRole().contains( "parentAccount" ) );
|
||||||
|
assertEquals( 1L, exception.getIdentifier() );
|
||||||
|
});
|
||||||
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap(Session.class)
|
||||||
|
.enableFilter("accountType")
|
||||||
|
.setParameter("type", "DEBIT");
|
||||||
|
|
||||||
|
EntityFilterException exception = assertThrows(
|
||||||
|
EntityFilterException.class,
|
||||||
|
() -> entityManager.createQuery(
|
||||||
|
"select a from AccountNotFoundException a left join fetch a.parentAccount where a.id = 2",
|
||||||
|
AccountNotFoundException.class
|
||||||
|
).getSingleResult()
|
||||||
|
);
|
||||||
|
// Account with id 1 does not exist
|
||||||
|
assertTrue( exception.getRole().contains( "parentAccount" ) );
|
||||||
|
assertEquals( 1L, exception.getIdentifier() );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@JiraKey("HHH-16830")
|
||||||
|
public void testApplyToLoadByKeyAssociationFilteringAccountNotFoundIgnore() {
|
||||||
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap(Session.class)
|
||||||
|
.enableFilter("accountType")
|
||||||
|
.setParameter("type", "DEBIT");
|
||||||
|
|
||||||
|
AccountNotFound account = entityManager.createQuery(
|
||||||
|
"select a from AccountNotFound a where a.id = 2",
|
||||||
|
AccountNotFound.class
|
||||||
|
).getSingleResult();
|
||||||
|
// No exception, since we use NotFoundAction.IGNORE
|
||||||
|
assertNull( account.getParentAccount() );
|
||||||
|
});
|
||||||
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.unwrap(Session.class)
|
||||||
|
.enableFilter("accountType")
|
||||||
|
.setParameter("type", "DEBIT");
|
||||||
|
|
||||||
|
AccountNotFound account = entityManager.createQuery(
|
||||||
|
"select a from AccountNotFound a left join fetch a.parentAccount where a.id = 2",
|
||||||
|
AccountNotFound.class
|
||||||
|
).getSingleResult();
|
||||||
|
// No exception, since we use NotFoundAction.IGNORE
|
||||||
|
assertNull( account.getParentAccount() );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,4 +716,93 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//end::pc-filter-resolver-Account-example[]
|
//end::pc-filter-resolver-Account-example[]
|
||||||
|
|
||||||
|
@Entity(name = "AccountEager")
|
||||||
|
@Table(name = "account_eager")
|
||||||
|
public static class AccountEager {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
private Account parentAccount;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountEager setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getParentAccount() {
|
||||||
|
return parentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountEager setParentAccount(Account parentAccount) {
|
||||||
|
this.parentAccount = parentAccount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "AccountNotFound")
|
||||||
|
@Table(name = "account_not_found")
|
||||||
|
public static class AccountNotFound {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@NotFound(action = NotFoundAction.IGNORE)
|
||||||
|
private Account parentAccount;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountNotFound setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getParentAccount() {
|
||||||
|
return parentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountNotFound setParentAccount(Account parentAccount) {
|
||||||
|
this.parentAccount = parentAccount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "AccountNotFoundException")
|
||||||
|
@Table(name = "account_not_found_exception")
|
||||||
|
public static class AccountNotFoundException {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@NotFound
|
||||||
|
private Account parentAccount;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountNotFoundException setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getParentAccount() {
|
||||||
|
return parentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountNotFoundException setParentAccount(Account parentAccount) {
|
||||||
|
this.parentAccount = parentAccount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue