PL/SQLcall SpringMVC jdbcTemplate 中的游标泄漏

Posted

技术标签:

【中文标题】PL/SQLcall SpringMVC jdbcTemplate 中的游标泄漏【英文标题】:Cursor leak in PL/SQLcall SpringMVC jdbcTemplate 【发布时间】:2018-04-10 11:32:17 【问题描述】:

我创建了一个在 for 中调用 PL/SQL 的函数,它正在创建游标泄漏。PL/SQL 工作正常,它返回所需的数据,但我注意到游标的数量会增加,直到 ora超过 -1000 个最大打开游标并抛出 ORA-00604: error occurred at recursive SQL level 1 错误。为了检查已使用游标的数量,我使用了以下 SQL 语句:

select * 
from
 (select a.value, s.username, s.sid, s.serial#
 from v$sesstat a, v$statname b, v$session s
 where a.statistic# = b.statistic#  
 and s.sid=a.sid
 and b.name = 'opened cursors current')
where sid = 'mySID';

我调试了我的代码,发现游标是在这里创建的:objectXStructResult.getAttributes()[X]。我试图关闭结果集和 callableStatement 但它不起作用。谁能帮帮我?

    function callToDao()
      for(Object X:LstObject)
        plsqlCall(X);
      
    

    funcion ObjectX plsqlCall(Object X)
        Object salidaX = null;
        //Obtención de parámetros de consulta
        final String p_x1 = X.getX();
        final String p_x2 = X.getX();
        final String p_x3 = X.getX();
        final String p_x4 = X.getX();
        final String p_x5 = X.getX();
        final String p_x6 = X.getX();
        final String p_x7 = X.getX();

        try
            CallableStatementCreator csCreator = new CallableStatementCreator() 

                @Override
                public CallableStatement createCallableStatement(Connection con) throws SQLException 
                    CallableStatement cs = null;
                    try 

                        cs = con.prepareCall("call PK_XXXXX.XXXXXX(?, ?, ?, ?, ?, ?, ?, ?) ");

                        cs.setInt(1, Integer.parseInt(p_x1));                        
                        cs.setString(2, p_x2);                          
                        cs.setString(3, p_x3);                          
                        cs.setLong(4, Long.parseLong(p_x4));                           
                        cs.setDouble(5,Double.parseDouble(p_x5));                           
                        cs.setDouble(6, Double.parseDouble(p_x6));                          
                        cs.setDouble(7, Double.parseDouble(p_x7));

                        cs.registerOutParameter(8, OracleTypes.STRUCT,"OBJECT_PLSQL");

                     catch (SQLException e) 
                        e.printStackTrace();                
                    
                    return cs;
                
            ;
            CallableStatementCallback csCallback = new CallableStatementCallback() 

                public Object doInCallableStatement(CallableStatement cs) throws SQLException 
                    ObjectX ret = null;
                    ResultSet rs = null;
                    try 
                        cs.execute();
                        ret = obtainObjectX(cs, rs, objectPos, type);
                     catch (SQLException e) 
                        e.printStackTrace();                
                    
                    return ret;
                
            ;

            salida = (ObjectX) this.jdbcTemplate.execute(csCreator,csCallback);
        catch (Exception e) 
            e.printStackTrace();
        

        return salida;
    

       private ObjectX obtainObjectX(CallableStatement cs, ResultSet rs, int objectPos, String p_tipo) throws SQLException

        ObjectX objectX= new ObjectX();
        List<ObjectX> objectXLst= new ArrayList<ObjectX>();

        try
            //Obtain exit parameter
            STRUCT objectXStructResult = (STRUCT)cs.getObject(objectPos);   

            //Obtain Struct data
            String att1 = (String)objectXStructResult.getAttributes()[1];
            String att2 = (String)objectXStructResult.getAttributes()[5];

            BigDecimal att3 = (BigDecimal)objectXStructResult.getAttributes()[6];
            String att4 = (String)objectXStructResult.getAttributes()[7];

            //Control de error en la obtención de datos
            if (new BigDecimal("0").equals(att3)) 
                //Obtención de los datos de salida
                ARRAY listaDatos = (ARRAY)objectXStructResult.getAttributes()[0];
                if (listaDatos!=null)
                    rs = listaDatos.getResultSet();

                    int rowNum = 0;
                    //Recorrido del listado de datos
                    while (rs.next()) 
                        STRUCT dataStruct= (STRUCT) listaDatos.getOracleArray()[rowNum];
                        ObjectX objXFor= this.mapRow(dataStruct, rowNum);
                        objectXLst.add(objXFor);
                        rowNum++;
                    
                
                objectX.setAtt1(att1);
                objectX.setAtt2(att2);

                objectX.setAtt3(new Long(att3.toString()));
                objectX.setAtt4(att4);
                objectX.setData(objectXLst); 

            
         catch (Exception e) 
            e.printStackTrace();            
        

        return objectX;
    


      private ObjectX mapRow(STRUCT dataStruct, int rowNum) throws SQLException 
        Object[] objectInfo = dataStruct.getAttributes();

        //Obtención de datos de la estructura de base de datos
        BigDecimal att1= ((BigDecimal)objectInfo[0]);
        BigDecimal att2= (BigDecimal)objectInfo[1];
        String att3= (String)objectInfo[2];
        String att4= (String)objectInfo[3];

        return new ObjectX(att1, att2, null, att3, null, null, att4, null);
    

