HHH-12661 - Hibernate types (e.g. NumericBooleanType, YesNoType and any implementations of UserType) cannot bind value in StoredProcedureQuery

This commit is contained in:
Roland Kurucz 2018-06-06 20:53:09 +02:00 committed by Vlad Mihalcea
parent 9ad030aee6
commit b6a16a3678
9 changed files with 523 additions and 2 deletions

View File

@ -79,7 +79,7 @@ public class ParameterBindImpl<T> implements ParameterBind<T> {
} }
if ( procedureParameter.getParameterType() != null ) { if ( procedureParameter.getParameterType() != null ) {
if ( !procedureParameter.getParameterType().isInstance( value ) ) { if ( !procedureParameter.getParameterType().isInstance( value ) && !procedureParameter.getHibernateType().getReturnedClass().isInstance( value ) ) {
throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter.getParameterType() ); throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter.getParameterType() );
} }
} }

View File

@ -1,3 +1,9 @@
/*
* 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.test.procedure; package org.hibernate.test.procedure;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;

View File

@ -1,3 +1,9 @@
/*
* 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.test.procedure; package org.hibernate.test.procedure;
import java.sql.CallableStatement; import java.sql.CallableStatement;

View File

@ -1,3 +1,9 @@
/*
* 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.test.procedure; package org.hibernate.test.procedure;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -27,6 +33,8 @@ import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.procedure.ProcedureCall; import org.hibernate.procedure.ProcedureCall;
import org.hibernate.result.Output; import org.hibernate.result.Output;
import org.hibernate.result.ResultSetOutput; import org.hibernate.result.ResultSetOutput;
import org.hibernate.type.NumericBooleanType;
import org.hibernate.type.YesNoType;
import org.hibernate.testing.FailureExpected; import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialect;
@ -54,7 +62,8 @@ public class OracleStoredProcedureTest extends BaseEntityManagerFunctionalTestCa
return new Class<?>[] { return new Class<?>[] {
Person.class, Person.class,
Phone.class, Phone.class,
IdHolder.class IdHolder.class,
Vote.class
}; };
} }
@ -173,6 +182,30 @@ public class OracleStoredProcedureTest extends BaseEntityManagerFunctionalTestCa
" SELECT 1 INTO p_value FROM dual; " + " SELECT 1 INTO p_value FROM dual; " +
" END; " " END; "
); );
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_phone_validity ( " +
" validity IN NUMBER, " +
" personPhones OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN personPhones FOR " +
" SELECT phone_number " +
" FROM phone " +
" WHERE valid = validity; " +
"END;"
);
statement.executeUpdate(
"CREATE OR REPLACE PROCEDURE sp_votes ( " +
" validity IN CHAR, " +
" votes OUT SYS_REFCURSOR ) " +
"AS " +
"BEGIN " +
" OPEN votes FOR " +
" SELECT id " +
" FROM vote " +
" WHERE vote_choice = validity; " +
"END;"
);
} finally { } finally {
if ( statement != null ) { if ( statement != null ) {
statement.close(); statement.close();
@ -198,11 +231,13 @@ public class OracleStoredProcedureTest extends BaseEntityManagerFunctionalTestCa
Phone phone1 = new Phone( "123-456-7890" ); Phone phone1 = new Phone( "123-456-7890" );
phone1.setId( 1L ); phone1.setId( 1L );
phone1.setValid( true );
person1.addPhone( phone1 ); person1.addPhone( phone1 );
Phone phone2 = new Phone( "098_765-4321" ); Phone phone2 = new Phone( "098_765-4321" );
phone2.setId( 2L ); phone2.setId( 2L );
phone2.setValid( false );
person1.addPhone( phone2 ); person1.addPhone( phone2 );
@ -458,4 +493,47 @@ public class OracleStoredProcedureTest extends BaseEntityManagerFunctionalTestCa
assertEquals( value, function.getOutputParameterValue( 2 ) ); assertEquals( value, function.getOutputParameterValue( 2 ) );
} ); } );
} }
@Test
@TestForIssue( jiraKey = "HHH-12661")
public void testBindParameterAsHibernateType() {
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_phone_validity")
.registerStoredProcedureParameter( 1, NumericBooleanType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR )
.setParameter( 1, true );
query.execute();
List phones = query.getResultList();
assertEquals( 1, phones.size() );
assertEquals( "123-456-7890", phones.get( 0 ) );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Vote vote1 = new Vote();
vote1.setId( 1L );
vote1.setVoteChoice( true );
entityManager.persist( vote1 );
Vote vote2 = new Vote();
vote2.setId( 2L );
vote2.setVoteChoice( false );
entityManager.persist( vote2 );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("sp_votes")
.registerStoredProcedureParameter( 1, YesNoType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR )
.setParameter( 1, true );
query.execute();
List votes = query.getResultList();
assertEquals( 1, votes.size() );
assertEquals( 1, ((Number) votes.get( 0 )).intValue() );
} );
}
} }

View File

@ -31,6 +31,8 @@ public class Phone {
@Column(name = "phone_number") @Column(name = "phone_number")
private String number; private String number;
private boolean valid;
@ElementCollection @ElementCollection
private List<Date> repairTimestamps = new ArrayList<>( ); private List<Date> repairTimestamps = new ArrayList<>( );
@ -59,4 +61,12 @@ public class Phone {
public void setPerson(Person person) { public void setPerson(Person person) {
this.person = person; this.person = person;
} }
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
} }

View File

@ -1,3 +1,9 @@
/*
* 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.test.procedure; package org.hibernate.test.procedure;
import java.sql.CallableStatement; import java.sql.CallableStatement;

View File

@ -1,3 +1,9 @@
/*
* 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.test.procedure; package org.hibernate.test.procedure;
import java.sql.CallableStatement; import java.sql.CallableStatement;

View File

@ -0,0 +1,364 @@
/*
* 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.test.procedure;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
import javax.persistence.ParameterMode;
import javax.sql.rowset.serial.SerialBlob;
import javax.sql.rowset.serial.SerialClob;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.type.BigDecimalType;
import org.hibernate.type.BigIntegerType;
import org.hibernate.type.BinaryType;
import org.hibernate.type.BlobType;
import org.hibernate.type.BooleanType;
import org.hibernate.type.ByteType;
import org.hibernate.type.CalendarType;
import org.hibernate.type.CharArrayType;
import org.hibernate.type.CharacterType;
import org.hibernate.type.ClassType;
import org.hibernate.type.ClobType;
import org.hibernate.type.CurrencyType;
import org.hibernate.type.DateType;
import org.hibernate.type.DoubleType;
import org.hibernate.type.FloatType;
import org.hibernate.type.IntegerType;
import org.hibernate.type.LocaleType;
import org.hibernate.type.LongType;
import org.hibernate.type.MaterializedClobType;
import org.hibernate.type.NumericBooleanType;
import org.hibernate.type.ShortType;
import org.hibernate.type.StringType;
import org.hibernate.type.TextType;
import org.hibernate.type.TimeType;
import org.hibernate.type.TimeZoneType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.TrueFalseType;
import org.hibernate.type.UUIDBinaryType;
import org.hibernate.type.UrlType;
import org.hibernate.type.YesNoType;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
/**
* @author Vlad Mihalcea
*/
@TestForIssue( jiraKey = "HHH-12661" )
public class StoredProcedureParameterTypeTest extends BaseNonConfigCoreFunctionalTestCase {
private static final String TEST_STRING = "test_string";
private static final char[] TEST_CHAR_ARRAY = TEST_STRING.toCharArray();
private static final byte[] TEST_BYTE_ARRAY = TEST_STRING.getBytes();
@Test
public void testNumericBooleanTypeInParameter() {
doInHibernate( this::sessionFactory, session -> {
session.createStoredProcedureQuery( "test" )
.registerStoredProcedureParameter( 1, NumericBooleanType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, String.class, ParameterMode.OUT )
.setParameter( 1, false );
} );
}
@Test
public void testYesNoTypeInParameter() {
doInHibernate( this::sessionFactory, session -> {
session.createStoredProcedureQuery( "test" )
.registerStoredProcedureParameter( 1, YesNoType.class, ParameterMode.IN )
.registerStoredProcedureParameter( 2, String.class, ParameterMode.OUT )
.setParameter( 1, false );
} );
}
@Test
public void testStringTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, StringType.class, ParameterMode.IN)
.setParameter(1, TEST_STRING)
);
}
@Test
public void testMaterializedClobTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, MaterializedClobType.class, ParameterMode.IN)
.setParameter(1, TEST_STRING)
);
}
@Test
public void testTextTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, TextType.class, ParameterMode.IN)
.setParameter(1, TEST_STRING)
);
}
@Test
public void testCharacterTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, CharacterType.class, ParameterMode.IN)
.setParameter(1, 'a')
);
}
@Test
public void testTrueFalseTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, TrueFalseType.class, ParameterMode.IN)
.setParameter(1, false)
);
}
@Test
public void testBooleanTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, BooleanType.class, ParameterMode.IN)
.setParameter(1, false)
);
}
@Test
public void testByteTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, ByteType.class, ParameterMode.IN)
.setParameter(1, (byte) 'a')
);
}
@Test
public void testShortTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, ShortType.class, ParameterMode.IN)
.setParameter(1, (short) 2)
);
}
@Test
public void testIntegerTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, IntegerType.class, ParameterMode.IN)
.setParameter(1, 2)
);
}
@Test
public void testLongTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, LongType.class, ParameterMode.IN)
.setParameter(1, 2L)
);
}
@Test
public void testFloatTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, FloatType.class, ParameterMode.IN)
.setParameter(1, 2.0F)
);
}
@Test
public void testDoubleTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, DoubleType.class, ParameterMode.IN)
.setParameter(1, 2.0D)
);
}
@Test
public void testBigIntegerTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, BigIntegerType.class, ParameterMode.IN)
.setParameter( 1, BigInteger.ONE)
);
}
@Test
public void testBigDecimalTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, BigDecimalType.class, ParameterMode.IN)
.setParameter( 1, BigDecimal.ONE)
);
}
@Test
public void testTimestampTypeDateInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, TimestampType.class, ParameterMode.IN)
.setParameter(1, new Date())
);
}
@Test
public void testTimestampTypeTimestampInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter(1, TimestampType.class, ParameterMode.IN)
.setParameter( 1, Timestamp.valueOf( LocalDateTime.now()))
);
}
@Test
public void testTimeTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, TimeType.class, ParameterMode.IN)
.setParameter( 1, Time.valueOf( LocalTime.now()))
);
}
@Test
public void testDateTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, DateType.class, ParameterMode.IN)
.setParameter(1, java.sql.Date.valueOf( LocalDate.now()))
);
}
@Test
public void testCalendarTypeCalendarInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, CalendarType.class, ParameterMode.IN)
.setParameter( 1, Calendar.getInstance())
);
}
@Test
public void testCurrencyTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, CurrencyType.class, ParameterMode.IN)
.setParameter( 1, Currency.getAvailableCurrencies().iterator().next())
);
}
@Test
public void testLocaleTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, LocaleType.class, ParameterMode.IN)
.setParameter( 1, Locale.ENGLISH)
);
}
@Test
public void testTimeZoneTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, TimeZoneType.class, ParameterMode.IN)
.setParameter( 1, TimeZone.getTimeZone( ZoneId.systemDefault()))
);
}
@Test
public void testUrlTypeInParameter() throws MalformedURLException {
final URL url = new URL( "http://example.com");
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, UrlType.class, ParameterMode.IN)
.setParameter(1, url)
);
}
@Test
public void testClassTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, ClassType.class, ParameterMode.IN)
.setParameter(1, Class.class)
);
}
@Test
public void testBlobTypeInParameter() throws SQLException {
final Blob blob = new SerialBlob( TEST_BYTE_ARRAY);
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, BlobType.class, ParameterMode.IN)
.setParameter(1, blob)
);
}
@Test
public void testClobTypeInParameter() throws SQLException {
final Clob clob = new SerialClob( TEST_CHAR_ARRAY);
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, ClobType.class, ParameterMode.IN)
.setParameter(1, clob)
);
}
@Test
public void testBinaryTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, BinaryType.class, ParameterMode.IN)
.setParameter(1, TEST_BYTE_ARRAY)
);
}
@Test
public void testCharArrayTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, CharArrayType.class, ParameterMode.IN)
.setParameter(1, TEST_CHAR_ARRAY)
);
}
@Test
public void testUUIDBinaryTypeInParameter() {
inTransaction(
session -> session.createStoredProcedureQuery("test")
.registerStoredProcedureParameter( 1, UUIDBinaryType.class, ParameterMode.IN)
.setParameter( 1, UUID.randomUUID())
);
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.test.procedure;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Type;
/**
* @author Vlad Mihalcea
*/
@Entity(name = "Vote")
@Table(name = "vote")
public class Vote {
@Id
private Long id;
@Column(name = "vote_choice")
@Type(type = "yes_no")
private boolean voteChoice;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public boolean isVoteChoice() {
return voteChoice;
}
public void setVoteChoice(boolean voteChoice) {
this.voteChoice = voteChoice;
}
}