HHH-16163 replace @OrderBy and @Where by @SQLOrder and @SQLRestriction

This commit is contained in:
Gavin 2023-04-08 00:08:30 +02:00 committed by Gavin King
parent 7b8cd14052
commit adffa890b1
11 changed files with 321 additions and 30 deletions

View File

@ -95,11 +95,14 @@ public interface DialectOverride {
/**
* Specializes an {@link org.hibernate.annotations.OrderBy}
* in a certain dialect.
*
* @deprecated Use {@link SQLOrder}
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Repeatable(OrderBys.class)
@OverridesAnnotation(org.hibernate.annotations.OrderBy.class)
@Deprecated(since = "6.3", forRemoval = true)
@interface OrderBy {
/**
* The {@link Dialect} in which this override applies.
@ -116,6 +119,30 @@ public interface DialectOverride {
OrderBy[] value();
}
/**
* Specializes an {@link org.hibernate.annotations.SQLOrder}
* in a certain dialect.
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Repeatable(SQLOrders.class)
@OverridesAnnotation(org.hibernate.annotations.SQLOrder.class)
@interface SQLOrder {
/**
* The {@link Dialect} in which this override applies.
*/
Class<? extends Dialect> dialect();
Version before() default @Version(major = MAX_VALUE);
Version sameOrAfter() default @Version(major = MIN_VALUE);
org.hibernate.annotations.SQLOrder override();
}
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@interface SQLOrders {
SQLOrder[] value();
}
/**
* Specializes a {@link org.hibernate.annotations.ColumnDefault}
* in a certain dialect.
@ -218,6 +245,7 @@ public interface DialectOverride {
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Repeatable(JoinFormulas.class)
@OverridesAnnotation(org.hibernate.annotations.JoinFormula.class)
@interface JoinFormula {
/**
@ -238,10 +266,14 @@ public interface DialectOverride {
/**
* Specializes a {@link org.hibernate.annotations.Where}
* in a certain dialect.
*
* @deprecated Use {@link SQLRestriction}
*/
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@Repeatable(Wheres.class)
@OverridesAnnotation(org.hibernate.annotations.Where.class)
@Deprecated(since = "6.3")
@interface Where {
/**
* The {@link Dialect} in which this override applies.
@ -252,12 +284,36 @@ public interface DialectOverride {
org.hibernate.annotations.Where override();
}
@Target({METHOD, FIELD, TYPE})
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@interface Wheres {
Where[] value();
}
/**
* Specializes a {@link org.hibernate.annotations.SQLRestriction}
* in a certain dialect.
*/
@Target({METHOD, FIELD, TYPE})
@Retention(RUNTIME)
@Repeatable(SQLRestrictions.class)
@OverridesAnnotation(org.hibernate.annotations.SQLRestriction.class)
@interface SQLRestriction {
/**
* The {@link Dialect} in which this override applies.
*/
Class<? extends Dialect> dialect();
Version before() default @Version(major = MAX_VALUE);
Version sameOrAfter() default @Version(major = MIN_VALUE);
org.hibernate.annotations.SQLRestriction override();
}
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@interface SQLRestrictions {
SQLRestriction[] value();
}
/**
* Specializes {@link org.hibernate.annotations.Filters}
* in a certain dialect.

View File

@ -51,9 +51,14 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* @author Steve Ebersole
*
* @see DialectOverride.OrderBy
*
* @deprecated Use {@link SQLOrder} instead. This annotation will be
* removed eventually, since its unqualified name collides
* with {@link jakarta.persistence.OrderBy}.
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Deprecated(since = "6.3", forRemoval = true)
public @interface OrderBy {
/**
* The native SQL expression used to sort the collection elements.

View File

@ -0,0 +1,45 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies a restriction written in native SQL to add to the generated SQL
* when querying the {@linkplain jakarta.persistence.JoinTable join table}
* of a collection.
* <p>
* For example, <code>&#64;SQLJoinTableRestriction("status &lt;&gt; 'DELETED'")</code>
* could be used to hide associations which have been soft-deleted from an
* association table.
*
* @apiNote This separate annotation is useful because it's possible to filter
* a many-to-many association <em>both</em> by a restriction on the
* join table, and, <em>simultaneously</em>, by a restriction on the
* associated entity table. The {@link SQLRestriction @SQLRestriction}
* annotation always filters entity tables.
*
* @since 6.3
*
* @author Gavin King
* @author Emmanuel Bernard
*
* @see SQLRestriction
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface SQLJoinTableRestriction {
/**
* A predicate, written in native SQL.
*/
String value();
}

