Jak wywołać procedurę składowaną z języka Java i JPA


95

Piszę prostą aplikację internetową do wywoływania procedury składowanej i pobierania niektórych danych. Jest to bardzo prosta aplikacja, która współpracuje z bazą danych klienta. Przekazujemy identyfikator pracownika i identyfikator firmy, a procedura składowana zwróci dane pracownika.

Aplikacja internetowa nie może aktualizować / usuwać danych i używa SQL Server.

Wdrażam moją aplikację internetową w Jboss AS. Czy powinienem używać JPA, aby uzyskać dostęp do procedury składowanej lub CallableStatement. Wszelkie korzyści z używania JPA w tym przypadku.

Jaka będzie instrukcja sql, aby wywołać tę procedurę składowaną. Nigdy wcześniej nie korzystałem z procedur składowanych i zmagam się z tym. Google niewiele pomogło.

Oto procedura składowana:

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

Aktualizacja:

Dla każdego, kto ma problem z wywołaniem procedury składowanej przy użyciu JPA .

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

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

Rzeczy, które zauważyłem:

  1. Nazwy parametrów nie działały, więc spróbuj użyć indeksu parametrów.
  2. Prawidłowa instrukcja sql {call sp_name(?,?)}zamiast call sp_name(?,?)
  3. Jeśli procedura składowana zwraca zestaw wyników, nawet jeśli wiesz, że ma tylko jeden wiersz, getSingleResultnie zadziała
  4. Przekaż resultSetMappingnazwę lub szczegóły klasy wyników

2
Nie można używać nazwanych parametrów w zapytaniach natywnych . Nazwane parametry są obsługiwane tylko w przypadku zapytań JPQL. (Jeśli wolisz parametrów nazwanych, można napisać własną klasę tłumaczyć wybrany do ponumerowanych parametrów.)
Viliam BUR

Zawsze używałem nazwanych parametrów z createNativeQueries i nigdy nie miałem żadnego problemu. Właśnie przyjrzałem się obecnemu systemowi, nad którym pracowałem, i jest mnóstwo natywnych zapytań z nazwanymi parametrami. Czy możesz nam podać jakieś odniesienie do swojej afirmacji? Nasz zestaw to JPA 2 i Hibernate 4+.
Jaumzera

Odpowiedzi:


59

JPA 2.1 obsługuje teraz procedurę składowaną, przeczytaj dokumentację Java tutaj .

Przykład:

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

Zobacz szczegółowy przykład tutaj .


23

Wdrażam moją aplikację internetową w Jboss AS. Czy powinienem używać JPA, aby uzyskać dostęp do procedury składowanej lub CallableStatement. Wszelkie korzyści z używania JPA w tym przypadku.

Tak naprawdę nie jest obsługiwany przez JPA, ale jest wykonalny . Mimo to nie poszedłbym w ten sposób:

  • używanie JPA tylko do odwzorowania wyniku wywołania procedury składowanej w niektórych ziarnach jest naprawdę przesadą,
  • zwłaszcza biorąc pod uwagę, że JPA nie nadaje się do wywoływania procedury składowanej (składnia będzie dość rozwlekła).

Dlatego wolałbym rozważyć użycie obsługi Springa dla dostępu do danych JDBC lub mapera danych, takiego jak MyBatis lub, biorąc pod uwagę prostotę aplikacji, surowe JDBC i CallableStatement. Właściwie JDBC byłby prawdopodobnie moim wyborem. Oto podstawowy przykład rozpoczęcia:

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

Odniesienie


Jak stwierdzono w odpowiedzi poniżej , jest obsługiwany - możesz chcieć edytować
Mr_and_Mrs_D

10

Musisz przekazać parametry do procedury składowanej.

Powinno działać tak:

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

Aktualizacja:

A może nie powinno.

W książce EJB3 w akcji na stronie 383 jest napisane, że JPA nie obsługuje procedur składowanych (strona jest tylko podglądem, nie otrzymujesz pełnego tekstu, cała książka jest dostępna do pobrania w kilku miejscach, w tym w tej , Nie wiem, czy jest to legalne).

W każdym razie tekst jest taki:

JPA i procedury składowane w bazie danych

Jeśli jesteś wielkim fanem SQL, możesz chcieć wykorzystać moc procedur składowanych w bazie danych. Niestety JPA nie obsługuje procedur składowanych i musisz polegać na zastrzeżonej funkcji swojego dostawcy trwałości. Można jednak używać prostych funkcji przechowywanych (bez parametrów wyjściowych) z natywnym zapytaniem SQL.


Próbowałem i otrzymałem ten komunikat o błędzie: java.sql.SQLException: Niepoprawna składnia w pobliżu „@ P0”.
user431514

3
Powinien to być „{call getEmployeeDetails (: workerId,: companyId)}”, dla serwera SQL musi mieć nawiasy klamrowe.
Vedran

@Vedran true. Interesowała mnie tylko część ustawień parametrów
Sean Patrick Floyd

9

Jak pobrać parametr wyjściowy procedury składowanej za pomocą JPA (2.0 wymaga importu EclipseLink, a 2.1 nie)

Mimo że ta odpowiedź zawiera szczegółowe informacje na temat zwracania zestawu rekordów z procedury składowanej, publikuję tutaj, ponieważ zajęło mi to wieki, a ten wątek mi pomógł.

Moja aplikacja korzystała z Eclipselink-2.3.1, ale wymuszę aktualizację do Eclipselink-2.5.0, ponieważ JPA 2.1 ma znacznie lepszą obsługę procedur składowanych.

Korzystanie z EclipseLink-2.3.1 / JPA-2.0: zależne od implementacji

Ta metoda wymaga importu klas EclipseLink z „org.eclipse.persistence”, więc jest specyficzna dla implementacji Eclipselink.

Znalazłem go pod adresemhttp://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

Korzystanie z EclipseLink-2.5.0 / JPA-2.1: niezależne od implementacji (udokumentowane już w tym wątku)

