Як викликати збережену процедуру з Java та JPA


94

Я пишу просту веб-програму для виклику збереженої процедури та отримання деяких даних. Це дуже просте додаток, яке взаємодіє з базою даних клієнта. Ми передаємо ідентифікатор співробітника та ідентифікатор компанії, і збережена процедура поверне реквізити працівника

Веб-додаток не може оновлювати / видаляти дані та використовує SQL Server.

Я розгортаю свій веб-додаток в Jboss AS. Чи слід використовувати JPA для доступу до збереженої процедури або CallableStatement. Будь-яка перевага використання JPA в цьому випадку.

Також те, що буде оператором sql, щоб викликати цю збережену процедуру. Я ніколи раніше не використовував збережені процедури, і я борюся з цією. Google не дуже допомагав.

Ось збережена процедура:

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

Оновлення:

Для всіх, хто має проблеми з викликом збереженої процедури за допомогою JPA .

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

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

Що я помітив:

  1. Імена параметрів для мене не спрацювали, тому спробуйте скористатися індексом параметра.
  2. Правильний оператор sql {call sp_name(?,?)}замість call sp_name(?,?)
  3. Якщо збережена процедура повертає набір результатів, навіть якщо ви знаєте лише один рядок, getSingleResultробота не буде
  4. Передайте resultSetMappingім'я або дані про клас результатів

2
Ви не можете використовувати названі параметри в нативних запитах. Названі параметри підтримуються лише для запитів JPQL. (Якщо ви віддаєте перевагу названим параметрам, ви можете написати власний клас для перекладу іменованих на нумеровані параметри.)
Viliam Búr

Я завжди використовував іменовані параметри з createNativeQueries і ніколи не мав жодних проблем. Я щойно подивився на поточну систему, над якою працюю, і є багато власних запитів із названими параметрами. Чи можете ви надати нам якусь довідку для вашого підтвердження? Наш набір - JPA 2 та Hibernate 4+.
Jaumzera

Відповіді:


58

Тепер JPA 2.1 підтримує збережену процедуру, читайте документ Java тут .

Приклад:

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

Дивіться детальний приклад тут .


23

Я розгортаю свій веб-додаток в Jboss AS. Чи слід використовувати JPA для доступу до збереженої процедури або CallableStatement. Будь-яка перевага використання JPA в цьому випадку.

Це не дуже підтримується JPA, але це можливо . Все-таки я б не пішов цим шляхом:

  • використання JPA просто для відображення результату збереженого виклику процедури в деяких бобах справді є надмірним,
  • особливо зважаючи на те, що JPA не дуже доречно викликати збережену процедуру (синтаксис буде досить багатослівним).

Таким чином, я вважаю за краще скористатися підтримкою Spring для доступу до даних JDBC або картографічним даним, таким як MyBatis, або, враховуючи простоту вашого додатка, необроблений JDBC та CallableStatement. Власне, JDBC, мабуть, був би моїм вибором. Ось основний приклад старту:

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

Довідково


Як зазначено у відповіді нижче, це підтримується - ви можете редагувати
Mr_and_Mrs_D

10

Вам потрібно передати параметри до збереженої процедури.

Це має працювати так:

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

Оновлення:

А може, не повинно.

У книзі EJB3 в дії йдеться, що на сторінці 383 сказано, що JPA не підтримує збережені процедури (сторінка - це лише попередній перегляд, не ви отримуєте повний текст, вся книга доступна як завантаження в декількох місцях, включаючи цю , Я не знаю, чи це законно).

У будь-якому випадку текст такий:

Процедури, що зберігаються в JPA та в базі даних

Якщо ви великий шанувальник SQL, можливо, ви будете готові використовувати потужність процедур, що зберігаються в базі даних. На жаль, JPA не підтримує збережені процедури, і ви повинні залежати від фірмової функції постачальника наполегливості. Однак ви можете використовувати прості функції, що зберігаються (без параметрів), із власним SQL-запитом.


Я спробував отримати це повідомлення про помилку: java.sql.SQLException: Неправильний синтаксис біля "@ P0".
користувач431514

3
Це повинно бути "{call getEposleeeDetails (: служитель, ID, companyId)}", для сервера SQL він повинен мати фігурні дужки.
Ведран

@Vedran правда. Мене зацікавив лише частину налаштування параметрів
Шон Патрік Флойд

9

Як отримати вихідний параметр збереженої процедури за допомогою JPA (для імпорту потрібен 2.0 імпорту EclipseLink, а 2.1 - не)

Навіть незважаючи на те, що ця відповідь пояснює повернення набору записів із збереженої процедури, я розміщую тут повідомлення, оскільки це знадобилося мені років, щоб зрозуміти це, і ця тема допомогла мені.

У моєму додатку було використано Eclipselink-2.3.1, але я змушу оновити Eclipselink-2.5.0, оскільки JPA 2.1 має набагато кращу підтримку збережених процедур.

Використання EclipseLink-2.3.1 / JPA-2.0: Залежність від реалізації

Цей метод вимагає імпорту класів EclipseLink з "org.eclipse.persistence", тому він специфічний для реалізації Eclipselink.

Я знайшов це на " http://www.yenlo.nl/uk/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

Використання EclipseLink-2.5.0 / JPA-2.1: Незалежно від впровадження (задокументовано вже в цій темі)

