HHH-15936 add applyInToManyFetch member to @Where
This commit is contained in:
parent
c9cd12c625
commit
31ff2851c7
|
@ -18,14 +18,39 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
* Specifies a restriction written in native SQL to add to the generated
|
* Specifies a restriction written in native SQL to add to the generated
|
||||||
* SQL when querying an entity or collection.
|
* SQL when querying an entity or collection.
|
||||||
* <p>
|
* <p>
|
||||||
* For example, {@code @Where("deleted = false")} could be used to hide
|
* For example, {@code @Where} could be used to hide entity instances which
|
||||||
* entity instances which have been soft-deleted.
|
* have been soft-deleted, either for the entity class itself:
|
||||||
* <p>
|
* <pre>{@code
|
||||||
* Note that {@code Where} restrictions are always applied and cannot be
|
* @Entity
|
||||||
* disabled. They're therefore much less flexible than {@link Filter filters}.
|
* @Where(clause = "status <> 'DELETED'")
|
||||||
|
* class Document {
|
||||||
|
* ...
|
||||||
|
* @Enumerated(STRING)
|
||||||
|
* Status status;
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
* or, at the level of an association to the entity:
|
||||||
|
* <pre>{@code
|
||||||
|
* @OneToMany(mappedBy = "owner")
|
||||||
|
* @Where(clause = "status <> 'DELETED'")
|
||||||
|
* List<Document> documents;
|
||||||
|
* }</pre>
|
||||||
|
* By default, {@code @Where} restrictions declared for an entity are not
|
||||||
|
* applied when loading a collection of that entity type. This behavior is
|
||||||
|
* controlled by:
|
||||||
|
* <ol>
|
||||||
|
* <li>the annotation member {@link #applyInToManyFetch()}, and
|
||||||
|
* <li>the configuration property
|
||||||
|
* {@value org.hibernate.cfg.AvailableSettings#USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS}.
|
||||||
|
* </ol>
|
||||||
|
* Note that {@code @Where} restrictions 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 Filter
|
||||||
* @see DialectOverride.Where
|
* @see DialectOverride.Where
|
||||||
|
* @see org.hibernate.cfg.AvailableSettings#USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS
|
||||||
*
|
*
|
||||||
* @author Emmanuel Bernard
|
* @author Emmanuel Bernard
|
||||||
*/
|
*/
|
||||||
|
@ -36,4 +61,21 @@ public @interface Where {
|
||||||
* A predicate, written in native SQL.
|
* A predicate, written in native SQL.
|
||||||
*/
|
*/
|
||||||
String clause();
|
String clause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this restriction applies to an entity type, should it also be
|
||||||
|
* applied when fetching a {@link jakarta.persistence.OneToMany} or
|
||||||
|
* {@link jakarta.persistence.ManyToOne} association that targets
|
||||||
|
* the entity type?
|
||||||
|
* <p>
|
||||||
|
* By default, the restriction is not applied unless the property
|
||||||
|
* {@value org.hibernate.cfg.AvailableSettings#USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS}
|
||||||
|
* is explicitly enabled.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the restriction should be applied even
|
||||||
|
* if the configuration property is not enabled
|
||||||
|
*
|
||||||
|
* @since 6.2
|
||||||
|
*/
|
||||||
|
boolean applyInToManyFetch() default false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1752,9 +1752,12 @@ public abstract class CollectionBinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getWhereOnClassClause() {
|
private String getWhereOnClassClause() {
|
||||||
if ( useEntityWhereClauseForCollections() && property.getElementClass() != null ) {
|
if ( property.getElementClass() != null ) {
|
||||||
final Where whereOnClass = getOverridableAnnotation( property.getElementClass(), Where.class, getBuildingContext() );
|
final Where whereOnClass = getOverridableAnnotation( property.getElementClass(), Where.class, getBuildingContext() );
|
||||||
return whereOnClass != null ? whereOnClass.clause() : null;
|
return whereOnClass != null
|
||||||
|
&& ( whereOnClass.applyInToManyFetch() || useEntityWhereClauseForCollections() )
|
||||||
|
? whereOnClass.clause()
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
/*
|
||||||
|
* 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.where.annotations;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.JoinTable;
|
||||||
|
import jakarta.persistence.ManyToMany;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import org.hibernate.annotations.Where;
|
||||||
|
import org.hibernate.annotations.WhereJoinTable;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests association collections with AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS = true
|
||||||
|
*
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
public class EagerToManyWhereUseClassWhereViaAnnotationTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class[] getAnnotatedClasses() {
|
||||||
|
return new Class[] { Product.class, Category.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addSettings(Map<String,Object> settings) {
|
||||||
|
settings.put( AvailableSettings.USE_ENTITY_WHERE_CLAUSE_FOR_COLLECTIONS, "false" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue( jiraKey = "HHH-15936" )
|
||||||
|
public void testAssociatedWhereClause() {
|
||||||
|
|
||||||
|
Product product = new Product();
|
||||||
|
Category flowers = new Category();
|
||||||
|
flowers.id = 1;
|
||||||
|
flowers.name = "flowers";
|
||||||
|
flowers.description = "FLOWERS";
|
||||||
|
product.categoriesOneToMany.add( flowers );
|
||||||
|
product.categoriesWithDescOneToMany.add( flowers );
|
||||||
|
product.categoriesManyToMany.add( flowers );
|
||||||
|
product.categoriesWithDescManyToMany.add( flowers );
|
||||||
|
product.categoriesWithDescIdLt4ManyToMany.add( flowers );
|
||||||
|
Category vegetables = new Category();
|
||||||
|
vegetables.id = 2;
|
||||||
|
vegetables.name = "vegetables";
|
||||||
|
vegetables.description = "VEGETABLES";
|
||||||
|
product.categoriesOneToMany.add( vegetables );
|
||||||
|
product.categoriesWithDescOneToMany.add( vegetables );
|
||||||
|
product.categoriesManyToMany.add( vegetables );
|
||||||
|
product.categoriesWithDescManyToMany.add( vegetables );
|
||||||
|
product.categoriesWithDescIdLt4ManyToMany.add( vegetables );
|
||||||
|
Category dogs = new Category();
|
||||||
|
dogs.id = 3;
|
||||||
|
dogs.name = "dogs";
|
||||||
|
dogs.description = null;
|
||||||
|
product.categoriesOneToMany.add( dogs );
|
||||||
|
product.categoriesWithDescOneToMany.add( dogs );
|
||||||
|
product.categoriesManyToMany.add( dogs );
|
||||||
|
product.categoriesWithDescManyToMany.add( dogs );
|
||||||
|
product.categoriesWithDescIdLt4ManyToMany.add( dogs );
|
||||||
|
Category building = new Category();
|
||||||
|
building.id = 4;
|
||||||
|
building.name = "building";
|
||||||
|
building.description = "BUILDING";
|
||||||
|
product.categoriesOneToMany.add( building );
|
||||||
|
product.categoriesWithDescOneToMany.add( building );
|
||||||
|
product.categoriesManyToMany.add( building );
|
||||||
|
product.categoriesWithDescManyToMany.add( building );
|
||||||
|
product.categoriesWithDescIdLt4ManyToMany.add( building );
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory,
|
||||||
|
session -> {
|
||||||
|
session.persist( flowers );
|
||||||
|
session.persist( vegetables );
|
||||||
|
session.persist( dogs );
|
||||||
|
session.persist( building );
|
||||||
|
session.persist( product );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory,
|
||||||
|
session -> {
|
||||||
|
Product p = session.get( Product.class, product.id );
|
||||||
|
assertNotNull( p );
|
||||||
|
assertEquals( 4, p.categoriesOneToMany.size() );
|
||||||
|
checkIds( p.categoriesOneToMany, new Integer[] { 1, 2, 3, 4 } );
|
||||||
|
assertEquals( 3, p.categoriesWithDescOneToMany.size() );
|
||||||
|
checkIds( p.categoriesWithDescOneToMany, new Integer[] { 1, 2, 4 } );
|
||||||
|
assertEquals( 4, p.categoriesManyToMany.size() );
|
||||||
|
checkIds( p.categoriesManyToMany, new Integer[] { 1, 2, 3, 4 } );
|
||||||
|
assertEquals( 3, p.categoriesWithDescManyToMany.size() );
|
||||||
|
checkIds( p.categoriesWithDescManyToMany, new Integer[] { 1, 2, 4 } );
|
||||||
|
assertEquals( 2, p.categoriesWithDescIdLt4ManyToMany.size() );
|
||||||
|
checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 1, 2 } );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory,
|
||||||
|
session -> {
|
||||||
|
Category c = session.get( Category.class, flowers.id );
|
||||||
|
assertNotNull( c );
|
||||||
|
c.inactive = 1;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory,
|
||||||
|
session -> {
|
||||||
|
Category c = session.get( Category.class, flowers.id );
|
||||||
|
assertNull( c );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory,
|
||||||
|
session -> {
|
||||||
|
Product p = session.get( Product.class, product.id );
|
||||||
|
assertNotNull( p );
|
||||||
|
assertEquals( 3, p.categoriesOneToMany.size() );
|
||||||
|
checkIds( p.categoriesOneToMany, new Integer[] { 2, 3, 4 } );
|
||||||
|
assertEquals( 2, p.categoriesWithDescOneToMany.size() );
|
||||||
|
checkIds( p.categoriesWithDescOneToMany, new Integer[] { 2, 4 } );
|
||||||
|
assertEquals( 3, p.categoriesManyToMany.size() );
|
||||||
|
checkIds( p.categoriesManyToMany, new Integer[] { 2, 3, 4 } );
|
||||||
|
assertEquals( 2, p.categoriesWithDescManyToMany.size() );
|
||||||
|
checkIds( p.categoriesWithDescManyToMany, new Integer[] { 2, 4 } );
|
||||||
|
assertEquals( 1, p.categoriesWithDescIdLt4ManyToMany.size() );
|
||||||
|
checkIds( p.categoriesWithDescIdLt4ManyToMany, new Integer[] { 2 } );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkIds(Set<Category> categories, Integer[] expectedIds) {
|
||||||
|
final Set<Integer> expectedIdSet = new HashSet<>( Arrays.asList( expectedIds ) );
|
||||||
|
for ( Category category : categories ) {
|
||||||
|
expectedIdSet.remove( category.id );
|
||||||
|
}
|
||||||
|
assertTrue( expectedIdSet.isEmpty() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Product")
|
||||||
|
public static class Product {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
@OneToMany(fetch = FetchType.EAGER)
|
||||||
|
@JoinColumn
|
||||||
|
private Set<Category> categoriesOneToMany = new HashSet<>();
|
||||||
|
|
||||||
|
@OneToMany(fetch = FetchType.EAGER)
|
||||||
|
@JoinColumn
|
||||||
|
@Where( clause = "description is not null" )
|
||||||
|
private Set<Category> categoriesWithDescOneToMany = new HashSet<>();
|
||||||
|
|
||||||
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
|
@JoinTable(name = "categoriesManyToMany")
|
||||||
|
private Set<Category> categoriesManyToMany = new HashSet<>();
|
||||||
|
|
||||||
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
|
@JoinTable(name = "categoriesWithDescManyToMany", inverseJoinColumns = { @JoinColumn( name = "categoryId" )})
|
||||||
|
@Where( clause = "description is not null" )
|
||||||
|
private Set<Category> categoriesWithDescManyToMany = new HashSet<>();
|
||||||
|
|
||||||
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
|
@JoinTable(name = "categoriesWithDescIdLt4MToM", inverseJoinColumns = { @JoinColumn( name = "categoryId" )})
|
||||||
|
@Where( clause = "description is not null" )
|
||||||
|
@WhereJoinTable( clause = "categoryId < 4")
|
||||||
|
private Set<Category> categoriesWithDescIdLt4ManyToMany = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Category")
|
||||||
|
@Table(name = "CATEGORY")
|
||||||
|
@Where(clause = "inactive = 0", applyInToManyFetch = true)
|
||||||
|
public static class Category {
|
||||||
|
@Id
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private int inactive;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue