Skip to content

Commit b4dbb90

Browse files
gbadnersebersole
authored andcommitted
HHH-10161 : Hibernate ignores return value from javax.persistence.Parameter#getParameterType()
1 parent b1e4fc1 commit b4dbb90

File tree

7 files changed

+629
-12
lines changed

7 files changed

+629
-12
lines changed

hibernate-core/src/main/java/org/hibernate/internal/AbstractQueryImpl.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,11 @@ public Query setParameter(String name, Object val, Type type) {
463463

464464
public Query setParameter(int position, Object val) throws HibernateException {
465465
if ( val == null ) {
466-
setParameter( position, val, StandardBasicTypes.SERIALIZABLE );
466+
Type type = parameterMetadata.getOrdinalParameterDescriptor( position + 1 ).getExpectedType();
467+
if ( type == null ) {
468+
type = StandardBasicTypes.SERIALIZABLE;
469+
}
470+
setParameter( position, val, type );
467471
}
468472
else {
469473
setParameter( position, val, determineType( position, val ) );
@@ -530,7 +534,7 @@ private Type guessType(Object param) throws HibernateException {
530534
return guessType( clazz );
531535
}
532536

533-
private Type guessType(Class clazz) throws HibernateException {
537+
public Type guessType(Class clazz) throws HibernateException {
534538
String typename = clazz.getName();
535539
Type type = session.getFactory().getTypeResolver().heuristicType( typename );
536540
boolean serializable = type != null && type instanceof SerializableType;

hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,21 @@
2222
import org.hibernate.Transaction;
2323
import org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl;
2424
import org.hibernate.cfg.Configuration;
25+
import org.hibernate.dialect.Oracle8iDialect;
2526
import org.hibernate.dialect.PostgreSQL81Dialect;
2627
import org.hibernate.dialect.PostgreSQLDialect;
2728
import org.hibernate.dialect.function.SQLFunction;
2829
import org.hibernate.stat.Statistics;
2930

3031
import org.hibernate.testing.FailureExpected;
3132
import org.hibernate.testing.SkipForDialect;
33+
import org.hibernate.testing.TestForIssue;
3234
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
3335
import org.hibernate.test.annotations.A320;
3436
import org.hibernate.test.annotations.A320b;
3537
import org.hibernate.test.annotations.Plane;
38+
import org.hibernate.type.StandardBasicTypes;
39+
3640
import org.junit.Test;
3741

3842
import static org.junit.Assert.assertEquals;
@@ -92,6 +96,174 @@ public void testNativeQueryWithFormulaAttributeWithoutAlias() {
9296
s.close();
9397
}
9498

99+
@Test
100+
@TestForIssue( jiraKey = "HHH-10161")
101+
@SkipForDialect(
102+
value = Oracle8iDialect.class,
103+
jiraKey = "HHH-10161",
104+
comment = "Oracle cannot convert untyped null (assumed to be BINARY type) to NUMBER")
105+
public void testQueryWithNullParameter(){
106+
Chaos c0 = new Chaos();
107+
c0.setId( 0L );
108+
c0.setName( "c0" );
109+
c0.setSize( 0L );
110+
Chaos c1 = new Chaos();
111+
c1.setId( 1L );
112+
c1.setName( "c1" );
113+
c1.setSize( 1L );
114+
Chaos c2 = new Chaos();
115+
c2.setId( 2L );
116+
c2.setName( "c2" );
117+
c2.setSize( null );
118+
119+
Session s = openSession();
120+
s.beginTransaction();
121+
s.persist( c0 );
122+
s.persist( c1 );
123+
s.persist( c2 );
124+
125+
s.flush();
126+
s.clear();
127+
128+
List chaoses = s.createQuery( "from Chaos where chaos_size is null or chaos_size = :chaos_size" )
129+
.setParameter( "chaos_size", null )
130+
.list();
131+
assertEquals( 1, chaoses.size() );
132+
133+
chaoses = s.createQuery( "from Chaos where chaos_size = :chaos_size" )
134+
.setParameter( "chaos_size", null )
135+
.list();
136+
// should be no results because null != null
137+
assertEquals( 0, chaoses.size() );
138+
139+
s.getTransaction().rollback();
140+
s.close();
141+
}
142+
143+
@Test
144+
@TestForIssue( jiraKey = "HHH-10161")
145+
public void testQueryWithNullParameterTyped(){
146+
Chaos c0 = new Chaos();
147+
c0.setId( 0L );
148+
c0.setName( "c0" );
149+
c0.setSize( 0L );
150+
Chaos c1 = new Chaos();
151+
c1.setId( 1L );
152+
c1.setName( "c1" );
153+
c1.setSize( 1L );
154+
Chaos c2 = new Chaos();
155+
c2.setId( 2L );
156+
c2.setName( "c2" );
157+
c2.setSize( null );
158+
159+
Session s = openSession();
160+
s.beginTransaction();
161+
s.persist( c0 );
162+
s.persist( c1 );
163+
s.persist( c2 );
164+
165+
s.flush();
166+
s.clear();
167+
168+
List chaoses = s.createQuery( "from Chaos where chaos_size is null or chaos_size = :chaos_size" )
169+
.setParameter( "chaos_size", null, StandardBasicTypes.LONG )
170+
.list();
171+
assertEquals( 1, chaoses.size() );
172+
173+
chaoses = s.createQuery( "from Chaos where chaos_size = :chaos_size" )
174+
.setParameter( "chaos_size", null, StandardBasicTypes.LONG )
175+
.list();
176+
// should be no results because null != null
177+
assertEquals( 0, chaoses.size() );
178+
179+
s.getTransaction().rollback();
180+
s.close();
181+
}
182+
183+
@Test
184+
@TestForIssue( jiraKey = "HHH-10161")
185+
@SkipForDialect(
186+
value = Oracle8iDialect.class,
187+
jiraKey = "HHH-10161",
188+
comment = "Oracle cannot convert untyped null (assumed to be BINARY type) to NUMBER")
189+
public void testNativeQueryWithNullParameter(){
190+
Chaos c0 = new Chaos();
191+
c0.setId( 0L );
192+
c0.setName( "c0" );
193+
c0.setSize( 0L );
194+
Chaos c1 = new Chaos();
195+
c1.setId( 1L );
196+
c1.setName( "c1" );
197+
c1.setSize( 1L );
198+
Chaos c2 = new Chaos();
199+
c2.setId( 2L );
200+
c2.setName( "c2" );
201+
c2.setSize( null );
202+
203+
Session s = openSession();
204+
s.beginTransaction();
205+
s.persist( c0 );
206+
s.persist( c1 );
207+
s.persist( c2 );
208+
209+
s.flush();
210+
s.clear();
211+
212+
List chaoses = s.createSQLQuery( "select * from Chaos where chaos_size is null or chaos_size = :chaos_size" )
213+
.setParameter( "chaos_size", null )
214+
.list();
215+
assertEquals( 1, chaoses.size() );
216+
217+
chaoses = s.createSQLQuery( "select * from Chaos where chaos_size = :chaos_size" )
218+
.setParameter( "chaos_size", null )
219+
.list();
220+
// should be no results because null != null
221+
assertEquals( 0, chaoses.size() );
222+
223+
s.getTransaction().rollback();
224+
s.close();
225+
}
226+
227+
@Test
228+
@TestForIssue( jiraKey = "HHH-10161")
229+
public void testNativeQueryWithNullParameterTyped(){
230+
Chaos c0 = new Chaos();
231+
c0.setId( 0L );
232+
c0.setName( "c0" );
233+
c0.setSize( 0L );
234+
Chaos c1 = new Chaos();
235+
c1.setId( 1L );
236+
c1.setName( "c1" );
237+
c1.setSize( 1L );
238+
Chaos c2 = new Chaos();
239+
c2.setId( 2L );
240+
c2.setName( "c2" );
241+
c2.setSize( null );
242+
243+
Session s = openSession();
244+
s.beginTransaction();
245+
s.persist( c0 );
246+
s.persist( c1 );
247+
s.persist( c2 );
248+
249+
s.flush();
250+
s.clear();
251+
252+
List chaoses = s.createSQLQuery( "select * from Chaos where chaos_size is null or chaos_size = :chaos_size" )
253+
.setParameter( "chaos_size", null, StandardBasicTypes.LONG )
254+
.list();
255+
assertEquals( 1, chaoses.size() );
256+
257+
chaoses = s.createSQLQuery( "select * from Chaos where chaos_size = :chaos_size" )
258+
.setParameter( "chaos_size", null, StandardBasicTypes.LONG )
259+
.list();
260+
// should be no results because null != null
261+
assertEquals( 0, chaoses.size() );
262+
263+
s.getTransaction().rollback();
264+
s.close();
265+
}
266+
95267
@Test
96268
public void testPackageQueries() throws Exception {
97269
Session s = openSession();

hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/QueryImpl.java

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,10 @@
2828
import org.hibernate.LockMode;
2929
import org.hibernate.SQLQuery;
3030
import org.hibernate.TypeMismatchException;
31-
import org.hibernate.engine.query.spi.HQLQueryPlan;
3231
import org.hibernate.engine.query.spi.NamedParameterDescriptor;
3332
import org.hibernate.engine.query.spi.OrdinalParameterDescriptor;
3433
import org.hibernate.engine.query.spi.ParameterMetadata;
3534
import org.hibernate.engine.spi.SessionFactoryImplementor;
36-
import org.hibernate.engine.spi.SessionImplementor;
3735
import org.hibernate.hql.internal.QueryExecutionRequestException;
3836
import org.hibernate.internal.SQLQueryImpl;
3937
import org.hibernate.jpa.AvailableSettings;
@@ -42,6 +40,7 @@
4240
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
4341
import org.hibernate.jpa.spi.AbstractEntityManagerImpl;
4442
import org.hibernate.jpa.spi.AbstractQueryImpl;
43+
import org.hibernate.jpa.spi.NullTypeBindableParameterRegistration;
4544
import org.hibernate.jpa.spi.ParameterBind;
4645
import org.hibernate.jpa.spi.ParameterRegistration;
4746
import org.hibernate.type.CompositeCustomType;
@@ -159,8 +158,8 @@ private boolean mightNeedRedefinition(Class javaType, Type expectedType) {
159158
}
160159
}
161160

162-
private static class ParameterRegistrationImpl<T> implements ParameterRegistration<T> {
163-
private final Query jpaQuery;
161+
private static class ParameterRegistrationImpl<T> implements NullTypeBindableParameterRegistration<T> {
162+
private final QueryImpl jpaQuery;
164163
private final org.hibernate.Query nativeQuery;
165164

166165
private final String name;
@@ -170,7 +169,7 @@ private static class ParameterRegistrationImpl<T> implements ParameterRegistrati
170169
private ParameterBind<T> bind;
171170

172171
protected ParameterRegistrationImpl(
173-
Query jpaQuery,
172+
QueryImpl jpaQuery,
174173
org.hibernate.Query nativeQuery,
175174
String name,
176175
Class<T> javaType) {
@@ -182,7 +181,7 @@ protected ParameterRegistrationImpl(
182181
}
183182

184183
protected ParameterRegistrationImpl(
185-
Query jpaQuery,
184+
QueryImpl jpaQuery,
186185
org.hibernate.Query nativeQuery,
187186
Integer position,
188187
Class<T> javaType) {
@@ -314,6 +313,41 @@ else if ( specifiedTemporalType == TIMESTAMP ) {
314313
public ParameterBind<T> getBind() {
315314
return bind;
316315
}
316+
317+
@Override
318+
public void bindNullValue(Class<?> nullParameterType) {
319+
if ( nullParameterType == null ) {
320+
throw new IllegalArgumentException( "nullParameterType must be non-null" );
321+
}
322+
if ( getParameterType() != null ) {
323+
throw new IllegalArgumentException(
324+
String.format(
325+
"Cannot bind null value as type [%s]; it is already mapped as type [%s]",
326+
nullParameterType.getName(),
327+
getParameterType().getName()
328+
)
329+
);
330+
}
331+
validateBinding( nullParameterType, null, null );
332+
333+
if ( !org.hibernate.internal.AbstractQueryImpl.class.isInstance( jpaQuery.getHibernateQuery() ) ) {
334+
throw new IllegalStateException(
335+
"Unknown query type for binding null value" + jpaQuery.getHibernateQuery()
336+
.getClass()
337+
.getName()
338+
);
339+
}
340+
org.hibernate.internal.AbstractQueryImpl abstractQuery =
341+
(org.hibernate.internal.AbstractQueryImpl) jpaQuery.getHibernateQuery();
342+
final Type explicitType = abstractQuery.guessType( nullParameterType );
343+
if ( name != null ) {
344+
nativeQuery.setParameter( name, null, explicitType );
345+
}
346+
else {
347+
nativeQuery.setParameter( position - 1, null, explicitType );
348+
}
349+
bind = new ParameterBindImpl<T>( null, null );
350+
}
317351
}
318352

319353
/**
@@ -325,7 +359,7 @@ public static class JpaPositionalParameterRegistrationImpl<T> extends ParameterR
325359
final Integer position;
326360

327361
protected JpaPositionalParameterRegistrationImpl(
328-
Query jpaQuery,
362+
QueryImpl jpaQuery,
329363
org.hibernate.Query nativeQuery,
330364
Integer position,
331365
Class<T> javaType) {

hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/BaseQueryImpl.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,41 @@ public <T> BaseQueryImpl setParameter(Parameter<T> param, T value) {
545545
checkOpen( true );
546546

547547
try {
548-
findParameterRegistration( param ).bindValue( value );
548+
final ParameterRegistration<T> parameterRegistrationExisting = findParameterRegistration( param );
549+
if ( value == null
550+
&& param.getParameterType() != null
551+
&& parameterRegistrationExisting.getParameterType() == null &&
552+
NullTypeBindableParameterRegistration.class.isInstance( parameterRegistrationExisting ) ) {
553+
// we have:
554+
// 1) a null value to bind;
555+
// 2) parameterRegistrationExisting has no information about the Java type for that null value;
556+
// 3) parameter.getParameterType() supplies a non-null parameter type;
557+
// NOTE: According to Javadoc for javax.persistenceParameter#getParameterType:
558+
// "Applications that use this method for Java Persistence query language
559+
// queries and native queries will not be portable."
560+
// and 4) parameterRegistrationExisting allows for overriding the null parameter type when
561+
// binding a null value;
562+
( (NullTypeBindableParameterRegistration) parameterRegistrationExisting ).bindNullValue( param.getParameterType() );
563+
}
564+
else {
565+
// NOTE: The Javadoc for javax.persistence.Query#setParameter(Parameter<T> param, T value)
566+
// does not say anything about throwing IllegalArgumentException if
567+
// javax.persistenceParameter#getParameterType is not assignable to the type, so we simply log
568+
// a message if this is the case.
569+
if ( param.getParameterType() != null &&
570+
parameterRegistrationExisting.getParameterType() != null &&
571+
!parameterRegistrationExisting.getParameterType().isAssignableFrom( param.getParameterType() ) ) {
572+
LOG.warnf(
573+
"Parameter type [%s] is not assignment compatible with requested type [%s] for parameter %s",
574+
parameterRegistrationExisting.getParameterType().getName(),
575+
param.getParameterType().getName(),
576+
parameterRegistrationExisting.getName() == null ?
577+
"at position [" + param.getPosition() + "]" :
578+
"named [" + parameterRegistrationExisting.getName() + "]"
579+
);
580+
}
581+
parameterRegistrationExisting.bindValue( value );
582+
}
549583
}
550584
catch (QueryParameterException e) {
551585
entityManager().markForRollbackOnly();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.jpa.spi;
8+
9+
/**
10+
* A {@link ParameterRegistration} that allows providing Java type information when
11+
* binding a null value for a parameter when there is no other available type information
12+
* for that parameter.
13+
*
14+
* @author Gail Badner
15+
*/
16+
public interface NullTypeBindableParameterRegistration<T> extends ParameterRegistration<T> {
17+
18+
/**
19+
* If bindable, bind a null value using the provided parameter type.
20+
* This method is only valid if {@link #getParameterType} returns {@code null}.
21+
*
22+
* @param nullParameterType the Java type to be used for binding the null value;
23+
* must be non-null.
24+
*
25+
* @throws IllegalArgumentException {@code parameterType} is null or if
26+
* {@link #getParameterType} does not return null.
27+
*/
28+
public void bindNullValue(Class<?> nullParameterType);
29+
}

0 commit comments

Comments
 (0)