Цей метод не залежить від реалізації (не потрібен імпорт 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
Аа, болять мені очі. Це насправді не набагато краще, ніж JDBC, чи не так?
Лукас Едер

Ха-ха, так взяти. Однак користь у використанні цих речей полягає в тому, що вам не потрібно вводити завантаження коду, щоб отримати клас об'єкта даних, і вам не потрібно робити біт, куди ви переносите всі дані з recordSet у свій клас даних . Є ще об'єкт даних (Entity), але майстер Eclipse генерує його для вас.
Малькольм Бокхофф

1
Так, ви могли. Але я говорю це як розробник jOOQ , де все генерується. Залишилося лише насправді викликати процедуру / функцію.
Лукас Едер

Ви насправді спробували нижній приклад (реалізація незалежна)? Я спробував це з тією різницею, що процедура була визначена у xmlфайлі і вона не працювала. Я не можу прочитати OUTпараметр.
Роланд

6

Для мене з Oracle 11g та Glassfish 2.1 (Toplink) працювали лише наступні:

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

Варіант із фігурними брекетами призвів до ORA-00900.


1
Для мене працює Oracle 11g, сплячий постачальник JPA.
Девід Манн

1
Це позбавило нас від надзвичайно величезної неприємності. Ми використовували java6, oracle11g, Jboss6, Hibernate. Дякую @Chornyi
Абдулла хан

6

Якщо ви використовуєте EclipseLink, ви можете використовувати @NamedStoredProcedureQuery або StoreProcedureCall для виконання будь-якої збереженої процедури, у тому числі з вихідними параметрами або вихідними курсорами. Також доступна підтримка збережених функцій та типів даних PLSQL.

Дивіться, http://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Stored_Procedures


1
Яка версія EclipseLink має EntityManager.createNamedStoredProcedureQuery ()?
Мірча Іон

6
  1. Для простої збереженої процедури, що використовує такі параметри IN / OUT

    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;

    Ви можете зателефонувати за допомогою JPA наступним чином:

    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. Для збереженої процедури, яка використовує SYS_REFCURSORпараметр 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;

    Ви можете назвати його наступним чином:

    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. Для функції SQL, яка виглядає так:

    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;

    Ви можете назвати це так:

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

    Принаймні при використанні Hibernate 4.x і 5.x, оскільки JPA StoredProcedureQueryне працює для SQL FUNCTIONS.

Детальніше про те, як викликати збережені процедури та функції при використанні JPA та Hibernate, перегляньте наступні статті


Я постійно отримував повідомлення про помилку "неправильна кількість або типи аргументів при виклику до ...". Я зрозумів, що дзвоню createNativeQuery. Я перейшов на createStoredProcedureQuery. Тоді, вуаля!
Ахмет


2

Можливо, це не те саме для Sql Srver, але для людей, що використовують Oracle і eclipslink, це працює для мене

Наприклад: процедура, що має один параметр IN (тип CHAR) та два параметри OUT (ЧИСЕЛЬ & VARCHAR)

у persistent.xml оголосити стійкість-одиниця:

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

і оголосити структуру проц в 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>

у коді потрібно просто зателефонувати вашому програмісту так:

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

щоб отримати два вихідних парами:

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

2

Це працювало для мене.

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

Примітка: у майбутньому, якщо ви вирішите використовувати версію за замовчуванням findOne, тоді просто прокоментуйте примітку NamedNativeQueries, і JPA перейде на стандартну


Якщо я хочу зателефонувати в певний пакет, я повинен зателефонувати таким чином: call {package}. {Процедура}?
Раджу вашПепе

1

Ця відповідь може бути корисною, якщо у вас є керівник юридичної особи

У мене була збережена процедура створення наступного номера, і на стороні сервера у мене є структура швів.

Сторона клієнта

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

Сторона бази даних (SQL-сервер) У мене збережена процедура з назвою getNextNmber


ExecuteUpdate () return int. Ви впевнені, що отримуєте вихідний відросток?
Костянтин Гладкий

1

JPA 2.0 не підтримує значення RETURN, а лише дзвінки.

Моє рішення було. Створіть функцію виклику ПРОЦЕДУРА.

Отже, всередині коду JAVA ви виконуєте ПРИРОДНИЙ ЗАПИТАННЯ, називаючи FUNCTION Oracle.


0

Для виклику збереженої процедури ми можемо використовувати заяву Callable у пакеті java.sql.


Спасибі за Вашу відповідь. Отже, sql для оператора, що викликається, буде {? = зателефонувати getE zaposleeeDetails (?,?)} або потрібно вказати всі вихідні параметри
user431514


0

Можна використовувати @Query(value = "{call PROC_TEST()}", nativeQuery = true) у своєму сховищі. Це працювало для мене.

Увага: використовуйте '{' і '}', інакше це не вийде.


0

стійкість.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/>

кодіго ява

  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
будь ласка, не соромтесь додати будь-який коментар до вашої відповіді (крім чистого коду).
ivan.mylyanyk

0

З JPA 2.1, JPA підтримує виклик збережених процедур за допомогою динамічного StoredProcedureQuery та декларативного @NamedStoredProcedureQuery.


-2

Моє рішення було. Створіть функцію виклику ПРОЦЕДУРА.

Отже, всередині коду JAVA ви виконуєте ПРИРОДНИЙ ЗАПИТАННЯ, називаючи FUNCTION Oracle.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.