HHH-11027 - Document the PASS_DISTINCT_THROUGH Query Hint
This commit is contained in:
parent
7a70103171
commit
b6ac8ec1be
|
@ -774,6 +774,89 @@ For further information about collection-valued association references, see <<hq
|
|||
In the case of component/embedded attributes, the join is simply logical and does not correlate to a physical (SQL) join.
|
||||
Unlike explicit joins, however, implicit joins may also reference basic state fields as long as the path expression ends there.
|
||||
|
||||
[[hql-distinct]]
|
||||
=== Distinct
|
||||
|
||||
For JPQL and HQL, `DISTINCT` has two meanings:
|
||||
|
||||
. It can be passed to the database so that duplicates are removed from a result set
|
||||
. It can be used to filter out the same parent entity references when join fetching a child collection
|
||||
|
||||
[[hql-distinct-projection-query]]
|
||||
==== Using DISTINCT with SQL projections
|
||||
|
||||
For SQL projections, `DISTINCT` needs to be passed to the database because the duplicated entries need to be filtered out before being returned to the database client.
|
||||
|
||||
[[hql-distinct-projection-query-example]]
|
||||
.Using DISTINCT with projection queries example
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/SelectDistinctTest.java[tags=hql-distinct-projection-query-example]
|
||||
----
|
||||
====
|
||||
|
||||
When running the query above, Hibernate generates the following SQL query:
|
||||
|
||||
====
|
||||
[source, SQL, indent=0]
|
||||
----
|
||||
include::{extrasdir}/hql-distinct-projection-query-example.sql[]
|
||||
----
|
||||
====
|
||||
|
||||
For this particular use case, passing the `DISTINCT` keyword from JPQL/HQL to the database is the right thing to do.
|
||||
|
||||
[[hql-distinct-entity-query]]
|
||||
==== Using DISTINCT with entity queries
|
||||
|
||||
`DISTINCT` can also be used to filter out entity object references when fetching a child association along with the parent entities.
|
||||
|
||||
[[hql-distinct-entity-query-example]]
|
||||
.Using DISTINCT with entity queries example
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/SelectDistinctTest.java[tags=hql-distinct-entity-query-example]
|
||||
----
|
||||
====
|
||||
|
||||
In this case, `DISTINCT` is used because there can be multiple `Books` entities associated to a given `Person`.
|
||||
If in the database there are 3 `Persons` in the database and each person has 2 `Books`, without `DISTINCT` this query will return 6 `Persons` since
|
||||
the SQL-level result-set size is given by the number of joined `Book` records.
|
||||
|
||||
However, the `DISTINCT` keyword is passed to the database as well:
|
||||
|
||||
====
|
||||
[source, SQL, indent=0]
|
||||
----
|
||||
include::{extrasdir}/hql-distinct-entity-query-example.sql[]
|
||||
----
|
||||
====
|
||||
|
||||
In this case, the `DISTINCT` SQL keyword is undesirable since it does a redundant result set sorting, as explained http://in.relation.to/2016/08/04/introducing-distinct-pass-through-query-hint/[in this blog post].
|
||||
To fix this issue, Hibernate 5.2.2 added support for the `HINT_PASS_DISTINCT_THROUGH` entity query hint:
|
||||
|
||||
[[hql-distinct-entity-query-hint-example]]
|
||||
.Using DISTINCT with entity queries example
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/SelectDistinctTest.java[tags=hql-distinct-entity-query-hint-example]
|
||||
----
|
||||
====
|
||||
|
||||
With this entity query hint, Hibernate will not pass the `DISTINCT` keyword to the SQL query:
|
||||
|
||||
====
|
||||
[source, SQL, indent=0]
|
||||
----
|
||||
include::{extrasdir}/hql-distinct-entity-query-hint-example.sql[]
|
||||
----
|
||||
====
|
||||
|
||||
When using the `HINT_PASS_DISTINCT_THROUGH` entity query hint, Hibernate can still remove the duplicated parent-side entities from the query result.
|
||||
|
||||
[[hql-collection-valued-associations]]
|
||||
=== Collection member references
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
SELECT DISTINCT
|
||||
p.id as id1_1_0_,
|
||||
b.id as id1_0_1_,
|
||||
p.first_name as first_na2_1_0_,
|
||||
p.last_name as last_nam3_1_0_,
|
||||
b.author_id as author_i3_0_1_,
|
||||
b.title as title2_0_1_,
|
||||
b.author_id as author_i3_0_0__,
|
||||
b.id as id1_0_0__
|
||||
FROM person p
|
||||
LEFT OUTER JOIN book b ON p.id=b.author_id
|
|
@ -0,0 +1,11 @@
|
|||
SELECT
|
||||
p.id as id1_1_0_,
|
||||
b.id as id1_0_1_,
|
||||
p.first_name as first_na2_1_0_,
|
||||
p.last_name as last_nam3_1_0_,
|
||||
b.author_id as author_i3_0_1_,
|
||||
b.title as title2_0_1_,
|
||||
b.author_id as author_i3_0_0__,
|
||||
b.id as id1_0_0__
|
||||
FROM person p
|
||||
LEFT OUTER JOIN book b ON p.id=b.author_id
|
|
@ -0,0 +1,3 @@
|
|||
SELECT DISTINCT
|
||||
p.last_name as col_0_0_
|
||||
FROM person p
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* 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.userguide.hql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.jpa.QueryHints;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
*/
|
||||
public class SelectDistinctTest extends BaseEntityManagerFunctionalTestCase {
|
||||
|
||||
private static final Logger log = Logger.getLogger( SelectDistinctTest.class );
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] {
|
||||
Person.class,
|
||||
Book.class
|
||||
};
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Person gavinKing = new Person("Gavin", "King" );
|
||||
Person stephanKing = new Person("Stephen", "King" );
|
||||
Person vladMihalcea = new Person("Vlad", "Mihalcea" );
|
||||
|
||||
gavinKing.addBook( new Book( "Hibernate in Action" ) );
|
||||
gavinKing.addBook( new Book( "Java Persistence with Hibernate" ) );
|
||||
|
||||
stephanKing.addBook( new Book( "The Green Mile" ) );
|
||||
|
||||
vladMihalcea.addBook( new Book( "High-Performance Java Persistence" ) );
|
||||
|
||||
entityManager.persist( gavinKing );
|
||||
entityManager.persist( stephanKing );
|
||||
entityManager.persist( vladMihalcea );
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistinctProjection() {
|
||||
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
//tag::hql-distinct-projection-query-example[]
|
||||
List<String> lastNames = entityManager.createQuery(
|
||||
"select distinct p.lastName " +
|
||||
"from Person p", String.class)
|
||||
.getResultList();
|
||||
//end::hql-distinct-projection-query-example[]
|
||||
|
||||
assertTrue(
|
||||
lastNames.size() == 2 &&
|
||||
lastNames.contains( "King" ) &&
|
||||
lastNames.contains( "Mihalcea" )
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllAuthors() {
|
||||
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
List<Person> authors = entityManager.createQuery(
|
||||
"select p " +
|
||||
"from Person p " +
|
||||
"left join fetch p.books", Person.class)
|
||||
.getResultList();
|
||||
|
||||
authors.forEach( author -> {
|
||||
log.infof( "Author %s wrote %d books",
|
||||
author.getFirstName() + " " + author.getLastName(),
|
||||
author.getBooks().size()
|
||||
);
|
||||
} );
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistinctAuthors() {
|
||||
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
//tag::hql-distinct-entity-query-example[]
|
||||
List<Person> authors = entityManager.createQuery(
|
||||
"select distinct p " +
|
||||
"from Person p " +
|
||||
"left join fetch p.books", Person.class)
|
||||
.getResultList();
|
||||
//end::hql-distinct-entity-query-example[]
|
||||
|
||||
authors.forEach( author -> {
|
||||
log.infof( "Author %s wrote %d books",
|
||||
author.getFirstName() + " " + author.getLastName(),
|
||||
author.getBooks().size()
|
||||
);
|
||||
} );
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDistinctAuthorsWithoutPassThrough() {
|
||||
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
//tag::hql-distinct-entity-query-hint-example[]
|
||||
List<Person> authors = entityManager.createQuery(
|
||||
"select distinct p " +
|
||||
"from Person p " +
|
||||
"left join fetch p.books", Person.class)
|
||||
.setHint( QueryHints.HINT_PASS_DISTINCT_THROUGH, false )
|
||||
.getResultList();
|
||||
//end::hql-distinct-entity-query-hint-example[]
|
||||
|
||||
authors.forEach( author -> {
|
||||
log.infof( "Author %s wrote %d books",
|
||||
author.getFirstName() + " " + author.getLastName(),
|
||||
author.getBooks().size()
|
||||
);
|
||||
} );
|
||||
});
|
||||
}
|
||||
|
||||
@Entity(name = "Person") @Table( name = "person")
|
||||
public static class Person {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Column(name = "first_name")
|
||||
private String firstName;
|
||||
|
||||
@Column(name = "last_name")
|
||||
private String lastName;
|
||||
|
||||
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
|
||||
private List<Book> books = new ArrayList<>( );
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public List<Book> getBooks() {
|
||||
return books;
|
||||
}
|
||||
|
||||
public void addBook(Book book) {
|
||||
books.add( book );
|
||||
book.setAuthor( this );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Book") @Table( name = "book")
|
||||
public static class Book {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private String title;
|
||||
|
||||
@ManyToOne
|
||||
private Person author;
|
||||
|
||||
public Book() {
|
||||
}
|
||||
|
||||
public Book(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public Person getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(Person author) {
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue