Cómo llamar a un procedimiento almacenado desde Java y JPA

95

Estoy escribiendo una aplicación web simple para llamar a un procedimiento almacenado y recuperar algunos datos. Es una aplicación muy simple, que interactúa con la base de datos del cliente. Pasamos la identificación del empleado y la identificación de la empresa y el procedimiento almacenado devolverá los detalles del empleado.

La aplicación web no puede actualizar / eliminar datos y está usando SQL Server.

Estoy implementando mi aplicación web en Jboss AS. ¿Debo usar JPA para acceder al procedimiento almacenado o CallableStatement. Cualquier ventaja de usar JPA en este caso.

Además, ¿cuál será la instrucción SQL para llamar a este procedimiento almacenado? Nunca antes había usado procedimientos almacenados y estoy luchando con este. Google no fue de mucha ayuda.

Aquí está el procedimiento almacenado:

CREATE procedure getEmployeeDetails (@employeeId int, @companyId int)
as
begin
    select firstName, 
           lastName, 
           gender, 
           address
      from employee et
     where et.employeeId = @employeeId
       and et.companyId = @companyId
end

Actualizar:

Para cualquier otra persona que tenga problemas para llamar al procedimiento almacenado usando JPA .

Query query = em.createNativeQuery("{call getEmployeeDetails(?,?)}",
                                   EmployeeDetails.class)           
                                   .setParameter(1, employeeId)
                                   .setParameter(2, companyId);

List<EmployeeDetails> result = query.getResultList();

Cosas que he notado:

  1. Los nombres de los parámetros no me funcionaron, así que intente usar el índice de parámetros.
  2. Corregir la declaración de SQL en {call sp_name(?,?)}lugar de call sp_name(?,?)
  3. Si el procedimiento almacenado devuelve un conjunto de resultados, incluso si sabe que solo tiene una fila, getSingleResultno funcionará
  4. Pasar un resultSetMappingnombre o detalles de la clase de resultado
user431514
fuente
2
No puede utilizar parámetros con nombre en consultas nativas . Los parámetros con nombre solo se admiten para consultas JPQL. (Si prefiere parámetros con nombre, puede escribir su propia clase para traducir parámetros con nombre a numerados.)
Viliam Búr
Siempre he usado parámetros con nombre con createNativeQueries y nunca tuve ningún problema. Acabo de echar un vistazo al sistema actual en el que he estado trabajando y hay toneladas de consultas nativas con parámetros con nombre. ¿Puede proporcionarnos alguna referencia para su afirmación? Nuestro conjunto es JPA 2 e Hibernate 4+.
Jaumzera

Respuestas:

59

JPA 2.1 ahora es compatible con el procedimiento almacenado, lea el documento de Java aquí .

Ejemplo:

StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("sales_tax");
// set parameters
storedProcedure.registerStoredProcedureParameter("subtotal", Double.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("tax", Double.class, ParameterMode.OUT);
storedProcedure.setParameter("subtotal", 1f);
// execute SP
storedProcedure.execute();
// get result
Double tax = (Double)storedProcedure.getOutputParameterValue("tax");

Vea un ejemplo detallado aquí .

Pau Kiat Wee
fuente
23

Estoy implementando mi aplicación web en Jboss AS. ¿Debo usar JPA para acceder al procedimiento almacenado o CallableStatement? Cualquier ventaja de usar JPA en este caso.

Realmente no es compatible con JPA, pero es factible . Aún así, no iría por este camino:

  • usar JPA solo para mapear el resultado de una llamada a un procedimiento almacenado en algunos beans es realmente excesivo,
  • especialmente dado que JPA no es realmente apropiado para llamar al procedimiento almacenado (la sintaxis será bastante detallada).

Por lo tanto, preferiría considerar usar el soporte de Spring para el acceso a datos JDBC , o un mapeador de datos como MyBatis o, dada la simplicidad de su aplicación, JDBC sin formato y CallableStatement. En realidad, JDBC probablemente sería mi elección. Aquí hay un ejemplo básico de inicio:

CallableStatement cstmt = con.prepareCall("{call getEmployeeDetails(?, ?)}");
cstmt.setInt("employeeId", 123);
cstmt.setInt("companyId", 456);
ResultSet rs = cstmt.executeQuery();

Referencia

Pascal Thivent
fuente
Como se indica en la respuesta a continuación , es compatible (es posible que desee editar
Mr_and_Mrs_D
10

Debe pasar los parámetros al procedimiento almacenado.

Debería funcionar así:

    List result = em
      .createNativeQuery("call getEmployeeDetails(:employeeId,:companyId)")
      .setParameter("emplyoyeeId", 123L)
      .setParameter("companyId", 456L)
      .getResultList();

Actualizar:

O tal vez no debería.

En el Libro EJB3 en acción , dice en la página 383, que JPA no admite procedimientos almacenados (la página es solo una vista previa, no se obtiene el texto completo, el libro completo está disponible como descarga en varios lugares, incluido este , Aunque no sé si esto es legal).

De todos modos, el texto es este:

Procedimientos almacenados de base de datos y JPA

Si es un gran fanático de SQL, es posible que esté dispuesto a aprovechar el poder de los procedimientos almacenados de la base de datos. Desafortunadamente, JPA no admite procedimientos almacenados y debe depender de una función patentada de su proveedor de persistencia. Sin embargo, puede usar funciones almacenadas simples (sin parámetros) con una consulta SQL nativa.

Sean Patrick Floyd
fuente
Intenté y recibí este mensaje de error: java.sql.SQLException: sintaxis incorrecta cerca de '@ P0'.
user431514
3
Debe ser "{call getEmployeeDetails (: employeeId,: companyId)}", para el servidor SQL debe tener llaves.
Vedran
@Vedran verdad. Solo estaba interesado en la parte de configuración de parámetros
Sean Patrick Floyd
9

Cómo recuperar el parámetro de salida del procedimiento almacenado usando JPA (2.0 necesita importaciones de EclipseLink y 2.1 no)

A pesar de que esta respuesta detalla la devolución de un conjunto de registros de un procedimiento almacenado, estoy publicando aquí, porque me tomó años resolverlo y este hilo me ayudó.

Mi aplicación estaba usando Eclipselink-2.3.1, pero forzaré una actualización a Eclipselink-2.5.0, ya que JPA 2.1 tiene mucho mejor soporte para procedimientos almacenados.

Uso de EclipseLink-2.3.1 / JPA-2.0: dependiente de la implementación

Este método requiere la importación de clases de EclipseLink desde "org.eclipse.persistence", por lo que es específico para la implementación de Eclipselink.

Lo encontré en " http://www.yenlo.nl/en/calling-oracle-stored-procedures-from-eclipselink-with-multiple-out-parameters ".

StoredProcedureCall storedProcedureCall = new StoredProcedureCall();
storedProcedureCall.setProcedureName("mypackage.myprocedure");
storedProcedureCall.addNamedArgument("i_input_1"); // Add input argument name.
storedProcedureCall.addNamedOutputArgument("o_output_1"); // Add output parameter name.
DataReadQuery query = new DataReadQuery();
query.setCall(storedProcedureCall);
query.addArgument("i_input_1"); // Add input argument names (again);
List<Object> argumentValues = new ArrayList<Object>();
argumentValues.add("valueOf_i_input_1"); // Add input argument values.
JpaEntityManager jpaEntityManager = (JpaEntityManager) getEntityManager();
Session session = jpaEntityManager.getActiveSession();
List<?> results = (List<?>) session.executeQuery(query, argumentValues);
DatabaseRecord record = (DatabaseRecord) results.get(0);
String result = String.valueOf(record.get("o_output_1")); // Get output parameter

Usando EclipseLink-2.5.0 / JPA-2.1: Independiente de la implementación (documentado ya en este hilo)

Este método es independiente de la implementación (no necesita importaciones de Eclipslink).

StoredProcedureQuery query = getEntityManager().createStoredProcedureQuery("mypackage.myprocedure");
query.registerStoredProcedureParameter("i_input_1", String.class, ParameterMode.IN);
query.registerStoredProcedureParameter("o_output_1", String.class, ParameterMode.OUT);
query.setParameter("i_input_1", "valueOf_i_input_1");
boolean queryResult = query.execute();
String result = String.valueOf(query.getOutputParameterValue("o_output_1"));
Malcolm Boekhoff
fuente
8
Aah, me duelen los ojos. Esto no es mucho mejor que JDBC, ¿verdad?
Lukas Eder
Jaja, sí, punto tomado. Sin embargo, el beneficio de usar estas cosas es que no tiene que escribir una carga de código para obtener la clase de objeto de datos y no tiene que hacer el bit en el que transfiere todos los datos del conjunto de registros a su clase de datos . Todavía hay un objeto de datos (Entidad), pero el asistente de Eclipse lo genera por usted.
Malcolm Boekhoff
1
Si, podrías. Pero lo digo como desarrollador de jOOQ , donde se genera todo. Lo único que queda por hacer es llamar al procedimiento / función.
Lukas Eder
¿De verdad probaste el ejemplo inferior (implementación independiente)? Lo probé con la diferencia de que el procedimiento estaba definido en un xmlarchivo y no funcionó. No puedo leer el OUTparámetro.
Roland
De alguna manera, para la implementación de JPA - 2.1, los parámetros con nombre no me funcionan. En cambio, tuve que pasar su índice de posición en los procedimientos almacenados y pude obtener con éxito el resultado del parámetro de salida. Este fue el caso cuando tengo un procedimiento almacenado que devuelve múltiples conjuntos de resultados. Para el 1 ResultSet, simplemente usé @Query
Radhesh Khanna
7
  1. Para un procedimiento almacenado simple que usa parámetros IN / OUT como este

    CREATE OR REPLACE PROCEDURE count_comments (  
       postId IN NUMBER,  
       commentCount OUT NUMBER )  
    AS 
    BEGIN 
        SELECT COUNT(*) INTO commentCount  
        FROM post_comment  
        WHERE post_id = postId; 
    END;

    Puede llamarlo desde JPA de la siguiente manera:

    StoredProcedureQuery query = entityManager
        .createStoredProcedureQuery("count_comments")
        .registerStoredProcedureParameter(1, Long.class, 
            ParameterMode.IN)
        .registerStoredProcedureParameter(2, Long.class, 
            ParameterMode.OUT)
        .setParameter(1, 1L);
    
    query.execute();
    
    Long commentCount = (Long) query.getOutputParameterValue(2);
  2. Para un procedimiento almacenado que usa un SYS_REFCURSORparámetro OUT:

    CREATE OR REPLACE PROCEDURE post_comments ( 
       postId IN NUMBER, 
       postComments OUT SYS_REFCURSOR ) 
    AS 
    BEGIN
        OPEN postComments FOR
        SELECT *
        FROM post_comment 
        WHERE post_id = postId; 
    END;

    Puede llamarlo de la siguiente manera:

    StoredProcedureQuery query = entityManager
        .createStoredProcedureQuery("post_comments")
        .registerStoredProcedureParameter(1, Long.class, 
             ParameterMode.IN)
        .registerStoredProcedureParameter(2, Class.class, 
             ParameterMode.REF_CURSOR)
        .setParameter(1, 1L);
    
    query.execute();
    
    List<Object[]> postComments = query.getResultList();
  3. Para una función SQL que tiene el siguiente aspecto:

    CREATE OR REPLACE FUNCTION fn_count_comments ( 
        postId IN NUMBER ) 
        RETURN NUMBER 
    IS
        commentCount NUMBER; 
    BEGIN
        SELECT COUNT(*) INTO commentCount 
        FROM post_comment 
        WHERE post_id = postId; 
        RETURN( commentCount ); 
    END;

    Puedes llamarlo así:

    BigDecimal commentCount = (BigDecimal) entityManager
    .createNativeQuery(
        "SELECT fn_count_comments(:postId) FROM DUAL"
    )
    .setParameter("postId", 1L)
    .getSingleResult();

    Al menos cuando se usa Hibernate 4.xy 5.x porque el JPA StoredProcedureQueryno funciona para las FUNCIONES SQL.

Para obtener más detalles sobre cómo llamar a funciones y procedimientos almacenados cuando se usa JPA e Hibernate, consulte los siguientes artículos

Vlad Mihalcea
fuente
Seguí recibiendo el mensaje de error "número incorrecto o tipos de argumentos en la llamada a ...". Me di cuenta de que estaba llamando createNativeQuery. Cambié a createStoredProcedureQuery. Entonces, ¡voilá!
Ahmet
6

Para mí, solo lo siguiente funcionó con Oracle 11g y Glassfish 2.1 (Toplink):

Query query = entityManager.createNativeQuery("BEGIN PROCEDURE_NAME(); END;");
query.executeUpdate();

La variante con llaves resultó en ORA-00900.

Dmitry Chornyi
fuente
1
Funciona para mí en Oracle 11g, proveedor de JPA de hibernación.
David Mann
1
Esto nos sacó de un problema enorme. Estábamos usando java6, oracle11g, Jboss6, Hibernate. Gracias @Chornyi.
Abdullah Khan
6

Si usa EclipseLink, puede usar @NamedStoredProcedureQuery o StoreProcedureCall para ejecutar cualquier procedimiento almacenado, incluidos aquellos con parámetros de salida o cursores de salida. También está disponible el soporte para funciones almacenadas y tipos de datos PLSQL.

Véase http://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Stored_Procedures

James
fuente
1
¿Qué versión de EclipseLink tiene EntityManager.createNamedStoredProcedureQuery ()?
Mircea Ion
3

Lo siguiente funciona para mí:

Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();
roberto quevedo
fuente
Los parámetros OUT e INOUT no funcionarán con esta API. Ver en.wikibooks.org/wiki/Java_Persistence/…
Valentin Jacquemin
2

Puede que no sea lo mismo para Sql Srver, pero para las personas que usan oracle y eclipslink está funcionando para mí

ej: un procedimiento que tiene un parámetro IN (tipo CHAR) y dos parámetros OUT (NUMBER & VARCHAR)

en persistence.xml declare la unidad de persistencia:

<persistence-unit name="presistanceNameOfProc" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/DataSourceName</jta-data-source>
    <mapping-file>META-INF/eclipselink-orm.xml</mapping-file>
    <properties>
        <property name="eclipselink.logging.level" value="FINEST"/>
        <property name="eclipselink.logging.logger" value="DefaultLogger"/>
        <property name="eclipselink.weaving" value="static"/>
        <property name="eclipselink.ddl.table-creation-suffix" value="JPA_STORED_PROC" />
    </properties>
</persistence-unit>

y declarar la estructura del proceso en eclipselink-orm.xml

<?xml version="1.0" encoding="UTF-8"?><entity-mappings version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd">
<named-stored-procedure-query name="PERSIST_PROC_NAME" procedure-name="name_of_proc" returns-result-set="false">
    <parameter direction="IN" name="in_param_char" query-parameter="in_param_char" type="Character"/>
    <parameter direction="OUT" name="out_param_int" query-parameter="out_param_int" type="Integer"/>
    <parameter direction="OUT" name="out_param_varchar" query-parameter="out_param_varchar" type="String"/>
</named-stored-procedure-query>

en el código solo tienes que llamar a tu proc así:

try {
        final Query query = this.entityManager
                .createNamedQuery("PERSIST_PROC_NAME");
        query.setParameter("in_param_char", 'V'); 
        resultQuery = (Object[]) query.getSingleResult();

    } catch (final Exception ex) {
        LOGGER.log(ex);
        throw new TechnicalException(ex);
    }

para obtener los dos parámetros de salida:

Integer myInt = (Integer) resultQuery[0];
String myStr =  (String) resultQuery[1];
Réda
fuente
2

Esto funcionó para mí.

@Entity
@Table(name="acct")
@NamedNativeQueries({
 @NamedNativeQuery(callable=true, name="Account.findOne", query="call sp_get_acct(?), resultClass=Account.class)})
public class Account{
 // Code 
}

Nota: en el futuro, si decide usar la versión predeterminada de findOne, simplemente comente la anotación NamedNativeQueries y JPA cambiará a la predeterminada

Amit
fuente
Si quiero llamar al procedimiento dentro del paquete específico, ¿debo llamar de esta manera: llamar al {paquete}. {Procedimiento}?
Raju yourPepe
1

Esta respuesta puede ser útil si tiene un administrador de entidad

Tenía un procedimiento almacenado para crear el siguiente número y en el lado del servidor tengo un marco de costura.

Lado del cliente

 Object on = entityManager.createNativeQuery("EXEC getNextNmber").executeUpdate();
        log.info("New order id: " + on.toString());

Lado de la base de datos (servidor SQL) Tengo un procedimiento almacenado llamado getNextNmber

tosha Shah
fuente
executeUpdate () devuelve int. ¿Está seguro de que recibe una salida de sproc?
Constantine Gladky
1

Puedes usar @Query(value = "{call PROC_TEST()}", nativeQuery = true) en su repositorio. Esto funcionó para mí.

Atención: use '{' y '}' o de lo contrario no funcionará.

Everton Oliveira
fuente
1

JPA 2.0 no admite valores RETURN, solo llamadas.

Mi solución fue. Cree una FUNCIÓN que llame al PROCEDIMIENTO.

Entonces, dentro del código JAVA, ejecuta una CONSULTA NATIVA llamando a la FUNCIÓN de oracle.

Musashi Miyamoto
fuente
0

Para llamar al procedimiento almacenado, podemos usar Callable Statement en el paquete java.sql.

Vinoth
fuente
Gracias por su respuesta. Entonces, el sql para la declaración invocable será {? = llamar a getEmployeeDetails (?,?)} o es necesario especificar todos los parámetros de salida
user431514
0

Prueba este código:

return em.createNativeQuery("{call getEmployeeDetails(?,?)}",
                               EmployeeDetails.class)           
                               .setParameter(1, employeeId)
                               .setParameter(2, companyId).getResultList();
Patricio Domínguez
fuente
0

persistence.xml

 <persistence-unit name="PU2" transaction-type="RESOURCE_LOCAL">
<non-jta-data-source>jndi_ws2</non-jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties/>

codigo java

  String PERSISTENCE_UNIT_NAME = "PU2";
    EntityManagerFactory factory2;
    factory2 = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);

    EntityManager em2 = factory2.createEntityManager();
    boolean committed = false;
    try {

        try {
            StoredProcedureQuery storedProcedure = em2.createStoredProcedureQuery("PKCREATURNO.INSERTATURNO");
            // set parameters
            storedProcedure.registerStoredProcedureParameter("inuPKEMPRESA", BigDecimal.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("inuPKSERVICIO", BigDecimal.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("inuPKAREA", BigDecimal.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("isbCHSIGLA", String.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INUSINCALIFICACION", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INUTIMBRAR", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INUTRANSFERIDO", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("INTESTADO", BigInteger.class, ParameterMode.IN);
            storedProcedure.registerStoredProcedureParameter("inuContador", BigInteger.class, ParameterMode.OUT);

            BigDecimal inuPKEMPRESA = BigDecimal.valueOf(1);
            BigDecimal inuPKSERVICIO = BigDecimal.valueOf(5);
            BigDecimal inuPKAREA = BigDecimal.valueOf(23);
            String isbCHSIGLA = "";
            BigInteger INUSINCALIFICACION = BigInteger.ZERO;
            BigInteger INUTIMBRAR = BigInteger.ZERO;
            BigInteger INUTRANSFERIDO = BigInteger.ZERO;
            BigInteger INTESTADO = BigInteger.ZERO;
            BigInteger inuContador = BigInteger.ZERO;

            storedProcedure.setParameter("inuPKEMPRESA", inuPKEMPRESA);
            storedProcedure.setParameter("inuPKSERVICIO", inuPKSERVICIO);
            storedProcedure.setParameter("inuPKAREA", inuPKAREA);
            storedProcedure.setParameter("isbCHSIGLA", isbCHSIGLA);
            storedProcedure.setParameter("INUSINCALIFICACION", INUSINCALIFICACION);
            storedProcedure.setParameter("INUTIMBRAR", INUTIMBRAR);
            storedProcedure.setParameter("INUTRANSFERIDO", INUTRANSFERIDO);
            storedProcedure.setParameter("INTESTADO", INTESTADO);
            storedProcedure.setParameter("inuContador", inuContador);

            // execute SP
            storedProcedure.execute();
            // get result

            try {
                long _inuContador = (long) storedProcedure.getOutputParameterValue("inuContador");
                varCon = _inuContador + "";
            } catch (Exception e) {
            } 
        } finally {

        }
    } finally {
        em2.close();
    }
Elohim Julio Cesar C
fuente
4
por favor, no dude en agregar cualquier comentario a su respuesta (que no sea código puro).
ivan.mylyanyk
0

Desde JPA 2.1, JPA admite la llamada a procedimientos almacenados utilizando StoredProcedureQuery dinámico y @NamedStoredProcedureQuery declarativo.

H.Ostwal
fuente
-2

Mi solución fue. Cree una FUNCIÓN que llame al PROCEDIMIENTO.

Entonces, dentro del código JAVA, ejecuta una CONSULTA NATIVA llamando a la FUNCIÓN de oracle.

Musashi Miyamoto
fuente