Skip to content

Commit 49ae7bd

Browse files
sebersoledreab8
authored andcommitted
HHH-14325 - Add Query hint for specifying "query spaces" for native queries
1 parent fe2230f commit 49ae7bd

File tree

9 files changed

+640
-115
lines changed

9 files changed

+640
-115
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
@@ -144,4 +144,17 @@ private QueryHints() {
144144
*/
145145
public static final String PASS_DISTINCT_THROUGH = "hibernate.query.passDistinctThrough";
146146

147+
/**
148+
* Hint for specifying query spaces to be applied to a native (SQL) query.
149+
*
150+
* Passed value can be any of:<ul>
151+
* <li>List of the spaces</li>
152+
* <li>array of the spaces</li>
153+
* <li>String "whitespace"-separated list of the spaces</li>
154+
* </ul>
155+
*
156+
* @see org.hibernate.SynchronizeableQuery
157+
*/
158+
public static final String NATIVE_SPACES = "org.hibernate.query.native.spaces";
159+
147160
}

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

+59-51
Original file line numberDiff line numberDiff line change
@@ -364,59 +364,67 @@ private static void bindGenericGenerator(GenericGenerator def, MetadataBuildingC
364364
context.getMetadataCollector().addIdentifierGenerator( buildIdGenerator( def, context ) );
365365
}
366366

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

421429
// NamedStoredProcedureQuery handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
422430
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() )
@@ -114,14 +118,17 @@ public static void bindNativeQuery(
114118

115119
if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) {
116120
//sql result set usage
117-
builder.setResultSetRef( resultSetMapping )
118-
.createNamedQueryDefinition();
121+
builder.setResultSetRef( resultSetMapping ).createNamedQueryDefinition();
119122
}
120123
else if ( !void.class.equals( queryAnn.resultClass() ) ) {
121124
//class mapping usage
122125
//FIXME should be done in a second pass due to entity name?
123-
final NativeSQLQueryRootReturn entityQueryReturn =
124-
new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ );
126+
final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn(
127+
"alias1",
128+
queryAnn.resultClass().getName(),
129+
new HashMap(),
130+
LockMode.READ
131+
);
125132
builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} );
126133
}
127134
else {
@@ -153,59 +160,50 @@ public static void bindNativeQuery(
153160
throw new AnnotationException( "A named query must have a name when used in class or package level" );
154161
}
155162

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

0 commit comments

Comments
 (0)