Cách gọi một thủ tục được lưu trữ từ Java và JPA


94

Tôi đang viết một ứng dụng web đơn giản để gọi một thủ tục được lưu trữ và truy xuất một số dữ liệu. Nó là một ứng dụng rất đơn giản, tương tác với cơ sở dữ liệu của khách hàng. Chúng tôi chuyển id nhân viên và id công ty và quy trình được lưu trữ sẽ trả về thông tin chi tiết của nhân viên.

Ứng dụng web không thể cập nhật / xóa dữ liệu và đang sử dụng SQL Server.

Tôi đang triển khai ứng dụng web của mình trong Jboss AS. Tôi có nên sử dụng JPA để truy cập quy trình được lưu trữ hay khôngCallableStatement . Bất kỳ lợi thế nào của việc sử dụng JPA trong trường hợp này.

Ngoài ra câu lệnh sql sẽ gọi thủ tục được lưu trữ này là gì. Tôi chưa bao giờ sử dụng các thủ tục được lưu trữ trước đây và tôi đang đấu tranh với cái này. Google không giúp được gì nhiều.

Đây là quy trình được lưu trữ:

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

Cập nhật:

Đối với bất kỳ ai khác gặp sự cố khi gọi thủ tục được lưu trữ bằng JPA .

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

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

Những điều tôi đã nhận thấy:

  1. Tên tham số không hoạt động với tôi, vì vậy hãy thử sử dụng chỉ mục tham số.
  2. Câu lệnh sql đúng {call sp_name(?,?)}thay vì call sp_name(?,?)
  3. Nếu thủ tục được lưu trữ đang trả về một tập hợp kết quả, ngay cả khi bạn biết chỉ với một hàng, getSingleResultsẽ không hoạt động
  4. Chuyển resultSetMappingtên hoặc chi tiết lớp kết quả

2
Bạn không thể sử dụng các tham số đã đặt tên trong các truy vấn gốc . Các tham số được đặt tên chỉ được hỗ trợ cho các truy vấn JPQL. (Nếu bạn thích đặt tên thông số, bạn có thể viết lớp của riêng bạn để dịch tên các thông số được đánh số.)
Viliam Bur

Tôi đã luôn sử dụng các tham số được đặt tên với createNativeQueries và chưa bao giờ gặp vấn đề gì. Tôi vừa xem qua hệ thống hiện tại mà tôi đang làm việc và có rất nhiều truy vấn gốc với các tham số được đặt tên. Bạn có thể cung cấp cho chúng tôi một số tài liệu tham khảo cho khẳng định của bạn? Bộ của chúng tôi là JPA 2 và Hibernate 4+.
Jaumzera

Câu trả lời:


58

JPA 2.1 hiện hỗ trợ Thủ tục lưu trữ, hãy đọc tài liệu Java tại đây .

Thí dụ:

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

Xem ví dụ chi tiết tại đây .


23

Tôi đang triển khai ứng dụng web của mình trong Jboss AS. Tôi có nên sử dụng JPA để truy cập quy trình được lưu trữ hoặc CallableStatement không. Bất kỳ lợi thế nào của việc sử dụng JPA trong trường hợp này.

Nó không thực sự được hỗ trợ bởi JPA nhưng nó có thể làm được . Tôi vẫn không đi theo cách này:

  • sử dụng JPA chỉ để ánh xạ kết quả của một lệnh gọi thủ tục được lưu trữ trong một số bean thực sự là quá mức cần thiết,
  • đặc biệt là JPA không thực sự thích hợp để gọi thủ tục lưu trữ (cú pháp sẽ khá dài dòng).

Do đó, tôi muốn xem xét sử dụng hỗ trợ Spring để truy cập dữ liệu JDBC hoặc trình ánh xạ dữ liệu như MyBatis hoặc, dựa trên sự đơn giản của ứng dụng của bạn, JDBC thô và CallableStatement. Trên thực tế, JDBC có lẽ sẽ là lựa chọn của tôi. Đây là một ví dụ khởi động cơ bản:

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

Tài liệu tham khảo


Như đã nêu trong câu trả lời bên dưới, nó được hỗ trợ - bạn có thể muốn chỉnh sửa
Mr_and_Mrs_D

10

Bạn cần chuyển các tham số cho thủ tục được lưu trữ.

Nó sẽ hoạt động như thế này:

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

Cập nhật:

Hoặc có lẽ nó không nên.

