簡體   English   中英

如何從 Java 和 JPA 調用存儲過程

[英]How to call a stored procedure from Java and JPA

我正在編寫一個簡單的 web 應用程序來調用存儲過程並檢索一些數據。 它是一個非常簡單的應用程序,它與客戶的數據庫進行交互。 我們傳遞員工 ID 和公司 ID,存儲過程將返回員工詳細信息。

Web 應用程序無法更新/刪除數據,並且正在使用 SQL 服務器。

我正在 Jboss AS 中部署我的 web 應用程序。 我應該使用 JPA 來訪問存儲過程還是CallableStatement 在這種情況下使用 JPA 的任何優勢。

還有什么是 sql 語句來調用這個存儲過程。 我以前從未使用過存儲過程,我正在努力解決這個問題。 谷歌沒有太大幫助。

這是存儲過程:

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名稱或結果 class 詳細信息

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

請參閱此處的詳細示例。

我正在 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();

參考

您需要將參數傳遞給存儲過程。

它應該像這樣工作:

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

更新:

或者也許它不應該。

在 Book EJB3 in Action 中,它在第 383 頁說, JPA 不支持存儲過程(頁面只是預覽,你沒有得到全文,整本書可以在幾個地方下載,包括這個,我不知道這是否合法)。

不管怎樣,正文是這樣的:

JPA 和數據庫存儲過程

如果您是 SQL 的忠實粉絲,您可能願意利用數據庫存儲過程的強大功能。 不幸的是,JPA 不支持存儲過程,您必須依賴持久性提供程序的專有功能。 但是,您可以在本機 SQL 查詢中使用簡單的存儲函數(不帶輸出參數)。

  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 FUNCTIONS。

有關在使用 JPA 和 Hibernate 時如何調用存儲過程和函數的更多詳細信息,請查看以下文章

如何使用 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"));

對我來說,只有以下內容適用於 Oracle 11g 和 Glassfish 2.1 (Toplink):

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

帶有花括號的變體導致 ORA-00900。

如果使用 EclipseLink,您可以使用 @NamedStoredProcedureQuery 或 StoreProcedureCall 來執行任何存儲過程,包括帶有輸出參數或輸出游標的存儲過程。 還提供對存儲函數和 PLSQL 數據類型的支持。

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

以下對我有用:

Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();

對於 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];

這對我有用。

@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 將切換到默認版本

如果您有實體管理器,此答案可能會有所幫助

我有一個存儲過程來創建下一個數字,在服務器端我有接縫框架。

客戶端

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

數據庫端(SQL 服務器)我有一個名為getNextNmber存儲過程

您可以在存儲庫中使用@Query(value = "{call PROC_TEST()}", nativeQuery = true) 這對我有用。

注意:使用 '{' 和 '}' 否則將不起作用。

JPA 2.0 不支持 RETURN 值,只支持調用。

我的解決方案是。 創建一個調用 PROCEDURE 的函數。

因此,在 JAVA 代碼中,您執行一個調用 oracle FUNCTION 的 NATIVE QUERY。

試試這個代碼:

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

持久化文件

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

爪哇爪哇

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

要調用存儲過程,我們可以使用 java.sql 包中的 Callable Statement。

從 JPA 2.1 開始,JPA 支持使用動態 StoredProcedureQuery 和聲明式 @NamedStoredProcedureQuery 調用存儲過程。

最簡單的方法是使用 JpaRepository

1- Create a stored procedure
CREATE PROCEDURE dbo.getEmployeeDetails
(
@employeeId         int,
@companyId          int
)  AS
BEGIN
 SELECT firstName,lastName,gender,address
 FROM employee et
 WHERE et.employeeId = @employeeId and et.companyId = @companyId
END


2- Create Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class EmployeeDetails {
    @Id
    private String firstName;
    private String lastName;
    private String gender;
    private String address;
 }


3- Create Repository
public interface EmployeeDetailsRepository extends 
JpaRepository<EmployeeDetails,String> {
@Query(value = "EXEC dbo.getEmployeeDetails @employeeId=:empId, 
                                          @companyId=:compId",nativeQuery =true)
List<EmployeeDetails> getEmployeeList(@Param("employeeId") Integer empId, 
                                      @Param("companyId") Integer compId);
}

4- create Controller
@CrossOrigin(origins = "*")
@RestController
@RequestMapping(value = "/api/employee")
public class EmployeeController {

@Autowired
private EmployeeDetailsRepository empRepo;

@GetMapping(value = "/details")
public ResponseEntity<List<EmployeeDetails>> getEmployeeDetails(@RequestParam 
            String empId, @RequestParam String compId) {
try {
   List<EmployeeDetails> result = empRepo.getEmployeeList(
                                Integer.valueOf(empId),Integer.valueOf(compId));
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }
    catch (Exception ex)
    {
        return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(null);
    }
}
}

如果您不太喜歡使用 JPA 或 JDBC 調用此特定過程,則可以使用jOOQ,這是一個第三方庫,可為您的所有存儲過程生成存根以簡化調用它們,並使調用類型安全。

調用返回未指定游標的過程

在您的特定情況下,該過程返回一個無類型、未聲明的 cursor (它可能返回多個游標和交錯更新計數)。 因此,您可以使用 jOOQ 調用這樣的過程:

GetEmployeeDetails proc = new GetEmployeeDetails();
proc.setEmployeeId(1);
proc.setCompanyId(2);
proc.execute(configuration);

// Iterate over potentially multiple results
for (Result<?> result : proc.getResults()) {

    // Print the first result set (your employee query)
    System.out.println(result);

    // Use your implicit knowledge of the content of the query
    // Without type safety
    for (Record record : result) {

        // All tables / columns are also generated
        System.out.println("First name: " + record.get(EMPLOYEE.FIRSTNAME));
        System.out.println("Last name: " + record.get(EMPLOYEE.LASTNAME));
        System.out.println("Gender: " + record.get(EMPLOYEE.GENDER));
        System.out.println("Address: " + record.get(EMPLOYEE.ADDRESS));
    }
}

使用實際表值 function,而不是

就個人而言,我不太喜歡一些 RDBMS(包括 SQL Server、MySQL)返回任意無類型游標的特性。 為什么不直接聲明結果類型? SQL 服務器具有強大的表值功能 例如,只需在此處使用此語法:

CREATE FUNCTION getEmployeeDetails (@employeeId int, @companyId int)
RETURNS TABLE
AS RETURN
  SELECT
    firstName,
    lastName,
    gender,
    address
  FROM employee et
  WHERE et.employeeId = @employeeId
  AND et.companyId = @companyId

現在,您的目錄中有與此 function 相關聯的完整類型信息,如果您仍在使用 jOOQ,代碼生成器將可以使用該信息,因此您可以像這樣調用 function:

for (GetEmployeeDetailsRecord record : ctx.selectFrom(getEmployeeDetails(1, 2))) {
    System.out.println("First name: " + record.getFirstName());
    System.out.println("Last name: " + record.getLastName());
    System.out.println("Gender: " + record.getGender());
    System.out.println("Address: " + record.getAddress());
}

免責聲明:我為 jOOQ 背后的公司工作

我正在編寫一個簡單的Web應用程序以調用存儲過程並檢索一些數據。 它是一個非常簡單的應用程序,可以與客戶的數據庫進行交互。 我們傳遞員工ID和公司ID,並且存儲過程將返回員工詳細信息。

Web應用程序無法更新/刪除數據,並且正在使用SQL Server。

我正在將Web應用程序部署在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名稱或結果類詳細信息

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM