Skip to content

Commit d5067ec

Browse files
committed
HHH-14325 - Add Query hint for specifying "query spaces" for native queries
1 parent 2896372 commit d5067ec

File tree

9 files changed

+640
-109
lines changed

9 files changed

+640
-109
lines changed

hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java

+54-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
* processed by auto-flush based on the table to which those entities are mapped and which are
1616
* determined to have pending state changes.
1717
*
18-
* In a similar manner, these query spaces also affect how query result caching can recognize invalidated results.
18+
* In a similar manner, these query spaces also affect how query result caching can recognize
19+
* invalidated results.
1920
*
2021
* @author Steve Ebersole
2122
*/
23+
@SuppressWarnings( { "unused", "UnusedReturnValue", "RedundantSuppression" } )
2224
public interface SynchronizeableQuery<T> {
2325
/**
2426
* Obtain the list of query spaces the query is synchronized on.
@@ -36,6 +38,32 @@ public interface SynchronizeableQuery<T> {
3638
*/
3739
SynchronizeableQuery<T> addSynchronizedQuerySpace(String querySpace);
3840

41+
/**
42+
* Adds one-or-more synchronized spaces
43+
*/
44+
default SynchronizeableQuery<T> addSynchronizedQuerySpace(String... querySpaces) {
45+
if ( querySpaces != null ) {
46+
for ( int i = 0; i < querySpaces.length; i++ ) {
47+
addSynchronizedQuerySpace( querySpaces[i] );
48+
}
49+
}
50+
return this;
51+
}
52+
53+
/**
54+
* Adds a table expression as a query space.
55+
*/
56+
default SynchronizeableQuery<T> addSynchronizedTable(String tableExpression) {
57+
return addSynchronizedQuerySpace( tableExpression );
58+
}
59+
60+
/**
61+
* Adds one-or-more synchronized table expressions
62+
*/
63+
default SynchronizeableQuery<T> addSynchronizedTable(String... tableExpressions) {
64+
return addSynchronizedQuerySpace( tableExpressions );
65+
}
66+
3967
/**
4068
* Adds an entity name for (a) auto-flush checking and (b) query result cache invalidation checking. Same as
4169
* {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
@@ -48,6 +76,18 @@ public interface SynchronizeableQuery<T> {
4876
*/
4977
SynchronizeableQuery<T> addSynchronizedEntityName(String entityName) throws MappingException;
5078

79+
/**
80+
* Adds one-or-more entities (by name) whose tables should be added as synchronized spaces
81+
*/
82+
default SynchronizeableQuery<T> addSynchronizedEntityName(String... entityNames) throws MappingException {
83+
if ( entityNames != null ) {
84+
for ( int i = 0; i < entityNames.length; i++ ) {
85+
addSynchronizedEntityName( entityNames[i] );
86+
}
87+
}
88+
return this;
89+
}
90+
5191
/**
5292
* Adds an entity for (a) auto-flush checking and (b) query result cache invalidation checking. Same as
5393
* {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
@@ -58,5 +98,18 @@ public interface SynchronizeableQuery<T> {
5898
*
5999
* @throws MappingException Indicates the given class could not be resolved as an entity
60100
*/
101+
@SuppressWarnings( "rawtypes" )
61102
SynchronizeableQuery<T> addSynchronizedEntityClass(Class entityClass) throws MappingException;
103+
104+
/**
105+
* Adds one-or-more entities (by class) whose tables should be added as synchronized spaces
106+
*/
107+
default SynchronizeableQuery<T> addSynchronizedEntityClass(Class<?>... entityClasses) throws MappingException {
108+
if ( entityClasses != null ) {
109+
for ( int i = 0; i < entityClasses.length; i++ ) {
110+
addSynchronizedEntityClass( entityClasses[i] );
111+
}
112+
}
113+
return this;
114+
}
62115
}

hibernate-core/src/main/java/org/hibernate/annotations/NamedNativeQuery.java

+7
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,11 @@
8989
* Whether the results should be read-only. Default is {@code false}.
9090
*/
9191
boolean readOnly() default false;
92+
93+
/**
94+
* The query spaces to apply for the query.
95+
*
96+
* @see org.hibernate.SynchronizeableQuery
97+
*/
98+
String[] querySpaces() default {};
9299
}

hibernate-core/src/main/java/org/hibernate/annotations/QueryHints.java

+13
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,17 @@ private QueryHints() {
137137
*/
138138
public static final String PASS_DISTINCT_THROUGH = "hibernate.query.passDistinctThrough";
139139

140+
/**
141+
* Hint for specifying query spaces to be applied to a native (SQL) query.
142+
*
143+
* Passed value can be any of:<ul>
144+
* <li>List of the spaces</li>
145+
* <li>array of the spaces</li>
146+
* <li>String "whitespace"-separated list of the spaces</li>
147+
* </ul>
148+
*
149+
* @see org.hibernate.SynchronizeableQuery
150+
*/
151+
public static final String NATIVE_SPACES = "org.hibernate.query.native.spaces";
152+
140153
}

hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java

+59-51
Original file line numberDiff line numberDiff line change
@@ -370,59 +370,67 @@ private static void bindGenericGenerator(GenericGenerator def, MetadataBuildingC
370370
context.getMetadataCollector().addIdentifierGenerator( buildIdGenerator( def, context ) );
371371
}
372372

373-
private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
374-
{
375-
SqlResultSetMapping ann = annotatedElement.getAnnotation( SqlResultSetMapping.class );
376-
QueryBinder.bindSqlResultSetMapping( ann, context, false );
377-
}
378-
{
379-
SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class );
380-
if ( ann != null ) {
381-
for ( SqlResultSetMapping current : ann.value() ) {
382-
QueryBinder.bindSqlResultSetMapping( current, context, false );
383-
}
373+
private static void bindNamedJpaQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
374+
QueryBinder.bindSqlResultSetMapping(
375+
annotatedElement.getAnnotation( SqlResultSetMapping.class ),
376+
context,
377+
false
378+
);
379+
380+
final SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class );
381+
if ( ann != null ) {
382+
for ( SqlResultSetMapping current : ann.value() ) {
383+
QueryBinder.bindSqlResultSetMapping( current, context, false );
384384
}
385385
}
386-
{
387-
NamedQuery ann = annotatedElement.getAnnotation( NamedQuery.class );
388-
QueryBinder.bindQuery( ann, context, false );
389-
}
390-
{
391-
org.hibernate.annotations.NamedQuery ann = annotatedElement.getAnnotation(
392-
org.hibernate.annotations.NamedQuery.class
393-
);
394-
QueryBinder.bindQuery( ann, context );
395-
}
396-
{
397-
NamedQueries ann = annotatedElement.getAnnotation( NamedQueries.class );
398-
QueryBinder.bindQueries( ann, context, false );
399-
}
400-
{
401-
org.hibernate.annotations.NamedQueries ann = annotatedElement.getAnnotation(
402-
org.hibernate.annotations.NamedQueries.class
403-
);
404-
QueryBinder.bindQueries( ann, context );
405-
}
406-
{
407-
NamedNativeQuery ann = annotatedElement.getAnnotation( NamedNativeQuery.class );
408-
QueryBinder.bindNativeQuery( ann, context, false );
409-
}
410-
{
411-
org.hibernate.annotations.NamedNativeQuery ann = annotatedElement.getAnnotation(
412-
org.hibernate.annotations.NamedNativeQuery.class
413-
);
414-
QueryBinder.bindNativeQuery( ann, context );
415-
}
416-
{
417-
NamedNativeQueries ann = annotatedElement.getAnnotation( NamedNativeQueries.class );
418-
QueryBinder.bindNativeQueries( ann, context, false );
419-
}
420-
{
421-
org.hibernate.annotations.NamedNativeQueries ann = annotatedElement.getAnnotation(
422-
org.hibernate.annotations.NamedNativeQueries.class
423-
);
424-
QueryBinder.bindNativeQueries( ann, context );
425-
}
386+
387+
QueryBinder.bindQuery(
388+
annotatedElement.getAnnotation( NamedQuery.class ),
389+
context,
390+
false
391+
);
392+
393+
QueryBinder.bindQueries(
394+
annotatedElement.getAnnotation( NamedQueries.class ),
395+
context,
396+
false
397+
);
398+
399+
QueryBinder.bindNativeQuery(
400+
annotatedElement.getAnnotation( NamedNativeQuery.class ),
401+
context,
402+
false
403+
);
404+
405+
QueryBinder.bindNativeQueries(
406+
annotatedElement.getAnnotation( NamedNativeQueries.class ),
407+
context,
408+
false
409+
);
410+
}
411+
412+
private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
413+
bindNamedJpaQueries( annotatedElement, context );
414+
415+
QueryBinder.bindQuery(
416+
annotatedElement.getAnnotation( org.hibernate.annotations.NamedQuery.class ),
417+
context
418+
);
419+
420+
QueryBinder.bindQueries(
421+
annotatedElement.getAnnotation( org.hibernate.annotations.NamedQueries.class ),
422+
context
423+
);
424+
425+
QueryBinder.bindNativeQuery(
426+
annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQuery.class ),
427+
context
428+
);
429+
430+
QueryBinder.bindNativeQueries(
431+
annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQueries.class ),
432+
context
433+
);
426434

