Как вызвать хранимую процедуру из Java и JPA

95

Я пишу простое веб-приложение для вызова хранимой процедуры и получения некоторых данных. Это очень простое приложение, которое взаимодействует с клиентской базой данных. Мы передаем идентификатор сотрудника и идентификатор компании, и хранимая процедура вернет данные о сотруднике.

Веб-приложение не может обновлять / удалять данные и использует 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имя или сведения о классе результата
user431514
источник
2
Вы не можете использовать именованные параметры в собственных запросах. Именованные параметры поддерживаются только для запросов JPQL. (Если вы предпочитаете именованные параметры, вы можете написать свой собственный класс для преобразования именованных параметров в нумерованные.)
Вилиам Бур,
Я всегда использовал именованные параметры с createNativeQueries, и у меня никогда не было проблем. Я только что взглянул на текущую систему, с которой работал, и есть множество собственных запросов с именованными параметрами. Не могли бы вы дать нам ссылку для вашего утверждения? Наш набор - это JPA 2 и Hibernate 4+.
Jaumzera

Ответы:

59

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 только для отображения результата вызова хранимой процедуры в некоторых bean-компонентах действительно излишне,
  • особенно учитывая, что 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 не поддерживает хранимые процедуры, и вам придется полагаться на проприетарную функцию вашего поставщика сохраняемости. Однако вы можете использовать простые хранимые функции (без параметров out) с собственным запросом SQL.

Шон Патрик Флойд
источник
Я попытался получить это сообщение об ошибке: java.sql.SQLException: неправильный синтаксис рядом с «@ P0».
user431514
3
Это должно быть "{call getEmployeeDetails (: employeeId,: 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/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

Использование 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, не так ли?
Лукас Эдер
Ха-ха, да, точка взята. Однако преимущество использования этих вещей заключается в том, что вам не нужно вводить нагрузку кода, чтобы получить класс объекта данных, и вам не нужно делать бит, в котором вы переносите все данные из набора записей в свой класс данных. . Есть еще объект данных (Entity), но мастер Eclipse генерирует его за вас.
Малкольм Бекхофф
1
Да, можно. Но я говорю это как разработчик jOOQ , где все генерируется. Осталось только вызвать процедуру / функцию.
Лукас Эдер
Вы действительно пробовали нижний пример (независимый от реализации)? Я пробовал это с той разницей, что процедура была определена в xmlфайле, и это не сработало. Я не могу прочитать OUTпараметр.
Роланд
Почему-то для реализации JPA - 2.1 именованные параметры у меня не работают. Вместо этого мне пришлось передать их индекс положения в хранимых процедурах, и мне удалось получить результат для выходного параметра. Так было, когда у меня была хранимая процедура, возвращающая несколько наборов результатов. Для 1 ResultSet я просто использовал @Query
Radhesh Khanna
7
  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.

Дополнительные сведения о том, как вызывать хранимые процедуры и функции при использовании JPA и Hibernate, см. В следующих статьях.

Влад Михалча
источник
Я все время получал сообщение об ошибке «Неправильное количество или типы аргументов при вызове ...». Я понял, что звоню createNativeQuery. Я перешел на createStoredProcedureQuery. Тогда вуаля!
Ahmet
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 ()?
Мирча Ион
3

Для меня работает следующее:

Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();
Роберто Кеведо
источник
Параметры OUT и INOUT не будут работать с этим API. См. En.wikibooks.org/wiki/Java_Persistence/…
Валентин Жакмен
2

Может быть, это не то же самое для Sql Srver, но для людей, использующих oracle и eclipslink, он работает для меня

Пример: процедура с одним параметром IN (тип CHAR) и двумя параметрами OUT (NUMBER и VARCHAR)

в файле persistence.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];
Réda
источник
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}. {Procedure}?
Raju yourPepe
1

Этот ответ может быть полезен, если у вас есть менеджер сущностей

У меня была хранимая процедура для создания следующего числа, а на стороне сервера - фреймворк.

Сторона клиента

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

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

Тоша Шах
источник
executeUpdate () return int. Уверены, что вы получаете вывод sproc?
Константин Гладкий
1

Вы можете использовать @Query(value = "{call PROC_TEST()}", nativeQuery = true)в своем репозитории. Это сработало для меня.

Внимание: используйте '{' и '}', иначе это не сработает.

Эвертон Оливейра
источник
1

JPA 2.0 не поддерживает значения RETURN, только вызовы.

Мое решение было. Создайте ФУНКЦИЮ, вызывающую ПРОЦЕДУРУ.

Итак, внутри кода JAVA вы выполняете НАТИВНЫЙ ЗАПРОС, вызывающий ФУНКЦИЮ оракула.

Мусаси Миямото
источник
0

Для вызова хранимой процедуры мы можем использовать Callable Statement в пакете java.sql.

Винот
источник
Спасибо за ваш ответ. Таким образом, sql для вызываемого оператора будет {? = call getEmployeeDetails (?,?)} или необходимо указать все выходные параметры
user431514
0

Попробуйте этот код:

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();
    }
Элохим Хулио Сезар C
источник
4
Пожалуйста, не стесняйтесь добавлять к своему ответу какие-либо комментарии (кроме чистого кода).
ivan.mylyanyk
0

Начиная с JPA 2.1, JPA поддерживает вызов хранимых процедур с использованием динамического StoredProcedureQuery и декларативного @NamedStoredProcedureQuery.

Х. Остваль
источник
-2

Мое решение было. Создайте ФУНКЦИЮ, вызывающую ПРОЦЕДУРУ.

Итак, внутри кода JAVA вы выполняете НАТИВНЫЙ ЗАПРОС, вызывающий ФУНКЦИЮ оракула.

Мусаси Миямото
источник