使用JPA和Hibernate调用存储过程的最佳方法 - Vlad Mihalcea

栏目: Hibernate · 发布时间: 6年前

内容简介:在本文中,您将学习使用JPA和Hibernate时调用存储过程的最佳方法,以便尽快释放底层JDBC资源。我决定写这篇文章,因为Hibernate处理存储过程的方式会导致

在本文中,您将学习使用JPA和Hibernate时调用存储过程的最佳方法,以便尽快释放底层JDBC资源。

我决定写这篇文章,因为Hibernate处理存储过程的方式会导致 ORA-01000: maximum open cursors exceeded Oracle 上出现问题,如本 Hibernate论坛帖子StackOverflow问题所述

存储过程调用如何与JPA和Hibernate一起使用

要使用JPA调用存储过程或数据库函数,可以使用 StoredProcedureQuery 如以下示例所示:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery(<font>"count_comments"</font><font>)
.registerStoredProcedureParameter(
    </font><font>"postId"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    </font><font>"commentCount"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.OUT
)
.setParameter(</font><font>"postId"</font><font>, 1L);

query.execute();
Long commentCount = (Long) query
.getOutputParameterValue(</font><font>"commentCount"</font><font>);
</font>

在幕后,StoredProcedureQuery接口通过特定于Hibernate 的接口进行扩展ProcedureCall,因此我们可以像这样重写前面的示例:

ProcedureCall query = session
.createStoredProcedureCall(<font>"count_comments"</font><font>);

query.registerParameter(
    </font><font>"postId"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.IN
)
.bindValue(1L);

query.registerParameter(
    </font><font>"commentCount"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.OUT
);

Long commentCount = (Long) call
.getOutputs()
.getOutputParameterValue(</font><font>"commentCount"</font><font>);
</font>

在执行Hibernate的ProcedureCall的JPA的StoredProcedureQuery或outputs().getCurrent()时,Hibernate执行以下操作:

请注意,JDBC CallableStatement已准备好并存储在关联的ProcedureOutputsImpl对象中。在调用getOutputParameterValue方法时,Hibernate将使用底层CallableStatement来获取OUT参数。

因此,CallableStatement即使在执行存储过程并获取OUT或REF_CURSOR参数之后,底层JDBC 仍保持打开状态。

现在,默认情况下,CallableStatement在当前正在运行的数据库事务结束时将关闭,这是通过调用commit或rollback实现。

测试时间

要验证此行为,请考虑以下测试用例:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery(<font>"count_comments"</font><font>)
.registerStoredProcedureParameter(
    </font><font>"postId"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    </font><font>"commentCount"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.OUT
)
.setParameter(</font><font>"postId"</font><font>, 1L);

query.execute();

Long commentCount = (Long) query
.getOutputParameterValue(</font><font>"commentCount"</font><font>);

assertEquals(Long.valueOf(2), commentCount);

ProcedureOutputs procedureOutputs = query
.unwrap(ProcedureOutputs.<b>class</b>);

CallableStatement callableStatement = ReflectionUtils
.getFieldValue(
    procedureOutputs, 
    </font><font>"callableStatement"</font><font>
);

assertFalse(callableStatement.isClosed());

procedureOutputs.release();

assertTrue(callableStatement.isClosed());
</font>

请注意,CallableStatement即使在调用execute或获取commentCount OUT参数后仍处于打开状态。只有调用完成后才释放ProcedureOutputs的对象时,CallableStatement也会封闭。

尽快关闭JDBC语句

因此,要CallableStatement尽快关闭JDBC ,在存储过程中获取所需的所有数据后调用release:

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery(<font>"count_comments"</font><font>)
.registerStoredProcedureParameter(
    </font><font>"postId"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    </font><font>"commentCount"</font><font>, 
    Long.<b>class</b>, 
    ParameterMode.OUT
)
.setParameter(</font><font>"postId"</font><font>, 1L);

<b>try</b> {
    query.execute();
    
    Long commentCount = (Long) query
    .getOutputParameterValue(</font><font>"commentCount"</font><font>);

    assertEquals(Long.valueOf(2), commentCount);
} <b>finally</b> {
    query.unwrap(ProcedureOutputs.<b>class</b>).release();
}

CallableStatement callableStatement = ReflectionUtils
.getFieldValue(
    query.unwrap(ProcedureOutputs.<b>class</b>), 
    </font><font>"callableStatement"</font><font>
);
assertTrue(callableStatement.isClosed());
</font>

在finally块中release的关联ProcedureOutputs对象上调用方法可确保CallableStatement无论存储过程调用的结果如何都关闭JDBC 。

现在,release手动调用有点乏味,所以我决定创建 HHH-13215 Jira问题,我将其集成到Hibernate ORM 6分支中。

因此,从Hibernate 6开始,您可以像这样重写前面的示例:

Long commentCount = doInJPA(entityManager -> {
    <b>try</b>(ProcedureCall query = entityManager
            .createStoredProcedureQuery(<font>"count_comments"</font><font>)
            .unwrap(ProcedureCall.<b>class</b>)) {
             
        <b>return</b> (Long) query
        .registerStoredProcedureParameter(
            </font><font>"postId"</font><font>,
            Long.<b>class</b>,
            ParameterMode.IN
        )
        .registerStoredProcedureParameter(
            </font><font>"commentCount"</font><font>,
            Long.<b>class</b>,
            ParameterMode.OUT
        )
        .setParameter(</font><font>"postId"</font><font>, 1L)
        .getOutputParameterValue(</font><font>"commentCount"</font><font>);
    }
});
</font>

好多了,对吧?

通过ProcedureCall扩展接口AutoClosable,我们可以使用try-with-resource Java语句,因此在解除分配JDBC资源时,调用数据库存储过程会更简洁,更直观。

结论

在CallableStatement使用JPA和Hibernate调用存储过程时,尽快释放底层JDBC 非常重要,否则,数据库游标将一直打开,直到提交或回滚当前事务为止。

因此,从Hibernate ORM 6开始,您应该使用try-finally块。同时,对于Hibernate 5和4,CallableStatement在完成获取所需的所有数据后,应该使用try-finally块关闭右侧。


以上所述就是小编给大家介绍的《使用JPA和Hibernate调用存储过程的最佳方法 - Vlad Mihalcea》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人人时代

人人时代

[美]克莱•舍基(Clay Shirky) / 胡泳、沈满琳 / 中国人民大学出版社 / 2012-8 / 49.90元

[内容简介] •一而再,再而三出现的公众事件,绝不仅是来自草根的随兴狂欢,而是在昭示着一种变革未来的力量之崛起!基于爱、正义、共同的喜好和经历,人和人可以超越传统社会的种种限制,灵活而有效地采用即时通信、移动电话、网络日志和维基百科等新的社会性工具联结起来,一起分享、合作乃至展开集体行动。人人时代已经到来。 •微软、诺基亚、宝洁、BBC、乐高、美国海军最推崇的咨询顾问,“互联网革命最伟......一起来看看 《人人时代》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具