Como chamar um procedimento armazenado de Java e JPA


94

Estou escrevendo um aplicativo da Web simples para chamar um procedimento armazenado e recuperar alguns dados. É uma aplicação bastante simples, que interage com a base de dados do cliente. Passamos a id do funcionário e a id da empresa e o procedimento armazenado retornará os detalhes do funcionário.

O aplicativo Web não pode atualizar / excluir dados e está usando o SQL Server.

Estou implantando meu aplicativo da web em Jboss AS. Devo usar JPA para acessar o procedimento armazenado ou CallableStatement. Qualquer vantagem de usar JPA neste caso.

Além disso, qual será a instrução sql para chamar este procedimento armazenado. Nunca usei stored procedures antes e estou tendo dificuldades com este. O Google não ajudou muito.

Aqui está o procedimento armazenado:

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

Atualizar:

Para qualquer outra pessoa que tenha problemas para chamar o procedimento armazenado usando JPA .

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

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

Coisas que notei:

  1. Nomes de parâmetro não funcionaram para mim, então tente usar o índice de parâmetro.
  2. Instrução sql correta em {call sp_name(?,?)}vez de call sp_name(?,?)
  3. Se o procedimento armazenado estiver retornando um conjunto de resultados, mesmo se você souber com apenas uma linha, getSingleResultnão funcionará
  4. Passe um resultSetMappingnome ou detalhes de classe de resultado

2
Você não pode usar parâmetros nomeados em consultas nativas . Parâmetros nomeados são suportados apenas para consultas JPQL. (Se preferir parâmetros nomeados, você pode escrever sua própria classe para traduzir parâmetros nomeados para numerados.)
— Viliam Búr

Sempre usei parâmetros nomeados com createNativeQueries e nunca tive nenhum problema. Acabei de dar uma olhada no sistema atual em que estou trabalhando e há toneladas de consultas nativas com parâmetros nomeados. Você pode nos fornecer alguma referência para sua afirmação? Nosso conjunto é JPA 2 e Hibernate 4+.
— Jaumzera

Respostas:


58

JPA 2.1 agora oferece suporte a Stored Procedure, leia o documento Java aqui .

Exemplo:

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

Veja o exemplo detalhado aqui .


23

Estou implantando meu aplicativo da web em Jboss AS. Devo usar JPA para acessar o procedimento armazenado ou CallableStatement. Qualquer vantagem de usar JPA neste caso.

Não é realmente suportado por JPA, mas é factível . Mesmo assim, eu não iria desta forma:

  • usar JPA apenas para mapear o resultado de uma chamada de procedimento armazenado em alguns beans é realmente um exagero,
  • especialmente considerando que JPA não é realmente apropriado para chamar procedimentos armazenados (a sintaxe será bastante detalhada).

Portanto, prefiro usar o suporte Spring para acesso a dados JDBC , ou um mapeador de dados como MyBatis ou, dada a simplicidade de seu aplicativo, JDBC bruto e CallableStatement. Na verdade, JDBC provavelmente seria minha escolha. Aqui está um exemplo básico de lançamento:

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

Referência


Conforme declarado na resposta abaixo , é compatível - talvez você queira editar
— Mr_and_Mrs_D

10

Você precisa passar os parâmetros para o procedimento armazenado.

Deve funcionar assim:

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

Atualizar:

Ou talvez não devesse.

No Livro EJB3 em Ação , diz na página 383, que JPA não suporta stored procedures (a página é apenas uma prévia, você não obtém o texto completo, o livro inteiro está disponível para download em vários lugares incluindo este , Não sei se isso é legal).

Enfim, o texto é este:

JPA e procedimentos armazenados de banco de dados

Se você é um grande fã de SQL, pode estar disposto a explorar o poder dos procedimentos armazenados de banco de dados. Infelizmente, o JPA não oferece suporte a procedimentos armazenados e você precisa depender de um recurso proprietário de seu provedor de persistência. No entanto, você pode usar funções armazenadas simples (sem parâmetros de saída) com uma consulta SQL nativa.


Tentei e recebi esta mensagem de erro: java.sql.SQLException: Sintaxe incorreta próxima a '@ P0'.
— user431514

3
Deve ser "{call getEmployeeDetails (: employeeId,: companyId)}", para o servidor SQL deve ter chaves.
— Vedran

@Vedran true. Eu estava interessado apenas na parte de configuração de parâmetros
— Sean Patrick Floyd

9

Como recuperar o parâmetro de saída do Stored Procedure usando JPA (2.0 precisa de importações EclipseLink e 2.1 não)

Mesmo que esta resposta elabore sobre o retorno de um conjunto de registros de um procedimento armazenado, estou postando aqui, porque levei muito tempo para descobrir e este tópico me ajudou.

Meu aplicativo estava usando Eclipselink-2.3.1, mas irei forçar uma atualização para Eclipselink-2.5.0, já que JPA 2.1 tem suporte muito melhor para procedimentos armazenados.

Usando EclipseLink-2.3.1 / JPA-2.0: Dependente de Implementação

Este método requer importações de classes EclipseLink de "org.eclipse.persistence", portanto, é específico para a implementação Eclipselink.

Eu o encontrei em " 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

Usando EclipseLink-2.5.0 / JPA-2.1: Implementation-Independent (já documentado neste tópico)