427435
// NamedStoredProcedureQuery handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
428436
bindNamedStoredProcedureQuery(

hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java

+48-50
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,15 @@ public static void bindQuery(
4848
NamedQuery queryAnn,
4949
MetadataBuildingContext context,
5050
boolean isDefault) {
51-
if ( queryAnn == null ) return;
51+
if ( queryAnn == null ) {
52+
return;
53+
}
54+
5255
if ( BinderHelper.isEmptyAnnotationValue( queryAnn.name() ) ) {
5356
throw new AnnotationException( "A named query must have a name when used in class or package level" );
5457
}
55-
//EJBQL Query
58+
59+
// JPA-QL Query
5660
QueryHintDefinition hints = new QueryHintDefinition( queryAnn.hints() );
5761
String queryName = queryAnn.query();
5862
NamedQueryDefinition queryDefinition = new NamedQueryDefinitionBuilder( queryAnn.name() )
@@ -112,14 +116,17 @@ public static void bindNativeQuery(
112116

113117
if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) {
114118
//sql result set usage
115-
builder.setResultSetRef( resultSetMapping )
116-
.createNamedQueryDefinition();
119+
builder.setResultSetRef( resultSetMapping ).createNamedQueryDefinition();
117120
}
118121
else if ( !void.class.equals( queryAnn.resultClass() ) ) {
119122
//class mapping usage
120123
//FIXME should be done in a second pass due to entity name?
121-
final NativeSQLQueryRootReturn entityQueryReturn =
122-
new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ );
124+
final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn(
125+
"alias1",
126+
queryAnn.resultClass().getName(),
127+
new HashMap(),
128+
LockMode.READ
129+
);
123130
builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} );
124131
}
125132
else {
@@ -151,59 +158,50 @@ public static void bindNativeQuery(
151158
throw new AnnotationException( "A named query must have a name when used in class or package level" );
152159
}
153160

154-
NamedSQLQueryDefinition query;
155-
String resultSetMapping = queryAnn.resultSetMapping();
161+
final String resultSetMapping = queryAnn.resultSetMapping();
162+
163+
final NamedSQLQueryDefinitionBuilder builder = new NamedSQLQueryDefinitionBuilder()
164+
.setName( queryAnn.name() )
165+
.setQuery( queryAnn.query() )
166+
.setCacheable( queryAnn.cacheable() )
167+
.setCacheRegion(
168+
BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() )
169+
? null
170+
: queryAnn.cacheRegion()
171+
)
172+
.setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() )
173+
.setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() )
174+
.setFlushMode( getFlushMode( queryAnn.flushMode() ) )
175+
.setCacheMode( getCacheMode( queryAnn.cacheMode() ) )
176+
.setReadOnly( queryAnn.readOnly() )
177+
.setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() )
178+
.setParameterTypes( null )
179+
.setCallable( queryAnn.callable() );
180+
181+
156182
if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) {
157183
//sql result set usage
158-
query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() )
159-
.setQuery( queryAnn.query() )
160-
.setResultSetRef( resultSetMapping )
161-
.setQuerySpaces( null )
162-
.setCacheable( queryAnn.cacheable() )
163-
.setCacheRegion(
164-
BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ?
165-
null :
166-
queryAnn.cacheRegion()
167-
)
168-
.setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() )
169-
.setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() )
170-
.setFlushMode( getFlushMode( queryAnn.flushMode() ) )
171-
.setCacheMode( getCacheMode( queryAnn.cacheMode() ) )
172-
.setReadOnly( queryAnn.readOnly() )
173-
.setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() )
174-
.setParameterTypes( null )
175-
.setCallable( queryAnn.callable() )
176-
.createNamedQueryDefinition();
184+
builder.setResultSetRef( resultSetMapping );
177185
}
178-
else if ( !void.class.equals( queryAnn.resultClass() ) ) {
186+
else if ( ! void.class.equals( queryAnn.resultClass() ) ) {
179187
//class mapping usage
180188
//FIXME should be done in a second pass due to entity name?
181-
final NativeSQLQueryRootReturn entityQueryReturn =
182-
new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ );
183-
query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() )
184-
.setQuery( queryAnn.query() )
185-
.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} )
186-
.setQuerySpaces( null )
187-
.setCacheable( queryAnn.cacheable() )
188-
.setCacheRegion(
189-
BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ?
190-
null :
191-
queryAnn.cacheRegion()
192-
)
193-
.setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() )
194-
.setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() )
195-
.setFlushMode( getFlushMode( queryAnn.flushMode() ) )
196-
.setCacheMode( getCacheMode( queryAnn.cacheMode() ) )
197-
.setReadOnly( queryAnn.readOnly() )
198-
.setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() )
199-
.setParameterTypes( null )
200-
.setCallable( queryAnn.callable() )
201-
.createNamedQueryDefinition();
189+
final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn(
190+
"alias1",
191+
queryAnn.resultClass().getName(),
192+
new HashMap(),
193+
LockMode.READ
194+
);
195+
builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} );
202196
}
203197
else {
204-
throw new NotYetImplementedException( "Pure native scalar queries are not yet supported" );
198+
LOG.debugf( "Raw scalar native-query (no explicit result mappings) found : %s", queryAnn.name() );
205199
}
200+
201+
final NamedSQLQueryDefinition query = builder.createNamedQueryDefinition();
202+
206203
context.getMetadataCollector().addNamedNativeQuery( query );
204+
207205
if ( LOG.isDebugEnabled() ) {
208206
LOG.debugf( "Binding named native query: %s => %s", query.getName(), queryAnn.query() );
209207
}

0 commit comments

Comments
 (0)