View File

@ -0,0 +1,76 @@
/*
* 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.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Order a collection using an expression or list of expression written
* in native SQL. For example, {@code @SQLOrder("first_name, last_name")},
* {@code @SQLOrder("char_length(name) desc")}, or even
* {@code @SQLOrder("name asc nulls last")}.
* <p>
* The order is applied by the database when the collection is fetched,
* but is not maintained by operations that mutate the collection in
* memory.
* <p>
* If the collection is a {@link java.util.Set} or {@link java.util.Map},
* the order is maintained using a {@link java.util.LinkedHashSet} or
* {@link java.util.LinkedHashMap}. If the collection is a bag or
* {@link java.util.List}, the order is maintained by the underlying
* {@link java.util.ArrayList}.
* <p>
* There are several other ways to order or sort a collection:
* <ul>
* <li>Use the JPA-defined {@link jakarta.persistence.OrderBy} annotation
* to order using an expression written in HQL/JPQL. Since HQL is more
* portable between databases, this is the preferred alternative most
* of the time.
* <li>Use {@link SortComparator} to sort the collection in memory using
* a {@link java.util.Comparator}, or {@link SortNatural} to sort the
* collection in memory according to its {@linkplain java.util.Comparator
* natural order}.
* <li>Use {@link jakarta.persistence.OrderColumn} to maintain the order
* of a {@link java.util.List} with a dedicated index column.
* </ul>
* <p>
* It's illegal to use {@code SQLOrder} together with the JPA-defined
* {@link jakarta.persistence.OrderBy} for the same collection.
*
* @see jakarta.persistence.OrderBy
* @see SortComparator
* @see SortNatural
*
* @since 6.3
*
* @author Gavin King
* @author Emmanuel Bernard
* @author Steve Ebersole
*
* @see DialectOverride.SQLOrder
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface SQLOrder {
/**
* A comma-separated list native SQL expressions used to sort the
* collection elements. Each element of the list may optionally
* specify:
* <ul>
* <li>{@code asc}-ending or {@code desc}-ending order, or even
* <li>{@code nulls first} or {@code nulls last}.
* </ul>
* Hibernate does not interpret these keywords, and simply passes
* them through to the generated SQL.
*/
String value();
}

View File

@ -0,0 +1,72 @@
/*
* 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.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies a restriction written in native SQL to add to the generated
* SQL when querying an entity or collection.
* <p>
* For example, {@code @SQLRestriction} could be used to hide entity
* instances which have been soft-deleted, either for the entity class
* itself:
* <pre>
* &#64;Entity
* &#64;SQLRestriction("status &lt;&gt; 'DELETED'")
* class Document {
* ...
* &#64;Enumerated(STRING)
* Status status;
* ...
* }
* </pre>
* <p>
* or, at the level of an association to the entity:
* <pre>
* &#64;OneToMany(mappedBy = "owner")
* &#64;SQLRestriction("status &lt;&gt; 'DELETED'")
* List&lt;Document&gt; documents;
* </pre>
* <p>
* The {@link SQLJoinTableRestriction} annotation lets a restriction be
* applied to an {@linkplain jakarta.persistence.JoinTable association table}:
* <pre>
* &#64;ManyToMany
* &#64;JoinTable(name = "collaborations")
* &#64;SQLRestriction("status &lt;&gt; 'DELETED'")
* &#64;SQLJoinTableRestriction("status = 'ACTIVE'")
* List&lt;Document&gt; documents;
* </pre>
* <p>
* Note that {@code @SQLRestriction}s are always applied and cannot be
* disabled. Nor may they be parameterized. They're therefore <em>much</em>
* less flexible than {@linkplain Filter filters}.
*
* @see Filter
* @see DialectOverride.SQLRestriction
* @see SQLJoinTableRestriction
*
* @since 6.3
*
* @author Gavin King
* @author Emmanuel Bernard
*/
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface SQLRestriction {
/**
* A predicate, written in native SQL.
*/
String value();
}

