web-dev-qa-db-fra.com

Comment appeler Oracle Function ou Procedure avec Hibernate (EntityManager) ou JPA 2

J'ai une fonction Oracle qui renvoie sys-refcursor et lorsque j'appelle cette fonction avec Hibernate 4, j'obtiens l'exception suivante.

Hibernate: { ? = call my_function(?) }
 org.hibernate.exception.GenericJDBCException: could not execute query
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not execute query
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.Java:1360)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.Java:1288)
    at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.Java:313)

Comment puis-je résoudre ça?

Fonction Oracle

create or replace 
FUNCTION my_function(p_val IN varchar2)
    RETURN SYS_REFCURSOR
  AS
    my_cursor SYS_REFCURSOR;
  BEGIN
    OPEN my_cursor FOR SELECT emp_name FROM employees
    WHERE lower(emp_name) like lower(p_val||'%');
    RETURN my_cursor;    
  END;

Mon cours d'entité

@Entity
@javax.persistence.NamedNativeQuery(name = "getFunc", query = 
"{ ? = call my_function(:empName) }", 
 resultClass = Employee.class, hints = 
 { @javax.persistence.QueryHint(name = "org.hibernate.callable", value = "true") })
 @Table(name = "EMPLOYEES")

et dans DAO  

    @Override
        public void findEmployees(QueryData data,
                String empName) {

        List query = (List) entityManager.createNamedQuery("getFunc")
                         .setParameter("empName", empName)
                         .getSingleResult();
                data.setResult(query);
}
11
Jåcob

La fonction Oracle ou une procédure stockée peut être appelée à l'aide de EntityManager de la manière suivante.

Pour la fonction Oracle

Créer une fonction avec sys_refcursor comme type de retour

CREATE OR REPLACE FUNCTION my_function
(p_val IN varchar2)
    RETURN SYS_REFCURSOR
  AS
    my_cursor SYS_REFCURSOR;
  BEGIN
    OPEN my_cursor FOR SELECT emp_name FROM employees
    WHERE lower(emp_name) like lower(p_val||'%');
    RETURN my_cursor;    
  END;

Dans la classe Entity, définissez la fonction comme

@javax.persistence.NamedNativeQuery(name = "getFunc", query = "{? =  call
my_function(:empName) }", resultClass = Employee.class, hints = {
@javax.persistence.QueryHint(name = "org.hibernate.callable", value = "true") })

Pour la procédure stockée Oracle

Créer une procédure avec sys_refcursor comme premier paramètre OUT

CREATE OR REPLACE PROCEDURE myProcedure(p_cursor out sys_refcursor,
     p_val  in varchar2
)
 AS
BEGIN
     OPEN o_cursor FOR
          SELECT     emp_name 
             FROM     employees 
            WHERE     LOWER (emp_name) LIKE lower(p_val||'%');

Dans la classe Entity, définissez la procédure comme suit:

@javax.persistence.NamedNativeQuery(name = "getProc", query = "{ call
my_procedure(?,:empName) }", resultClass = Employee.class, hints = {
@javax.persistence.QueryHint(name = "org.hibernate.callable", value = "true") })

et enfin dans la fonction ou la procédure d'appel de classe DAO

Query query = entityManager.createNamedQuery("getFunc"); // if procedure then getProc 
query.setParameter("empName","smith"); 
query.getResultList(); 

Merci

19
Jåcob

Cet article explique en détail toutes les combinaisons d'appels de procédures et de fonctions stockées Oracle.

Pour votre fonction,

create or replace 
FUNCTION my_function(p_val IN varchar2)
    RETURN SYS_REFCURSOR
  AS
    my_cursor SYS_REFCURSOR;
  BEGIN
    OPEN my_cursor FOR SELECT emp_name FROM employees
    WHERE lower(emp_name) like lower(p_val||'%');
    RETURN my_cursor;    
  END;

Vous pouvez définir la NamedNativeQuery suivante:

@NamedNativeQuery(
    name = "my_function",
    query = "{ ? = call my_function( ? ) }",
    callable = true,
    resultClass = String.class
)

Et, vous pouvez appeler la requête comme ceci:

List<String> employeeNames = entityManager
    .createNamedQuery("my_function")
    .setParameter(1, 1L)
    .getResultList();

Pour une procédure stockée:

