不能用Hibernate调用PostgreSQL的11个存储过程

Posted

技术标签:

【中文标题】不能用Hibernate调用PostgreSQL的11个存储过程【英文标题】:Can't call PostgreSQL's 11 Stored Procedure with Hibernate 【发布时间】:2019-07-10 22:43:27 【问题描述】:

PostgreSQL 11 现在支持存储过程,我正在尝试使用 Hibernate 5.3.7.FinalPostgresql 42.2.5 JDBC 驱动程序 调用一个。在 PostgreSQL 11 之前,我们有可以使用 JPA 的 @NamedStoredProcedure 调用的函数。但是,函数是用SELECT my_func(); 执行的,新的存储过程必须用CALL my_procedure(); 执行

我正在尝试执行以下简单的存储过程:

CREATE OR REPLACE PROCEDURE p_raise_wage_employee_older_than(operating_years 
int, raise int)
AS $$
    BEGIN
       UPDATE employees
       SET wage = wage + raise 
       WHERE EXTRACT(year FROM age(entrance_date)) >= operating_years;
    END $$
LANGUAGE plpgsql;

JPA 注释如下所示:

@NamedStoredProcedureQuery(name = "raiseWage", 
   procedureName = "p_raise_wage_employee_older_than", 
   parameters = 
        @StoredProcedureParameter(name = "operating_years", type = Integer.class, 
           mode = ParameterMode.IN),
        @StoredProcedureParameter(name = "raise", type = Integer.class, 
            mode = ParameterMode.IN)
)

我正在调用存储过程:

StoredProcedureQuery storedProcedureQuery = this.em.createNamedStoredProcedureQuery("raiseWage"); 
storedProcedureQuery.setParameter("operating_years", 20);
storedProcedureQuery.setParameter("raise", 1000);
storedProcedureQuery.execute();

我的日志如下所示:

Hibernate: call p_raise_wage_employee_older_than(?,?)
2019-02-17 11:07:41.290  WARN 11168 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: 42809
2019-02-17 11:07:41.291 ERROR 11168 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: p_raise_wage_employee_older_than(integer, integer) is a procedure
  Hinweis: To call a procedure, use CALL.
  Position: 15

第一个 Hibernate 日志表明 Hibernate 使用 call 来执行存储过程,但我收到了一个 SQL 异常,指出未使用 CALL。这是 Postgresql 11 之前的 Postgresql 方言中的错误吗?您可以将 FUNCTIONS 建模为 JPA 中的存储过程,因此使用了 SELECT 而不是 CALL

【问题讨论】:

你能使用一个基本的 JDBC CallableStatement 吗?建议您尝试一下,因为这可能是所有 JPA 提供程序都在使用的,所以它可以将问题隔离到不同之处 由于 pgJDBC 42.2.5 于 2018 年 8 月在 PostgreSQL 11(2018 年 10 月)之前发布,我怀疑其中是否支持 CALL 语法。您在日志中看到的call ... 语法是特定于 JDBC 的语法,它应该由 JDBC 驱动程序转换为适当的语法。您可能想发布此here,因为我在那里没有看到任何相关问题。 -- 目前,你可以使用“旧”方式(即用RETURNS VOID定义FUNCTIONs)。 【参考方案1】:

由于 pgJDBC 42.2.5 是在 PostgreSQL 11 版本(2018 年 10 月)之前(2018 年 8 月)发布的,我认为这目前是 PostgreSQL 本身的 JDBC 驱动程序中的一个问题。我在 GitHub 存储库中创建了一个issue。

对于解决方法,您可以将 STORED PROCEDURE 重写为 FUNCTION 并使用 @NamedStoredProcedureQuery 或直接与 JDBC CallableStatement 交互,例如:

Connection conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/", "postgres", "postgres");

CallableStatement callableStatement = conn.prepareCall("call f_raise_wage_employee_older_than(?,?)");
callableStatement.setInt(1, 20);
callableStatement.setInt(2, 500);
callableStatement.executeUpdate();

或使用EntityManager 执行本机查询:

this.em.createNativeQuery("CALL p_raise_wage_employee_older_than(1, 20)");

我会在收到 pgJDBC 维护者的答复后立即更新此答案。

更新:

这个话题已经在 Postgres 邮件列表 (https://www.postgresql.org/message-id/4285.1537201440%40sss.pgh.pa.us) 中讨论过,目前还没有解决方案。唯一的方法是将原生 SQL 查询传递给数据库或将STORED PROCEDURE 重写为FUNCTION

【讨论】:

42.2.6 还是有这个问题,你得到答案了吗? 我还没有查看此版本的发行说明【参考方案2】:

在 PostgreSQL 11 之后,PostgreSQL JDBC 驱动团队在 PostgreSQL 驱动版本 42.2.16 中引入了一个 ENUM 名称 EscapeSyntaxCallMode。所以我们可以在创建数据库连接或创建 DataSource 对象时使用这个枚举。这个枚举有 3 种类型的值:

    "func" - 当我们总是想调用函数时设置它。 "call" - 当我们总是想调用过程时设置它。 "callIfNoReturn" - 它检查调用函数/过程中的返回类型,如果返回类型存在,PostgreSQL 将其视为函数并将其作为函数方式调用。否则将其称为过程方式。所以在我的项目中,我使用了这个“callIfNoReturn”,因为我希望 PostgreSQL 自动检测我是在调用函数还是过程。

我已经给出了详细的答案以及要遵循的正确步骤:

Postgresql 11: Stored Procedure call error - To call a procedure, use CALL, Java

【讨论】:

非常感谢您抽出宝贵时间来添加此问题的答案

以上是关于不能用Hibernate调用PostgreSQL的11个存储过程的主要内容,如果未能解决你的问题,请参考以下文章

hibernate映射postgreSQL数据库中的表时,表名是大写的时候为啥hibernate不能映射实体

Hibernate 不使用 Spring boot 在 PostgreSql 中创建数据库

JOOQ快速上手(基于springboot 和 postgresql)

PostgreSQL 与 JDBC 或 Hibernate 用于使用 CSV 数据的 Web 应用程序

PostgreSQL中表名字段名大小写问题

Hibetnate+postgresql 初体验及demo