Skip to content

Commit

Permalink
HHH-14325 - Add Query hint for specifying "query spaces" for native q…
Browse files Browse the repository at this point in the history
…ueries
  • Loading branch information
sebersole authored and dreab8 committed Nov 16, 2020
1 parent fe2230f commit 49ae7bd
Show file tree
Hide file tree
Showing 9 changed files with 640 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
* processed by auto-flush based on the table to which those entities are mapped and which are
* determined to have pending state changes.
*
* In a similar manner, these query spaces also affect how query result caching can recognize invalidated results.
* In a similar manner, these query spaces also affect how query result caching can recognize
* invalidated results.
*
* @author Steve Ebersole
*/
@SuppressWarnings( { "unused", "UnusedReturnValue", "RedundantSuppression" } )
public interface SynchronizeableQuery<T> {
/**
* Obtain the list of query spaces the query is synchronized on.
Expand All @@ -36,6 +38,32 @@ public interface SynchronizeableQuery<T> {
*/
SynchronizeableQuery<T> addSynchronizedQuerySpace(String querySpace);

/**
* Adds one-or-more synchronized spaces
*/
default SynchronizeableQuery<T> addSynchronizedQuerySpace(String... querySpaces) {
if ( querySpaces != null ) {
for ( int i = 0; i < querySpaces.length; i++ ) {
addSynchronizedQuerySpace( querySpaces[i] );
}
}
return this;
}

/**
* Adds a table expression as a query space.
*/
default SynchronizeableQuery<T> addSynchronizedTable(String tableExpression) {
return addSynchronizedQuerySpace( tableExpression );
}

/**
* Adds one-or-more synchronized table expressions
*/
default SynchronizeableQuery<T> addSynchronizedTable(String... tableExpressions) {
return addSynchronizedQuerySpace( tableExpressions );
}

/**
* Adds an entity name for (a) auto-flush checking and (b) query result cache invalidation checking. Same as
* {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
Expand All @@ -48,6 +76,18 @@ public interface SynchronizeableQuery<T> {
*/
SynchronizeableQuery<T> addSynchronizedEntityName(String entityName) throws MappingException;

/**
* Adds one-or-more entities (by name) whose tables should be added as synchronized spaces
*/
default SynchronizeableQuery<T> addSynchronizedEntityName(String... entityNames) throws MappingException {
if ( entityNames != null ) {
for ( int i = 0; i < entityNames.length; i++ ) {
addSynchronizedEntityName( entityNames[i] );
}
}
return this;
}

/**
* Adds an entity for (a) auto-flush checking and (b) query result cache invalidation checking. Same as
* {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
Expand All @@ -58,5 +98,18 @@ public interface SynchronizeableQuery<T> {
*
* @throws MappingException Indicates the given class could not be resolved as an entity
*/
@SuppressWarnings( "rawtypes" )
SynchronizeableQuery<T> addSynchronizedEntityClass(Class entityClass) throws MappingException;

/**
* Adds one-or-more entities (by class) whose tables should be added as synchronized spaces
*/
default SynchronizeableQuery<T> addSynchronizedEntityClass(Class<?>... entityClasses) throws MappingException {
if ( entityClasses != null ) {
for ( int i = 0; i < entityClasses.length; i++ ) {
addSynchronizedEntityClass( entityClasses[i] );
}
}
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,11 @@
* Whether the results should be read-only. Default is {@code false}.
*/
boolean readOnly() default false;

/**
* The query spaces to apply for the query.
*
* @see org.hibernate.SynchronizeableQuery
*/
String[] querySpaces() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,17 @@ private QueryHints() {
*/
public static final String PASS_DISTINCT_THROUGH = "hibernate.query.passDistinctThrough";

/**
* Hint for specifying query spaces to be applied to a native (SQL) query.
*
* Passed value can be any of:<ul>
* <li>List of the spaces</li>
* <li>array of the spaces</li>
* <li>String "whitespace"-separated list of the spaces</li>
* </ul>
*
* @see org.hibernate.SynchronizeableQuery
*/
public static final String NATIVE_SPACES = "org.hibernate.query.native.spaces";

}
110 changes: 59 additions & 51 deletions hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -364,59 +364,67 @@ private static void bindGenericGenerator(GenericGenerator def, MetadataBuildingC
context.getMetadataCollector().addIdentifierGenerator( buildIdGenerator( def, context ) );
}

private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
{
SqlResultSetMapping ann = annotatedElement.getAnnotation( SqlResultSetMapping.class );
QueryBinder.bindSqlResultSetMapping( ann, context, false );
}
{
SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class );
if ( ann != null ) {
for ( SqlResultSetMapping current : ann.value() ) {
QueryBinder.bindSqlResultSetMapping( current, context, false );
}
private static void bindNamedJpaQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
QueryBinder.bindSqlResultSetMapping(
annotatedElement.getAnnotation( SqlResultSetMapping.class ),
context,
false
);

final SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class );
if ( ann != null ) {
for ( SqlResultSetMapping current : ann.value() ) {
QueryBinder.bindSqlResultSetMapping( current, context, false );
}
}
{
NamedQuery ann = annotatedElement.getAnnotation( NamedQuery.class );
QueryBinder.bindQuery( ann, context, false );
}
{
org.hibernate.annotations.NamedQuery ann = annotatedElement.getAnnotation(
org.hibernate.annotations.NamedQuery.class
);
QueryBinder.bindQuery( ann, context );
}
{
NamedQueries ann = annotatedElement.getAnnotation( NamedQueries.class );
QueryBinder.bindQueries( ann, context, false );
}
{
org.hibernate.annotations.NamedQueries ann = annotatedElement.getAnnotation(
org.hibernate.annotations.NamedQueries.class
);
QueryBinder.bindQueries( ann, context );
}
{
NamedNativeQuery ann = annotatedElement.getAnnotation( NamedNativeQuery.class );
QueryBinder.bindNativeQuery( ann, context, false );
}
{
org.hibernate.annotations.NamedNativeQuery ann = annotatedElement.getAnnotation(
org.hibernate.annotations.NamedNativeQuery.class
);
QueryBinder.bindNativeQuery( ann, context );
}
{
NamedNativeQueries ann = annotatedElement.getAnnotation( NamedNativeQueries.class );
QueryBinder.bindNativeQueries( ann, context, false );
}
{
org.hibernate.annotations.NamedNativeQueries ann = annotatedElement.getAnnotation(
org.hibernate.annotations.NamedNativeQueries.class
);
QueryBinder.bindNativeQueries( ann, context );
}

QueryBinder.bindQuery(
annotatedElement.getAnnotation( NamedQuery.class ),
context,
false
);

QueryBinder.bindQueries(
annotatedElement.getAnnotation( NamedQueries.class ),
context,
false
);

QueryBinder.bindNativeQuery(
annotatedElement.getAnnotation( NamedNativeQuery.class ),
context,
false
);

QueryBinder.bindNativeQueries(
annotatedElement.getAnnotation( NamedNativeQueries.class ),
context,
false
);
}

private static void bindQueries(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
bindNamedJpaQueries( annotatedElement, context );

QueryBinder.bindQuery(
annotatedElement.getAnnotation( org.hibernate.annotations.NamedQuery.class ),
context
);

QueryBinder.bindQueries(
annotatedElement.getAnnotation( org.hibernate.annotations.NamedQueries.class ),
context
);

QueryBinder.bindNativeQuery(
annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQuery.class ),
context
);

QueryBinder.bindNativeQueries(
annotatedElement.getAnnotation( org.hibernate.annotations.NamedNativeQueries.class ),
context
);

// NamedStoredProcedureQuery handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bindNamedStoredProcedureQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ public static void bindQuery(
NamedQuery queryAnn,
MetadataBuildingContext context,
boolean isDefault) {
if ( queryAnn == null ) return;
if ( queryAnn == null ) {
return;
}

if ( BinderHelper.isEmptyAnnotationValue( queryAnn.name() ) ) {
throw new AnnotationException( "A named query must have a name when used in class or package level" );
}
//EJBQL Query

// JPA-QL Query
QueryHintDefinition hints = new QueryHintDefinition( queryAnn.hints() );
String queryName = queryAnn.query();
NamedQueryDefinition queryDefinition = new NamedQueryDefinitionBuilder( queryAnn.name() )
Expand Down Expand Up @@ -114,14 +118,17 @@ public static void bindNativeQuery(

if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) {
//sql result set usage
builder.setResultSetRef( resultSetMapping )
.createNamedQueryDefinition();
builder.setResultSetRef( resultSetMapping ).createNamedQueryDefinition();
}
else if ( !void.class.equals( queryAnn.resultClass() ) ) {
//class mapping usage
//FIXME should be done in a second pass due to entity name?
final NativeSQLQueryRootReturn entityQueryReturn =
new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ );
final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn(
"alias1",
queryAnn.resultClass().getName(),
new HashMap(),
LockMode.READ
);
builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} );
}
else {
Expand Down Expand Up @@ -153,59 +160,50 @@ public static void bindNativeQuery(
throw new AnnotationException( "A named query must have a name when used in class or package level" );
}

NamedSQLQueryDefinition query;
String resultSetMapping = queryAnn.resultSetMapping();
final String resultSetMapping = queryAnn.resultSetMapping();

final NamedSQLQueryDefinitionBuilder builder = new NamedSQLQueryDefinitionBuilder()
.setName( queryAnn.name() )
.setQuery( queryAnn.query() )
.setCacheable( queryAnn.cacheable() )
.setCacheRegion(
BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() )
? null
: queryAnn.cacheRegion()
)
.setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() )
.setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() )
.setFlushMode( getFlushMode( queryAnn.flushMode() ) )
.setCacheMode( getCacheMode( queryAnn.cacheMode() ) )
.setReadOnly( queryAnn.readOnly() )
.setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() )
.setParameterTypes( null )
.setCallable( queryAnn.callable() );


