如何从Java和JPA调用存储过程


94

我正在编写一个简单的Web应用程序以调用存储过程并检索一些数据。它是一个非常简单的应用程序,可以与客户的数据库进行交互。我们传递员工ID和公司ID,存储过程将返回员工详细信息。

Web应用程序无法更新/删除数据,并且正在使用SQL Server。

我正在Jboss AS中部署Web应用程序。我应该使用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查询支持命名参数。(如果您更喜欢命名参数,则可以编写自己的类以将命名转换为编号参数。)
ViliamBúr2013年

我一直使用带有createNativeQueries的命名参数,从来没有任何问题。我刚刚看过我正在使用的当前系统,并且有大量带有命名参数的本机查询。您可以为我们的确认提供一些参考吗?我们的套件是JPA 2和Hibernate 4+。
Jaumzera '16

Answers:


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中部署Web应用程序。我应该使用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 2014年

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'附近的语法不正确。
user431514 2010年

3
它应该是“ {call getEmployeeDetails(:employeeId,:companyId)}”,对于SQL Server,它必须有花括号。
Vedran

@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:与实现有关

此方法需要从“ org.eclipse.persistence”导入EclipseLink类,因此它特定于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好多少,不是吗?
卢卡斯·埃德

哈哈,是的。但是,使用这些东西的好处是不必键入代码即可获取数据对象类,也无需执行将所有数据从recordSet传输到数据类的工作。 。仍然有一个数据对象(实体),但是Eclipse向导会为您生成它。
Malcolm Boekhoff 2014年

1
是的,你可以。但是我是说这是jOOQ的开发人员,在那里一切都生成了。剩下要做的就是实际调用过程/函数。
卢卡斯·埃德

您是否实际尝试了下面的示例(与实现无关)?我尝试了一下,但过程是在xml文件,但没有用。我无法读取OUT参数。
罗兰

6

对我来说,只有以下几种才能与Oracle 11g和Glassfish 2.1(Toplink)一起使用:

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

带有花括号的变量产生了ORA-00900。


1
在休眠的JPA提供程序Oracle 11g上为我工作。
大卫·曼

1
这使我们摆脱了巨大的麻烦。我们正在使用java6,oracle11g,Jboss6,Hibernate。谢谢@Chornyi。
阿卜杜拉·汗


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_REFCURSOROUT参数的存储过程:

    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。然后,瞧!
Ahmet


2

对于Sql Srver来说可能不一样,但是对于使用oracle和eclipslink的人来说,它对我有用

例如:具有一个IN参数(CHAR类型)和两个OUT参数(NUMBER和VARCHAR)的过程

在persistence.xml中声明persistence-unit:

<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中声明proc的结构

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

在代码中,您只需要这样调用proc:

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将切换为默认版本


如果要在特定程序包中调用过程,是否应该以这种方式调用:调用{package}。{procedure}?
Raju yourPepe

1

如果您有实体经理,此答案可能会有所帮助

我有一个存储过程来创建下一个号码,而在服务器端,我有接缝框架。

客户端

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

数据库端(SQL Server)我已将存储过程命名为 getNextNmber


executeUpdate()返回int。确定您接收到sproc的输出?
君士坦丁·格拉迪基2013年

1

JPA 2.0不支持RETURN值,仅支持调用。

我的解决方案是。创建一个调用PROCEDURE的FUNCTION。

因此,在JAVA代码中,您可以执行一个调用oracle FUNCTION的NATIVE QUERY。


0

要调用存储过程,我们可以在java.sql包中使用Callable Statement。


感谢您的回复。因此,可调用语句的sql将是{?=调用getEmployeeDetails(?,?)}或需要指定所有输出参数
user431514 2010年



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
请不要为您的答案添加任何注释(纯代码除外)。
ivan.mylyanyk 2015年

0

从JPA 2.1开始,JPA支持使用动态StoredProcedureQuery和声明性@NamedStoredProcedureQuery调用存储过程。


-2

我的解决方案是。创建一个调用PROCEDURE的FUNCTION。

因此,在JAVA代码中,您可以执行一个调用oracle FUNCTION的NATIVE QUERY。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.