CREATE OR REPLACE 
PROCEDURE my_procedure(p_val IN VARCHAR2, 
    my_cursor OUT SYS_REFCURSOR,
) 
AS
BEGIN
    OPEN my_cursor FOR
    SELECT emp_name FROM employees
    WHERE lower(emp_name) like lower(p_val||'%');
END;

, vous pouvez utiliser la requête JPA 2.1 suivante:

StoredProcedureQuery query = entityManager
    .createStoredProcedureQuery("my_procedure")
    .registerStoredProcedureParameter(1, String.class, 
         ParameterMode.IN)
    .registerStoredProcedureParameter(2, Class.class, 
         ParameterMode.REF_CURSOR)
    .setParameter(1, 1L);

query.execute();

List<Object[]> result = query.getResultList();
3
Vlad Mihalcea

Pour la procédure:

CREATE OR REPLACE PROCEDURE my_procedure(p_val IN VARCHAR2, 
  my_cursor OUT SYS_REFCURSOR) 
AS
BEGIN
  OPEN my_cursor FOR SELECT emp_name FROM employees 
      WHERE lower(emp_name) like lower(p_val||'%');
END;

Solution alternative: Appelez la procédure avec sys_refcursor comme paramètre OUT sans définir @NamedNativeQuery

StoredProcedureQuery query = entityManager.createStoredProcedureQuery("myProcedure");
    query.registerStoredProcedureParameter(1, void.class, ParameterMode.REF_CURSOR);
    query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);
    query.setParameter(2, "Umesh");
    List result = query.getResultList();
2
ukchaudhary

Les premières versions de JPA 2.1 indiquent que les procédures stockées seront prises en charge, selon Arun Gupta d’Oracle.

Prise en charge des procédures stockées: prise en charge du langage de requête Java Persistence pour la création de fonctions de base de données prédéfinies et de fonctions de base de données définies par l'utilisateur.

Il existe différentes variantes des méthodes EntityManager.createXXXStoredProcedureQuery qui renvoient un StoredProcedureQuery pour l'exécution d'une procédure stockée. Juste aimé @NamedQuery, il existe @NamedStoredProcedureQuery qui spécifie et nomme une procédure stockée, ses paramètres et son type de résultat. Cette annotation peut être spécifiée sur une entité ou une super-classe mappée. Le nom spécifié dans l'annotation est ensuite utilisé dans EntityManager.createNamedStoredProcedureQuery. Les paramètres IN, OUT et INOUT peuvent être définis et utilisés pour extraire les valeurs renvoyées de la procédure. Par exemple:

@Entity
@NamedStoredProcedureQuery(name="topGiftsStoredProcedure", procedureName="Top10Gifts")
public class Product {
 . . .
}

// In your client

StoredProcedreQuery query = EntityManager.createNamedStoredProcedureQuery("topGiftsStoredProcedure");
query.registerStoredProcedureParameter(1, String.class, ParameterMode.INOUT);
query.setParameter(1, "top10");
query.registerStoredProcedureParameter(2, Integer.class, ParameterMode.IN);
query.setParameter(2, 100);
// there are other setParameter methods for defining the temporal type of a parameter
. . .
query.execute();
String response = query.getOutputParameterValue(1);

Je ne saurais dire quand les spécifications vont être finalisées ou quand Hibernate supportera JPA 2.1. Mais cela pourrait valoir la peine de garder un œil sur.

1
prunge

La solution précédente ne semble pas fonctionner avec eclipselink. Je l’ai exécuté avec des requêtes natives sous JPA avec 

            List result = em.createNativeQuery("select YOURFUNCTION(?) from dual ")
                      .setParameter(1, ONEPARAMETER)
                      .getResultList();
1

Vous semblez confondre les fonctions Oracle avec les procédures stockées Oracle.

Les fonctions peuvent être appelées à partir d'une instruction select. Les fonctions définies par l'utilisateur, comme le vôtre, agissent de la même manière que les fonctions intégrées, comme min () et max (). Ils ne peuvent pas être appelés par un "appel" externe, contrairement aux procédures stockées.

Voir http://docs.Oracle.com/cd/B19306_01/server.102/b14200/functions231.htm#i1012049 pour la définition d'une fonction.

Vous devrez probablement réécrire votre fonction en tant que procédure stockée.

0
GreyBeardedGeek