如何从 Java 和 JPA 调用存储过程
Posted
技术标签:
【中文标题】如何从 Java 和 JPA 调用存储过程【英文标题】:How to call a stored procedure from Java and JPA 【发布时间】:2011-04-04 02:40:00 【问题描述】:我正在编写一个简单的 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();
我注意到的事情:
-
参数名称对我不起作用,因此请尝试使用参数索引。
更正sql语句
call sp_name(?,?)
而不是call sp_name(?,?)
如果存储过程返回一个结果集,即使您只知道一行,getSingleResult
也不起作用
传递resultSetMapping
名称或结果类详细信息
【问题讨论】:
您不能在 native 查询中使用命名参数。仅 JPQL 查询支持命名参数。 (如果您更喜欢命名参数,您可以编写自己的类来将命名参数转换为编号参数。) 我一直将命名参数与 createNativeQueries 一起使用,从未遇到任何问题。我刚刚查看了我一直在使用的当前系统,并且有大量带有命名参数的本机查询。您能为我们的肯定提供一些参考吗?我们的设置是 JPA 2 和 Hibernate 4+。 【参考方案1】:JPA 2.1 现在支持存储过程,请阅读 Java 文档 here。
例子:
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");
查看详细示例here。
【讨论】:
【参考方案2】:我正在 Jboss AS 中部署我的 Web 应用程序。我应该使用 JPA 来访问存储过程还是 CallableStatement。在这种情况下使用 JPA 的任何优势。
JPA 并不真正支持它,但它是 doable。我还是不会走这条路:
使用 JPA 只是在某些 bean 中映射存储过程调用的结果实在是大材小用, 尤其是考虑到 JPA 并不适合调用存储过程(语法会非常冗长)。因此,我宁愿考虑使用Spring support for JDBC data access,或者像MyBatis 这样的数据映射器,或者考虑到您的应用程序的简单性,使用原始JDBC 和CallableStatement
。实际上,JDBC 可能是我的选择。这是一个基本的启动示例:
CallableStatement cstmt = con.prepareCall("call getEmployeeDetails(?, ?)");
cstmt.setInt("employeeId", 123);
cstmt.setInt("companyId", 456);
ResultSet rs = cstmt.executeQuery();
参考
JDBC 文档:Java SE 6【讨论】:
如answer below 中所述,它受支持 - 您可能需要编辑【参考方案3】:您需要将参数传递给存储过程。
它应该像这样工作:
List result = em
.createNativeQuery("call getEmployeeDetails(:employeeId,:companyId)")
.setParameter("emplyoyeeId", 123L)
.setParameter("companyId", 456L)
.getResultList();
更新:
也许不应该。
在书EJB3 in Action中,它在第383页上写着JPA does not support stored procedures(页面只是预览,您没有获得全文,整本书可在多个地方下载,包括@987654323 @,我不知道这是否合法)。
反正正文是这样的:
JPA 和数据库存储过程
如果您是 SQL 的忠实粉丝,您可能会 愿意利用自己的力量 数据库存储过程。 不幸的是,JPA 不支持 存储过程,你必须 取决于的专有功能 您的持久性提供者。然而, 您可以使用简单的存储函数 (没有输出参数)与原生 SQL 查询。
【讨论】:
我试过并收到此错误消息:java.sql.SQLException: Incorrect syntax near '@P0'。 应该是“call getEmployeeDetails(:employeeId,:companyId)”,对于SQL server,它必须有大括号。 @Vedran 是的。我只对参数设置部分感兴趣【参考方案4】:对于像这样使用 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);
对于使用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();
对于如下所示的 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时如何调用存储过程和函数的更多细节,请查看以下文章
How to call Oracle stored procedures and functions with JPA and Hibernate How to call SQL Server stored procedures and functions with JPA and Hibernate How to call PostgreSQL functions (stored procedures) with JPA and Hibernate How to call mysql stored procedures and functions with JPA and Hibernate【讨论】:
我不断收到“调用...的参数数量或类型错误”错误消息。我意识到我在打电话给createNativeQuery
。我切换到createStoredProcedureQuery
。然后,瞧!【参考方案5】:
如何使用 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"));
【讨论】:
啊,我的眼睛受伤了。这并不比 JDBC 好多少,是吗? 哈哈,没错。然而,使用这些东西的好处是您不必输入大量代码来获取数据对象类,并且您不必将所有数据从 recordSet 传输到数据类中.仍然有一个数据对象(实体),但 Eclipse 向导会为您生成它。 是的,你可以。但我是以jOOQ 的开发者的身份说的,一切都是在这里生成的。剩下要做的就是实际调用过程/函数。 你真的尝试过底部的例子(独立于实现)吗?我尝试了它,不同之处在于该过程是在xml
文件中定义的,但它不起作用。我无法读取OUT
参数。
不知何故对于 JPA - 2.1 实现,命名参数对我不起作用。相反,我必须在存储过程中传递它们的位置索引,并成功地获得输出参数的结果。当我有存储过程返回多个结果集时就是这种情况。对于 1 ResultSet,我只使用了 @Query【参考方案6】:
对我来说,只有以下内容适用于 Oracle 11g 和 Glassfish 2.1 (Toplink):
Query query = entityManager.createNativeQuery("BEGIN PROCEDURE_NAME(); END;");
query.executeUpdate();
带有花括号的变体导致 ORA-00900。
【讨论】:
在 Oracle 11g 上为我工作,休眠 JPA 提供程序。 这让我们摆脱了巨大的麻烦。我们使用的是 java6、oracle11g、Jboss6、Hibernate。谢谢@Chornyi。【参考方案7】:如果使用 EclipseLink,您可以使用 @NamedStoredProcedureQuery 或 StoreProcedureCall 来执行任何存储过程,包括带有输出参数或输出游标的存储过程。还提供对存储函数和 PLSQL 数据类型的支持。
看, http://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Stored_Procedures
【讨论】:
哪个版本的 EclipseLink 有 EntityManager.createNamedStoredProcedureQuery()?【参考方案8】:以下对我有用:
Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();
【讨论】:
OUT 和 INOUT 参数在使用此 API 时不起作用。见en.wikibooks.org/wiki/Java_Persistence/…【参考方案9】:对于 Sql Srver 可能不一样,但对于使用 oracle 和 eclipslink 的人来说,它对我有用
例如:具有一个 IN 参数(CHAR 类型)和两个 OUT 参数(NUMBER 和 VARCHAR)的过程
在persistence.xml中声明持久化单元:
<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>
在代码中你只需要像这样调用你的过程:
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];
【讨论】:
【参考方案10】:这对我有用。
@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?【参考方案11】:如果您有实体经理,此答案可能会有所帮助
我有一个存储过程来创建下一个数字,并且在服务器端我有接缝框架。
客户端
Object on = entityManager.createNativeQuery("EXEC getNextNmber").executeUpdate();
log.info("New order id: " + on.toString());
数据库端(SQL server)我有一个名为getNextNmber
的存储过程
【讨论】:
executeUpdate() 返回整数。确定您收到 sproc 的输出吗?【参考方案12】:您可以在您的存储库中使用@Query(value = "call PROC_TEST()", nativeQuery = true)
。这对我有用。
注意:使用''和'',否则不起作用。
【讨论】:
【参考方案13】:JPA 2.0 不支持 RETURN 值,只支持调用。
我的解决方案是。创建一个调用 PROCEDURE 的 FUNCTION。
因此,在 JAVA 代码中,您执行一个调用 oracle FUNCTION 的 NATIVE QUERY。
【讨论】:
【参考方案14】:调用存储过程可以使用java.sql包中的Callable Statement。
【讨论】:
感谢您的回复。所以可调用语句的 sql 将是 ? = call getEmployeeDetails(?,?) 或者需要指定所有输出参数【参考方案15】:试试这个代码:
return em.createNativeQuery("call getEmployeeDetails(?,?)",
EmployeeDetails.class)
.setParameter(1, employeeId)
.setParameter(2, companyId).getResultList();
【讨论】:
【参考方案16】: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();
【讨论】:
请不要犹豫,为您的答案添加任何评论(纯代码除外)。【参考方案17】:从 JPA 2.1 开始,JPA 支持使用动态 StoredProcedureQuery 和声明性 @NamedStoredProcedureQuery 调用存储过程。
【讨论】:
以上是关于如何从 Java 和 JPA 调用存储过程的主要内容,如果未能解决你的问题,请参考以下文章