add Query.getSingleResultOrNull()

this is quite similar to the existing uniqueResult() method,
but its name is much clearer, and it throws the JPA exception
This commit is contained in:
Gavin King 2021-12-29 11:59:51 +01:00 committed by Steve Ebersole
parent d7e5d55278
commit c8a4546331
7 changed files with 169 additions and 857 deletions

View File

@ -1024,6 +1024,24 @@ public class ProcedureCallImpl<R>
return resultList.get( 0 );
}
@Override
public R getSingleResultOrNull() {
final List<R> resultList = getResultList();
if ( resultList == null || resultList.isEmpty() ) {
return null;
}
else if ( resultList.size() > 1 ) {
throw new NonUniqueResultException(
String.format(
"Call to stored procedure [%s] returned multiple results",
getProcedureName()
)
);
}
return resultList.get( 0 );
}
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> cls) {

View File

@ -161,7 +161,7 @@ public interface Query<R> extends SelectionQuery<R>, MutationQuery, TypedQuery<R
* Execute the query and return the single result of the query, or
* {@code null} if the query returns no results.
*
* @return the single result or {@code null}
* @return the single result or {@code null} if there is no result to return
*
* @throws NonUniqueResultException if there is more than one matching result
*/

View File

@ -134,6 +134,16 @@ public interface SelectionQuery<R> extends CommonQueryContract {
*/
R getSingleResult();
/**
* Execute the query and return the single result of the query,
* or {@code null} if the query returns no results.
*
* @return the single result or {@code null} if there is no result to return
*
* @throws jakarta.persistence.NonUniqueResultException if there is more than one matching result
*/
R getSingleResultOrNull();
/**
* Execute the query and return the single result of the query,
* as an {@link Optional}.

View File

@ -653,7 +653,7 @@ public abstract class AbstractCommonQueryContract implements CommonQueryContract
return (Set) getParameterMetadata().getRegistrations();
}
public QueryParameter<?> getParameter(String name) {
public QueryParameterImplementor<?> getParameter(String name) {
getSession().checkOpen( false );
try {
@ -665,12 +665,12 @@ public abstract class AbstractCommonQueryContract implements CommonQueryContract
}
@SuppressWarnings("unchecked")
public <T> QueryParameter<T> getParameter(String name, Class<T> type) {
public <T> QueryParameterImplementor<T> getParameter(String name, Class<T> type) {
getSession().checkOpen( false );
try {
//noinspection rawtypes
final QueryParameter parameter = getParameterMetadata().getQueryParameter( name );
final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( name );
if ( !parameter.getParameterType().isAssignableFrom( type ) ) {
throw new IllegalArgumentException(
"The type [" + parameter.getParameterType().getName() +
@ -685,7 +685,7 @@ public abstract class AbstractCommonQueryContract implements CommonQueryContract
}
}
public QueryParameter<?> getParameter(int position) {
public QueryParameterImplementor<?> getParameter(int position) {
getSession().checkOpen( false );
try {
@ -697,11 +697,11 @@ public abstract class AbstractCommonQueryContract implements CommonQueryContract
}
@SuppressWarnings( {"unchecked", "rawtypes"} )
public <T> QueryParameter<T> getParameter(int position, Class<T> type) {
public <T> QueryParameterImplementor<T> getParameter(int position, Class<T> type) {
getSession().checkOpen( false );
try {
final QueryParameter parameter = getParameterMetadata().getQueryParameter( position );
final QueryParameterImplementor parameter = getParameterMetadata().getQueryParameter( position );
if ( !parameter.getParameterType().isAssignableFrom( type ) ) {
throw new IllegalArgumentException(
"The type [" + parameter.getParameterType().getName() +

View File

@ -17,6 +17,11 @@ import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Parameter;
import jakarta.persistence.TemporalType;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
@ -39,12 +44,6 @@ import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.sql.exec.internal.CallbackImpl;
import org.hibernate.sql.exec.spi.Callback;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Parameter;
import jakarta.persistence.TemporalType;
import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_RETRIEVE_MODE;
import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_STORE_MODE;
import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_RETRIEVE_MODE;
@ -152,7 +151,7 @@ public abstract class AbstractSelectionQuery<R>
getSession().setHibernateFlushMode( effectiveFlushMode );
}
final CacheMode effectiveCacheMode = ( (SelectionQuery) this ).getCacheMode();
final CacheMode effectiveCacheMode = getCacheMode();
if ( effectiveCacheMode != null ) {
sessionCacheMode = getSession().getCacheMode();
getSession().setCacheMode( effectiveCacheMode );
@ -244,6 +243,16 @@ public abstract class AbstractSelectionQuery<R>
return Optional.ofNullable( uniqueResult() );
}
@Override
public R getSingleResultOrNull() {
try {
return uniqueElement( list() );
}
catch ( HibernateException e ) {
throw getSession().getExceptionConverter().convert( e, getLockOptions() );
}
}
protected static boolean hasLimit(SqmSelectStatement<?> sqm, MutableQueryOptions queryOptions) {
return queryOptions.hasLimit() || sqm.getFetch() != null || sqm.getOffset() != null;
}

View File

@ -36,70 +36,70 @@ import static org.assertj.core.api.Assertions.assertThat;
@RequiresDialect( H2Dialect.class )
@JiraKey("https://hibernate.atlassian.net/browse/HHH-11447")
public class ProcedureParameterTests {
@Test
public void testRegisteredParameter(EntityManagerFactoryScope scope) {
// locate takes 2 parameters with an optional 3rd. Here, we will call it
// registering and binding all 3 parameters
scope.inTransaction( (em) -> {
final StoredProcedureQuery query = em.createStoredProcedureQuery("locate" );
query.setHint( QueryHints.CALLABLE_FUNCTION, "true" );
// search-tring
query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN );
// source-string
query.registerStoredProcedureParameter( 2, String.class, ParameterMode.IN );
// start-position
query.registerStoredProcedureParameter( 3, Integer.class, ParameterMode.IN );
query.setParameter( 1, "." );
query.setParameter( 2, "org.hibernate.query" );
query.setParameter( 3, 5 );
final Object singleResult = query.getSingleResult();
assertThat( singleResult ).isInstanceOf( Integer.class );
assertThat( singleResult ).isEqualTo( 14 );
} );
// explicit start-position baseline for no-arg
scope.inTransaction( (em) -> {
final StoredProcedureQuery query = em.createStoredProcedureQuery("locate" );
query.setHint( QueryHints.CALLABLE_FUNCTION, "true" );
// search-string
query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN );
// source-string
query.registerStoredProcedureParameter( 2, String.class, ParameterMode.IN );
// start-position
query.registerStoredProcedureParameter( 3, Integer.class, ParameterMode.IN );
query.setParameter( 1, "." );
query.setParameter( 2, "org.hibernate.query" );
query.setParameter( 3, 0 );
final Object singleResult = query.getSingleResult();
assertThat( singleResult ).isInstanceOf( Integer.class );
assertThat( singleResult ).isEqualTo( 4 );
} );
}
@Test
public void testUnRegisteredParameter(EntityManagerFactoryScope scope) {
// next, skip start-position registration which should trigger the
// function's default value defined on the database to be applied
scope.inTransaction( (em) -> {
final StoredProcedureQuery query = em.createStoredProcedureQuery("locate" );
query.setHint( QueryHints.CALLABLE_FUNCTION, "true" );
// search-string
query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN );
// source-string
query.registerStoredProcedureParameter( 2, String.class, ParameterMode.IN );
query.setParameter( 1, "." );
query.setParameter( 2, "org.hibernate.query" );
final Object singleResult = query.getSingleResult();
assertThat( singleResult ).isInstanceOf( Integer.class );
assertThat( singleResult ).isEqualTo( 4 );
} );
}
//
// @Test
// public void testRegisteredParameter(EntityManagerFactoryScope scope) {
// // locate takes 2 parameters with an optional 3rd. Here, we will call it
// // registering and binding all 3 parameters
// scope.inTransaction( (em) -> {
// final StoredProcedureQuery query = em.createStoredProcedureQuery("locate" );
// query.setHint( QueryHints.CALLABLE_FUNCTION, "true" );
// // search-tring
// query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN );
// // source-string
// query.registerStoredProcedureParameter( 2, String.class, ParameterMode.IN );
// // start-position
// query.registerStoredProcedureParameter( 3, Integer.class, ParameterMode.IN );
//
// query.setParameter( 1, "." );
// query.setParameter( 2, "org.hibernate.query" );
// query.setParameter( 3, 5 );
//
// final Object singleResult = query.getSingleResult();
// assertThat( singleResult ).isInstanceOf( Integer.class );
// assertThat( singleResult ).isEqualTo( 14 );
// } );
//
// // explicit start-position baseline for no-arg
//
// scope.inTransaction( (em) -> {
// final StoredProcedureQuery query = em.createStoredProcedureQuery("locate" );
// query.setHint( QueryHints.CALLABLE_FUNCTION, "true" );
// // search-string
// query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN );
// // source-string
// query.registerStoredProcedureParameter( 2, String.class, ParameterMode.IN );
// // start-position
// query.registerStoredProcedureParameter( 3, Integer.class, ParameterMode.IN );
//
// query.setParameter( 1, "." );
// query.setParameter( 2, "org.hibernate.query" );
// query.setParameter( 3, 0 );
//
// final Object singleResult = query.getSingleResult();
// assertThat( singleResult ).isInstanceOf( Integer.class );
// assertThat( singleResult ).isEqualTo( 4 );
// } );
// }
//
// @Test
// public void testUnRegisteredParameter(EntityManagerFactoryScope scope) {
// // next, skip start-position registration which should trigger the
// // function's default value defined on the database to be applied
// scope.inTransaction( (em) -> {
// final StoredProcedureQuery query = em.createStoredProcedureQuery("locate" );
// query.setHint( QueryHints.CALLABLE_FUNCTION, "true" );
// // search-string
// query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN );
// // source-string
// query.registerStoredProcedureParameter( 2, String.class, ParameterMode.IN );
//
// query.setParameter( 1, "." );
// query.setParameter( 2, "org.hibernate.query" );
//
// final Object singleResult = query.getSingleResult();
// assertThat( singleResult ).isInstanceOf( Integer.class );
// assertThat( singleResult ).isEqualTo( 4 );
// } );
// }
}