【问题标题】:The JPA createStoredProcedureQuery throws "ORA-01000: maximum open cursors exceeded" when using HibernateJPA createStoredProcedureQuery 在使用 Hibernate 时抛出“ORA-01000: maximum open cursors exceeded”
【发布时间】:2018-10-23 07:49:15
【问题描述】:

我尝试通过这种方式使用EntityManager的“createStoredProcedureQuery”调用Oracle存储过程:

@Transactional(readOnly = false, propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public void saveMeterVol(Meter meter, Double vol1, Chng chng, User user, Date dt1, Date dt2) {
    StoredProcedureQuery qr = em.createStoredProcedureQuery("mt.P_METER.meter_vol_ins_upd_java");
    qr.registerStoredProcedureParameter(1, Integer.class, ParameterMode.OUT);
    qr.registerStoredProcedureParameter(2, Integer.class, ParameterMode.IN);
    qr.registerStoredProcedureParameter(3, Integer.class, ParameterMode.IN);
    qr.registerStoredProcedureParameter(4, Double.class, ParameterMode.IN);
    qr.registerStoredProcedureParameter(5, Date.class, ParameterMode.IN);
    qr.registerStoredProcedureParameter(6, Date.class, ParameterMode.IN);
    qr.registerStoredProcedureParameter(7, String.class, ParameterMode.IN);

    qr.setParameter(2, meter.getId());
    qr.setParameter(3, chng.getId());
    qr.setParameter(4, vol1);
    qr.setParameter(5, dt1);
    qr.setParameter(6, dt2);
    qr.setParameter(7, user.getCd());
    qr.execute();
}

当我调用此方法超过 300 次时,Oracle 陷入异常: ORA-01000: 超出最大打开游标数

据我了解,Java 在调用我的过程后不会关闭 Oracle 游标,但我不明白为什么?

我试过了

em.close();

但它没有帮助。

我用:

<spring-framework.version>5.0.5.RELEASE</spring-framework.version>
<hibernate.version>5.1.0.Final</hibernate.version>
<java.version>1.8</java.version>

【问题讨论】:

  • 尝试在您的数据库中打开更多游标ALTER SYSTEM SET OPEN_CURSORS=1000 SID='*' SCOPE=BOTH;,也请阅读此内容,也许它可以帮助您stackoverflow.com/questions/12192592/…
  • 这不是解决方案,根据您的链接:“游标泄漏是错误;增加数据库上的游标数量只会延迟不可避免的故障”如果我想调用我的方法,这只是临时解决方案超过1000次,它会再次陷入异常

标签: java oracle hibernate jpa stored-procedures


【解决方案1】:

默认CallableStatement处理机制

当在 JPA StoredProcedureQuery 上调用 execute 方法或在 Hibernate ProcedureCall 上调用 outputs().getCurrent() 时,Hibernate 执行以下操作:

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

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

现在,默认情况下,CallableStatement 在结束当前正在运行的数据库事务时关闭,方法是调用 commitrollback

尽快关闭JDBC语句

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

StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("count_comments")
.registerStoredProcedureParameter(
    "postId",
    Long.class,
    ParameterMode.IN
)
.registerStoredProcedureParameter(
    "commentCount",
    Long.class,
    ParameterMode.OUT
)
.setParameter("postId", 1L);
 
try {
    query.execute();
     
    Long commentCount = (Long) query
    .getOutputParameterValue("commentCount");
 
    assertEquals(Long.valueOf(2), commentCount);
} finally {
    query.unwrap(ProcedureOutputs.class).release();
}

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

休眠 6 起

现在,手动调用 release 有点乏味,所以我决定创建 HHH-13215 Jira 问题,从 Hibernate ORM 6 开始,您可以像这样重写前面的示例:

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

【讨论】:

  • 嗨弗拉德,我读了你的文章。但是当将它实施到应用程序时,我总是在警告 Connection oracle.jdbc.driver.T4CConnection@704e89f9 marked as broken because of SQLSTATE(08003), ErrorCode(17009) 之后得到 java.sql.SQLRecoverableException: Closed Statement 。我搜索了谷歌,但找不到任何解决方案。如果您能给我一些信息来解决我的问题,那就太好了。谢谢
【解决方案2】:

这是我的解决方案:

int           retValue = -1;
ProcedureCall query    = null;

try {
  query = (ProcedureCall)entityManager.createNamedStoredProcedureQuery("MyStoredProcedure");

  query
    .setParameter("param1", value1)
    .setParameter("param2", value2)
    .execute();

  retValue = (int)query.getOutputParameterValue("outParam");
} finally {
  query.getOutputs().release();
}

【讨论】:

    【解决方案3】:

    最终我找到了解决办法,我换了行

    qr.execute();
    

    qr.executeUpdate();
    

    根据文档:“当对 StoredProcedureQuery 对象调用 executeUpdate 时,提供程序将对未执行的存储过程查询调用 execute,然后是 getUpdateCount。executeUpdate 的结果将是 getUpdateCount 的结果”

    但他们没有说任何关于关闭游标的内容,但我的方法现在效果很好。

    【讨论】:

      猜你喜欢
      • 2013-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-02
      相关资源
      最近更新 更多