更新 1:

我已经设法将光标的创建从 5 个减少到 1 个更改代码,但我仍然找不到关闭该光标的方法。

发件人:

        String att1 = (String)objectXStructResult.getAttributes()[1];
        String att2 = (String)objectXStructResult.getAttributes()[5];

        BigDecimal att3 = (BigDecimal)objectXStructResult.getAttributes()[6];
        String att4 = (String)objectXStructResult.getAttributes()[7];

        //Control de error en la obtención de datos
        if (new BigDecimal("0").equals(att3)) 
            //Obtención de los datos de salida
            ARRAY listaDatos = (ARRAY)objectXStructResult.getAttributes()[0];
        

收件人:

        Object[] atributos = objectXStructResult.getAttributes();
        String att1 = (String)atributos[1];
        String att2 = (String)atributos[5];

        BigDecimal att3 = (BigDecimal)atributos[6];
        String att4 = (String)atributos[7];

        //Control de error en la obtención de datos
        if (new BigDecimal("0").equals(att3)) 
            //Obtención de los datos de salida
            ARRAY listaDatos = atributos[0];
        

【问题讨论】:

【参考方案1】:

注意:任何打开的结果集都应该在回调实现中的 finally 块中关闭。 Spring 将在回调返回后关闭 Statement 对象,但这并不一定意味着将关闭 ResultSet 资源:Statement 对象可能会被连接池池化,close 调用仅将对象返回到池中而不是物理关闭资源。

请注意,jdbcTemplate 会自动关闭 csCreatorcsCallback,但您必须始终注意 ResultSet .记得在 finally 块中关闭它们,以确保当异常发生时,你的 close() 会被调用。

例如,您当前的陈述:

ResultSet rs = null;
try 
    cs.execute();
    ret = obtainObjectX(cs, rs, objectPos, type);
 catch (SQLException e) 
    e.printStackTrace();

应替换为:

ResultSet rs = null;
try 
    cs.execute();
    ret = obtainObjectX(cs, rs, objectPos, type);
 catch (SQLException e) 
    e.printStackTrace();
 finally
    if(rs != null)
        rs.close()

更新: 来自oracle 文档:

默认情况下,自动索引未启用。对于 JDBC 应用程序,如果可能通过 getArray 和 getResultSet 方法随机访问数组元素,请为 ARRAY 对象启用自动索引。

所以也许这会减少数据库上的打开游标:

array.setAutoIndexing(true);

顺便说一句,oracle.sql.ARRAY 类已被弃用:

已弃用。使用 java.sql.Array 接口进行声明,而不是使用具体类 oracle.sql.ARRAY。

【讨论】:

感谢您的回答。在阅读您的答案之前,我尝试添加该代码,它仍然为每次调用 plsql 创建一个光标。我也处理了以防万一关闭cs,但是光标仍然在那里。 如果您查看 org.springframework.jdbc.core.JdbcTemplate.query 方法源代码,您会看到调用 - JdbcUtils.closeResultSet(rs);在 finally 块中 - 如果它不起作用,那么它可能是 spring jdbctemplate 或 ojdbc 驱动程序的错误

以上是关于PL/SQLcall SpringMVC jdbcTemplate 中的游标泄漏的主要内容,如果未能解决你的问题,请参考以下文章

JDBC在springMvc等框架中使用的方式

SSM(MyBatis+Spring+SpringMVC)之MyBatis总结

无法在 Spring MVC 中建立 JDBC 连接

springmvc+jdbc连接数据库(第一个微商项目,代理注册)

Java项目:校园招聘平台系统(java+MySQL+Jdbc+Servlert+SpringMvc+Jsp)

springmvc使用weblogic发布,怎么配置