Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e232eea

Browse files
committed
introduce incubating ProjectionSpecification API
1 parent 87b4532 commit e232eea

File tree

5 files changed

+296
-3
lines changed

5 files changed

+296
-3
lines changed

documentation/src/main/asciidoc/userguide/chapters/tooling/modelgen.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ a static metamodel class based on the following rules:
4040
* The name of the metamodel class is derived from the name of the managed class by appending "_" to the managed class name.
4141
* The metamodel class `X_` must be annotated with the `jakarta.persistence.StaticMetamodel` annotation. The generation
4242
can also be configured to add the `javax.annotation.processing.Generated` annotation.
43-
* If class `X` extends another class `S`, where `S` is the most derived managed class extended by `X`, then
44-
class `X_` must extend class `S_`, where `S_` is the metamodel class created for `S`.
43+
* If class `X` extends another class `T`, where `T` is the most derived managed class extended by `X`, then
44+
class `X_` must extend class `S_`, where `S_` is the metamodel class created for `T`.
4545
* For every persistent singular attribute `y` declared by class `X`, where the type of `y` is `Y`,
4646
the metamodel class must contain a declaration as follows:
4747

@@ -69,7 +69,7 @@ class must contain a declaration as follows:
6969
where `K` is the type of the key of the map in class `X`
7070

7171
* Import statements must be included for `jakarta.persistence.metamodel` types as
72-
needed, as well as all domain model classes (i.e., `X`, `S`, `Y`, `Z`, and `K`).
72+
needed, as well as all domain model classes (i.e., `X`, `T`, `Y`, `Z`, and `K`).
7373