Ta metoda jest niezależna od implementacji (nie wymaga importu 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
Ach, bolą mnie oczy. To naprawdę nie jest o wiele lepsze niż JDBC, prawda?
Lukas Eder

Haha, tak, uwaga. Jednak zaletą korzystania z tych rzeczy jest to, że nie musisz wpisywać obciążenia kodu, aby uzyskać klasę obiektu danych i nie musisz robić bitu, w którym przenosisz wszystkie dane z zestawu rekordów do swojej klasy danych . Nadal istnieje obiekt danych (Entity), ale kreator Eclipse generuje go za Ciebie.
Malcolm Boekhoff,

1
Tak, mogłeś. Ale mówię to jako twórca jOOQ , w którym wszystko jest generowane. Jedyne, co pozostało, to faktyczne wywołanie procedury / funkcji.
Lukas Eder

Czy faktycznie wypróbowałeś dolny przykład (niezależny od implementacji)? Wypróbowałem to z tą różnicą, że procedura została zdefiniowana w xmlpliku i nie zadziałała. Nie mogę odczytać OUTparametru.
Roland

Jakoś dla implementacji JPA - 2.1, nazwane parametry nie działają dla mnie. Zamiast tego musiałem przekazać ich indeks pozycji w procedurach składowanych i udało mi się uzyskać wynik dla parametru wyjściowego. Tak było w przypadku, gdy mam procedurę składowaną zwracającą wiele zestawów wyników. Dla 1 zestawu wyników po prostu użyłem @Query
Radhesh Khanna

7
  1. Dla prostej procedury składowanej, która używa parametrów IN / OUT takich jak ten

    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;
    

    Możesz to nazwać z JPA w następujący sposób:

    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. W przypadku procedury składowanej, która używa SYS_REFCURSORparametru 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;
    

    Możesz to nazwać w następujący sposób:

    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. W przypadku funkcji SQL, która wygląda następująco:

    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;
    

    Możesz to nazwać tak:

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

    Przynajmniej przy korzystaniu z Hibernate 4.x i 5.x, ponieważ JPA StoredProcedureQuerynie działa w przypadku FUNKCJI SQL.

Aby uzyskać więcej informacji na temat wywoływania procedur składowanych i funkcji podczas korzystania z JPA i hibernacji, zapoznaj się z następującymi artykułami


Ciągle otrzymywałem komunikat o błędzie „zła liczba lub typy argumentów w wywołaniu ...”. Zrozumiałem, że dzwonię createNativeQuery. Przerzuciłem się na createStoredProcedureQuery. Następnie voila!
Ahmet

6

Dla mnie tylko następujące działały z Oracle 11g i Glassfish 2.1 (łącze górne):

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

Wariant z aparatami kręconymi zaowocował ORA-00900.


1
Działa dla mnie na Oracle 11g, dostawca hibernacji JPA.
David Mann,

1
To wyciągnęło nas z ogromnych kłopotów. Używaliśmy java6, oracle11g, Jboss6, Hibernate. Dzięki @Chornyi.
Abdullah Khan



2

Może to nie to samo dla Sql Srver, ale dla osób używających Oracle i Eclipslink to działa dla mnie

np. procedura, która ma jeden parametr IN (typ CHAR) i dwa parametry OUT (NUMBER i VARCHAR)

w pliku persistence.xml zadeklaruj jednostkę-trwałości:

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

i zadeklaruj strukturę proc w 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>

w kodzie wystarczy wywołać swój proc w ten sposób:

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

aby uzyskać dwa parametry wyjściowe:

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

2

To zadziałało dla mnie.

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

Uwaga: w przyszłości, jeśli zdecydujesz się użyć domyślnej wersji findOne, po prostu skomentuj adnotację NamedNativeQueries, a JPA przełączy się na domyślną


Jeśli chcę wywołać procedurę w ramach określonego pakietu, powinienem wywołać w ten sposób: call {pakiet}. {Procedura}?
Raju yourPepe

1

Ta odpowiedź może być pomocna, jeśli masz menedżera encji

Miałem procedurę składowaną, aby utworzyć następny numer, a po stronie serwera mam szkielet szwu.

Strona klienta

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

Strona bazy danych (serwer SQL) Mam procedurę składowaną o nazwie getNextNmber


executeUpdate () return int. Czy jesteś pewien, że otrzymujesz wyjście sproc?
Constantine Gladky

1

Możesz użyć @Query(value = "{call PROC_TEST()}", nativeQuery = true)w swoim repozytorium. To zadziałało dla mnie.

Uwaga: użyj '{' i '}', bo inaczej to nie zadziała.


1

JPA 2.0 nie obsługuje wartości RETURN, tylko wywołania.

Moje rozwiązanie było. Utwórz FUNKCJĘ wywołującą PROCEDURĘ.

Tak więc w kodzie JAVA wykonujesz NATIVE QUERY wywołując funkcję Oracle FUNCTION.


0

Aby wywołać procedurę składowaną, możemy użyć instrukcji Callable w pakiecie java.sql.


Dzięki za odpowiedź. Więc sql dla wywoływalnej instrukcji będzie wynosić {? = wywołaj getEmployeeDetails (?,?)} lub musisz określić wszystkie parametry wyjściowe
user431514

0

Wypróbuj ten kod:

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

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
proszę, nie wahaj się dodać komentarza do swojej odpowiedzi (poza czystym kodem).
ivan.mylyanyk

0

Od JPA 2.1, JPA obsługuje wywoływanie procedur składowanych przy użyciu dynamicznej StoredProcedureQuery i deklaratywnej @NamedStoredProcedureQuery.


-2

Moje rozwiązanie było. Utwórz FUNKCJĘ wywołującą PROCEDURĘ.

Tak więc w kodzie JAVA wykonujesz NATIVE QUERY wywołując funkcję Oracle FUNCTION.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.