View File

@ -63,9 +63,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* @see WhereJoinTable
*
* @author Emmanuel Bernard
*
* @deprecated Use {@link SQLRestriction}
*/
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
@Deprecated(since = "6.3")
public @interface Where {
/**
* A predicate, written in native SQL.

View File

@ -31,9 +31,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* @author Emmanuel Bernard
*
* @see Where
*
* @deprecated Use {@link SQLJoinTableRestriction}
*/
@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Deprecated(since = "6.3")
public @interface WhereJoinTable {
/**
* A predicate, written in native SQL.

View File

@ -323,12 +323,12 @@
* <li>{@link org.hibernate.annotations.Check} specifies a check constraint condition,
* <li>{@link org.hibernate.annotations.ColumnDefault} specifies a default value, and
* {@link org.hibernate.annotations.GeneratedColumn} specifies a generated value,
* <li>{@link org.hibernate.annotations.Filter} and {@link org.hibernate.annotations.Where}
* <li>{@link org.hibernate.annotations.Filter} and {@link org.hibernate.annotations.SQLRestriction}
* each specify a restriction written in SQL,
* <li>{@link org.hibernate.annotations.OrderBy} specifies an ordering written in SQL, and
* <li>{@link org.hibernate.annotations.SQLUpdate}, {@link org.hibernate.annotations.SQLInsert},
* and {@link org.hibernate.annotations.SQLDelete} allow a whole handwritten SQL statement
* to be given in place of the SQL generated by Hibernate.
* <li>{@link org.hibernate.annotations.SQLOrder} specifies an ordering written in SQL, and
* <li>{@link org.hibernate.annotations.SQLSelect}, {@link org.hibernate.annotations.SQLUpdate},
* {@link org.hibernate.annotations.SQLInsert}, and {@link org.hibernate.annotations.SQLDelete}
* allow a whole handwritten SQL statement to be given in place of the SQL generated by Hibernate.
* </ul>
* <p>
* A major disadvantage to annotation-based mappings for programs which target multiple databases

View File

@ -66,6 +66,9 @@ import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLSelect;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.annotations.SQLRestriction;
import org.hibernate.annotations.SQLJoinTableRestriction;
import org.hibernate.annotations.SQLOrder;
import org.hibernate.annotations.SortComparator;
import org.hibernate.annotations.SortNatural;
import org.hibernate.annotations.Synchronize;
@ -77,7 +80,6 @@ import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.BootLogging;
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.model.source.internal.hbm.ModelBinder;
import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.InFlightMetadataCollector.CollectionTypeRegistrationDescriptor;
@ -166,6 +168,7 @@ import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.boot.model.source.internal.hbm.ModelBinder.useEntityWhereClauseForCollections;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
import static org.hibernate.internal.util.StringHelper.isEmpty;
@ -232,6 +235,7 @@ public abstract class CollectionBinder {
private OrderBy jpaOrderBy;
private org.hibernate.annotations.OrderBy sqlOrderBy;
private SQLOrder sqlOrder;
private SortNatural naturalSort;
private SortComparator comparatorSort;
@ -274,6 +278,7 @@ public abstract class CollectionBinder {
collectionBinder.setBatchSize( property.getAnnotation( BatchSize.class ) );
collectionBinder.setJpaOrderBy( property.getAnnotation( OrderBy.class ) );
collectionBinder.setSqlOrderBy( getOverridableAnnotation( property, org.hibernate.annotations.OrderBy.class, context ) );
collectionBinder.setSqlOrder( getOverridableAnnotation( property, SQLOrder.class, context ) );
collectionBinder.setNaturalSort( property.getAnnotation( SortNatural.class ) );
collectionBinder.setComparatorSort( property.getAnnotation( SortComparator.class ) );
collectionBinder.setCache( property.getAnnotation( Cache.class ) );
@ -780,6 +785,10 @@ public abstract class CollectionBinder {
this.sqlOrderBy = sqlOrderBy;
}
public void setSqlOrder(SQLOrder sqlOrder) {
this.sqlOrder = sqlOrder;
}
public void setNaturalSort(SortNatural naturalSort) {
this.naturalSort = naturalSort;
}
@ -1362,15 +1371,18 @@ public abstract class CollectionBinder {
comparatorClass = null;
}
if ( jpaOrderBy != null && sqlOrderBy != null ) {
if ( jpaOrderBy != null && ( sqlOrderBy != null || sqlOrder != null ) ) {
throw buildIllegalOrderCombination();
}
boolean ordered = jpaOrderBy != null || sqlOrderBy != null;
boolean ordered = jpaOrderBy != null || sqlOrderBy != null || sqlOrder != null ;
if ( ordered ) {
// we can only apply the sql-based order by up front. The jpa order by has to wait for second pass
if ( sqlOrderBy != null ) {
collection.setOrderBy( sqlOrderBy.clause() );
}
if ( sqlOrder != null ) {
collection.setOrderBy( sqlOrder.value() );
}
}
final boolean isSorted = isSortedCollection || sorted;
@ -1774,6 +1786,10 @@ public abstract class CollectionBinder {
}
private String getWhereJoinTableClause() {
final SQLJoinTableRestriction joinTableRestriction = property.getAnnotation( SQLJoinTableRestriction.class );
if ( joinTableRestriction != null ) {
return joinTableRestriction.value();
}
final WhereJoinTable whereJoinTable = property.getAnnotation( WhereJoinTable.class );
return whereJoinTable == null ? null : whereJoinTable.clause();
}
@ -1789,16 +1805,29 @@ public abstract class CollectionBinder {
}
private String getWhereOnCollectionClause() {
final Where whereOnCollection = getOverridableAnnotation( property, Where.class, getBuildingContext() );
return whereOnCollection != null ? whereOnCollection.clause() : null;
final SQLRestriction restrictionOnCollection = getOverridableAnnotation( property, SQLRestriction.class, getBuildingContext() );
if ( restrictionOnCollection != null ) {
return restrictionOnCollection.value();
}
final Where whereOnCollection = getOverridableAnnotation( property, Where.class, buildingContext );
if ( whereOnCollection != null ) {
return whereOnCollection.clause();
}
return null;
}
private String getWhereOnClassClause() {
if ( property.getElementClass() != null ) {
final Where whereOnClass = getOverridableAnnotation( property.getElementClass(), Where.class, getBuildingContext() );
return whereOnClass != null && ModelBinder.useEntityWhereClauseForCollections( buildingContext )
? whereOnClass.clause()
: null;
XClass elementClass = property.getElementClass();
if ( elementClass != null && useEntityWhereClauseForCollections( buildingContext ) ) {
final SQLRestriction restrictionOnClass = getOverridableAnnotation( elementClass, SQLRestriction.class, buildingContext );
if ( restrictionOnClass != null ) {
return restrictionOnClass.value();
}
final Where whereOnClass = getOverridableAnnotation( elementClass, Where.class, buildingContext );
if ( whereOnClass != null ) {
return whereOnClass.clause();
}
return null;
}
else {
return null;

View File

@ -53,6 +53,7 @@ import org.hibernate.annotations.SQLInserts;
import org.hibernate.annotations.SQLSelect;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.annotations.SQLUpdates;
import org.hibernate.annotations.SQLRestriction;
import org.hibernate.annotations.SecondaryRow;
import org.hibernate.annotations.SecondaryRows;
import org.hibernate.annotations.SelectBeforeUpdate;
@ -1481,6 +1482,10 @@ public class EntityBinder {
if ( where != null ) {
this.where = where.clause();
}
final SQLRestriction restriction = getOverridableAnnotation( annotatedClass, SQLRestriction.class, context );
if ( restriction != null ) {
this.where = restriction.value();
}
}
public void setWrapIdsInEmbeddedComponents(boolean wrapIdsInEmbeddedComponents) {

View File

@ -109,7 +109,6 @@ import org.hibernate.generator.internal.SourceGeneration;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.log.DeprecationLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
@ -162,6 +161,8 @@ import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import static org.hibernate.cfg.AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS;
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY;
@ -213,7 +214,7 @@ public class ModelBinder {
.getSettings()
.get( USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS );
if ( explicitSetting != null ) {
DeprecationLogger.DEPRECATION_LOGGER.deprecatedSettingNoReplacement( USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS );
DEPRECATION_LOGGER.deprecatedSettingNoReplacement( USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS );
return ConfigurationHelper.toBoolean( explicitSetting, true );
}
@ -356,31 +357,28 @@ public class ModelBinder {
EntityHierarchySourceImpl hierarchySource,
RootClass rootEntityDescriptor) {
switch ( hierarchySource.getIdentifierSource().getNature() ) {
case SIMPLE: {
case SIMPLE:
bindSimpleEntityIdentifier(
mappingDocument,
hierarchySource,
rootEntityDescriptor
);
break;
}
case AGGREGATED_COMPOSITE: {
case AGGREGATED_COMPOSITE:
bindAggregatedCompositeEntityIdentifier(
mappingDocument,
hierarchySource,
rootEntityDescriptor
);
break;
}
case NON_AGGREGATED_COMPOSITE: {
case NON_AGGREGATED_COMPOSITE:
bindNonAggregatedCompositeEntityIdentifier(
mappingDocument,
hierarchySource,
rootEntityDescriptor
);
break;
}
default: {
default:
throw new MappingException(
String.format(
Locale.ENGLISH,
@ -390,7 +388,6 @@ public class ModelBinder {
),
mappingDocument.getOrigin()
);
}
}
}
@ -2077,7 +2074,7 @@ public class ModelBinder {
oneToOneBinding.setReferencedEntityName( oneToOneSource.getReferencedEntityName() );
if ( oneToOneSource.isEmbedXml() == Boolean.TRUE ) {
DeprecationLogger.DEPRECATION_LOGGER.logDeprecationOfEmbedXmlSupport();
DEPRECATION_LOGGER.logDeprecationOfEmbedXmlSupport();
}
if ( StringHelper.isNotEmpty( oneToOneSource.getExplicitForeignKeyName() ) ) {
@ -2194,7 +2191,7 @@ public class ModelBinder {
);
if ( manyToOneSource.isEmbedXml() == Boolean.TRUE ) {
DeprecationLogger.DEPRECATION_LOGGER.logDeprecationOfEmbedXmlSupport();
DEPRECATION_LOGGER.logDeprecationOfEmbedXmlSupport();
}
manyToOneBinding.setIgnoreNotFound( manyToOneSource.isIgnoreNotFound() );
@ -3452,7 +3449,7 @@ public class ModelBinder {
// Collection#setWhere is used to set the "where" clause that applies to the collection table
// (which is the associated entity table for a one-to-many association).
collectionBinding.setWhere(
StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty(
getNonEmptyOrConjunctionIfBothNonEmpty(
referencedEntityBinding.getWhere(),
getPluralAttributeSource().getWhere()
)
@ -3522,7 +3519,7 @@ public class ModelBinder {
// Collection#setManytoManyWhere is used to set the "where" clause that applies to
// to the many-to-many associated entity table (not the join table).
getCollectionBinding().setManyToManyWhere(
StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty(
getNonEmptyOrConjunctionIfBothNonEmpty(
referencedEntityBinding.getWhere(),
elementSource.getWhere()
)