* Added JpaCompliance for @OrderBy handling

* Added `NonTransientException` checked during MappingModelCreationProcess "post init callback" handling to indicate non-recoverable errors
* Redesigned `MappingModelCreationProcess#executePostInitCallbacks` to account for `NonTransientException`
This commit is contained in:
Steve Ebersole 2020-04-30 09:13:37 -05:00
parent d180cb63d2
commit 41093ae66c
22 changed files with 484 additions and 88 deletions

View File

@ -102,4 +102,8 @@
|90005700 |90005700
|org.hibernate.envers.boot.EnversBootLogger |org.hibernate.envers.boot.EnversBootLogger
|90005701
|90005800
|org.hibernate.metamodel.mapping.MappingModelCreationLogger
|=== |===

View File

@ -728,6 +728,11 @@ public interface SessionFactoryBuilder {
*/ */
SessionFactoryBuilder enableJpaQueryCompliance(boolean enabled); SessionFactoryBuilder enableJpaQueryCompliance(boolean enabled);
/**
* @see JpaCompliance#isJpaQueryComplianceEnabled()
*/
SessionFactoryBuilder enableJpaOrderByMappingCompliance(boolean enabled);
/** /**
* @see JpaCompliance#isJpaTransactionComplianceEnabled() * @see JpaCompliance#isJpaTransactionComplianceEnabled()
*/ */

View File

@ -416,6 +416,12 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
return this; return this;
} }
@Override
public SessionFactoryBuilder enableJpaOrderByMappingCompliance(boolean enabled) {
this.optionsBuilder.enableJpaOrderByMappingCompliance( enabled );
return this;
}
@Override @Override
public SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled) { public SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled) {
this.optionsBuilder.enableJpaTransactionCompliance( enabled ); this.optionsBuilder.enableJpaTransactionCompliance( enabled );

View File

@ -1478,6 +1478,14 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
mutableJpaCompliance().setCachingCompliance( enabled ); mutableJpaCompliance().setCachingCompliance( enabled );
} }
public void enableJpaOrderByMappingCompliance(boolean enabled) {
mutableJpaCompliance().setOrderByMappingCompliance( enabled );
}
public void enableGeneratorNameScopeCompliance(boolean enabled) {
mutableJpaCompliance().setGeneratorNameScopeCompliance( enabled );
}
public void disableRefreshDetachedEntity() { public void disableRefreshDetachedEntity() {
this.allowRefreshDetachedEntity = false; this.allowRefreshDetachedEntity = false;
} }

View File