7474
As an example, consider the following domain model -
7575

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query.specification;
6+
7+
import jakarta.persistence.EntityManager;
8+
import jakarta.persistence.TypedQueryReference;
9+
import jakarta.persistence.criteria.CriteriaBuilder;
10+
import jakarta.persistence.criteria.CriteriaQuery;
11+
import jakarta.persistence.metamodel.SingularAttribute;
12+
import org.hibernate.Incubating;
13+
import org.hibernate.Session;
14+
import org.hibernate.StatelessSession;
15+
import org.hibernate.query.SelectionQuery;
16+
import org.hibernate.query.restriction.Path;
17+
import org.hibernate.query.specification.internal.ProjectionSpecificationImpl;
18+
19+
import java.util.function.Function;
20+
21+
/**
22+
* Allows a {@link SelectionSpecification} to be augmented with the specification
23+
* of a projection list.
24+
* <pre>
25+
* var specification =
26+
* SelectionSpecification.create(Book.class)
27+
* .restrict(Restriction.contains(Book_.title, "hibernate", false))
28+
* .sort(Order.desc(Book_.title));
29+
* var projection = ProjectionSpecification.create( specification );
30+
* var bookIsbn = projection.select(Book_.isbn);
31+
* var bookTitle = projection.select(Book_.title);
32+
* var results = projection.createQuery(session).getResultList();
33+
* for (var result : results) {
34+
* var isbn = bookIsbn.in(result);
35+
* var title = bookTitle.in(result);
36+
* ...
37+
* }
38+
* </pre>
39+
* <p>
40+
* A {@code ProjectionSpecification} always results in a query which with result
41+
* type {@code Object[]}. The {@link #select(SingularAttribute) select()} methods
42+
* return {@link Element}, allowing easy and typesafe access to the elements of
43+
* the returned array.
44+
*
45+
* @param <T> The result type of the {@link SelectionSpecification}
46+
*
47+
* @since 7.2
48+
*
49+
* @author Gavin King
50+
*/
51+
@Incubating
52+
public interface ProjectionSpecification<T> extends QuerySpecification<Object[]> {
53+
54+
/**
55+
* Create a new {@code ProjectionSpecification} which augments the given
56+
* {@link SelectionSpecification}.
57+
*/
58+
static <T> ProjectionSpecification<T> create(SelectionSpecification<T> selectionSpecification) {
59+
return new ProjectionSpecificationImpl<>( selectionSpecification );
60+
}
61+
62+
/**
63+
* Allows typesafe access to elements of the {@code Object[]}
64+
* arrays returned by the query.
65+
*
66+
* @param <X> The type of the element of the projection list
67+
*/
68+
@FunctionalInterface
69+
interface Element<X> extends Function<Object[],X> {
70+
X in(Object[] tuple);
71+
72+
@Override
73+
default X apply(Object[] tuple) {
74+
return in(tuple);
75+
}
76+
}
77+
78+
/**
79+
* Select the given attribute of the root entity.
80+
*
81+
* @param attribute An attribute of the root entity
82+
* @return An {@link Element} allowing typesafe access to the results
83+
*/
84+
<X> Element<X> select(SingularAttribute<T,X> attribute);
85+
86+
/**
87+
* Select the given field or property identified by the given path
88+
* from of the root entity.
89+
*
90+
* @param path A path from the root entity
91+
* @return An {@link Element} allowing typesafe access to the results
92+
*/
93+
<X> Element<X> select(Path<T,X> path);
94+
95+
@Override
96+
SelectionQuery<Object[]> createQuery(Session session);
97+
98+
@Override
99+
SelectionQuery<Object[]> createQuery(StatelessSession session);
100+
101+
@Override
102+
SelectionQuery<Object[]> createQuery(EntityManager entityManager);
103+
104+
@Override
105+
CriteriaQuery<Object[]> buildCriteria(CriteriaBuilder builder);
106+
107+
@Override
108+
TypedQueryReference<Object[]> reference();
109+
110+
@Override
111+
ProjectionSpecification<T> validate(CriteriaBuilder builder);
112+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query.specification.internal;
6+
7+
import jakarta.persistence.EntityManager;
8+
import jakarta.persistence.TypedQueryReference;
9+
import jakarta.persistence.criteria.CriteriaBuilder;
10+
import jakarta.persistence.criteria.CriteriaQuery;
11+
import jakarta.persistence.metamodel.SingularAttribute;
12+
import org.hibernate.Session;
13+
import org.hibernate.SharedSessionContract;
14+
import org.hibernate.StatelessSession;
15+
import org.hibernate.query.SelectionQuery;
16+
import org.hibernate.query.restriction.Path;
17+
import org.hibernate.query.restriction.Restriction;
18+
import org.hibernate.query.specification.ProjectionSpecification;
19+
import org.hibernate.query.specification.QuerySpecification;
20+
import org.hibernate.query.specification.SelectionSpecification;
21+
import org.hibernate.query.sqm.NodeBuilder;
22+
import org.hibernate.query.sqm.tree.domain.SqmPath;
23+
import org.hibernate.query.sqm.tree.from.SqmRoot;
24+
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
25+
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
26+
import org.hibernate.query.sqm.tree.select.SqmSelection;
27+
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.Map;
32+
import java.util.function.BiFunction;
33+
34+
/**
35+
* @author Gavin King
36+
*/
37+
public class ProjectionSpecificationImpl<T> implements ProjectionSpecification<T>, TypedQueryReference<Object[]> {
38+
39+
private final SelectionSpecification<T> selectionSpecification;
40+
private final List<BiFunction<SqmSelectStatement<Object[]>, SqmRoot<T>, SqmSelectableNode<?>>> specifications = new ArrayList<>();
41+
42+
public ProjectionSpecificationImpl(SelectionSpecification<T> selectionSpecification) {
43+
this.selectionSpecification = selectionSpecification;
44+
}
45+
46+
@Override
47+
public <X> Element<X> select(SingularAttribute<T, X> attribute) {
48+
final int position = specifications.size();
49+
specifications.add( (select, root) -> root.get( attribute ) );
50+
return tuple -> (X) tuple[position];
51+
}
52+
53+
@Override
54+
public <X> Element<X> select(Path<T, X> path) {
55+
final int position = specifications.size();
56+
specifications.add( (select, root) -> (SqmPath<X>) path.path( root ) );
57+
return tuple -> (X) tuple[position];
58+
}
59+
60+
@Override
61+
public QuerySpecification<Object[]> restrict(Restriction<? super Object[]> restriction) {
62+
throw new UnsupportedOperationException( "This is not supported yet!" );
63+
}
64+
65+
@Override
66+
public SelectionQuery<Object[]> createQuery(Session session) {
67+
return session.createSelectionQuery( buildCriteria( session.getCriteriaBuilder() ) );
68+
}
69+
70+
@Override
71+
public SelectionQuery<Object[]> createQuery(StatelessSession session) {
72+
return session.createSelectionQuery( buildCriteria( session.getCriteriaBuilder() ) );
73+
}
74+
75+
@Override
76+
public SelectionQuery<Object[]> createQuery(EntityManager entityManager) {
77+
return entityManager.unwrap( SharedSessionContract.class )
78+
.createQuery( buildCriteria( entityManager.getCriteriaBuilder() ) );
79+
}
80+
81+
@Override
82+
public CriteriaQuery<Object[]> buildCriteria(CriteriaBuilder builder) {
83+
var impl = (SelectionSpecificationImpl<T>) selectionSpecification;
84+
// TODO: handle HQL, existing criteria
85+
final var tupleQuery =
86+
(SqmSelectStatement<Object[]>)
87+
builder.createQuery(Object[].class);
88+
final var root = tupleQuery.from( impl.getResultType() );
89+
// This cast is completely bogus
90+
final var castStatement = (SqmSelectStatement<T>) tupleQuery;
91+
impl.getSpecifications().forEach( spec -> spec.accept( castStatement, root ) );
92+
final var nodeBuilder = (NodeBuilder) builder;
93+
final var selectClause = tupleQuery.getQuerySpec().getSelectClause();
94+
for ( int i = 0; i < specifications.size(); i++ ) {
95+
final var selection = specifications.get( i ).apply( tupleQuery, root );
96+
selectClause.addSelection( new SqmSelection<>( selection, selection.getAlias(), nodeBuilder ) );
97+
}
98+
return tupleQuery;
99+
}
100+
101+
@Override
102+
public ProjectionSpecification<T> validate(CriteriaBuilder builder) {
103+
selectionSpecification.validate( builder );
104+
// TODO: validate projection
105+
return this;
106+
}
107+
108+
@Override
109+
public TypedQueryReference<Object[]> reference() {
110+
return this;
111+
}
112+
113+
@Override
114+
public String getName() {
115+
return null;
116+
}
117+
118+
@Override
119+
public Class<? extends Object[]> getResultType() {
120+
return Object[].class;
121+
}
122+
123+
@Override
124+
public Map<String, Object> getHints() {
125+
return Collections.emptyMap();
126+
}
127+
}

hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ public TypedQueryReference<T> reference() {
9696
return this;
9797
}
9898

99+
public List<BiConsumer<SqmSelectStatement<T>, SqmRoot<T>>> getSpecifications() {
100+
return specifications;
101+
}
102+
99103
@Override
100104
public SelectionSpecification<T> restrict(Restriction<? super T> restriction) {
101105
specifications.add( (sqmStatement, root) -> {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.query.dynamic;
6+
7+
import org.hibernate.query.restriction.Path;
8+
import org.hibernate.query.specification.ProjectionSpecification;
9+
import org.hibernate.query.specification.SelectionSpecification;
10+
import org.hibernate.testing.orm.junit.DomainModel;
11+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
12+
import org.junit.jupiter.api.BeforeAll;
13+
import org.junit.jupiter.api.Test;
14+
15+
import static org.junit.jupiter.api.Assertions.assertEquals;
16+
import static org.junit.jupiter.api.Assertions.assertNull;
17+
18+
/**
19+
* @author Steve Ebersole
20+
*/
21+
@SuppressWarnings("JUnitMalformedDeclaration")
22+
@DomainModel(annotatedClasses = {BasicEntity.class, OtherEntity.class})
23+
@org.hibernate.testing.orm.junit.SessionFactory(useCollectingStatementInspector = true)
24+
public class ProjectionSpecificationTest {
25+
26+
@BeforeAll
27+
public static void setup(SessionFactoryScope factoryScope) {
28+
factoryScope.inTransaction( (session) -> {
29+
session.createMutationQuery( "insert BasicEntity (id, name, position) values (1, 'Gavin', 2)" )
30+
.executeUpdate();
31+
} );
32+
}
33+
34+
@Test
35+
void testProjection(SessionFactoryScope factoryScope) {
36+
factoryScope.inTransaction( (session) -> {
37+
var spec = SelectionSpecification.create( BasicEntity.class );
38+
var projection = ProjectionSpecification.create( spec );
39+
var position = projection.select( BasicEntity_.position );
40+
var name = projection.select( BasicEntity_.name );
41+
var id = projection.select( Path.from( BasicEntity.class ).to( BasicEntity_.id ) );
42+
var otherId = projection.select( Path.from( BasicEntity.class ).to( BasicEntity_.other ).to( OtherEntity_.id ) );
43+
var tuple = projection.createQuery( session ).getSingleResult();
44+
assertEquals( 2, position.in(tuple) );
45+
assertEquals( "Gavin", name.in(tuple) );
46+
assertEquals( 1, id.in(tuple) );
47+
assertNull( otherId.in( tuple ) );
48+
});
49+
}
50+
}

0 commit comments

Comments
 (0)