Come chiamare una procedura memorizzata da Java e JPA


94

Sto scrivendo una semplice applicazione web per chiamare una stored procedure e recuperare alcuni dati. È un'applicazione molto semplice, che interagisce con il database del cliente. Passiamo l'ID del dipendente e l'ID dell'azienda e la procedura memorizzata restituirà i dettagli del dipendente.

L'applicazione Web non può aggiornare / eliminare i dati e utilizza SQL Server.

Sto distribuendo la mia applicazione web in Jboss AS. Devo usare JPA per accedere alla stored procedure o CallableStatement. Qualsiasi vantaggio nell'usare JPA in questo caso.

Inoltre, quale sarà l'istruzione sql per chiamare questa stored procedure. Non ho mai utilizzato procedure memorizzate prima e sto lottando con questo. Google non è stato di grande aiuto.

Ecco la procedura memorizzata:

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

Aggiornare:

Per chiunque altro abbia problemi a chiamare la procedura memorizzata utilizzando JPA .

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

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

Cose che ho notato:

  1. I nomi dei parametri non hanno funzionato per me, quindi prova a utilizzare l'indice dei parametri.
  2. Correggere l'istruzione sql {call sp_name(?,?)}invece di call sp_name(?,?)
  3. Se la stored procedure restituisce un set di risultati, anche se sai con una sola riga, getSingleResultnon funzionerà
  4. Passa un resultSetMappingnome o dettagli sulla classe di risultati

2
Non è possibile utilizzare parametri denominati nelle query native . I parametri denominati sono supportati solo per le query JPQL. (Se preferisci parametri con nome, potresti scrivere la tua classe per tradurre parametri con nome in parametri numerati.)
— Viliam Búr

Ho sempre usato parametri con nome con createNativeQueries e non ho mai avuto problemi. Ho appena dato un'occhiata al sistema attuale su cui ho lavorato e ci sono tonnellate di query native con parametri denominati. Puoi fornirci qualche riferimento per la tua affermazione? Il nostro set è JPA 2 e Hibernate 4+.
— Jaumzera

Risposte:


58

JPA 2.1 ora supporta la Stored Procedure, leggi il documento Java qui .

Esempio:

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");

Vedi esempio dettagliato qui .


23

Sto distribuendo la mia applicazione web in Jboss AS. Devo usare JPA per accedere alla stored procedure o CallableStatement. Qualsiasi vantaggio nell'usare JPA in questo caso.

Non è realmente supportato da JPA ma è fattibile . Tuttavia non andrei in questo modo:

  • usare JPA solo per mappare il risultato di una chiamata di procedura memorizzata in alcuni bean è davvero eccessivo,
  • soprattutto dato che JPA non è realmente appropriato per chiamare stored procedure (la sintassi sarà piuttosto prolissa).

Preferirei quindi considerare l'utilizzo di supporto Spring per l'accesso ai dati JDBC , o un mappatore dati come MyBatis o, data la semplicità della tua applicazione, JDBC grezzo e CallableStatement. In realtà, JDBC sarebbe probabilmente la mia scelta. Ecco un esempio di kickoff di base:

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

Riferimento


Come indicato nella risposta di seguito , è supportato - potresti voler modificare
— Mr_and_Mrs_D

10

È necessario passare i parametri alla stored procedure.

Dovrebbe funzionare in questo modo:

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

Aggiornare:

O forse non dovrebbe.

Nel libro EJB3 in azione , a pagina 383 si dice che JPA non supporta le procedure memorizzate (la pagina è solo un'anteprima, non si ottiene il testo completo, l'intero libro è disponibile come download in diversi posti, incluso questo , Non so se sia legale però).

Comunque, il testo è questo:

Procedure memorizzate per JPA e database

Se sei un grande fan di SQL, potresti essere disposto a sfruttare la potenza delle stored procedure del database. Sfortunatamente, JPA non supporta le stored procedure e devi dipendere da una funzione proprietaria del tuo provider di persistenza. Tuttavia, è possibile utilizzare semplici funzioni memorizzate (senza parametri out) con una query SQL nativa.


Ho provato a ricevere questo messaggio di errore: java.sql.SQLException: sintassi non corretta vicino a "@ P0".
— user431514

3
Dovrebbe essere "{call getEmployeeDetails (: employeeId,: companyId)}", per il server SQL deve avere le parentesi graffe.
— Vedran

@Vedran vero. Ero interessato solo alla parte dell'impostazione dei parametri
— Sean Patrick Floyd

9

Come recuperare il parametro di output della stored procedure utilizzando JPA (2.0 richiede le importazioni di EclipseLink e 2.1 no)

Anche se questa risposta è elaborata sulla restituzione di un recordset da una procedura memorizzata, sto postando qui, perché mi ci sono voluti anni per capirlo e questo thread mi ha aiutato.

La mia applicazione utilizzava Eclipselink-2.3.1, ma forzerò un aggiornamento a Eclipselink-2.5.0, poiché JPA 2.1 ha un supporto molto migliore per le procedure memorizzate.

Utilizzo di EclipseLink-2.3.1 / JPA-2.0: dipendente dall'implementazione

Questo metodo richiede l'importazione di classi EclipseLink da "org.eclipse.persistence", quindi è specifico per l'implementazione di Eclipselink.

