HHH-10915 - NullPointerException from AbstractProducedQuery.getParameters()

This commit is contained in:
Andrea Boriero 2016-07-02 09:36:36 +02:00 committed by Vlad Mihalcea
parent 26df5d9e11
commit 5e69823b79
5 changed files with 318 additions and 26 deletions

View File

@ -51,6 +51,8 @@ import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.procedure.spi.ProcedureCallImplementor;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.internal.AbstractProducedQuery;
import org.hibernate.query.procedure.internal.ProcedureParameterImpl;
import org.hibernate.query.procedure.internal.ProcedureParameterMetadata;
import org.hibernate.result.NoMoreReturnsException;
import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput;
@ -94,7 +96,7 @@ public class ProcedureCallImpl<R>
* @param procedureName The name of the procedure to call
*/
public ProcedureCallImpl(SharedSessionContractImplementor session, String procedureName) {
super( session, null );
super( session, new ProcedureParameterMetadata() );
this.procedureName = procedureName;
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
@ -109,7 +111,7 @@ public class ProcedureCallImpl<R>
* @param resultClasses The classes making up the result
*/
public ProcedureCallImpl(final SharedSessionContractImplementor session, String procedureName, Class... resultClasses) {
super( session, null );
super( session, new ProcedureParameterMetadata() );
this.procedureName = procedureName;
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
@ -148,7 +150,7 @@ public class ProcedureCallImpl<R>
* @param resultSetMappings The names of the result set mappings making up the result
*/
public ProcedureCallImpl(final SharedSessionContractImplementor session, String procedureName, String... resultSetMappings) {
super( session, null );
super( session, new ProcedureParameterMetadata() );
this.procedureName = procedureName;
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
@ -192,7 +194,7 @@ public class ProcedureCallImpl<R>
*/
@SuppressWarnings("unchecked")
ProcedureCallImpl(SharedSessionContractImplementor session, ProcedureCallMementoImpl memento) {
super( session, null );
super( session, new ProcedureParameterMetadata() );
this.procedureName = memento.getProcedureName();
this.globalParameterPassNullsSetting = session.getFactory().getSessionFactoryOptions().isProcedureParameterNullPassingEnabled();
@ -250,6 +252,7 @@ public class ProcedureCallImpl<R>
storedRegistration.isPassNullsEnabled()
);
}
getParameterMetadata().registerParameter( new ProcedureParameterImpl( registration ) );
parameterRegistrations.add( registration );
}
this.registeredParameters = parameterRegistrations;
@ -259,6 +262,11 @@ public class ProcedureCallImpl<R>
}
}
@Override
public ProcedureParameterMetadata getParameterMetadata() {
return (ProcedureParameterMetadata) super.getParameterMetadata();
}
@Override
public SharedSessionContractImplementor getSession() {
return getProducer();
@ -286,6 +294,7 @@ public class ProcedureCallImpl<R>
@Override
@SuppressWarnings("unchecked")
public <T> ParameterRegistration<T> registerParameter(int position, Class<T> type, ParameterMode mode) {
final PositionalParameterRegistration parameterRegistration =
new PositionalParameterRegistration( this, position, mode, type, globalParameterPassNullsSetting );
registerParameter( parameterRegistration );
@ -309,6 +318,8 @@ public class ProcedureCallImpl<R>
else {
throw new IllegalArgumentException( "Given parameter did not define name or position [" + parameter + "]" );
}
((ProcedureParameterMetadata)getParameterMetadata()).registerParameter( new ProcedureParameterImpl( parameter ) );
registeredParameters.add( parameter );
}

View File

@ -44,6 +44,7 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.NonUniqueResultException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.QueryParameterException;
import org.hibernate.ScrollMode;
import org.hibernate.TypeMismatchException;
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
@ -617,13 +618,13 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
@Override
public Set<Parameter<?>> getParameters() {
return parameterMetadata.collectAllParametersJpa();
return getParameterMetadata().collectAllParametersJpa();
}
@Override
public Parameter<?> getParameter(String name) {
try {
return parameterMetadata.getQueryParameter( name );
return getParameterMetadata().getQueryParameter( name );
}
catch ( HibernateException e ) {
throw getExceptionConverter().convert( e );
@ -634,7 +635,7 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
@SuppressWarnings("unchecked")
public <T> Parameter<T> getParameter(String name, Class<T> type) {
try {
final QueryParameter parameter = parameterMetadata.getQueryParameter( name );
final QueryParameter parameter = getParameterMetadata().getQueryParameter( name );
if ( !parameter.getParameterType().isAssignableFrom( type ) ) {
throw new IllegalArgumentException(
"The type [" + parameter.getParameterType().getName() +
@ -657,18 +658,23 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
// deprecated since 5.x. These are numbered starting from 0 and kept in the
// ParameterMetadata positional-parameter array keyed by this zero-based position
// 2) JPA's definition is really just a named parameter, but expected to explicitly be
// sequential intergers starting from 0 (ZERO); they can repeat.
// sequential integers starting from 1 (ONE); they can repeat.
//
// It is considered illegal to mix positional-parameter with named parameters of any kind. So therefore.
// if ParameterMetadata reports that it has any positional-parameters it is talking about the
// legacy Hibernate concept.
// lookup jpa-based positional parameters first by name.
try {
if ( parameterMetadata.getPositionalParameterCount() == 0 ) {
return parameterMetadata.getQueryParameter( Integer.toString( position ) );
if ( getParameterMetadata().getPositionalParameterCount() == 0 ) {
try {
return getParameterMetadata().getQueryParameter( Integer.toString( position ) );
}
// fallback to oridinal lookup
return parameterMetadata.getQueryParameter( position );
catch (HibernateException e) {
throw new QueryParameterException( "could not locate parameter at position [" + position + "]" );
}
}
// fallback to ordinal lookup
return getParameterMetadata().getQueryParameter( position );
}
catch (HibernateException e) {
throw getExceptionConverter().convert( e );
@ -679,7 +685,7 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
@SuppressWarnings("unchecked")
public <T> Parameter<T> getParameter(int position, Class<T> type) {
try {
final QueryParameter parameter = parameterMetadata.getQueryParameter( position );
final QueryParameter parameter = getParameterMetadata().getQueryParameter( position );
if ( !parameter.getParameterType().isAssignableFrom( type ) ) {
throw new IllegalArgumentException(
"The type [" + parameter.getParameterType().getName() +
@ -750,7 +756,7 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
protected Type determineType(String namedParam, Class retType) {
Type type = queryParameterBindings.getBinding( namedParam ).getBindType();
if ( type == null ) {
type = parameterMetadata.getQueryParameter( namedParam ).getType();
type = getParameterMetadata().getQueryParameter( namedParam ).getType();
}
if ( type == null ) {
type = getProducer().getFactory().resolveParameterBindType( retType );

View File

@ -13,6 +13,7 @@ import java.util.List;
import java.util.Set;
import javax.persistence.Parameter;
import org.hibernate.QueryParameterException;
import org.hibernate.query.ParameterMetadata;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.procedure.ProcedureParameter;
@ -27,6 +28,10 @@ public class ProcedureParameterMetadata implements ParameterMetadata {
private boolean hasNamed;
private int ordinalParamCount;
public ProcedureParameterMetadata() {
parameters = new ArrayList<>( );
}
public void registerParameter(ProcedureParameterImplementor parameter) {
if ( parameters == null ) {
parameters = new ArrayList<>();
@ -91,16 +96,19 @@ public class ProcedureParameterMetadata implements ParameterMetadata {
@SuppressWarnings("unchecked")
public <T> QueryParameter<T> getQueryParameter(String name) {
assert name != null;
QueryParameter<T> result = null;
if ( hasNamed ) {
for ( ProcedureParameter parameter : parameters ) {
if ( name.equals( parameter.getName() ) ) {
return parameter;
result = parameter;
break;
}
}
}
return null;
if ( result != null ) {
return result;
}
throw new QueryParameterException( "could not locate named parameter [" + name + "]" );
}
@Override
@ -115,8 +123,7 @@ public class ProcedureParameterMetadata implements ParameterMetadata {
}
}
}
return null;
throw new QueryParameterException( "could not locate parameter at position [" + position + "]" );
}
@Override

View File

@ -0,0 +1,159 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.procedure;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.Parameter;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureQuery;
import javax.persistence.Table;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.testing.RequiresDialect;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author Andrea Boriero
*/
@RequiresDialect(H2Dialect.class)
public class H2StoreProcedureTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {MyEntity.class};
}
@Before
public void setUp() {
final EntityManager entityManager = getOrCreateEntityManager();
try {
entityManager.getTransaction().begin();
entityManager.createNativeQuery( "CREATE ALIAS get_all_entities FOR \"" + H2StoreProcedureTest.class.getCanonicalName() + ".getAllEntities\";" )
.executeUpdate();
entityManager.createNativeQuery( "CREATE ALIAS by_id FOR \"" + H2StoreProcedureTest.class.getCanonicalName() + ".entityById\";" )
.executeUpdate();
MyEntity entity = new MyEntity();
entity.id = 1;
entity.name = "entity1";
entityManager.persist( entity );
entityManager.getTransaction().commit();
}
catch (Exception e) {
if ( entityManager.getTransaction().isActive() ) {
entityManager.getTransaction().rollback();
}
throw e;
}
finally {
entityManager.close();
}
}
@After
public void tearDown() {
final EntityManager entityManager = getOrCreateEntityManager();
try {
entityManager.getTransaction().begin();
entityManager.createNativeQuery( "DROP ALIAS IF EXISTS get_all_entities" ).executeUpdate();
entityManager.createNativeQuery( "DROP ALIAS IF EXISTS by_id" ).executeUpdate();
entityManager.getTransaction().commit();
}
catch (Exception e) {
if ( entityManager.getTransaction().isActive() ) {
entityManager.getTransaction().rollback();
}
throw e;
}
finally {
entityManager.close();
}
}
public static ResultSet getAllEntities(Connection conn) throws SQLException {
return conn.createStatement().executeQuery( "select * from MY_ENTITY" );
}
public static ResultSet entityById(Connection conn, long id) throws SQLException {
return conn.createStatement().executeQuery( "select * from MY_ENTITY where id = " + Long.toString( id ) );
}
@Test
public void testStoreProcedureGetParameters() {
final EntityManager entityManager = getOrCreateEntityManager();
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "get_all_entities", MyEntity.class );
final Set<Parameter<?>> parameters = query.getParameters();
assertThat( parameters.size(), is( 0 ) );
final List resultList = query.getResultList();
assertThat( resultList.size(), is( 1 ) );
}
finally {
entityManager.close();
}
}
@Test
public void testStoreProcedureGetParameterByPosition() {
final EntityManager entityManager = getOrCreateEntityManager();
try {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "by_Id", MyEntity.class );
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
query.setParameter( 1, 1L );
final List resultList = query.getResultList();
assertThat( resultList.size(), is( 1 ) );
final Set<Parameter<?>> parameters = query.getParameters();
assertThat( parameters.size(), is( 1 ) );
final Parameter<?> parameter = query.getParameter( 1 );
assertThat( parameter, not( nullValue() ) );
try {
query.getParameter( 2 );
fail( "IllegalArgumentException expected, parameter at position 2 does not exist" );
}
catch (IllegalArgumentException iae) {
//expected
}
}
finally {
entityManager.close();
}
}
@Entity(name = "MyEntity")
@Table(name = "MY_ENTITY")
public static class MyEntity {
@Id
long id;
String name;
}
}

View File

@ -11,7 +11,9 @@ import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.Parameter;
import javax.persistence.ParameterMode;
import javax.persistence.StoredProcedureParameter;
import javax.persistence.StoredProcedureQuery;
@ -19,6 +21,7 @@ import javax.persistence.Table;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Set;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
@ -27,18 +30,22 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.fail;
/**
* @author Andrea Boriero
*/
@TestForIssue(jiraKey = "HHH-10515")
@RequiresDialect(value = HSQLDialect.class)
public class HSQLStoreProcedureTest extends BaseEntityManagerFunctionalTestCase {
EntityManagerFactory entityManagerFactory;
@ -61,6 +68,7 @@ public class HSQLStoreProcedureTest extends BaseEntityManagerFunctionalTestCase
}
@Test
@TestForIssue(jiraKey = "HHH-10515")
public void testNamedStoredProcedureExecution() {
EntityManager em = entityManagerFactory.createEntityManager();
try {
@ -73,6 +81,99 @@ public class HSQLStoreProcedureTest extends BaseEntityManagerFunctionalTestCase
}
}
@Test
@TestForIssue(jiraKey = "HHH-10915")
public void testGetNamedParameters() {
EntityManager em = entityManagerFactory.createEntityManager();
try {
StoredProcedureQuery query = em.createNamedStoredProcedureQuery( "User.inoutproc" );
final Set<Parameter<?>> parameters = query.getParameters();
assertThat( parameters.size(), is( 2 ) );
assertThat( query.getParameter( "arg1" ), not( nullValue() ) );
assertThat( query.getParameter( "res" ), not( nullValue() ) );
assertThat( query.getParameter( "arg1", Integer.class ), not( nullValue() ) );
try {
query.getParameter( "arg1", String.class );
fail( "An IllegalArgumentException is expected, A parameter with name arg1 and type String does not exist" );
}
catch (IllegalArgumentException iae) {
//expected
}
try {
query.getParameter( "arg2" );
fail( "An IllegalArgumentException is expected, A parameter with name arg2 does not exist" );
}
catch (IllegalArgumentException iae) {
//expected
}
}
finally {
em.close();
}
}
@Test
@TestForIssue(jiraKey = "HHH-10915")
public void testGetPositionalParameters() {
EntityManager em = entityManagerFactory.createEntityManager();
try {
StoredProcedureQuery query = em.createNamedStoredProcedureQuery( "User.inoutproc" );
final Set<Parameter<?>> parameters = query.getParameters();
assertThat( parameters.size(), is( 2 ) );
try {
query.getParameter( 1 );
fail( "An IllegalArgumentException is expected, The stored procedure has named parameters not positional" );
}
catch (IllegalArgumentException iae) {
//expected
}
try {
query.getParameter( 1, String.class );
fail( "An IllegalArgumentException is expected, The stored procedure has named parameters not positional" );
}
catch (IllegalArgumentException iae) {
//expected
}
}
finally {
em.close();
}
}
@Test
@TestForIssue(jiraKey = "HHH-10915")
public void testGetPositionalParameters2() {
EntityManager em = entityManagerFactory.createEntityManager();
try {
StoredProcedureQuery query = em.createNamedStoredProcedureQuery( "User.inoutprocpositional" );
final Set<Parameter<?>> parameters = query.getParameters();
assertThat( parameters.size(), is( 2 ) );
assertThat( query.getParameter( 1 ), not( nullValue() ) );
assertThat( query.getParameter( 2 ), not( nullValue() ) );
assertThat( query.getParameter( 1, Integer.class ), not( nullValue() ) );
try {
query.getParameter( 3 );
fail( "An IllegalArgumentException is expected, A parameter at position 3 does not exist" );
}
catch (IllegalArgumentException iae) {
//expected
}
try {
query.getParameter( 1, String.class );
fail( "An IllegalArgumentException is expected, The parameter at position 1 is of type Integer not String" );
}
catch (IllegalArgumentException iae) {
//expected
}
}
finally {
em.close();
}
}
private void createProcedures(EntityManagerFactory emf) {
final String procedureStatement = "CREATE procedure inoutproc (IN arg1 int, OUT res int) " +
"BEGIN ATOMIC set res = arg1 + 1;" +
@ -127,10 +228,18 @@ public class HSQLStoreProcedureTest extends BaseEntityManagerFunctionalTestCase
}
@Entity(name = "User")
@NamedStoredProcedureQueries(value = {
@NamedStoredProcedureQuery(name = "User.inoutproc", procedureName = "inoutproc", parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg1", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class)
})
,
@NamedStoredProcedureQuery(name = "User.inoutprocpositional", procedureName = "inoutproc", parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, type = Integer.class)
})
}
)
@Table(name = "USERS")
public class User {