So rufen Sie eine gespeicherte Prozedur von Java und JPA aus auf

94

Ich schreibe eine einfache Webanwendung, um eine gespeicherte Prozedur aufzurufen und einige Daten abzurufen. Es ist eine sehr einfache Anwendung, die mit der Datenbank des Kunden interagiert. Wir übergeben die Mitarbeiter- und Firmen-ID und die gespeicherte Prozedur gibt die Mitarbeiterdaten zurück.

Die Webanwendung kann keine Daten aktualisieren / löschen und verwendet SQL Server.

Ich stelle meine Webanwendung in Jboss AS bereit. Soll ich JPA verwenden, um auf die gespeicherte Prozedur zuzugreifen oder CallableStatement. Jeder Vorteil der Verwendung von JPA in diesem Fall.

Wie lautet auch die SQL-Anweisung zum Aufrufen dieser gespeicherten Prozedur? Ich habe noch nie gespeicherte Prozeduren verwendet und habe Probleme mit dieser. Google war keine große Hilfe.

Hier ist die gespeicherte Prozedur:

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

Aktualisieren:

Für alle anderen, die Probleme beim Aufrufen der gespeicherten Prozedur mit JPA haben .

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

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

Dinge, die mir aufgefallen sind:

  1. Parameternamen haben bei mir nicht funktioniert, versuchen Sie also, den Parameterindex zu verwenden.
  2. Korrigieren Sie die SQL-Anweisung {call sp_name(?,?)}anstelle von call sp_name(?,?)
  3. Wenn die gespeicherte Prozedur eine Ergebnismenge getSingleResultzurückgibt, funktioniert dies nicht , selbst wenn Sie nur eine Zeile kennen
  4. Übergeben Sie einen resultSetMappingNamen oder Details zur Ergebnisklasse
user431514
quelle
2
Sie können benannte Parameter nicht in nativen Abfragen verwenden. Benannte Parameter werden nur für JPQL-Abfragen unterstützt. (Wenn Sie benannte Parameter bevorzugen, können Sie Ihre eigene Klasse schreiben, um benannte in nummerierte Parameter zu übersetzen.)
Viliam Búr
Ich habe bei createNativeQueries immer benannte Parameter verwendet und hatte nie ein Problem. Ich habe mir gerade das aktuelle System angesehen, an dem ich gearbeitet habe, und es gibt Unmengen nativer Abfragen mit benannten Parametern. Können Sie uns eine Referenz für Ihre Bestätigung geben? Unser Set ist JPA 2 und Hibernate 4+.
Jaumzera

Antworten:

58

JPA 2.1 unterstützt jetzt gespeicherte Prozeduren. Lesen Sie das Java-Dokument hier .

Beispiel:

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

Siehe detailliertes Beispiel hier .

Pau Kiat Wee
quelle
23

Ich stelle meine Webanwendung in Jboss AS bereit. Sollte ich JPA verwenden, um auf die gespeicherte Prozedur oder CallableStatement zuzugreifen? Jeder Vorteil der Verwendung von JPA in diesem Fall.

Es wird von JPA nicht wirklich unterstützt, ist aber machbar . Trotzdem würde ich diesen Weg nicht gehen:

  • Die Verwendung von JPA nur zum Zuordnen des Ergebnisses eines Aufrufs einer gespeicherten Prozedur in einigen Beans ist wirklich übertrieben.
  • vor allem, weil JPA nicht wirklich geeignet ist, gespeicherte Prozeduren aufzurufen (die Syntax ist ziemlich ausführlich).

Ich würde daher lieber die Unterstützung von Spring für den JDBC-Datenzugriff oder einen Daten-Mapper wie MyBatis oder, angesichts der Einfachheit Ihrer Anwendung, Raw-JDBC und verwenden CallableStatement. Eigentlich wäre JDBC wahrscheinlich meine Wahl. Hier ist ein einfaches Kickoff-Beispiel:

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

Referenz

Pascal Thivent
quelle
Wie in der Antwort unten angegeben , wird es unterstützt - Sie möchten es vielleicht bearbeiten
Mr_and_Mrs_D
10

Sie müssen die Parameter an die gespeicherte Prozedur übergeben.

Es sollte so funktionieren:

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

Aktualisieren:

Oder vielleicht sollte es nicht.

