JPA Hibernate 调用 Postgres 函数 Void 返回 MappingException:

Posted

技术标签:

【中文标题】JPA Hibernate 调用 Postgres 函数 Void 返回 MappingException:【英文标题】:JPA Hibernate Call Postgres Function Void Return MappingException: 【发布时间】:2012-09-15 11:53:09 【问题描述】:

在尝试使用 JPA 创建本机查询调用 postgres 函数时,我遇到了一个问题:org.hibernate.MappingException: No Dialect mapping for JDBC type: 1111

我在启动单例中创建了一个 EJB 计时器,以每 6 小时运行一次 Postgres 函数。该函数返回 void 并检查过期记录、删除它们并更新一些状态。它不接受任何参数,并且返回 void。

如果我使用 PgAdmin 查询工具 (select function();) 调用 postgres 函数并返回 void,则它可以完美运行。

当我在 Glassfish 3.1.1 上部署应用程序时,出现异常并且无法部署。

这是(缩短的)堆栈跟踪:

WARNING: A system exception occurred during an invocation on EJB UserQueryBean method public void com.mysoftwareco.entity.utility.UserQueryBean.runRequestCleanup()
javax.ejb.TransactionRolledbackLocalException: Exception thrown from bean
...STACK TRACE BLAH BLAH BLAH ...
Caused by: javax.persistence.PersistenceException: org.hibernate.MappingException: No Dialect mapping for JDBC type: 1111

代码如下:

首先是 JPA 的东西:

public void runRequestCleanup() 
    String queryString = "SELECT a_function_that_hibernate_chokes_on()";
    Query query = em.createNativeQuery(queryString);
    Object result = query.getSingleResult();

这是单例调用它:

@Startup
@Singleton
public class RequestCleanupTimer 
    @Resource
    TimerService timerService;
    @EJB
    UserQueryBean queryBean;

    @PostConstruct
    @Schedule(hour = "*/6")
    void runCleanupTimer() 
        queryBean.runRequestCleanup();
    

以及功能:

CREATE OR REPLACE FUNCTION a_function_that_hibernate_chokes_on()
  RETURNS void AS
$BODY$
    DECLARE 
        var_field_id myTable.field_id%TYPE;
    BEGIN
        FOR var_field_id IN
                select field_id from myTable 
                where status = 'some status'
                and disposition = 'some disposition'
                and valid_through < now()
        LOOP
            BEGIN
                -- Do Stuff
            END;
        END LOOP;
    END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

【问题讨论】:

【参考方案1】:

这可能是一个 hack,但它对我有用并且非常简单。只需将查询更改为:

SELECT count(*) FROM your_function();

现在它返回一个正确的整数,hibernate 很高兴。

【讨论】:

好主意。我看到了另一个答案,我认为你的答案是最好的。 你也可以SELECT 1 FROM...【参考方案2】:

我已经受够了 JPA 的麻烦,试图让它运行一个存储过程。

我最终使用了带有预处理语句的 JDBC。我花了几个小时试图将方形钉插入圆孔后,在 15 分钟内完成了。我调用了我的持久性单元用来获取连接的同一个 jndi 数据源,创建了一个准备好的语句并在完成后关闭它。

因此,如果您需要从(现在主要是)JPA 应用程序运行存储过程(或 Postgres 函数),这对我有用:

@Stateless
@LocalBean
public class UserQueryBean implements Serializable 

    @Resource(mappedName="jdbc/DatabasePool") 
    private javax.sql.DataSource ds;

    ...

    public void runRequestCleanup() 

        String queryString = "SELECT cleanup_function_that_hibernateJPA_choked_on()";
        Connection conn = null;
        PreparedStatement statement = null;
        try 
            conn = ds.getConnection();
            statement = conn.prepareCall(queryString);
            statement.executeQuery();
         catch (SQLException ex) 
            Logger.getLogger(UserQueryBean.class.getName()).log(Level.SEVERE, null, ex);
        finally
            try 
                statement.close();
                conn.close();
             catch (SQLException ex) 
                Logger.getLogger(UserQueryBean.class.getName()).log(Level.SEVERE, null, ex);
            
        
        // bit of logging code here    
    
    ...