if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) {
//sql result set usage
query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() )
.setQuery( queryAnn.query() )
.setResultSetRef( resultSetMapping )
.setQuerySpaces( null )
.setCacheable( queryAnn.cacheable() )
.setCacheRegion(
BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ?
null :
queryAnn.cacheRegion()
)
.setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() )
.setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() )
.setFlushMode( getFlushMode( queryAnn.flushMode() ) )
.setCacheMode( getCacheMode( queryAnn.cacheMode() ) )
.setReadOnly( queryAnn.readOnly() )
.setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() )
.setParameterTypes( null )
.setCallable( queryAnn.callable() )
.createNamedQueryDefinition();
builder.setResultSetRef( resultSetMapping );
}
else if ( !void.class.equals( queryAnn.resultClass() ) ) {
else if ( ! void.class.equals( queryAnn.resultClass() ) ) {
//class mapping usage
//FIXME should be done in a second pass due to entity name?
final NativeSQLQueryRootReturn entityQueryReturn =
new NativeSQLQueryRootReturn( "alias1", queryAnn.resultClass().getName(), new HashMap(), LockMode.READ );
query = new NamedSQLQueryDefinitionBuilder().setName( queryAnn.name() )
.setQuery( queryAnn.query() )
.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} )
.setQuerySpaces( null )
.setCacheable( queryAnn.cacheable() )
.setCacheRegion(
BinderHelper.isEmptyAnnotationValue( queryAnn.cacheRegion() ) ?
null :
queryAnn.cacheRegion()
)
.setTimeout( queryAnn.timeout() < 0 ? null : queryAnn.timeout() )
.setFetchSize( queryAnn.fetchSize() < 0 ? null : queryAnn.fetchSize() )
.setFlushMode( getFlushMode( queryAnn.flushMode() ) )
.setCacheMode( getCacheMode( queryAnn.cacheMode() ) )
.setReadOnly( queryAnn.readOnly() )
.setComment( BinderHelper.isEmptyAnnotationValue( queryAnn.comment() ) ? null : queryAnn.comment() )
.setParameterTypes( null )
.setCallable( queryAnn.callable() )
.createNamedQueryDefinition();
final NativeSQLQueryRootReturn entityQueryReturn = new NativeSQLQueryRootReturn(
"alias1",
queryAnn.resultClass().getName(),
new HashMap(),
LockMode.READ
);
builder.setQueryReturns( new NativeSQLQueryReturn[] {entityQueryReturn} );
}
else {
throw new NotYetImplementedException( "Pure native scalar queries are not yet supported" );
LOG.debugf( "Raw scalar native-query (no explicit result mappings) found : %s", queryAnn.name() );
}

final NamedSQLQueryDefinition query = builder.createNamedQueryDefinition();

context.getMetadataCollector().addNamedNativeQuery( query );

if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Binding named native query: %s => %s", query.getName(), queryAnn.query() );
}
Expand Down
Loading

0 comments on commit 49ae7bd

Please sign in to comment.