【问题标题】:How can we call a stored procedure with Hibernate and JPA?我们如何使用 Hibernate 和 JPA 调用存储过程?
【发布时间】:2011-05-30 07:38:17
【问题描述】:

我们如何使用 Hibernate 或 JPA 调用存储过程?

【问题讨论】:

标签: java database hibernate stored-procedures orm


【解决方案1】:

考虑以下仅返回基本返回值的存储过程:

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

如果存储过程返回 SYS_REFCURSOR:

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

如果要调用Oracle数据库函数:

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;

你不能使用StoredProcedureQuery,因为它不适用于Hibernate 5,所以你可以这样称呼它:

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

或使用普通 JDBC:

Session session = entityManager.unwrap( Session.class ); 

Integer commentCount = session.doReturningWork( connection -> {
    try (CallableStatement function = connection.prepareCall(
            "{ ? = call fn_count_comments(?) }" )) {
        function.registerOutParameter( 1, Types.INTEGER );
        function.setInt( 2, 1 );
        function.execute();
        return function.getInt( 1 );
    }
} );

有关更多详细信息,请查看以下文章:

【讨论】:

【解决方案2】:

要执行远程过程,请使用以下构造:

映射

<sql-query name="RP">   
    {call some_rp(:param1, :param2)}
</sql-query>

Java 代码

session.getNamedQuery("RP").setInteger("param1", 1).setInteger("param2", 2).executeUpdate();

【讨论】:

  • 此方法是否兼容两种 RDBM(Oracle 和 Sql Server)?
【解决方案3】:

从休眠中调用存储过程的一种方法

@NamedNativeQueries 注释中声明您的存储过程

//Stock.java

@NamedNativeQueries({
    @NamedNativeQuery(
    name = "callStockStoreProcedure",
    query = "CALL GetStocks(:stockCode)",
    resultClass = Stock.class
    )
})
@Entity
@Table(name = "stock")
public class Stock implements java.io.Serializable {

// Call it with getNamedQuery().

Query query = session.getNamedQuery("callStockStoreProcedure")
    .setParameter("stockCode", "7277");
List result = query.list();
for(int i=0; i<result.size(); i++){
    Stock stock = (Stock)result.get(i);
    System.out.println(stock.getStockCode());
}

这行得通

【讨论】:

    【解决方案4】:

    您可以执行以下操作

     Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    PreparedStatement st = session.connection().prepareStatement("{call procedureName(?, ?)}");
                    st.setString(1, formatter.format(parameter1));
                    st.setString(2, formatter.format(parameter2));
                    st.execute();
    tx.commit();
    

    请在需要的地方添加异常处理。

    【讨论】:

    • 您正在从休眠会话中检索连接,然后使用普通 JDBC。此代码未利用 Hibernate 支持。
    【解决方案5】:

    这是使用 Just IN 参数调用存储过程的完整解决方案 ---

    1) 创建存储过程以作用于一个表或一组表:

    CREATE OR REPLACE procedure insertHouseHello (
    house_date in timestamp,
    house_name in varchar2,
    house_number in number,
    house_value in float) 
    is
    begin
     insert into House("HOUSE_DATE","HOUSE_NAME","HOUSE_NUMBER","HOUSE_VALUE")
     values ( house_date, house_name,house_number,house_value);
     commit;
     end;
    

    2) 从 SQL 提示符执行存储过程以检查输入。当您也从 Java/Hibernate 调用该过程时,您应该会看到类似的结果:

    exec insertHouseHello(sysdate,'one',123,104); 
    

    3) 在 Java 代码中:

    log.info("Now trying to call the Stored Procedure*****************");
    Query exQuery = session.createSQLQuery("CALL " +
            "insertHouseHello(:timestmp,:hname,:hno,:hvalue)");
    exQuery.setParameter("timestmp", 
            new java.sql.Timestamp(Calendar.getInstance().getTime().getTime()));
    exQuery.setParameter("hname", 34);
    exQuery.setParameter("hno", 212);
    exQuery.setParameter("hvalue", 12);
    int exRows = exQuery.executeUpdate();
    log.info("Executed Rows from Stored Procedure****************"+exRows);
    

    4) 现在检查表中的结果,应该会相应更新:

    【讨论】:

      【解决方案6】:

      Hibernate 通过存储过程和函数提供对查询的支持。例如,如果我们有以下存储过程,

      CREATE OR REPLACE FUNCTION selectAllEmployments
      RETURN SYS_REFCURSOR
      AS
          st_cursor SYS_REFCURSOR;
      BEGIN
          OPEN st_cursor FOR
       SELECT EMPLOYEE, EMPLOYER,
       STARTDATE, ENDDATE,
       REGIONCODE, EID, VALUE, CURRENCY
       FROM EMPLOYMENT;
            RETURN  st_cursor;
       END;
      

      返回所有员工的列表。存储过程/函数必须返回一个结果集作为第一个输出参数才能使用 Hibernate。

      要在 Hibernate 中使用上述查询,您需要通过命名查询对其进行映射。

      <sql-query name="selectAllEmployees_SP" callable="true">
          <return alias="emp" class="Employment">
              <return-property name="employee" column="EMPLOYEE"/>
              <return-property name="employer" column="EMPLOYER"/>
              <return-property name="startDate" column="STARTDATE"/>
              <return-property name="endDate" column="ENDDATE"/>
              <return-property name="regionCode" column="REGIONCODE"/>
              <return-property name="id" column="EID"/>
              <return-property name="salary">
                  <return-column name="VALUE"/>
                  <return-column name="CURRENCY"/>
              </return-property>
          </return>
          { ? = call selectAllEmployments() }
      </sql-query>
      

      使用存储过程的规则/限制:

      • 无法使用 setFirstResult()/setMaxResults() 分页存储过程查询。
      • 推荐的调用形式为标准SQL92:{ ? = call functionName(&lt;parameters&gt;) }{ ? = call procedureName(&lt;parameters&gt;}。不支持本机调用语法。

      For Oracle the following rules apply:

      • 函数必须返回结果集。
      • 过程的第一个参数必须是返回结果集的 OUT。这是通过在 Oracle 9 或 10 中使用 SYS_REFCURSOR 类型来完成的。在 Oracle 中,您需要定义一个 REF CURSOR 类型。有关详细信息,请参阅 Oracle 文献。

      For Sybase or MS SQL server the following rules apply:

      • 该过程必须返回一个结果集。请注意,由于这些服务器可以返回多个结果集和更新计数,Hibernate 将迭代结果并将作为结果集的第一个结果作为其返回值。其他所有内容都将被丢弃。
      • 如果您可以在您的过程中启用 SET NOCOUNT ON,它可能会更有效,但这不是必需的。

      Source Ref.: From the official Hibernate docuementation.

      【讨论】:

        【解决方案7】:

        一种方法是使用 getNamedQuery()。

        Query query = session.getNamedQuery("callStockStoreProcedure")
            .setParameter("stockCode", "7277");
        List result = query.list();
        for(int i=0; i<result.size(); i++){
            Stock stock = (Stock)result.get(i);
            System.out.println(stock.getStockCode());
        }
        

        你必须映射或使用注释

        还有其他人:source

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-25
          • 1970-01-01
          • 1970-01-01
          • 2011-04-04
          相关资源
          最近更新 更多