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