org.hibernate.query.internal.NativeQueryImpl Maven / Gradle / Ivy
/*
* 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 .
*/
package org.hibernate.query.internal;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.Parameter;
import jakarta.persistence.TemporalType;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.ScrollMode;
import org.hibernate.SynchronizeableQuery;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
import org.hibernate.engine.spi.NamedSQLQueryDefinition;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.ParameterMetadata;
import org.hibernate.query.Query;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.spi.NativeQueryImplementor;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.Type;
import static org.hibernate.jpa.QueryHints.HINT_NATIVE_LOCKMODE;
/**
* @author Steve Ebersole
*/
public class NativeQueryImpl extends AbstractProducedQuery implements NativeQueryImplementor {
private final String sqlString;
private final QueryParameterBindingsImpl queryParameterBindings;
private List queryReturns;
private List queryReturnBuilders;
private boolean autoDiscoverTypes;
private Collection querySpaces;
private final boolean callable;
private final LockOptions lockOptions = new LockOptions();
private Serializable collectionKey;
/**
* Constructs a NativeQueryImpl given a sql query defined in the mappings.
*
* @param queryDef The representation of the defined .
* @param session The session to which this NativeQuery belongs.
* @param parameterMetadata Metadata about parameters found in the query.
*/
public NativeQueryImpl(
NamedSQLQueryDefinition queryDef,
SharedSessionContractImplementor session,
ParameterMetadata parameterMetadata) {
super( session, parameterMetadata );
this.sqlString = queryDef.getQueryString();
this.callable = queryDef.isCallable();
this.querySpaces = queryDef.getQuerySpaces() == null ? null : new ArrayList<>( queryDef.getQuerySpaces() );
if ( queryDef.getResultSetRef() != null ) {
ResultSetMappingDefinition definition = session.getFactory()
.getNamedQueryRepository()
.getResultSetMappingDefinition( queryDef.getResultSetRef() );
if ( definition == null ) {
throw new MappingException(
"Unable to find resultset-ref definition: " +
queryDef.getResultSetRef()
);
}
this.queryReturns = new ArrayList<>( Arrays.asList( definition.getQueryReturns() ) );
}
else if ( queryDef.getQueryReturns() != null && queryDef.getQueryReturns().length > 0 ) {
this.queryReturns = new ArrayList<>( Arrays.asList( queryDef.getQueryReturns() ) );
}
else {
this.queryReturns = new ArrayList<>();
}
this.queryParameterBindings = QueryParameterBindingsImpl.from(
parameterMetadata,
session.getFactory(),
session.isQueryParametersValidationEnabled()
);
}
public NativeQueryImpl(
String sqlString,
boolean callable,
SharedSessionContractImplementor session,
ParameterMetadata sqlParameterMetadata) {
super( session, sqlParameterMetadata );
this.queryReturns = new ArrayList<>();
this.sqlString = sqlString;
this.callable = callable;
this.querySpaces = new ArrayList<>();
this.queryParameterBindings = QueryParameterBindingsImpl.from(
sqlParameterMetadata,
session.getFactory(),
session.isQueryParametersValidationEnabled()
);
}
@Override
protected QueryParameterBindings getQueryParameterBindings() {
return queryParameterBindings;
}
@Override
public NativeQuery setResultSetMapping(String name) {
ResultSetMappingDefinition mapping = getProducer().getFactory().getNamedQueryRepository().getResultSetMappingDefinition( name );
if ( mapping == null ) {
throw new MappingException( "Unknown SqlResultSetMapping [" + name + "]" );
}
NativeSQLQueryReturn[] returns = mapping.getQueryReturns();
queryReturns.addAll( Arrays.asList( returns ) );
return this;
}
@Override
public String getQueryString() {
return sqlString;
}
@Override
public boolean isCallable() {
return callable;
}
@Override
public List getQueryReturns() {
prepareQueryReturnsIfNecessary();
return queryReturns;
}
@Override
@SuppressWarnings("unchecked")
protected List doList() {
return getProducer().list(
generateQuerySpecification(),
getQueryParameters()
);
}
private NativeSQLQuerySpecification generateQuerySpecification() {
return new NativeSQLQuerySpecification(
getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ),
queryReturns.toArray( new NativeSQLQueryReturn[queryReturns.size()] ),
querySpaces
);
}
@Override
public QueryParameters getQueryParameters() {
final QueryParameters queryParameters = super.getQueryParameters();
queryParameters.setCallable( callable );
queryParameters.setAutoDiscoverScalarTypes( autoDiscoverTypes );
if ( collectionKey != null ) {
queryParameters.setCollectionKeys( new Serializable[] {collectionKey} );
}
return queryParameters;
}
private void prepareQueryReturnsIfNecessary() {
if ( queryReturnBuilders != null ) {
if ( !queryReturnBuilders.isEmpty() ) {
if ( queryReturns != null ) {
queryReturns.clear();
queryReturns = null;
}
queryReturns = new ArrayList<>();
for ( NativeQueryReturnBuilder builder : queryReturnBuilders ) {
queryReturns.add( builder.buildReturn() );
}
queryReturnBuilders.clear();
}
queryReturnBuilders = null;
}
}
@Override
protected ScrollableResultsImplementor doScroll(ScrollMode scrollMode) {
final NativeSQLQuerySpecification nativeSQLQuerySpecification = generateQuerySpecification();
final QueryParameters queryParameters = getQueryParameters();
queryParameters.setScrollMode( scrollMode );
return getProducer().scroll(
nativeSQLQuerySpecification,
queryParameters
);
}
@Override
protected void beforeQuery() {
prepareQueryReturnsIfNecessary();
boolean noReturns = queryReturns == null || queryReturns.isEmpty();
if ( noReturns ) {
this.autoDiscoverTypes = true;
}
else {
for ( NativeSQLQueryReturn queryReturn : queryReturns ) {
if ( queryReturn instanceof NativeSQLQueryScalarReturn ) {
NativeSQLQueryScalarReturn scalar = (NativeSQLQueryScalarReturn) queryReturn;
if ( scalar.getType() == null ) {
autoDiscoverTypes = true;
break;
}
}
else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) {
autoDiscoverTypes = true;
break;
}
}
}
super.beforeQuery();
if ( CollectionHelper.isNotEmpty( getSynchronizedQuerySpaces() ) ) {
// The application defined query spaces on the Hibernate native SQLQuery which means the query will already
// perform a partial flush according to the defined query spaces, no need to do a full flush.
return;
}
// otherwise we need to flush. the query itself is not required to execute in a transaction; if there is
// no transaction, the flush would throw a TransactionRequiredException which would potentially break existing
// apps, so we only do the flush if a transaction is in progress.
//
// NOTE : this was added for JPA initially. Perhaps we want to only do this from JPA usage?
if ( shouldFlush() ) {
getProducer().flush();
}
}
@Override
public Iterator iterate() {
throw new UnsupportedOperationException( "SQL queries do not currently support iteration" );
}
private boolean shouldFlush() {
if ( getProducer().isTransactionInProgress() ) {
FlushMode effectiveFlushMode = getHibernateFlushMode();
if ( effectiveFlushMode == null ) {
effectiveFlushMode = getProducer().getHibernateFlushMode();
}
if ( effectiveFlushMode == FlushMode.ALWAYS ) {
return true;
}
if ( effectiveFlushMode == FlushMode.AUTO ) {
if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
return true;
}
}
}
return false;
}
protected int doExecuteUpdate() {
return getProducer().executeNativeUpdate(
generateQuerySpecification(),
getQueryParameters()
);
}
@Override
public NativeQueryImplementor setCollectionKey(Serializable key) {
this.collectionKey = key;
return this;
}
@Override
public NativeQueryImplementor addScalar(String columnAlias) {
return addScalar( columnAlias, null );
}
@Override
public NativeQueryImplementor addScalar(String columnAlias, Type type) {
addReturnBuilder(
new NativeQueryReturnBuilder() {
public NativeSQLQueryReturn buildReturn() {
return new NativeSQLQueryScalarReturn( columnAlias, type );
}
}
);
return this;
}
protected void addReturnBuilder(NativeQueryReturnBuilder builder) {
if ( queryReturnBuilders == null ) {
queryReturnBuilders = new ArrayList<>();
}
queryReturnBuilders.add( builder );
}
@Override
public RootReturn addRoot(String tableAlias, String entityName) {
NativeQueryReturnBuilderRootImpl builder = new NativeQueryReturnBuilderRootImpl( tableAlias, entityName );
addReturnBuilder( builder );
return builder;
}
@Override
public RootReturn addRoot(String tableAlias, Class entityType) {
return addRoot( tableAlias, entityType.getName() );
}
@Override
public NativeQueryImplementor addEntity(String entityName) {
return addEntity( StringHelper.unqualify( entityName ), entityName );
}
@Override
public NativeQueryImplementor addEntity(String tableAlias, String entityName) {
addRoot( tableAlias, entityName );
return this;
}
@Override
public NativeQueryImplementor addEntity(String tableAlias, String entityName, LockMode lockMode) {
addRoot( tableAlias, entityName ).setLockMode( lockMode );
return this;
}
@Override
public NativeQueryImplementor addEntity(Class entityType) {
return addEntity( entityType.getName() );
}
@Override
public NativeQueryImplementor addEntity(String tableAlias, Class entityClass) {
return addEntity( tableAlias, entityClass.getName() );
}
@Override
public NativeQueryImplementor addEntity(String tableAlias, Class entityClass, LockMode lockMode) {
return addEntity( tableAlias, entityClass.getName(), lockMode );
}
@Override
public FetchReturn addFetch(String tableAlias, String ownerTableAlias, String joinPropertyName) {
NativeQueryReturnBuilderFetchImpl builder = new NativeQueryReturnBuilderFetchImpl( tableAlias, ownerTableAlias, joinPropertyName );
addReturnBuilder( builder );
return builder;
}
@Override
public NativeQueryImplementor addJoin(String tableAlias, String path) {
createFetchJoin( tableAlias, path );
return this;
}
private FetchReturn createFetchJoin(String tableAlias, String path) {
int loc = path.indexOf( '.' );
if ( loc < 0 ) {
throw new QueryException( "not a property path: " + path );
}
final String ownerTableAlias = path.substring( 0, loc );
final String joinedPropertyName = path.substring( loc + 1 );
return addFetch( tableAlias, ownerTableAlias, joinedPropertyName );
}
@Override
public NativeQueryImplementor addJoin(String tableAlias, String ownerTableAlias, String joinPropertyName) {
addFetch( tableAlias, ownerTableAlias, joinPropertyName );
return this;
}
@Override
public NativeQueryImplementor addJoin(String tableAlias, String path, LockMode lockMode) {
createFetchJoin( tableAlias, path ).setLockMode( lockMode );
return this;
}
@Override
public String[] getReturnAliases() {
throw new UnsupportedOperationException( "Native (SQL) queries do not support returning aliases" );
}
@Override
public Type[] getReturnTypes() {
throw new UnsupportedOperationException( "Native (SQL) queries do not support returning 'return types'" );
}
@Override
public NativeQueryImplementor setEntity(int position, Object val) {
setParameter( position, val, getProducer().getFactory().getTypeHelper().entity( resolveEntityName( val ) ) );
return this;
}
@Override
public NativeQueryImplementor setEntity(String name, Object val) {
setParameter( name, val, getProducer().getFactory().getTypeHelper().entity( resolveEntityName( val ) ) );
return this;
}
@Override
public Collection getSynchronizedQuerySpaces() {
return querySpaces;
}
@Override
public NativeQueryImplementor addSynchronizedQuerySpace(String querySpace) {
addQuerySpaces( querySpace );
return this;
}
@Override
public SynchronizeableQuery addSynchronizedQuerySpace(String... querySpaces) {
addQuerySpaces( querySpaces );
return this;
}
protected void addQuerySpaces(String... spaces) {
if ( spaces != null ) {
if ( querySpaces == null ) {
querySpaces = new ArrayList<>();
}
querySpaces.addAll( Arrays.asList( spaces ) );
}
}
protected void addQuerySpaces(Serializable... spaces) {
if ( spaces != null ) {
if ( querySpaces == null ) {
querySpaces = new ArrayList<>();
}
querySpaces.addAll( Arrays.asList( (String[]) spaces ) );
}
}
@Override
public NativeQueryImplementor addSynchronizedEntityName(String entityName) throws MappingException {
addQuerySpaces( getProducer().getFactory().getMetamodel().entityPersister( entityName ).getQuerySpaces() );
return this;
}
@Override
public NativeQueryImplementor addSynchronizedEntityClass(Class entityClass) throws MappingException {
addQuerySpaces( getProducer().getFactory().getMetamodel().entityPersister( entityClass.getName() ).getQuerySpaces() );
return this;
}
@Override
protected boolean applyQuerySpaces(Object value) {
if ( value == null ) {
return false;
}
if ( value instanceof String[] ) {
addSynchronizedQuerySpace( (String[]) value );
return true;
}
if ( value instanceof Collection ) {
if ( querySpaces == null ) {
querySpaces = new ArrayList<>();
}
querySpaces.addAll( (Collection) value );
return true;
}
if ( value instanceof String ) {
final StringTokenizer spaces = new StringTokenizer( (String) value, "," );
while ( spaces.hasMoreTokens() ) {
addQuerySpaces( spaces.nextToken() );
}
return true;
}
return false;
}
@Override
protected boolean isNativeQuery() {
return true;
}
@Override
public NativeQueryImplementor setHibernateFlushMode(FlushMode flushMode) {
super.setHibernateFlushMode( flushMode );
return this;
}
@Override
public NativeQueryImplementor setFlushMode(FlushMode flushMode) {
super.setFlushMode( flushMode );
return this;
}
@Override
public NativeQueryImplementor setFlushMode(FlushModeType flushModeType) {
super.setFlushMode( flushModeType );
return this;
}
@Override
public NativeQueryImplementor setCacheMode(CacheMode cacheMode) {
super.setCacheMode( cacheMode );
return this;
}
@Override
public NativeQueryImplementor setCacheable(boolean cacheable) {
super.setCacheable( cacheable );
return this;
}
@Override
public NativeQueryImplementor setCacheRegion(String cacheRegion) {
super.setCacheRegion( cacheRegion );
return this;
}
@Override
public NativeQueryImplementor setTimeout(int timeout) {
super.setTimeout( timeout );
return this;
}
@Override
public NativeQueryImplementor setFetchSize(int fetchSize) {
super.setFetchSize( fetchSize );
return this;
}
@Override
public NativeQueryImplementor setReadOnly(boolean readOnly) {
super.setReadOnly( readOnly );
return this;
}
@Override
public NativeQueryImplementor setLockOptions(LockOptions lockOptions) {
super.setLockOptions( lockOptions );
return this;
}
@Override
public NativeQueryImplementor setLockMode(String alias, LockMode lockMode) {
super.setLockMode( alias, lockMode );
return this;
}
@Override
public NativeQueryImplementor setLockMode(LockModeType lockModeType) {
throw new IllegalStateException( "Illegal attempt to set lock mode on a native SQL query" );
}
@Override
public NativeQueryImplementor setComment(String comment) {
super.setComment( comment );
return this;
}
@Override
public NativeQueryImplementor addQueryHint(String hint) {
super.addQueryHint( hint );
return this;
}
@Override
protected void collectHints(Map hints) {
super.collectHints( hints );
putIfNotNull( hints, HINT_NATIVE_LOCKMODE, getLockOptions().getLockMode() );
}
@Override
protected boolean applyNativeQueryLockMode(Object value) {
if ( LockMode.class.isInstance( value ) ) {
applyHibernateLockModeHint( (LockMode) value );
}
else if ( LockModeType.class.isInstance( value ) ) {
applyLockModeTypeHint( (LockModeType) value );
}
else {
throw new IllegalArgumentException(
String.format(
"Native lock-mode hint [%s] must specify %s or %s. Encountered type : %s",
HINT_NATIVE_LOCKMODE,
LockMode.class.getName(),
LockModeType.class.getName(),
value.getClass().getName()
)
);
}
return true;
}
@Override
@SuppressWarnings("unchecked")
public NativeQueryImplementor setParameter(QueryParameter parameter, Object value) {
super.setParameter( (Parameter
© 2015 - 2025 Weber Informatics LLC | Privacy Policy