Im Buch EJB3 in Aktion heißt es auf Seite 383, dass JPA gespeicherte Prozeduren nicht unterstützt (Seite ist nur eine Vorschau, Sie erhalten nicht den vollständigen Text, das gesamte Buch steht an mehreren Stellen als Download zur Verfügung, einschließlich dieser Ich weiß aber nicht, ob das legal ist.

Wie auch immer, der Text lautet wie folgt:

Gespeicherte JPA- und Datenbankprozeduren

Wenn Sie ein großer SQL-Fan sind, sind Sie möglicherweise bereit, die Leistungsfähigkeit gespeicherter Datenbankprozeduren zu nutzen. Leider unterstützt JPA keine gespeicherten Prozeduren und Sie müssen sich auf eine proprietäre Funktion Ihres Persistenzanbieters verlassen. Sie können jedoch einfache gespeicherte Funktionen (ohne Out-Parameter) mit einer nativen SQL-Abfrage verwenden.

Sean Patrick Floyd
quelle
Ich habe versucht, diese Fehlermeldung zu erhalten: java.sql.SQLException: Falsche Syntax in der Nähe von '@ P0'.
user431514
3
Es sollte "{call getEmployeeDetails (: employeeId ,: companyId)}" sein, für SQL Server muss es geschweifte Klammern haben.
Vedran
@Vedran wahr. Ich war nur an der Parametereinstellung interessiert
Sean Patrick Floyd
9

So rufen Sie den Ausgabeparameter für gespeicherte Prozeduren mit JPA ab (2.0 erfordert EclipseLink-Importe und 2.1 nicht)

Obwohl diese Antwort die Rückgabe eines Recordset von einer gespeicherten Prozedur ausführlich beschreibt, poste ich hier, da ich ewig gebraucht habe, um es herauszufinden, und dieser Thread mir geholfen hat.

Meine Anwendung verwendete Eclipselink-2.3.1, aber ich werde ein Upgrade auf Eclipselink-2.5.0 erzwingen, da JPA 2.1 gespeicherte Prozeduren viel besser unterstützt.

Verwenden von EclipseLink-2.3.1 / JPA-2.0: Implementierungsabhängig

Diese Methode erfordert den Import von EclipseLink-Klassen aus "org.eclipse.persistence" und ist daher spezifisch für die Eclipselink-Implementierung.

Ich fand es unter " 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

Verwenden von EclipseLink-2.5.0 / JPA-2.1: Implementierungsunabhängig (bereits in diesem Thread dokumentiert)

Diese Methode ist implementierungsunabhängig (keine Eclipslink-Importe erforderlich).

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
quelle
8
Aah, meine Augen tun weh. Das ist nicht wirklich viel besser als JDBC, oder?
Lukas Eder
Haha, ja Punkt genommen. Der Vorteil dieser Verwendung besteht jedoch darin, dass Sie keine Codeladung eingeben müssen, um die Datenobjektklasse abzurufen, und dass Sie nicht das Bit ausführen müssen, bei dem Sie alle Daten aus dem recordSet in Ihre Datenklasse übertragen . Es gibt noch ein Datenobjekt (Entität), aber der Eclipse-Assistent generiert es für Sie.
Malcolm Boekhoff
1
Ja du könntest. Aber ich sage das als Entwickler von jOOQ , wo alles generiert wird. Sie müssen nur noch die Prozedur / Funktion aufrufen.
Lukas Eder
Haben Sie tatsächlich das unterste Beispiel ausprobiert (implementierungsunabhängig)? Ich habe es mit dem Unterschied versucht, dass die Prozedur in einer xmlDatei definiert wurde und nicht funktioniert hat. Ich kann den OUTParameter nicht lesen .
Roland
6

Bei Oracle 11g und Glassfish 2.1 (Toplink) funktionierte nur Folgendes:

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

Die Variante mit geschweiften Klammern ergab ORA-00900.

Dmitry Chornyi
quelle
1
Funktioniert für mich unter Oracle 11g, JPA-Anbieter im Ruhezustand.
David Mann
1
Dies brachte uns aus einem extrem großen Ärger heraus. Wir haben Java6, Oracle11g, Jboss6, Hibernate verwendet. Danke @Chornyi.
Abdullah Khan
6

Wenn Sie EclipseLink verwenden, können Sie mit @NamedStoredProcedureQuery oder StoreProcedureCall alle gespeicherten Prozeduren ausführen, einschließlich solcher mit Ausgabeparametern oder Out-Cursorn. Unterstützung für gespeicherte Funktionen und PLSQL-Datentypen ist ebenfalls verfügbar.

Siehe http://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Stored_Procedures

James
quelle
1
Welche Version von EclipseLink verfügt über EntityManager.createNamedStoredProcedureQuery ()?
Mircea Ion
6
  1. Für eine einfache gespeicherte Prozedur, die solche IN / OUT-Parameter verwendet

    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;
    

    Sie können es von JPA wie folgt aufrufen:

    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. Für eine gespeicherte Prozedur, die einen SYS_REFCURSOROUT-Parameter verwendet:

    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;
    

    Sie können es wie folgt nennen:

    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. Für eine SQL-Funktion, die wie folgt aussieht:

    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;
    

    Sie können es so nennen:

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

    Zumindest bei Verwendung von Hibernate 4.x und 5.x, da der JPA StoredProcedureQueryfür SQL-FUNKTIONEN nicht funktioniert.

Weitere Informationen zum Aufrufen gespeicherter Prozeduren und Funktionen bei Verwendung von JPA und Hibernate finden Sie in den folgenden Artikeln

Vlad Mihalcea
quelle
Ich habe immer wieder die Fehlermeldung "Falsche Anzahl oder Arten von Argumenten beim Aufruf von ..." erhalten. Mir wurde klar, dass ich anrief createNativeQuery. Ich wechselte zu createStoredProcedureQuery. Dann voila!
Ahmet
3

Folgendes funktioniert für mich:

Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();
Roberto Quevedo
quelle
Die Parameter OUT und INOUT funktionieren mit dieser API nicht. Siehe en.wikibooks.org/wiki/Java_Persistence/…
Valentin Jacquemin
2

Vielleicht ist es nicht dasselbe für SQL Srver, aber für Leute, die Orakel und Eclipslink verwenden, funktioniert es für mich

Beispiel: Eine Prozedur mit einem IN-Parameter (Typ CHAR) und zwei OUT-Parametern (NUMBER & VARCHAR).

deklarieren Sie in der Datei persistence.xml die Persistenz-Einheit:

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

und deklarieren Sie die Struktur des Prozesses in der Datei 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>

Im Code müssen Sie Ihren Proc nur so aufrufen:

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

So erhalten Sie die beiden Ausgabeparameter:

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

Das hat bei mir funktioniert.

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

Hinweis: Wenn Sie sich in Zukunft für die Verwendung der Standardversion von findOne entscheiden, kommentieren Sie einfach die Annotation NamedNativeQueries, und JPA wechselt zur Standardversion

Amit
quelle
Wenn ich die Prozedur innerhalb des spezifischen Pakets aufrufen möchte, sollte ich folgendermaßen aufrufen: {package} aufrufen. {Procedure}?
Raju yourPepe
1

Diese Antwort kann hilfreich sein, wenn Sie einen Entitätsmanager haben

Ich hatte eine gespeicherte Prozedur, um die nächste Nummer zu erstellen, und auf der Serverseite habe ich ein Naht-Framework.

Client-Seite

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

Datenbankseite (SQL Server) Ich habe die Prozedur mit dem Namen gespeichert getNextNmber

tosha Shah
quelle
executeUpdate () return int. Sind Sie sicher, dass Sie eine empfangene Ausgabe von sproc?
Constantine Gladky
1

JPA 2.0 unterstützt keine RETURN-Werte, sondern nur Aufrufe.

Meine Lösung war. Erstellen Sie eine FUNKTION, die PROCEDURE aufruft.

Im JAVA-Code führen Sie also eine NATIVE QUERY aus, die das Orakel FUNCTION aufruft.

Musashi Miyamoto
quelle
0

Um die gespeicherte Prozedur aufzurufen, können wir die Callable-Anweisung im Paket java.sql verwenden.

Vinoth
quelle
Danke für deine Antwort. Die SQL für die aufrufbare Anweisung lautet also {? = Rufen Sie getEmployeeDetails (?,?)} auf oder müssen Sie alle Ausgabeparameter angeben
user431514
0

Versuchen Sie diesen Code:

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

Sie können @Query(value = "{call PROC_TEST()}", nativeQuery = true)in Ihrem Repository verwenden. Das hat bei mir funktioniert.

Achtung: benutze '{' und '}' oder es wird nicht funktionieren.

Everton Oliveira
quelle
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.
quelle
4
Bitte zögern Sie nicht, Ihrer Antwort einen Kommentar hinzuzufügen (außer reinem Code).
ivan.mylyanyk
0

Ab JPA 2.1 unterstützt JPA das Aufrufen gespeicherter Prozeduren mithilfe der dynamischen StoredProcedureQuery und der deklarativen @NamedStoredProcedureQuery.

H. Ostwal
quelle
-2

Meine Lösung war. Erstellen Sie eine FUNKTION, die PROCEDURE aufruft.

Im JAVA-Code führen Sie also eine NATIVE QUERY aus, die das Orakel FUNCTION aufruft.

Musashi Miyamoto
quelle