@ -394,6 +394,12 @@ public abstract class AbstractDelegatingSessionFactoryBuilder<T extends SessionF
return getThis(); return getThis();
} }
@Override
public SessionFactoryBuilder enableJpaOrderByMappingCompliance(boolean enabled) {
delegate.enableJpaOrderByMappingCompliance( enabled );
return getThis();
}
@Override @Override
public SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled) { public SessionFactoryBuilder enableJpaTransactionCompliance(boolean enabled) {
delegate.enableJpaTransactionCompliance( enabled ); delegate.enableJpaTransactionCompliance( enabled );

View File

@ -2006,6 +2006,19 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/ */
String JPA_LIST_COMPLIANCE = "hibernate.jpa.compliance.list"; String JPA_LIST_COMPLIANCE = "hibernate.jpa.compliance.list";
/**
* Controls whether Hibernate should recognize what it considers a "bag"
* ({@link org.hibernate.collection.internal.PersistentBag}) as a List
* ({@link org.hibernate.collection.internal.PersistentList}) or as a bag.
*
* If enabled, we will recognize it as a List where {@link javax.persistence.OrderColumn}
* is just missing (and its defaults will apply).
*
* @see JpaCompliance#isJpaOrderByMappingComplianceEnabled() ()
* @since 6.0
*/
String JPA_ORDER_BY_MAPPING_COMPLIANCE = "hibernate.jpa.compliance.orderby";
/** /**
* JPA defines specific exceptions on specific methods when called on * JPA defines specific exceptions on specific methods when called on
* {@link javax.persistence.EntityManager} and {@link javax.persistence.EntityManagerFactory} * {@link javax.persistence.EntityManager} and {@link javax.persistence.EntityManagerFactory}

View File

@ -0,0 +1,15 @@
/*
* 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.jpa;
/**
* Contract for exceptions describing JPA compliance violations
*
* @author Steve Ebersole
*/
public interface JpaComplianceViolation {
}

View File

@ -12,22 +12,24 @@ import org.hibernate.jpa.spi.JpaCompliance;
* @author Andrea Boriero * @author Andrea Boriero
*/ */
public class JpaComplianceImpl implements JpaCompliance { public class JpaComplianceImpl implements JpaCompliance {
private boolean queryCompliance; private final boolean listCompliance;
private boolean transactionCompliance; private final boolean orderByMappingCompliance;
private boolean listCompliance; private final boolean proxyCompliance;
private boolean closedCompliance; private final boolean globalGeneratorNameScopeCompliance;
private boolean proxyCompliance; private final boolean queryCompliance;
private boolean cachingCompliance; private final boolean transactionCompliance;
private boolean globalGeneratorNameScopeCompliance; private final boolean closedCompliance;
private final boolean cachingCompliance;
private JpaComplianceImpl( private JpaComplianceImpl(
boolean listCompliance,
boolean orderByMappingCompliance,
boolean proxyCompliance,
boolean globalGeneratorNameScopeCompliance,
boolean queryCompliance, boolean queryCompliance,
boolean transactionCompliance, boolean transactionCompliance,
boolean listCompliance,
boolean closedCompliance, boolean closedCompliance,
boolean proxyCompliance, boolean cachingCompliance) {
boolean cachingCompliance,
boolean globalGeneratorNameScopeCompliance) {
this.queryCompliance = queryCompliance; this.queryCompliance = queryCompliance;
this.transactionCompliance = transactionCompliance; this.transactionCompliance = transactionCompliance;
this.listCompliance = listCompliance; this.listCompliance = listCompliance;
@ -35,6 +37,7 @@ public class JpaComplianceImpl implements JpaCompliance {
this.proxyCompliance = proxyCompliance; this.proxyCompliance = proxyCompliance;
this.cachingCompliance = cachingCompliance; this.cachingCompliance = cachingCompliance;
this.globalGeneratorNameScopeCompliance = globalGeneratorNameScopeCompliance; this.globalGeneratorNameScopeCompliance = globalGeneratorNameScopeCompliance;
this.orderByMappingCompliance = orderByMappingCompliance;
} }
@Override @Override
@ -72,18 +75,39 @@ public class JpaComplianceImpl implements JpaCompliance {
return globalGeneratorNameScopeCompliance; return globalGeneratorNameScopeCompliance;
} }
@Override
public boolean isJpaOrderByMappingComplianceEnabled() {
return orderByMappingCompliance;
}
public static class JpaComplianceBuilder { public static class JpaComplianceBuilder {
private boolean queryCompliance; private boolean queryCompliance;
private boolean transactionCompliance;
private boolean listCompliance; private boolean listCompliance;
private boolean closedCompliance; private boolean orderByMappingCompliance;
private boolean proxyCompliance; private boolean proxyCompliance;
private boolean cachingCompliance;
private boolean globalGeneratorNameScopeCompliance; private boolean globalGeneratorNameScopeCompliance;
private boolean cachingCompliance;
private boolean transactionCompliance;
private boolean closedCompliance;
public JpaComplianceBuilder() { public JpaComplianceBuilder() {
} }
public JpaComplianceBuilder setListCompliance(boolean listCompliance) {
this.listCompliance = listCompliance;
return this;
}
public JpaComplianceBuilder setOrderByMappingCompliance(boolean orderByMappingCompliance) {
this.orderByMappingCompliance = orderByMappingCompliance;
return this;
}
public JpaComplianceBuilder setProxyCompliance(boolean proxyCompliance) {
this.proxyCompliance = proxyCompliance;
return this;
}
public JpaComplianceBuilder setQueryCompliance(boolean queryCompliance) { public JpaComplianceBuilder setQueryCompliance(boolean queryCompliance) {
this.queryCompliance = queryCompliance; this.queryCompliance = queryCompliance;
return this; return this;
@ -94,21 +118,11 @@ public class JpaComplianceImpl implements JpaCompliance {
return this; return this;
} }
public JpaComplianceBuilder setListCompliance(boolean listCompliance) {
this.listCompliance = listCompliance;
return this;
}
public JpaComplianceBuilder setClosedCompliance(boolean closedCompliance) { public JpaComplianceBuilder setClosedCompliance(boolean closedCompliance) {
this.closedCompliance = closedCompliance; this.closedCompliance = closedCompliance;
return this; return this;
} }
public JpaComplianceBuilder setProxyCompliance(boolean proxyCompliance) {
this.proxyCompliance = proxyCompliance;
return this;
}
public JpaComplianceBuilder setCachingCompliance(boolean cachingCompliance) { public JpaComplianceBuilder setCachingCompliance(boolean cachingCompliance) {
this.cachingCompliance = cachingCompliance; this.cachingCompliance = cachingCompliance;
return this; return this;
@ -121,13 +135,14 @@ public class JpaComplianceImpl implements JpaCompliance {
JpaCompliance createJpaCompliance() { JpaCompliance createJpaCompliance() {
return new JpaComplianceImpl( return new JpaComplianceImpl(
listCompliance,
orderByMappingCompliance,
proxyCompliance,
globalGeneratorNameScopeCompliance,
queryCompliance, queryCompliance,
transactionCompliance, transactionCompliance,
listCompliance,
closedCompliance, closedCompliance,
proxyCompliance, cachingCompliance
cachingCompliance,
globalGeneratorNameScopeCompliance
); );
} }
} }

View File

@ -17,50 +17,60 @@ import org.hibernate.jpa.spi.MutableJpaCompliance;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class MutableJpaComplianceImpl implements MutableJpaCompliance { public class MutableJpaComplianceImpl implements MutableJpaCompliance {
private boolean listCompliance;
private boolean orderByMappingCompliance;
private boolean proxyCompliance;
private boolean generatorNameScopeCompliance;
private boolean queryCompliance; private boolean queryCompliance;
private boolean transactionCompliance; private boolean transactionCompliance;
private boolean listCompliance;
private boolean closedCompliance; private boolean closedCompliance;
private boolean proxyCompliance;
private boolean cachingCompliance; private boolean cachingCompliance;
private final boolean globalGeneratorNameScopeCompliance;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public MutableJpaComplianceImpl(Map configurationSettings, boolean jpaByDefault) { public MutableJpaComplianceImpl(Map configurationSettings, boolean jpaByDefault) {
final Object legacyQueryCompliance = configurationSettings.get( AvailableSettings.JPAQL_STRICT_COMPLIANCE ); final Object legacyQueryCompliance = configurationSettings.get( AvailableSettings.JPAQL_STRICT_COMPLIANCE );
queryCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_QUERY_COMPLIANCE,
configurationSettings,
ConfigurationHelper.toBoolean( legacyQueryCompliance, jpaByDefault )
);
transactionCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_TRANSACTION_COMPLIANCE,
configurationSettings,
jpaByDefault
);
listCompliance = ConfigurationHelper.getBoolean( listCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_LIST_COMPLIANCE, AvailableSettings.JPA_LIST_COMPLIANCE,
configurationSettings, configurationSettings,
jpaByDefault jpaByDefault
); );
closedCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_CLOSED_COMPLIANCE,
configurationSettings,
jpaByDefault
);
proxyCompliance = ConfigurationHelper.getBoolean( proxyCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_PROXY_COMPLIANCE, AvailableSettings.JPA_PROXY_COMPLIANCE,
configurationSettings, configurationSettings,
jpaByDefault jpaByDefault
); );
cachingCompliance = ConfigurationHelper.getBoolean( generatorNameScopeCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_CACHING_COMPLIANCE, AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE,
configurationSettings, configurationSettings,
jpaByDefault jpaByDefault
); );
globalGeneratorNameScopeCompliance = ConfigurationHelper.getBoolean( orderByMappingCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE,
configurationSettings,
jpaByDefault
);
queryCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_QUERY_COMPLIANCE,
configurationSettings,
ConfigurationHelper.toBoolean( legacyQueryCompliance, jpaByDefault )
);
transactionCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_TRANSACTION_COMPLIANCE,
configurationSettings,
jpaByDefault
);
closedCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_CLOSED_COMPLIANCE,
configurationSettings,
jpaByDefault
);
cachingCompliance = ConfigurationHelper.getBoolean(
AvailableSettings.JPA_CACHING_COMPLIANCE,
configurationSettings, configurationSettings,
jpaByDefault jpaByDefault
); );
@ -98,46 +108,69 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
@Override @Override
public boolean isGlobalGeneratorScopeEnabled() { public boolean isGlobalGeneratorScopeEnabled() {
return globalGeneratorNameScopeCompliance; return generatorNameScopeCompliance;
}
@Override
public boolean isJpaOrderByMappingComplianceEnabled() {
return orderByMappingCompliance;
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Mutators // Mutators
public void setQueryCompliance(boolean queryCompliance) { @Override
this.queryCompliance = queryCompliance;
}
public void setTransactionCompliance(boolean transactionCompliance) {
this.transactionCompliance = transactionCompliance;
}
public void setListCompliance(boolean listCompliance) { public void setListCompliance(boolean listCompliance) {
this.listCompliance = listCompliance; this.listCompliance = listCompliance;
} }
public void setClosedCompliance(boolean closedCompliance) { @Override
this.closedCompliance = closedCompliance; public void setOrderByMappingCompliance(boolean orderByMappingCompliance) {
this.orderByMappingCompliance = orderByMappingCompliance;
} }
@Override
public void setProxyCompliance(boolean proxyCompliance) { public void setProxyCompliance(boolean proxyCompliance) {
this.proxyCompliance = proxyCompliance; this.proxyCompliance = proxyCompliance;
} }
@Override
public void setGeneratorNameScopeCompliance(boolean enabled) {
this.generatorNameScopeCompliance = enabled;
}
public void setQueryCompliance(boolean queryCompliance) {
this.queryCompliance = queryCompliance;
}
@Override
public void setTransactionCompliance(boolean transactionCompliance) {
this.transactionCompliance = transactionCompliance;
}
@Override
public void setClosedCompliance(boolean closedCompliance) {
this.closedCompliance = closedCompliance;
}
@Override
public void setCachingCompliance(boolean cachingCompliance) { public void setCachingCompliance(boolean cachingCompliance) {
this.cachingCompliance = cachingCompliance; this.cachingCompliance = cachingCompliance;
} }
@Override @Override
public JpaCompliance immutableCopy() { public JpaCompliance immutableCopy() {
JpaComplianceImpl.JpaComplianceBuilder builder = new JpaComplianceImpl.JpaComplianceBuilder(); JpaComplianceImpl.JpaComplianceBuilder builder = new JpaComplianceImpl.JpaComplianceBuilder();
builder.setQueryCompliance( queryCompliance ) builder = builder.setListCompliance( listCompliance )
.setTransactionCompliance( transactionCompliance )
.setListCompliance( listCompliance )
.setClosedCompliance( closedCompliance )
.setProxyCompliance( proxyCompliance ) .setProxyCompliance( proxyCompliance )
.setCachingCompliance( cachingCompliance ) .setOrderByMappingCompliance( orderByMappingCompliance )
.setGlobalGeneratorNameCompliance( globalGeneratorNameScopeCompliance ); .setGlobalGeneratorNameCompliance( generatorNameScopeCompliance )
.setQueryCompliance( queryCompliance )
.setTransactionCompliance( transactionCompliance )
.setClosedCompliance( closedCompliance )
.setCachingCompliance( cachingCompliance );
return builder.createJpaCompliance(); return builder.createJpaCompliance();
} }
} }

View File

@ -97,4 +97,12 @@ public interface JpaCompliance {
* @return {@code true} indicates the generator name scope is considered global. * @return {@code true} indicates the generator name scope is considered global.
*/ */
boolean isGlobalGeneratorScopeEnabled(); boolean isGlobalGeneratorScopeEnabled();
/**
* Should we strictly handle {@link javax.persistence.OrderBy} expressions?
*
* JPA says the order-items can only be attribute references whereas Hibernate supports a wide range of items. With
* this enabled, Hibernate will throw a compliance error when a non-attribute-reference is used.
*/
boolean isJpaOrderByMappingComplianceEnabled();
} }

View File

@ -10,17 +10,21 @@ package org.hibernate.jpa.spi;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface MutableJpaCompliance extends JpaCompliance { public interface MutableJpaCompliance extends JpaCompliance {
void setListCompliance(boolean listCompliance);
void setOrderByMappingCompliance(boolean orderByCompliance);
void setProxyCompliance(boolean proxyCompliance);
void setQueryCompliance(boolean queryCompliance); void setQueryCompliance(boolean queryCompliance);
void setTransactionCompliance(boolean transactionCompliance); void setTransactionCompliance(boolean transactionCompliance);
void setListCompliance(boolean listCompliance);
void setClosedCompliance(boolean closedCompliance); void setClosedCompliance(boolean closedCompliance);
void setProxyCompliance(boolean proxyCompliance);
void setCachingCompliance(boolean cachingCompliance); void setCachingCompliance(boolean cachingCompliance);
void setGeneratorNameScopeCompliance(boolean generatorScopeCompliance);
JpaCompliance immutableCopy(); JpaCompliance immutableCopy();
} }

View File

@ -0,0 +1,30 @@
/*
* 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.metamodel.mapping;
import org.hibernate.query.QueryLogger;
import org.jboss.logging.BasicLogger;
import org.jboss.logging.Logger;
import org.jboss.logging.annotations.MessageLogger;
import org.jboss.logging.annotations.ValidIdRange;
/**
* Logger used during mapping-model creation
*
* @author Steve Ebersole
*/
@MessageLogger( projectCode = "HHH" )
@ValidIdRange( min = 90005701, max = 90005800 )
public interface MappingModelCreationLogger extends BasicLogger {
String LOGGER_NAME = "org.hibernate.orm.model.mapping.creation";
MappingModelCreationLogger LOGGER = Logger.getMessageLogger( MappingModelCreationLogger.class, LOGGER_NAME );
boolean TRACE_ENABLED = LOGGER.isTraceEnabled();
boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();
}

View File

@ -0,0 +1,16 @@
/*
* 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.metamodel.mapping;
/**
* Marker interface for exceptions thrown during mapping-model creation which
* are not transient errors - they will never succeed
*
* @author Steve Ebersole
*/
public interface NonTransientException {
}

View File

@ -61,6 +61,7 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NonTransientException;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata; import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
@ -793,7 +794,11 @@ public class MappingModelCreationHelper {
catch (NotYetImplementedFor6Exception nye) { catch (NotYetImplementedFor6Exception nye) {
throw nye; throw nye;
} }
catch (Exception wait) { catch (Exception e) {
if ( e instanceof NonTransientException ) {
throw e;
}
return false; return false;
} }
} }

View File

@ -10,7 +10,9 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.hibernate.metamodel.mapping.MappingModelCreationLogger;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NonTransientException;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -69,25 +71,40 @@ public class MappingModelCreationProcess {
entityPersister.prepareMappingModel( this ); entityPersister.prepareMappingModel( this );
} }
executePostInitCallbakcs( postInitCallbacks ); MappingModelCreationLogger.LOGGER.debugf( "Starting generic post-init callbacks" );
executePostInitCallbacks( postInitCallbacks );
executePostInitCallbakcs( foreignKeyPostInitCallbacks ); MappingModelCreationLogger.LOGGER.debugf( "Starting foreign-key post-init callbacks" );
executePostInitCallbacks( foreignKeyPostInitCallbacks );
} }
private void executePostInitCallbakcs(List<PostInitCallback> postInitCallbacks) { private void executePostInitCallbacks(List<PostInitCallback> postInitCallbacks) {
while ( postInitCallbacks != null && !postInitCallbacks.isEmpty() ) { while ( postInitCallbacks != null && !postInitCallbacks.isEmpty() ) {
// copy to avoid CCME
final ArrayList<PostInitCallback> copy = new ArrayList<>( new ArrayList<>( postInitCallbacks ) );
for ( PostInitCallback callback : copy ) { // copy to avoid CCME
final ArrayList<PostInitCallback> copy = new ArrayList<>( postInitCallbacks );
for ( int i = 0; i < copy.size(); i++ ) {
final PostInitCallback callback = copy.get( i );
try {
final boolean completed = callback.process(); final boolean completed = callback.process();
if ( completed ) { if ( completed ) {
postInitCallbacks.remove( callback ); postInitCallbacks.remove( callback );
} }
} }
catch (Exception e) {
if ( e instanceof NonTransientException ) {
MappingModelCreationLogger.LOGGER.debugf( "Mapping-model creation encountered non-transient error : %s", e );
throw e;
}
MappingModelCreationLogger.LOGGER.debugf( "Mapping-model creation encountered (possibly) transient error : %s", e );
}
}
if ( copy.size() == postInitCallbacks.size() ) { if ( copy.size() == postInitCallbacks.size() ) {
// none of the processes could complete fully, this is an error // none of the remaining callbacks could complete fully, this is an error
throw new IllegalStateException( "No post-init callbacks could complete" ); throw new IllegalStateException( "No post-init callbacks could complete" );
} }
} }

View File

@ -17,6 +17,7 @@ import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.List; import org.hibernate.mapping.List;
@ -246,6 +247,10 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
if ( hasOrder || hasManyToManyOrder ) { if ( hasOrder || hasManyToManyOrder ) {
final TranslationContext context = new TranslationContext() { final TranslationContext context = new TranslationContext() {
@Override
public JpaCompliance getJpaCompliance() {
return collectionDescriptor.getFactory().getSessionFactoryOptions().getJpaCompliance();
}
}; };
if ( hasOrder ) { if ( hasOrder ) {

View File

@ -6,10 +6,13 @@
*/ */
package org.hibernate.metamodel.mapping.ordering; package org.hibernate.metamodel.mapping.ordering;
import org.hibernate.jpa.spi.JpaCompliance;
/** /**
* Access to information needed while translating a collection's order-by fragment * Access to information needed while translating a collection's order-by fragment
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface TranslationContext { public interface TranslationContext {
JpaCompliance getJpaCompliance();
} }

View File

@ -0,0 +1,24 @@
/*
* 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.metamodel.mapping.ordering.ast;
import org.hibernate.HibernateException;
import org.hibernate.jpa.JpaComplianceViolation;
import org.hibernate.metamodel.mapping.NonTransientException;
/**
* @author Steve Ebersole
*/
public class OrderByComplianceViolation extends HibernateException implements JpaComplianceViolation, NonTransientException {
public OrderByComplianceViolation(String message) {
super( message );
}
public OrderByComplianceViolation(String message, Throwable cause) {
super( message, cause );
}
}

View File

@ -17,16 +17,12 @@ import org.hibernate.grammars.ordering.OrderingParserBaseVisitor;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext; import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.jboss.logging.Logger;
import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.TerminalNode;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class ParseTreeVisitor extends OrderingParserBaseVisitor { public class ParseTreeVisitor extends OrderingParserBaseVisitor<Object> {
private static final Logger log = Logger.getLogger( ParseTreeVisitor.class );
private final PathConsumer pathConsumer; private final PathConsumer pathConsumer;
private final TranslationContext translationContext; private final TranslationContext translationContext;
@ -60,7 +56,21 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor {
assert parsedSpec != null; assert parsedSpec != null;
assert parsedSpec.expression() != null; assert parsedSpec.expression() != null;
final OrderingSpecification result = new OrderingSpecification( visitExpression( parsedSpec.expression() ) ); final OrderingExpression orderingExpression = visitExpression( parsedSpec.expression() );
if ( translationContext.getJpaCompliance().isJpaOrderByMappingComplianceEnabled() ) {
if ( orderingExpression instanceof DomainPath ) {
// nothing to do
}
else {
throw new OrderByComplianceViolation(
"`@OrderBy` expression (" + parsedSpec.expression().getText()
+ ") resolved to `" + orderingExpression
+ "` which is not a domain-model reference which violates the JPA specification"
);
}
}
final OrderingSpecification result = new OrderingSpecification( orderingExpression );
if ( parsedSpec.collationSpecification() != null ) { if ( parsedSpec.collationSpecification() != null ) {
result.setCollation( parsedSpec.collationSpecification().identifier().getText() ); result.setCollation( parsedSpec.collationSpecification().identifier().getText() );

View File

@ -0,0 +1,56 @@
/*
* 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.jpa;
import org.hibernate.jpa.spi.JpaCompliance;
/**
* Stubbed impl of JpaCompliance
*
* @author Steve Ebersole
*/
public class JpaComplianceStub implements JpaCompliance {
@Override
public boolean isJpaQueryComplianceEnabled() {
return false;
}
@Override
public boolean isJpaTransactionComplianceEnabled() {
return false;
}
@Override
public boolean isJpaListComplianceEnabled() {
return false;
}
@Override
public boolean isJpaClosedComplianceEnabled() {
return false;
}
@Override
public boolean isJpaProxyComplianceEnabled() {
return false;
}
@Override
public boolean isJpaCacheComplianceEnabled() {
return false;
}
@Override
public boolean isGlobalGeneratorScopeEnabled() {
return false;
}
@Override
public boolean isJpaOrderByMappingComplianceEnabled() {
return false;
}
}

View File

@ -47,6 +47,7 @@ public class JpaComplianceTestingImpl implements JpaCompliance {
private boolean proxyCompliance; private boolean proxyCompliance;
private boolean cacheCompliance; private boolean cacheCompliance;
private boolean idGeneratorNameScopeCompliance; private boolean idGeneratorNameScopeCompliance;
private boolean orderByCompliance;
@Override @Override
public boolean isJpaQueryComplianceEnabled() { public boolean isJpaQueryComplianceEnabled() {
@ -63,6 +64,11 @@ public class JpaComplianceTestingImpl implements JpaCompliance {
return listCompliance; return listCompliance;
} }
@Override
public boolean isJpaOrderByMappingComplianceEnabled() {
return orderByCompliance;
}
@Override @Override
public boolean isJpaClosedComplianceEnabled() { public boolean isJpaClosedComplianceEnabled() {
return closedCompliance; return closedCompliance;

View File

@ -0,0 +1,107 @@
/*
* 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.binding.annotations.plural.orderby.compliance;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.ordering.ast.OrderByComplianceViolation;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.transaction.TransactionUtil2;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Steve Ebersole
*/
public class OrderByMappingComplianceTest {
@Test
public void testComplianceChecking() {
check( false );
try {
check( true );
fail( "Expecting an exception" );
}
catch (OrderByComplianceViolation expected) {
// nothing to do
}
}
private void check(boolean complianceEnabled) {
final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder()
.applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" )
.build();
try {
final Metadata bootModel = new MetadataSources( ssr )
.addAnnotatedClass( Order.class )
.addAnnotatedClass( LineItem.class )
.buildMetadata();
final SessionFactory sf = bootModel.getSessionFactoryBuilder()
.enableJpaOrderByMappingCompliance( complianceEnabled )
.build();
try {
TransactionUtil2.inTransaction(
(SessionFactoryImplementor) sf,
session -> session.createQuery( "from Order" ).list()
);
}
finally {
try {
sf.close();
}
catch (Exception ignore) {
}
}
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity( name = "Order" )
@Table( name = "orders" )
public static class Order {
@Id
private Integer id;
private String invoice;
@OneToMany
@OrderBy( "qty" )
private Set<LineItem> lineItems;
}
@Entity( name = "LineItem" )
@Table( name = "line_items" )
public static class LineItem {
@Id
private Integer id;
private String sku;
@Column( name = "qty" )
private int quantity;
}
}