L'ho trovato in " 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

Utilizzo di EclipseLink-2.5.0 / JPA-2.1: indipendente dall'implementazione (documentato già in questo thread)

Questo metodo è indipendente dall'implementazione (non è necessario importare 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"));

8
Aah, i miei occhi fanno male. Questo non è molto meglio di JDBC, vero?
— Lukas Eder

Haha, sì, punto preso. Tuttavia il vantaggio nell'usare queste cose è che non devi digitare un carico di codice per ottenere la classe dell'oggetto dati e non devi fare la parte in cui trasferisci tutti i dati dal recordSet alla tua classe dati . C'è ancora un oggetto dati (Entità), ma la procedura guidata di Eclipse lo genera per te.
— Malcolm Boekhoff

1
Sì, potresti. Ma lo dico come sviluppatore di jOOQ , dove tutto viene generato. L'unica cosa che resta da fare è chiamare effettivamente la procedura / funzione.
— Lukas Eder

Hai effettivamente provato l'esempio in basso (indipendente dall'implementazione)? L'ho provato con la differenza che la procedura era definita in un xmlfile e non ha funzionato. Non riesco a leggere il OUTparametro.
— Roland

6

Per me, solo quanto segue ha funzionato con Oracle 11g e Glassfish 2.1 (Toplink):

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

La variante con le parentesi graffe ha prodotto ORA-00900.


1
Funziona per me su Oracle 11g, provider JPA di ibernazione.
— David Mann

1
Questo ci ha tirato fuori da un problema enorme. Stavamo usando java6, oracle11g, Jboss6, Hibernate. Grazie @Chornyi.
— Abdullah Khan


6
  1. Per una semplice procedura memorizzata che utilizza parametri IN / OUT come questo

    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;

    Puoi chiamarlo da JPA come segue:

    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. Per una procedura memorizzata che utilizza un SYS_REFCURSORparametro 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;

    Puoi chiamarlo come segue:

    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. Per una funzione SQL che ha il seguente aspetto:

    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;

    Puoi chiamarlo così:

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

    Almeno quando si utilizza Hibernate 4.x e 5.x perché JPA StoredProcedureQuerynon funziona per le FUNZIONI SQL.

Per ulteriori dettagli su come chiamare stored procedure e funzioni quando si utilizza JPA e Hibernate, consultare i seguenti articoli


Continuavo a ricevere il messaggio di errore "numero o tipi di argomenti errati nella chiamata a ...". Ho capito che stavo chiamando createNativeQuery. Sono passato a createStoredProcedureQuery. Quindi, voilà!
— Ahmet


2

Potrebbe non essere lo stesso per Sql Srver ma per le persone che usano oracle ed eclipslink funziona per me

es: una procedura che ha un parametro IN (tipo CHAR) e due parametri OUT (NUMBER e VARCHAR)

nel persistence.xml dichiarare l'unità di persistenza:

<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>

e dichiarare la struttura del proc in 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>

nel codice devi solo chiamare il tuo proc in questo modo:

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);
    }

per ottenere i due parametri di output:

Integer myInt = (Integer) resultQuery[0];
String myStr =  (String) resultQuery[1];

2

Questo ha funzionato per me.

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

Nota: in futuro se decidi di utilizzare la versione predefinita di findOne, commenta semplicemente l'annotazione NamedNativeQueries e JPA passerà all'impostazione predefinita


Se voglio chiamare procedure all'interno del pacchetto specifico, devo chiamare in questo modo: call {package}. {Procedure}?
— Raju yourPepe

1

Questa risposta potrebbe essere utile se hai un gestore entità

Avevo una procedura memorizzata per creare il numero successivo e sul lato server ho seam framework.

Dalla parte del cliente

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

Lato database (server SQL) Ho una procedura memorizzata denominata getNextNmber


executeUpdate () return int. Sei sicuro di un output di ricezione di sproc?
— Constantine Gladky

1

JPA 2.0 non supporta i valori RETURN, solo le chiamate.

La mia soluzione era. Creare una FUNZIONE chiamando PROCEDURA.

Quindi, all'interno del codice JAVA esegui una NATIVE QUERY chiamando l'oracolo FUNCTION.


0

Per chiamare la procedura memorizzata possiamo utilizzare l'istruzione Callable nel pacchetto java.sql.


Grazie per la tua risposta. Quindi lo sql per l'istruzione callable sarà {? = chiama getEmployeeDetails (?,?)} o è necessario specificare tutti i parametri di output
— user431514

0

Prova questo codice:

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

0

Puoi usare @Query(value = "{call PROC_TEST()}", nativeQuery = true)nel tuo repository. Questo ha funzionato per me.

Attenzione: usa "{" e "}" altrimenti non funzionerà.


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();
    }

4
per favore, non esitare ad aggiungere qualsiasi commento alla tua risposta (diverso dal puro codice).
— ivan.mylyanyk

0

Da JPA 2.1, JPA supporta la chiamata a stored procedure utilizzando StoredProcedureQuery dinamico e @NamedStoredProcedureQuery dichiarativo.


-2

La mia soluzione era. Creare una FUNZIONE chiamando PROCEDURA.

Quindi, all'interno del codice JAVA esegui una NATIVE QUERY chiamando l'oracolo FUNCTION.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.