似乎有一个可怕的疏忽,忽略了 JPA 在服务器上运行函数或存储过程的简单能力;尤其是除了 void 或受影响的行数之外什么都不返回的。如果这是故意的......没有评论。

编辑:添加关闭连接。

【讨论】:

【参考方案3】:

对于本期的未来访问者,演员表也可以。也发布在this thread上

public void runRequestCleanup() 
    String queryString = "SELECT cast(a_function_that_hibernate_chokes_on() as text)";
    Query query = em.createNativeQuery(queryString);
    query.getSingleResult();

【讨论】:

【参考方案4】:

似乎是postgres存储过程返回void时出现问题。尝试更改返回类型以返回一些虚拟值,可能是字符串。这在我的情况下有效。

【讨论】:

我正在尝试调用一个返回 void 的 postgres 管理函数。 pg_advisory_xact_lock 就是我所说的。我该如何改变呢?【参考方案5】:

这是不久前发布的,但我遇到了类似的问题。如前所述,Hibernate 看起来对 void 过敏,并且每次都会尝试将您锁定为使用返回类型。从我的角度来看,这是一种很好的做法,因为您通常应该总是有一个返回值,至少可以指定它是否成功:抛出异常通常是滥用。

然而,Hibernate 确实提供了一种绕过它的限制的方法:org.hibernate.jdbc.Work

您可以轻松地在小班中重现所需要的内容:

class VoidProcedureWork implements Work 

    String query;

    private VoidProcedureWork(String sql) 
        query = sql;
    

    public static boolean execute(Session session, String sql) 
        try 
            // We assume that we will succeed or receive an exception.
            session.doWork(new VoidProcedureWork(sql));
            return true;
         catch (Throwable e) 
            // log exception
            return false;
        
    

    /** 
     * @see org.hibernate.jdbc.Work#execute(java.sql.Connection)
     */
    @Override
    public void execute(Connection connection) throws SQLException 
        Statement statement = null;
        try 
            statement = connection.createStatement();
            statement.execute(query);
         finally 
            if (statement != null)
                statement.close();
        
    

现在您可以随时使用

调用它

VoidProcedureWork.execute(hibernateSession, sqlQuery);

你会注意到关于这个类的两件事: 1)我不关闭连接。我离开它是因为我不知道谁打开了连接,如何以及为什么。事务中是否使用相同的连接?如果我关闭它之后有人使用它,它会崩溃吗?等等。我没有编写 Hibernate 代码,也没有使用 connection.open()。因此,我不会关闭它,并假设无论打开它都会关闭它。 2)它比OOP更程序化。我知道,但我很懒:使用 VoidProcedureWork.execute(session, sql) 比使用 new VoidProcedureWork(sql).execute(session) 更容易(而且更清晰 imo)。或者更糟糕的是:每次我想使用那个小技巧时重现执行代码(session.doWork(new VoidProcedureWork(sql)) 和异常处理)。

【讨论】:

【参考方案6】:

老兄!就像引用函数名一样简单。像这样:

public void runRequestCleanup() 
    String queryString = "SELECT \"a_function_that_hibernate_chokes_on\"()";
    Query query = em.createNativeQuery(queryString);
    Object result = query.getSingleResult();

【讨论】:

【参考方案7】:

将查询转换为:

SELECT cast(sqlFunction() as text)

例如:"SELECT cast(pushlogsToMainTable() as text)"

它对我有用。

【讨论】:

与@srex 在 2015 年的同一问题中的answer 相比,这没有提供额外的帮助

以上是关于JPA Hibernate 调用 Postgres 函数 Void 返回 MappingException:的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate - JPA 在不同的情况下生成具有相同名称的重复表

如何在 JPA 中使用 Postgres JSONB 数据类型?

Postgres - Spring Data JPA - 自动生成数据库和表

无法构建 Hibernate SessionFactory - spring data/jpa/hibernate 逆向工程

Spring Data JPA + Postgres - 无法使用一对一映射插入数据

Java Spring JPA Hibernate没有更新列