From 97e06d16ccf6cbde99578ab1995117612c5f9762 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Tue, 5 Mar 2013 23:13:41 +0100 Subject: [PATCH] HHH-8022 - Reading REF CURSOR --- .../jdbc/internal/ResultSetReturnImpl.java | 5 + .../engine/jdbc/spi/ResultSetReturn.java | 3 +- .../sql/refcursor/CursorFromCallableTest.java | 103 ++++++++++++++++++ .../test/sql/refcursor/NumValue.java | 75 +++++++++++++ 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/CursorFromCallableTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/NumValue.java diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java index 5612367c35..7014f3c44d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java @@ -44,6 +44,11 @@ public class ResultSetReturnImpl implements ResultSetReturn { @Override public ResultSet extract(PreparedStatement statement) { // sql logged by StatementPreparerImpl + if ( statement instanceof CallableStatement ) { + // we actually need to extract from callable statement + CallableStatement callableStatement = (CallableStatement) statement; + return extract( callableStatement ); + } try { ResultSet rs = statement.executeQuery(); postExtract( rs ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ResultSetReturn.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ResultSetReturn.java index 6e75aa2e6d..2dbcc27ed1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ResultSetReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/ResultSetReturn.java @@ -40,7 +40,8 @@ import java.sql.Statement; public interface ResultSetReturn { /** - * Extract the ResultSet from the statement. + * Extract the ResultSet from the statement. If user passes {@link CallableStatement} + * reference, method calls {@link #extract(CallableStatement)} internally. * * @param statement * diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/CursorFromCallableTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/CursorFromCallableTest.java new file mode 100644 index 0000000000..ad771e033a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/CursorFromCallableTest.java @@ -0,0 +1,103 @@ +package org.hibernate.test.sql.refcursor; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Arrays; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.Session; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.jdbc.spi.ResultSetReturn; +import org.hibernate.engine.jdbc.spi.StatementPreparer; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.jdbc.Work; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@RequiresDialect( Oracle8iDialect.class ) +public class CursorFromCallableTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { NumValue.class }; + } + + @Before + public void createRefCursorFunction() { + executeStatement( "CREATE OR REPLACE FUNCTION f_test_return_cursor RETURN SYS_REFCURSOR " + + "IS " + + " l_Cursor SYS_REFCURSOR; " + + "BEGIN " + + " OPEN l_Cursor FOR " + + " SELECT 1 AS BOT_NUM " + + " , 'Line 1' AS BOT_VALUE " + + " FROM DUAL " + + " UNION " + + " SELECT 2 AS BOT_NUM " + + " , 'Line 2' AS BOT_VALUE " + + " FROM DUAL; " + + " RETURN(l_Cursor); " + + "END f_test_return_cursor;" ); + } + + @After + public void dropRefCursorFunction() { + executeStatement( "DROP FUNCTION f_test_return_cursor" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-8022" ) + public void testReadResultSetFromRefCursor() { + Session session = openSession(); + session.getTransaction().begin(); + + Assert.assertEquals( + Arrays.asList( new NumValue( 1, "Line 1" ), new NumValue( 2, "Line 2" ) ), + session.getNamedQuery( "NumValue.getSomeValues" ).list() + ); + + session.getTransaction().commit(); + session.close(); + } + + private void executeStatement(final String sql) { + final Session session = openSession(); + session.getTransaction().begin(); + + session.doWork( new Work() { + @Override + public void execute(Connection connection) throws SQLException { + final JdbcCoordinator jdbcCoordinator = ( (SessionImplementor) session ).getTransactionCoordinator().getJdbcCoordinator(); + final StatementPreparer statementPreparer = jdbcCoordinator.getStatementPreparer(); + final ResultSetReturn resultSetReturn = jdbcCoordinator.getResultSetReturn(); + PreparedStatement preparedStatement = null; + try { + preparedStatement = statementPreparer.prepareStatement( sql ); + resultSetReturn.execute( preparedStatement ); + } + finally { + if ( preparedStatement != null ) { + try { + jdbcCoordinator.release( preparedStatement ); + } + catch ( Throwable ignore ) { + // ignore... + } + } + } + } + } ); + + session.getTransaction().commit(); + session.close(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/NumValue.java b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/NumValue.java new file mode 100644 index 0000000000..178214dbc4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/refcursor/NumValue.java @@ -0,0 +1,75 @@ +package org.hibernate.test.sql.refcursor; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedNativeQueries; +import javax.persistence.NamedNativeQuery; +import javax.persistence.QueryHint; +import javax.persistence.Table; + +@Entity +@Table(name = "BOT_NUMVALUE") +@NamedNativeQueries({ + @NamedNativeQuery(name = "NumValue.getSomeValues", + query = "{ ? = call f_test_return_cursor() }", + resultClass = NumValue.class, hints = { @QueryHint(name = "org.hibernate.callable", value = "true") }) +}) +public class NumValue implements Serializable { + @Id + @Column(name = "BOT_NUM", nullable = false) + private long num; + + @Column(name = "BOT_VALUE") + private String value; + + public NumValue() { + } + + public NumValue(long num, String value) { + this.num = num; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof NumValue ) ) return false; + + NumValue numValue = (NumValue) o; + + if ( num != numValue.num ) return false; + if ( value != null ? !value.equals( numValue.value ) : numValue.value != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) ( num ^ ( num >>> 32 ) ); + result = 31 * result + ( value != null ? value.hashCode() : 0 ); + return result; + } + + @Override + public String toString() { + return "NumValue(num = " + num + ", value = " + value + ")"; + } + + public long getNum() { + return num; + } + + public void setNum(long num) { + this.num = num; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file