Trong Sách EJB3 trong Hành động , nó nói ở trang 383, rằng JPA không hỗ trợ các thủ tục được lưu trữ (trang chỉ là bản xem trước, bạn không nhận được toàn bộ nội dung, toàn bộ sách có sẵn dưới dạng tải xuống ở một số nơi bao gồm cả trang này , Tôi không biết liệu điều này có hợp pháp hay không).

Dù sao, văn bản là thế này:

JPA và thủ tục lưu trữ cơ sở dữ liệu

Nếu bạn là người yêu thích SQL, bạn có thể sẵn sàng khai thác sức mạnh của các thủ tục được lưu trữ trong cơ sở dữ liệu. Thật không may, JPA không hỗ trợ các thủ tục được lưu trữ và bạn phải phụ thuộc vào một tính năng độc quyền của nhà cung cấp độ bền của bạn. Tuy nhiên, bạn có thể sử dụng các hàm được lưu trữ đơn giản (không có tham số) với một truy vấn SQL gốc.


Tôi đã thử và nhận được thông báo lỗi này: java.sql.SQLException: Cú pháp không chính xác gần '@ P0'.
user431514,

3
Nó phải là "{call getEustingeeDetails (: workerId,: companyId)}", đối với máy chủ SQL, nó phải có dấu ngoặc nhọn.
Vedran

@Vedran đúng. Tôi chỉ quan tâm đến phần cài đặt thông số
Sean Patrick Floyd

9

Cách truy xuất tham số đầu ra Thủ tục đã lưu bằng JPA (2.0 cần nhập EclipseLink và 2.1 thì không)

Mặc dù câu trả lời này giải thích chi tiết về việc trả lại một tập bản ghi từ một thủ tục được lưu trữ, tôi đang đăng ở đây, vì tôi đã mất nhiều thời gian để tìm ra nó và chủ đề này đã giúp tôi.

Ứng dụng của tôi đang sử dụng Eclipselink-2.3.1, nhưng tôi sẽ buộc nâng cấp lên Eclipselink-2.5.0, vì JPA 2.1 hỗ trợ tốt hơn nhiều cho các thủ tục được lưu trữ.

Sử dụng EclipseLink-2.3.1 / JPA-2.0: Triển khai-Phụ thuộc

Phương thức này yêu cầu nhập các lớp EclipseLink từ "org.eclipse.persistence", vì vậy nó dành riêng cho việc triển khai Eclipselink.

Tôi đã tìm thấy nó tại " http://www.yenlo.nl/vi/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

Sử dụng EclipseLink-2.5.0 / JPA-2.1: Thực thi-Độc lập (đã có tài liệu trong luồng này)

Phương pháp này độc lập với việc triển khai (không cần nhập 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, tôi đau mắt. Điều này không thực sự tốt hơn nhiều so với JDBC, phải không?
Lukas Eder

Haha, đúng vậy. Tuy nhiên, lợi ích của việc sử dụng những thứ này là bạn không phải nhập một đoạn mã để có được lớp đối tượng dữ liệu và bạn không phải thực hiện một chút khi chuyển tất cả dữ liệu từ recordSet vào lớp dữ liệu của mình . Vẫn còn một đối tượng dữ liệu (Thực thể), nhưng trình hướng dẫn Eclipse tạo nó cho bạn.
Malcolm Boekhoff

1
Đúng bạn có thể. Nhưng tôi đang nói điều này với tư cách là nhà phát triển của jOOQ , nơi mọi thứ được tạo ra. Việc còn lại chỉ cần làm là thực sự gọi thủ tục / hàm.
Lukas Eder

Bạn đã thực sự thử ví dụ dưới cùng (triển khai độc lập)? Tôi đã thử nó với sự khác biệt là thủ tục được xác định trong một xmltệp và nó không hoạt động. Tôi không thể đọc OUTthông số.
Roland

6

Đối với tôi, chỉ những điều sau đây hoạt động với Oracle 11g và Glassfish 2.1 (Toplink):

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

Biến thể có dấu ngoặc nhọn dẫn đến ORA-00900.


1
Hoạt động cho tôi trên Oracle 11g, nhà cung cấp JPA ngủ đông.
David Mann

1
Điều này đã giúp chúng tôi thoát khỏi một rắc rối cực kỳ lớn. Chúng tôi đang sử dụng java6, oracle11g, Jboss6, Hibernate. Cảm ơn @Chornyi.
Abdullah Khan

6

Nếu sử dụng EclipseLink, bạn có thể sử dụng @NamedStoredProcedureQuery hoặc StoreProcedureCall để thực thi bất kỳ thủ tục nào được lưu trữ, bao gồm các thủ tục có tham số đầu ra hoặc con trỏ ra. Hỗ trợ các hàm được lưu trữ và kiểu dữ liệu PLSQL cũng có sẵn.

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


1
Phiên bản nào của EclipseLink có EntityManager.createNamedStoredProcedureQuery ()?
Mircea Ion vào

6
  1. Đối với thủ tục lưu trữ đơn giản sử dụng các tham số IN / OUT như thế này

    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;

    Bạn có thể gọi nó từ JPA như sau:

    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. Đối với thủ tục được lưu trữ sử dụng SYS_REFCURSORtham số 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;

    Bạn có thể gọi nó như sau:

    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. Đối với một hàm SQL trông như sau:

    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;

    Bạn có thể gọi nó như thế này:

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

    Ít nhất là khi sử dụng Hibernate 4.x và 5.x vì JPA StoredProcedureQuerykhông hoạt động cho các CHỨC NĂNG SQL.

Để biết thêm chi tiết về cách gọi các thủ tục và hàm được lưu trữ khi sử dụng JPA và Hibernate, hãy xem các bài viết sau


Tôi tiếp tục nhận được thông báo lỗi "sai số hoặc loại đối số trong cuộc gọi tới ...". Tôi nhận ra rằng tôi đang gọi createNativeQuery. Tôi đã chuyển sang createStoredProcedureQuery. Vậy thì, thì đấy!
Ahmet


2

Có thể nó không giống với Sql Srver nhưng đối với những người sử dụng oracle và eclipslink, nó hoạt động với tôi

ví dụ: một thủ tục có một tham số IN (loại CHAR) và hai tham số OUT (NUMBER & VARCHAR)

trong Persence.xml khai báo đơn vị đo lường:

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

và khai báo cấu trúc của proc trong 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>

trong mã, bạn chỉ cần gọi proc của mình như thế này:

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

để có được hai thông số đầu ra:

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

2

Điều này đã làm việc cho tôi.

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

Lưu ý: trong tương lai nếu bạn quyết định sử dụng phiên bản mặc định của findOne thì chỉ cần nhận xét chú thích NamedNativeQueries và JPA sẽ chuyển sang mặc định


Nếu tôi muốn gọi thủ tục trong gói cụ thể, tôi có nên gọi theo cách này: gọi {package}. {Procedure} không?
Raju yourPepe

1

Câu trả lời này có thể hữu ích nếu bạn có người quản lý tổ chức

Tôi đã có một thủ tục được lưu trữ để tạo số tiếp theo và ở phía máy chủ, tôi có khuôn khổ đường nối.

Phía khách hàng

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

Phía cơ sở dữ liệu (máy chủ SQL) Tôi đã lưu trữ thủ tục có tên getNextNmber


executeUpdate () trả về int. Có chắc chắn rằng bạn là một đầu ra nhận được của cải?
Constantine Gladky

1

JPA 2.0 không hỗ trợ các giá trị RETURN, chỉ các cuộc gọi.

Giải pháp của tôi là. Tạo một THỦ TỤC gọi FUNCTION.

Vì vậy, bên trong mã JAVA, bạn thực thi MỘT CÂU HỎI TỰ NHIÊN gọi hàm oracle FUNCTION.


0

Để gọi thủ tục được lưu trữ, chúng ta có thể sử dụng Câu lệnh có thể gọi trong gói java.sql.


Cảm ơn vì đã trả lời. Vì vậy, câu lệnh sql for callable sẽ là {? = Gọi getEmployeeDetails} hoặc cần phải xác định tất cả các thông số đầu ra (,?)
user431514

0

Hãy thử mã này:

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

0

Bạn có thể sử dụng @Query(value = "{call PROC_TEST()}", nativeQuery = true)trong kho lưu trữ của mình. Điều này đã làm việc cho tôi.

Chú ý: sử dụng '{' và '}' nếu không nó sẽ không hoạt động.


0

Persence.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
xin vui lòng, đừng ngần ngại thêm bất kỳ nhận xét nào cho câu trả lời của bạn (ngoài mã thuần túy).
ivan.mylyanyk

0

Từ JPA 2.1, JPA hỗ trợ gọi các thủ tục được lưu trữ bằng cách sử dụng StoredProcedureQuery động và @NamedStoredProcedureQuery khai báo.


-2

Giải pháp của tôi là. Tạo một THỦ TỤC gọi FUNCTION.

Vì vậy, bên trong mã JAVA, bạn thực thi MỘT CÂU HỎI TỰ NHIÊN gọi hàm oracle FUNCTION.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.