diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 77e117abde..0d64ae8fe9 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -290,6 +290,16 @@ Should we strictly adhere to JPA Query Language (JPQL) syntax, or more broadly s Setting this to `true` may cause valid HQL to throw an exception because it violates the JPQL subset. |`hibernate.query.startup_check` | `true` (default value) or `false` |Should named queries be checked during startup? +|`hibernate.query.conventional_java_constants` | `true` (default value) or `false` | +Setting which indicates whether or not Java constant follow the https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html[Java Naming conventions]. + +Default is `true`. +Existing applications may want to disable this (set it `false`) if non-conventional Java constants are used. +However, there is a significant performance overhead for using non-conventional Java constants +since Hibernate cannot determine if aliases should be treated as Java constants or not. + +Check out https://hibernate.atlassian.net/browse/HHH-4959[HHH-4959] for more details. + |`hibernate.hql.bulk_id_strategy` | A fully-qualified class name, an instance, or a `Class` object reference |Provide a custom `org.hibernate.hql.spi.id.MultiTableBulkIdStrategy` implementation for handling multi-table bulk HQL operations. |`hibernate.proc.param_null_passing` | `true` or `false` (default value) | @@ -310,7 +320,6 @@ Can reference * `StatementInspector` instance * `StatementInspector` implementation {@link Class} reference * `StatementInspector` implementation class name (fully-qualified class name) - |=================================================================================================================================================================================================================================== [[configurations-batch]] diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java index d085c8862f..a6ede7c0bf 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryBuilderImpl.java @@ -483,6 +483,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement private Map querySubstitutions; private boolean strictJpaQueryLanguageCompliance; private boolean namedQueryStartupCheckingEnabled; + private boolean conventionalJavaConstants; private final boolean procedureParameterNullPassingEnabled; private final boolean collectionJoinSubqueryRewriteEnabled; @@ -600,6 +601,8 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement this.querySubstitutions = ConfigurationHelper.toMap( QUERY_SUBSTITUTIONS, " ,=;:\n\t\r\f", configurationSettings ); this.strictJpaQueryLanguageCompliance = cfgService.getSetting( JPAQL_STRICT_COMPLIANCE, BOOLEAN, false ); this.namedQueryStartupCheckingEnabled = cfgService.getSetting( QUERY_STARTUP_CHECKING, BOOLEAN, true ); + this.conventionalJavaConstants = cfgService.getSetting( + CONVENTIONAL_JAVA_CONSTANTS, BOOLEAN, true ); this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false ); this.collectionJoinSubqueryRewriteEnabled = cfgService.getSetting( COLLECTION_JOIN_SUBQUERY, BOOLEAN, true ); @@ -851,6 +854,10 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement return namedQueryStartupCheckingEnabled; } + public boolean isConventionalJavaConstants() { + return conventionalJavaConstants; + } + @Override public boolean isProcedureParameterNullPassingEnabled() { return procedureParameterNullPassingEnabled; @@ -1132,6 +1139,10 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement return options.isNamedQueryStartupCheckingEnabled(); } + public boolean isConventionalJavaConstants() { + return options.isConventionalJavaConstants(); + } + @Override public boolean isProcedureParameterNullPassingEnabled() { return options.isProcedureParameterNullPassingEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java index abb5ec698a..fd8bfe03b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsImpl.java @@ -88,6 +88,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions { private final Map querySubstitutions; private final boolean strictJpaQueryLanguageCompliance; private final boolean namedQueryStartupCheckingEnabled; + private final boolean conventionalJavaConstants; private final boolean procedureParameterNullPassingEnabled; private final boolean collectionJoinSubqueryRewriteEnabled; @@ -160,6 +161,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions { this.querySubstitutions = state.getQuerySubstitutions(); this.strictJpaQueryLanguageCompliance = state.isStrictJpaQueryLanguageCompliance(); this.namedQueryStartupCheckingEnabled = state.isNamedQueryStartupCheckingEnabled(); + this.conventionalJavaConstants = state.isConventionalJavaConstants(); this.procedureParameterNullPassingEnabled = state.isProcedureParameterNullPassingEnabled(); this.collectionJoinSubqueryRewriteEnabled = state.isCollectionJoinSubqueryRewriteEnabled(); @@ -339,6 +341,11 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions { return namedQueryStartupCheckingEnabled; } + @Override + public boolean isConventionalJavaConstants() { + return conventionalJavaConstants; + } + @Override public boolean isProcedureParameterNullPassingEnabled() { return procedureParameterNullPassingEnabled; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java index 95fb243352..b36737911c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsState.java @@ -99,6 +99,8 @@ public interface SessionFactoryOptionsState { boolean isNamedQueryStartupCheckingEnabled(); + boolean isConventionalJavaConstants(); + boolean isProcedureParameterNullPassingEnabled(); boolean isCollectionJoinSubqueryRewriteEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 823fee5d26..0fb09844c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -198,6 +198,11 @@ public abstract class AbstractDelegatingSessionFactoryOptions implements Session return delegate.isNamedQueryStartupCheckingEnabled(); } + @Override + public boolean isConventionalJavaConstants() { + return delegate.isConventionalJavaConstants(); + } + @Override public boolean isProcedureParameterNullPassingEnabled() { return delegate.isProcedureParameterNullPassingEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 7df039ed88..dd55bd18bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -122,6 +122,8 @@ public interface SessionFactoryOptions { boolean isNamedQueryStartupCheckingEnabled(); + boolean isConventionalJavaConstants(); + boolean isSecondLevelCacheEnabled(); boolean isQueryCacheEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 3d2dff72de..1ff65da04c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -562,6 +562,17 @@ public interface AvailableSettings { */ String QUERY_STARTUP_CHECKING = "hibernate.query.startup_check"; + /** + * Setting which indicates whether or not Java constant follow the Java Naming conventions. + *
+ * Default is {@code true}. Existing applications may want to disable this (set it {@code false}) if non-conventional Java constants are used. + * However, there is a significant performance overhead for using non-conventional Java constants since Hibernate cannot determine if aliases + * should be treated as Java constants or not. + * + * @since 5.2 + */ + String CONVENTIONAL_JAVA_CONSTANTS = "hibernate.query.conventional_java_constants"; + /** * The {@link org.hibernate.exception.spi.SQLExceptionConverter} to use for converting SQLExceptions * to Hibernate's JDBCException hierarchy. The default is to use the configured diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java index 6479b4ac2c..ff85b0433f 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java @@ -19,7 +19,6 @@ import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.ScrollableResults; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.engine.query.spi.EntityGraphQueryHint; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; @@ -590,7 +589,6 @@ public class QueryTranslatorImpl implements FilterTranslator { private AST dotRoot; public JavaConstantConverter(SessionFactoryImplementor factory) { - this.factory = factory; } @@ -612,7 +610,7 @@ public class QueryTranslatorImpl implements FilterTranslator { } private void handleDotStructure(AST dotStructureRoot) { final String expression = ASTUtil.getPathText( dotStructureRoot ); - final Object constant = ReflectHelper.getConstantValue( expression, factory.getServiceRegistry().getService( ClassLoaderService.class ) ); + final Object constant = ReflectHelper.getConstantValue( expression, factory ); if ( constant != null ) { dotStructureRoot.setFirstChild( null ); dotStructureRoot.setType( HqlTokenTypes.JAVA_CONSTANT ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java index a9613b235b..9a99cc49e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/JavaConstantNode.java @@ -9,7 +9,6 @@ package org.hibernate.hql.internal.ast.tree; import java.util.Locale; import org.hibernate.QueryException; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.spi.QueryTranslator; @@ -39,7 +38,7 @@ public class JavaConstantNode extends Node implements ExpectedTypeAwareNode, Ses // this method to get called twice. The first time with an empty string if ( StringHelper.isNotEmpty( s ) ) { constantExpression = s; - constantValue = ReflectHelper.getConstantValue( s, factory.getServiceRegistry().getService( ClassLoaderService.class ) ); + constantValue = ReflectHelper.getConstantValue( s, factory ); heuristicType = factory.getTypeResolver().heuristicType( constantValue.getClass().getName() ); super.setText( s ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java index e5da5ee836..147531dd2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/LiteralProcessor.java @@ -13,13 +13,11 @@ import java.text.DecimalFormat; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.QueryException; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.dialect.Dialect; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.antlr.SqlTokenTypes; import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.InvalidPathException; -import org.hibernate.hql.internal.ast.tree.BooleanLiteralNode; import org.hibernate.hql.internal.ast.tree.DotNode; import org.hibernate.hql.internal.ast.tree.FromClause; import org.hibernate.hql.internal.ast.tree.IdentNode; @@ -35,7 +33,6 @@ import org.jboss.logging.Logger; import antlr.SemanticException; import antlr.collections.AST; -import java.util.Locale; /** * A delegate that handles literals and constants for HqlSqlWalker, performing the token replacement functions and @@ -109,7 +106,7 @@ public class LiteralProcessor implements HqlSqlTokenTypes { setSQLValue( node, text, discrim ); } else { - Object value = ReflectHelper.getConstantValue( text, walker.getSessionFactoryHelper().getFactory().getServiceRegistry().getService( ClassLoaderService.class ) ); + Object value = ReflectHelper.getConstantValue( text, walker.getSessionFactoryHelper().getFactory() ); if ( value == null ) { throw new InvalidPathException( "Invalid path: '" + text + "'" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java index 1b09019838..8faa0b1713 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/classic/WhereParser.java @@ -16,7 +16,6 @@ import java.util.StringTokenizer; import org.hibernate.MappingException; import org.hibernate.QueryException; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.engine.internal.JoinSequence; import org.hibernate.hql.spi.QueryTranslator; import org.hibernate.internal.util.ReflectHelper; @@ -419,7 +418,7 @@ public class WhereParser implements Parser { Object constant; if ( token.indexOf( '.' ) > -1 && - ( constant = ReflectHelper.getConstantValue( token, q.getFactory().getServiceRegistry().getService( ClassLoaderService.class ) ) ) != null + ( constant = ReflectHelper.getConstantValue( token, q.getFactory() ) ) != null ) { Type type; try { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index d7116f5077..e5b847e7ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -13,12 +13,14 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Locale; +import java.util.regex.Pattern; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; import org.hibernate.PropertyNotFoundException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.property.access.internal.PropertyAccessStrategyMixedImpl; import org.hibernate.property.access.spi.Getter; import org.hibernate.type.PrimitiveType; @@ -32,6 +34,10 @@ import org.hibernate.type.Type; */ @SuppressWarnings("unchecked") public final class ReflectHelper { + + private static final Pattern JAVA_CONSTANT_PATTERN = Pattern.compile( + "[a-z]+\\.([A-Z]{1}[a-z]+)+\\$?([A-Z]{1}[a-z]+)*\\.[A-Z_\\$]+" ); + public static final Class[] NO_PARAM_SIGNATURE = new Class[0]; public static final Object[] NO_PARAMS = new Object[0]; @@ -229,9 +235,15 @@ public final class ReflectHelper { return PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess( clazz, name ).getGetter(); } - public static Object getConstantValue(String name, ClassLoaderService classLoaderService) { + public static Object getConstantValue(String name, SessionFactoryImplementor factory) { + boolean conventionalJavaConstants = factory.getSessionFactoryOptions().isConventionalJavaConstants(); Class clazz; try { + if ( conventionalJavaConstants && + !JAVA_CONSTANT_PATTERN.matcher( name ).find() ) { + return null; + } + ClassLoaderService classLoaderService = factory.getServiceRegistry().getService( ClassLoaderService.class ); clazz = classLoaderService.classForName( StringHelper.qualifier( name ) ); } catch ( Throwable t ) { @@ -331,7 +343,7 @@ public final class ReflectHelper { throw new PropertyNotFoundException( "no appropriate constructor in class: " + clazz.getName() ); } - + public static Method getMethod(Class clazz, Method method) { try { return clazz.getMethod( method.getName(), method.getParameterTypes() ); diff --git a/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java b/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java new file mode 100644 index 0000000000..cf2e1cb3e0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java @@ -0,0 +1,126 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or