JPA Hibernate Call Postgres Function Void Return MappingException:

一曲冷凌霜 提交于 2019-11-30 12:06:54

This could be a hack, but it worked for me and is pretty simple. Just change the query to:

SELECT count(*) FROM your_function();

Now it returns a proper integer and hibernate is happy.

I had enough messing around with JPA trying to get it to run a stored procedure.

I ended up using JDBC with a prepared statement. I did it in 15 minutes after spending several fruitless hours trying to fit a square peg into a round hole. I called the same jndi datasource my persistence unit uses to get a connection, created a prepared statement and closed it when done.

So if you need to run a stored procedure (or Postgres function) from a (now mostly) JPA app, here is what worked for me:

@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    
    }
    ...
}

There seems to be a horrible oversight to leave out the simple ability to run a function or stored procedure on the server from JPA; especially one that doesn't return anything except void or the number of rows affected. And if it was deliberate ... no comment.

Edit: added close connection.

It seems that the problem occurs when the postgres stored procedure returns void. Try to change the return type to return some dummy value, perhaps a string. This worked in my case.

This was posted some time ago, but I had the similar issue. As stated, Hibernate looks allergic to void and will try to lock you into using the return type every time. From my point of view, this is a good practice as you should normally always have a return at least to specify if it succeeded: throwing exception is often abusive.

Yet, Hibernate DOES offer a way to bypass it's limitations: org.hibernate.jdbc.Work

You can easily reproduce what was required in a small class:

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

Now you can call it whenever you want by using

VoidProcedureWork.execute(hibernateSession, sqlQuery);

You will notice two things about this class: 1) I do NOT close the Connection. I leave it that way because I do not know who opened the Connection, how and why. Is the same connection used in a transaction? If I close it ant someone uses it after, will it crash? Etc. I did not code Hibernate and did not use connection.open(). Therefore, I do not close it and assume whatever opened it will close it too. 2) It is more procedural programming than OOP. I know, but I am lazy: it is easier (and clearer imo) to use VoidProcedureWork.execute(session, sql) than new VoidProcedureWork(sql).execute(session). Or even worst: reproduce the code of execute everytime I want to use that little trick (session.doWork(new VoidProcedureWork(sql)) with exception processing).

srex

For future visitors of this issue, a cast would have worked too. posted on this thread as well

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

Dude! It's as easy as quoting the function name. Like so:

public void runRequestCleanup() {
    String queryString = "SELECT \"a_function_that_hibernate_chokes_on\"()";
    Query query = em.createNativeQuery(queryString);
    Object result = query.getSingleResult();
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!