Este método é independente da implementação (não precisa de importações do 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, meus olhos doem. Isso não é realmente muito melhor do que JDBC, não é?
— Lukas Eder de

Haha, sim, ponto assumido. No entanto, o benefício de usar essas coisas é que você não precisa digitar uma carga de código para obter a classe de objeto de dados e não precisa fazer a parte em que transfere todos os dados do recordSet para sua classe de dados . Ainda há um objeto de dados (Entidade), mas o assistente do Eclipse o gera para você.
— Malcolm Boekhoff

1
Sim você pode. Mas estou dizendo isso como desenvolvedor do jOOQ , onde tudo é gerado. A única coisa que falta fazer é realmente chamar o procedimento / função.
— Lukas Eder de

Você realmente tentou o exemplo inferior (independente da implementação)? Tentei com a diferença que o procedimento estava definido em um xmlarquivo e não funcionou. Não consigo ler o OUTparâmetro.
— Roland

6

Para mim, apenas o seguinte funcionou com Oracle 11g e Glassfish 2.1 (Toplink):

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

A variante com chaves resultou em ORA-00900.


1
Funciona para mim no Oracle 11g, provedor JPA de hibernação.
— David Mann,

1
Isso nos livrou de um problema extremamente grande. Estávamos usando java6, oracle11g, Jboss6, Hibernate. Obrigado @Chornyi.
— Abdullah Khan

6

Se estiver usando EclipseLink, você pode usar @NamedStoredProcedureQuery ou StoreProcedureCall para executar qualquer procedimento armazenado, incluindo aqueles com parâmetros de saída ou cursores de saída. Suporte para funções armazenadas e tipos de dados PLSQL também estão disponíveis.

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


1
Qual versão do EclipseLink tem EntityManager.createNamedStoredProcedureQuery ()?
— Mircea Ion

6
  1. Para um procedimento armazenado simples que usa parâmetros IN / OUT como este

    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;

    Você pode chamá-lo do JPA da seguinte maneira:

    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. Para um procedimento armazenado que usa um SYS_REFCURSORparâmetro 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;

    Você pode chamá-lo da seguinte maneira:

    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. Para uma função SQL semelhante a esta:

    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;

    Você pode chamá-lo assim:

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

    Ao menos ao usar Hibernate 4.xe 5.x porque o JPA StoredProcedureQuerynão funciona para FUNÇÕES SQL.

Para obter mais detalhes sobre como chamar procedimentos e funções armazenados ao usar JPA e Hibernate, consulte os seguintes artigos


Continuei recebendo a mensagem de erro "número ou tipo de argumento errado na chamada para ...". Eu percebi que estava ligando createNativeQuery. Eu mudei para createStoredProcedureQuery. Então, voila!
— Ahmet


2

Pode ser que não seja o mesmo para Sql Srver, mas para pessoas que usam oracle e eclipslink está funcionando para mim

ex: um procedimento que tem um parâmetro IN (tipo CHAR) e dois parâmetros OUT (NUMBER e VARCHAR)

no persistence.xml declare a unidade de persistência:

<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 declara a estrutura do proc no 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>

no código, você só precisa chamar seu proc assim:

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

para obter os dois parâmetros de saída:

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

2

Isso funcionou para mim.

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

Nota: no futuro, se você decidir usar a versão padrão de findOne, basta comentar a anotação NamedNativeQueries e o JPA mudará para o padrão


Se eu quiser chamar o procedimento dentro do pacote específico, devo chamar desta forma: chamar {pacote}. {Procedimento}?
— Raju yourPepe

1

Esta resposta pode ser útil se você tiver um gerente de entidade

Eu tinha um procedimento armazenado para criar o próximo número e no lado do servidor tenho estrutura de costura.

Lado do cliente

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

Lado do banco de dados (servidor SQL) Eu armazenei o procedimento denominado getNextNmber


executeUpdate () return int. Tem certeza de que está recebendo uma saída de sproc?
— Constantine Gladky

1

JPA 2.0 não suporta valores RETURN, apenas chamadas.

Minha solução foi. Crie uma FUNCTION chamando PROCEDURE.

Então, dentro do código JAVA você executa uma NATIVE QUERY chamando o oracle FUNCTION.


0

Para chamar o procedimento armazenado, podemos usar a instrução Callable no pacote java.sql.


Obrigado pela sua resposta. Portanto, o sql para declaração de chamada será {? = chamar getEmployeeDetails (?,?)} ou precisar especificar todos os parâmetros de saída
— user431514

0

Experimente este código:

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

0

Você pode usar @Query(value = "{call PROC_TEST()}", nativeQuery = true)em seu repositório. Isso funcionou para mim.

Atenção: use '{' e '}' ou então não funcionará.


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
por favor, não hesite em adicionar qualquer comentário à sua resposta (exceto código puro).
— ivan.mylyanyk

0

A partir do JPA 2.1, o JPA suporta a chamada de procedimentos armazenados usando StoredProcedureQuery dinâmico e o declarativo @NamedStoredProcedureQuery.


-2

Minha solução foi. Crie uma FUNCTION chamando PROCEDURE.

Então, dentro do código JAVA você executa uma NATIVE QUERY chamando o oracle FUNCTION.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.