Update stored procedures User Guide chapter
This commit is contained in:
parent
0fccf6b7bd
commit
af083e92ce
|
@ -641,82 +641,146 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-composite-key-entity-associ
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
[[sp_query]]
|
[[sql-sp]]
|
||||||
=== Using stored procedures for querying
|
=== Using stored procedures for querying
|
||||||
|
|
||||||
Hibernate provides support for queries via stored procedures and functions.
|
Hibernate provides support for queries via stored procedures and functions.
|
||||||
Most of the following documentation is equivalent for both.
|
A stored procedure arguments are declared using the `IN` parameter type, and the result can be either marked with an `OUT`
|
||||||
The stored procedure/function must return a resultset as the first out-parameter to be able to work with Hibernate.
|
parameter type, a `REF_CURSOR` or it could just return the result like a function.
|
||||||
An example of such a stored function in Oracle 9 and higher is as follows:
|
|
||||||
|
|
||||||
[source,xml]
|
[[sql-sp-out-mysql-example]]
|
||||||
|
.MySQL stored procedure with `OUT` parameter type
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
----
|
----
|
||||||
CREATE OR REPLACE FUNCTION selectAllEmployments
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-sp-out-mysql-example]
|
||||||
RETURN SYS_REFCURSOR
|
|
||||||
AS
|
|
||||||
st_cursor SYS_REFCURSOR;
|
|
||||||
BEGIN
|
|
||||||
OPEN st_cursor FOR
|
|
||||||
SELECT EMPLOYEE, EMPLOYER,
|
|
||||||
STARTDATE, ENDDATE,
|
|
||||||
REGIONCODE, EID, VALUE, CURRENCY
|
|
||||||
FROM EMPLOYMENT;
|
|
||||||
RETURN st_cursor;
|
|
||||||
END;
|
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
To use this query in Hibernate you need to map it via a named query.
|
To use this stored procedure, you can execute the following JPA 2.1 query:
|
||||||
|
|
||||||
[source,xml]
|
[[sql-jpa-call-sp-out-mysql-example]]
|
||||||
|
.Calling a MySQL stored procedure with `OUT` parameter type using JPA
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
----
|
----
|
||||||
<sql-query name = "selectAllEmployees_SP" callable = "true">
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-jpa-call-sp-out-mysql-example]
|
||||||
<return alias="emp" class="Employment">
|
|
||||||
<return-property name = "employee" column = "EMPLOYEE"/>
|
|
||||||
<return-property name = "employer" column = "EMPLOYER"/>
|
|
||||||
<return-property name = "startDate" column = "STARTDATE"/>
|
|
||||||
<return-property name = "endDate" column = "ENDDATE"/>
|
|
||||||
<return-property name = "regionCode" column = "REGIONCODE"/>
|
|
||||||
<return-property name = "id" column = "EID"/>
|
|
||||||
<return-property name = "salary">
|
|
||||||
<return-column name = "VALUE"/>
|
|
||||||
<return-column name = "CURRENCY"/>
|
|
||||||
</return-property>
|
|
||||||
</return>
|
|
||||||
{ ? = call selectAllEmployments() }
|
|
||||||
</sql-query>
|
|
||||||
----
|
----
|
||||||
|
====
|
||||||
|
|
||||||
Stored procedures currently only return scalars and entities.
|
[[sql-hibernate-call-sp-out-mysql-example]]
|
||||||
`<return-join>` and `<load-collection>` are not supported.
|
.Calling a MySQL stored procedure with `OUT` parameter type using Hibernate
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-hibernate-call-sp-out-mysql-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[[sql-limits-storedprocedures]]
|
If the stored procedure outputs the result directly without an `OUT` parameter type:
|
||||||
=== Rules/limitations for using stored procedures
|
|
||||||
|
|
||||||
You cannot use stored procedures with Hibernate unless you follow some procedure/function rules.
|
[[sql-sp-mysql-return-no-out-example]]
|
||||||
If they do not follow those rules they are not usable with Hibernate.
|
.MySQL stored procedure without an `OUT` parameter type
|
||||||
If you still want to use these procedures you have to execute them via `session.doWork()`.
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-sp-no-out-mysql-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
The rules are different for each database, since database vendors have different stored procedure semantics/syntax.
|
You can retrieve the results of the aforementioned MySQL stored procedure as follows:
|
||||||
|
|
||||||
|
[[sql-jpa-call-sp-no-out-mysql-example]]
|
||||||
|
.Calling a MySQL stored procedure and fetching the result set without an `OUT` parameter type using JPA
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-jpa-call-sp-no-out-mysql-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[sql-hibernate-call-sp-no-out-mysql-example]]
|
||||||
|
.Calling a MySQL stored procedure and fetching the result set without an `OUT` parameter type using Hibernate
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-hibernate-call-sp-no-out-mysql-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
For a `REF_CURSOR` result sets, we'll consider the following Oracle stored procedure:
|
||||||
|
|
||||||
|
[[sql-sp-ref-cursor-oracle-example]]
|
||||||
|
.Oracle `REF_CURSOR` stored procedure
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/OracleStoredProcedureTest.java[tags=sql-sp-ref-cursor-oracle-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[IMPORTANT]
|
||||||
|
====
|
||||||
|
`REF_CURSOR` result sets are only supported by Oracle and PostgreSQL because other database systems JDBC drivers don't support this feature.
|
||||||
|
====
|
||||||
|
|
||||||
|
This function can be called using the standard Java Persistence API:
|
||||||
|
|
||||||
|
[[sql-jpa-call-sp-ref-cursor-oracle-example]]
|
||||||
|
.Calling an Oracle `REF_CURSOR` stored procedure using JPA
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/OracleStoredProcedureTest.java[tags=sql-jpa-call-sp-ref-cursor-oracle-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[sql-hibernate-call-sp-ref-cursor-oracle-example]]
|
||||||
|
.Calling an Oracle `REF_CURSOR` stored procedure using Hibernate
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/OracleStoredProcedureTest.java[tags=sql-hibernate-call-sp-ref-cursor-oracle-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
If the database defines an SQL function:
|
||||||
|
|
||||||
|
[[sql-function-mysql-example]]
|
||||||
|
.MySQL function
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-function-mysql-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Because the current `StoredProcedureQuery` implementation doesn't yet support SQL functions,
|
||||||
|
we need to use the JDBC syntax.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
This limitation is acknowledged and it will be addressed by the https://hibernate.atlassian.net/browse/HHH-10530[HHH-10530] issue.
|
||||||
|
====
|
||||||
|
|
||||||
|
[[sql-call-function-mysql-example]]
|
||||||
|
.Calling a MySQL function
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/MySQLStoredProcedureTest.java[tags=sql-call-function-mysql-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
Stored procedure queries cannot be paged with `setFirstResult()/setMaxResults()`.
|
Stored procedure queries cannot be paged with `setFirstResult()/setMaxResults()`.
|
||||||
|
|
||||||
The recommended call form is standard SQL92: `{ ? = call functionName(<parameters>) }` or `{ ? = call procedureName(<parameters>}`.
|
Since these servers can return multiple result sets and update counts,
|
||||||
Native call syntax is not supported.
|
Hibernate will iterate the results and take the first result that is a result set as its return value, so everything else will be discarded.
|
||||||
|
|
||||||
For Oracle the following rules apply:
|
For SQL Server, if you can enable `SET NOCOUNT ON` in your procedure it will probably be more efficient, but this is not a requirement.
|
||||||
|
====
|
||||||
* A function must return a result set.
|
|
||||||
The first parameter of a procedure must be an `OUT` that returns a result set.
|
|
||||||
This is done by using a `SYS_REFCURSOR` type in Oracle 9 or 10.
|
|
||||||
In Oracle you need to define a `REF CURSOR` type.
|
|
||||||
See Oracle literature for further information.
|
|
||||||
|
|
||||||
For Sybase or MS SQL server the following rules apply:
|
|
||||||
|
|
||||||
* The procedure must return a result set.
|
|
||||||
Note that since these servers can return multiple result sets and update counts, Hibernate will iterate the results and take the first result that is a result set as its return value.
|
|
||||||
Everything else will be discarded.
|
|
||||||
* If you can enable `SET NOCOUNT ON` in your procedure it will probably be more efficient, but this is not a requirement.
|
|
||||||
|
|
||||||
[[sql-cud]]
|
[[sql-cud]]
|
||||||
=== Custom SQL for create, update and delete
|
=== Custom SQL for create, update and delete
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
package org.hibernate.userguide.sql;
|
||||||
|
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.sql.Types;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.persistence.ParameterMode;
|
||||||
|
import javax.persistence.StoredProcedureQuery;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.dialect.MySQL5Dialect;
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
import org.hibernate.procedure.ProcedureCall;
|
||||||
|
import org.hibernate.result.Output;
|
||||||
|
import org.hibernate.result.ResultSetOutput;
|
||||||
|
import org.hibernate.userguide.model.AddressType;
|
||||||
|
import org.hibernate.userguide.model.Call;
|
||||||
|
import org.hibernate.userguide.model.Partner;
|
||||||
|
import org.hibernate.userguide.model.Person;
|
||||||
|
import org.hibernate.userguide.model.Phone;
|
||||||
|
import org.hibernate.userguide.model.PhoneType;
|
||||||
|
|
||||||
|
import org.hibernate.testing.RequiresDialect;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
@RequiresDialect(MySQL5Dialect.class)
|
||||||
|
public class MySQLStoredProcedureTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
Person.class,
|
||||||
|
Partner.class,
|
||||||
|
Phone.class,
|
||||||
|
Call.class,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
session.doWork( connection -> {
|
||||||
|
try(Statement statement = connection.createStatement()) {
|
||||||
|
statement.executeUpdate("DROP PROCEDURE IF EXISTS count_phones");
|
||||||
|
}
|
||||||
|
catch (SQLException ignore) {
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
});
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
session.doWork( connection -> {
|
||||||
|
try(Statement statement = connection.createStatement()) {
|
||||||
|
statement.executeUpdate("DROP PROCEDURE IF EXISTS person_phones");
|
||||||
|
}
|
||||||
|
catch (SQLException ignore) {
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
});
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
session.doWork( connection -> {
|
||||||
|
try(Statement statement = connection.createStatement()) {
|
||||||
|
statement.executeUpdate("DROP FUNCTION IF EXISTS fn_count_comments");
|
||||||
|
}
|
||||||
|
catch (SQLException ignore) {
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
});
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
session.doWork( connection -> {
|
||||||
|
try(Statement statement = connection.createStatement()) {
|
||||||
|
//tag::sql-sp-out-mysql-example[]
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE PROCEDURE count_phones (" +
|
||||||
|
" IN personId INT, " +
|
||||||
|
" OUT phoneCount INT " +
|
||||||
|
") " +
|
||||||
|
"BEGIN " +
|
||||||
|
" SELECT COUNT(*) INTO phoneCount " +
|
||||||
|
" FROM person_phone " +
|
||||||
|
" WHERE person_phone.person_id = personId; " +
|
||||||
|
"END"
|
||||||
|
);
|
||||||
|
//end::sql-sp-out-mysql-example[]
|
||||||
|
//tag::sql-sp-no-out-mysql-example[]
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE PROCEDURE person_phones(IN personId INT) " +
|
||||||
|
"BEGIN " +
|
||||||
|
" SELECT * " +
|
||||||
|
" FROM person_phone " +
|
||||||
|
" WHERE person_id = personId; " +
|
||||||
|
"END"
|
||||||
|
);
|
||||||
|
//end::sql-sp-no-out-mysql-example[]
|
||||||
|
//tag::sql-function-mysql-example[]
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE FUNCTION fn_count_comments(postId integer) " +
|
||||||
|
"RETURNS integer " +
|
||||||
|
"DETERMINISTIC " +
|
||||||
|
"READS SQL DATA " +
|
||||||
|
"BEGIN " +
|
||||||
|
" DECLARE commentCount integer; " +
|
||||||
|
" SELECT COUNT(*) INTO commentCount " +
|
||||||
|
" FROM post_comment " +
|
||||||
|
" WHERE post_comment.post_id = postId; " +
|
||||||
|
" RETURN commentCount; " +
|
||||||
|
"END"
|
||||||
|
);
|
||||||
|
//end::sql-function-mysql-example[]
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
});
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Person person1 = new Person("John Doe" );
|
||||||
|
person1.setNickName( "JD" );
|
||||||
|
person1.setAddress( "Earth" );
|
||||||
|
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) )) ;
|
||||||
|
person1.getAddresses().put( AddressType.HOME, "Home address" );
|
||||||
|
person1.getAddresses().put( AddressType.OFFICE, "Office address" );
|
||||||
|
|
||||||
|
entityManager.persist(person1);
|
||||||
|
|
||||||
|
Phone phone1 = new Phone( "123-456-7890" );
|
||||||
|
phone1.setId( 1L );
|
||||||
|
phone1.setType( PhoneType.MOBILE );
|
||||||
|
|
||||||
|
person1.addPhone( phone1 );
|
||||||
|
|
||||||
|
Phone phone2 = new Phone( "098_765-4321" );
|
||||||
|
phone2.setId( 2L );
|
||||||
|
phone2.setType( PhoneType.LAND_LINE );
|
||||||
|
|
||||||
|
person1.addPhone( phone2 );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoredProcedureOutParameter() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::sql-jpa-call-sp-out-mysql-example[]
|
||||||
|
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "count_phones");
|
||||||
|
query.registerStoredProcedureParameter( "personId", Long.class, ParameterMode.IN);
|
||||||
|
query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT);
|
||||||
|
|
||||||
|
query.setParameter("personId", 1L);
|
||||||
|
|
||||||
|
query.execute();
|
||||||
|
Long phoneCount = (Long) query.getOutputParameterValue("phoneCount");
|
||||||
|
//end::sql-jpa-call-sp-out-mysql-example[]
|
||||||
|
assertEquals(Long.valueOf(2), phoneCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHibernateProcedureCallOutParameter() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::sql-hibernate-call-sp-out-mysql-example[]
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
|
||||||
|
ProcedureCall call = session.createStoredProcedureCall( "count_phones" );
|
||||||
|
call.registerParameter( "personId", Long.class, ParameterMode.IN ).bindValue( 1L );
|
||||||
|
call.registerParameter( "phoneCount", Long.class, ParameterMode.OUT );
|
||||||
|
|
||||||
|
Long phoneCount = (Long) call.getOutputs().getOutputParameterValue( "phoneCount" );
|
||||||
|
assertEquals( Long.valueOf( 2 ), phoneCount );
|
||||||
|
//end::sql-hibernate-call-sp-out-mysql-example[]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoredProcedureRefCursor() {
|
||||||
|
try {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "person_phones");
|
||||||
|
query.registerStoredProcedureParameter( 1, void.class, ParameterMode.REF_CURSOR);
|
||||||
|
query.registerStoredProcedureParameter( 2, Long.class, ParameterMode.IN);
|
||||||
|
|
||||||
|
query.setParameter(2, 1L);
|
||||||
|
|
||||||
|
List<Object[]> personComments = query.getResultList();
|
||||||
|
assertEquals(2, personComments.size());
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertTrue(Pattern.compile("Dialect .*? not known to support REF_CURSOR parameters").matcher(e.getCause().getMessage()).matches());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoredProcedureReturnValue() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::sql-jpa-call-sp-no-out-mysql-example[]
|
||||||
|
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "person_phones");
|
||||||
|
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN);
|
||||||
|
|
||||||
|
query.setParameter(1, 1L);
|
||||||
|
|
||||||
|
List<Object[]> personComments = query.getResultList();
|
||||||
|
//end::sql-jpa-call-sp-no-out-mysql-example[]
|
||||||
|
assertEquals(2, personComments.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHibernateProcedureCallReturnValueParameter() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::sql-hibernate-call-sp-no-out-mysql-example[]
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
|
||||||
|
ProcedureCall call = session.createStoredProcedureCall( "post_comments" );
|
||||||
|
call.registerParameter( 1, Long.class, ParameterMode.IN ).bindValue( 1L );
|
||||||
|
|
||||||
|
Output output = call.getOutputs().getCurrent();
|
||||||
|
|
||||||
|
List<Object[]> personComments = ( (ResultSetOutput) output ).getResultList();
|
||||||
|
//end::sql-hibernate-call-sp-no-out-mysql-example[]
|
||||||
|
assertEquals( 2, personComments.size() );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFunctionWithJDBC() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::sql-call-function-mysql-example[]
|
||||||
|
final AtomicReference<Integer> commentCount = new AtomicReference<>();
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
session.doWork( connection -> {
|
||||||
|
try (CallableStatement function = connection.prepareCall(
|
||||||
|
"{ ? = call fn_count_comments(?) }" )) {
|
||||||
|
function.registerOutParameter( 1, Types.INTEGER );
|
||||||
|
function.setInt( 2, 1 );
|
||||||
|
function.execute();
|
||||||
|
commentCount.set( function.getInt( 1 ) );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
//end::sql-call-function-mysql-example[]
|
||||||
|
assertEquals(Integer.valueOf(2), commentCount.get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package org.hibernate.userguide.sql;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.persistence.ParameterMode;
|
||||||
|
import javax.persistence.StoredProcedureQuery;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.dialect.Oracle8iDialect;
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
import org.hibernate.procedure.ProcedureCall;
|
||||||
|
import org.hibernate.result.Output;
|
||||||
|
import org.hibernate.result.ResultSetOutput;
|
||||||
|
import org.hibernate.userguide.model.AddressType;
|
||||||
|
import org.hibernate.userguide.model.Call;
|
||||||
|
import org.hibernate.userguide.model.Partner;
|
||||||
|
import org.hibernate.userguide.model.Person;
|
||||||
|
import org.hibernate.userguide.model.Phone;
|
||||||
|
import org.hibernate.userguide.model.PhoneType;
|
||||||
|
|
||||||
|
import org.hibernate.testing.RequiresDialect;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
@RequiresDialect(Oracle8iDialect.class)
|
||||||
|
public class OracleStoredProcedureTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
Person.class,
|
||||||
|
Partner.class,
|
||||||
|
Phone.class,
|
||||||
|
Call.class,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Session session = entityManager.unwrap( Session.class );
|
||||||
|
session.doWork( connection -> {
|
||||||
|
try(Statement statement = connection.createStatement()) {
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE OR REPLACE PROCEDURE count_phones ( " +
|
||||||
|
" personId IN NUMBER, " +
|
||||||
|
" phoneCount OUT NUMBER ) " +
|
||||||
|
"AS " +
|
||||||
|
"BEGIN " +
|
||||||
|
" SELECT COUNT(*) INTO phoneCount " +
|
||||||
|
" FROM person_phone " +
|
||||||
|
" WHERE person_id = personId; " +
|
||||||
|
"END;"
|
||||||
|
);
|
||||||
|
//tag::sql-sp-ref-cursor-oracle-example[]
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE OR REPLACE PROCEDURE person_phones ( " +
|
||||||
|
" personId IN NUMBER, " +
|
||||||
|
" personPhones OUT SYS_REFCURSOR ) " +
|
||||||
|
"AS " +
|
||||||
|
"BEGIN " +
|
||||||
|
" OPEN personPhones FOR " +
|
||||||
|
" SELECT *" +
|
||||||
|
" FROM person_phone " +
|
||||||
|
" WHERE person_id = personId; " +
|
||||||
|
"END;"
|
||||||
|
);
|
||||||
|
//end::sql-sp-ref-cursor-oracle-example[]
|
||||||
|
statement.executeUpdate(
|
||||||
|
"CREATE OR REPLACE FUNCTION fn_count_phones ( " +
|
||||||
|
" personId IN NUMBER ) " +
|
||||||
|
" RETURN NUMBER " +
|
||||||
|
"IS " +
|
||||||
|
" phoneCount NUMBER; " +
|
||||||
|
"BEGIN " +
|
||||||
|
" SELECT COUNT(*) INTO phoneCount " +
|
||||||
|
" FROM person_phone " +
|
||||||
|
" WHERE person_id = personId; " +
|
||||||
|
" RETURN( phoneCount ); " +
|
||||||
|
"END;"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
});
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Person person1 = new Person("John Doe" );
|
||||||
|
person1.setNickName( "JD" );
|
||||||
|
person1.setAddress( "Earth" );
|
||||||
|
person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ).toInstant( ZoneOffset.UTC ) )) ;
|
||||||
|
person1.getAddresses().put( AddressType.HOME, "Home address" );
|
||||||
|
person1.getAddresses().put( AddressType.OFFICE, "Office address" );
|
||||||
|
|
||||||
|
entityManager.persist(person1);
|
||||||
|
|
||||||
|
Phone phone1 = new Phone( "123-456-7890" );
|
||||||
|
phone1.setId( 1L );
|
||||||
|
phone1.setType( PhoneType.MOBILE );
|
||||||
|
|
||||||
|
person1.addPhone( phone1 );
|
||||||
|
|
||||||
|
Phone phone2 = new Phone( "098_765-4321" );
|
||||||
|
phone2.setId( 2L );
|
||||||
|
phone2.setType( PhoneType.LAND_LINE );
|
||||||
|
|
||||||
|
person1.addPhone( phone2 );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoredProcedureOutParameter() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
StoredProcedureQuery query = entityManager.createStoredProcedureQuery("count_phones");
|
||||||
|
query.registerStoredProcedureParameter("personId", Long.class, ParameterMode.IN);
|
||||||
|
query.registerStoredProcedureParameter("phoneCount", Long.class, ParameterMode.OUT);
|
||||||
|
|
||||||
|
query.setParameter("person_id", 1L);
|
||||||
|
|
||||||
|
query.execute();
|
||||||
|
Long phoneCount = (Long) query.getOutputParameterValue("phoneCount");
|
||||||
|
assertEquals(Long.valueOf(2), phoneCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoredProcedureRefCursor() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::sql-jpa-call-sp-ref-cursor-oracle-example[]
|
||||||
|
StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "personPhones" );
|
||||||
|
query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
|
||||||
|
query.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR );
|
||||||
|
query.setParameter( 1, 1L );
|
||||||
|
|
||||||
|
query.execute();
|
||||||
|
List<Object[]> postComments = query.getResultList();
|
||||||
|
//end::sql-jpa-call-sp-ref-cursor-oracle-example[]
|
||||||
|
assertNotNull( postComments );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHibernateProcedureCallRefCursor() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::sql-hibernate-call-sp-ref-cursor-oracle-example[]
|
||||||
|
Session session = entityManager.unwrap(Session.class);
|
||||||
|
|
||||||
|
ProcedureCall call = session.createStoredProcedureCall( "personPhones");
|
||||||
|
call.registerParameter(1, Long.class, ParameterMode.IN).bindValue(1L);
|
||||||
|
call.registerParameter(2, Class.class, ParameterMode.REF_CURSOR);
|
||||||
|
|
||||||
|
Output output = call.getOutputs().getCurrent();
|
||||||
|
List<Object[]> postComments = ( (ResultSetOutput) output ).getResultList();
|
||||||
|
assertEquals(2, postComments.size());
|
||||||
|
//end::sql-hibernate-call-sp-ref-cursor-oracle-example[]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoredProcedureReturnValue() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
BigDecimal phoneCount = (BigDecimal) entityManager
|
||||||
|
.createNativeQuery("SELECT fn_count_phones(:personId) FROM DUAL")
|
||||||
|
.setParameter("personId", 1L)
|
||||||
|
.getSingleResult();
|
||||||
|
assertEquals(BigDecimal.valueOf(2), phoneCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue