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.