在 Oracle 数据库中调用查询时绑定后更改参数值

Tur*_*ral 5 java oracle spring tomcat mybatis

我们公司有使用 Java 7、Spring Framework 3.1.2、MyBatis 3.1.1、MyBatis Spring 1.2.2、JasperReports 6.1.0 等开发的应用程序。应用程序工作在 Tomcat 7.0.35,使用 Tomcat Connection Pool连接到 Oracle 数据库 10g 企业版 10.2.0.4.0 版 - 64 位。JRE 版本 1.7.0_09-b05。应用程序在 RHEL Server 6.5 上运行。

问题不时出现,然后在几个小时(3-6 小时)后消失,有时几天(1-3 天)后消失。当 Web Service 创建报告时,应用程序调用 MyBatis 映射器的方法,返回 List<MonthlyReport>,然后应用程序将此列表传递给在文件系统上创建报告的 JasperReport 引擎,最后应用程序在响应时返回文件流(MTOM )。问题是,定期尝试在数据库中运行查询以创建报告时,会导致以下异常:

ERROR 2015-07-23 11:44:03,012 [http-bio-8280-exec-2] exception type: org.springframework.jdbc.UncategorizedSQLException
ERROR 2015-07-23 11:44:03,012 [http-bio-8280-exec-2] exception message: 
### Error querying database.  Cause: java.sql.SQLException: ORA-12801: error signaled in parallel query server P010
ORA-01841: (full) year must be between -4713 and +9999, and not be 0
Run Code Online (Sandbox Code Playgroud)

这是查询:

<select id="getMonthlyReportData" resultType="MonthlyReport" parameterType="map">
<![CDATA[
SELECT r.bank_name bankName,
       r.user_name userName,
       r.descr userDescription,
       CASE WHEN r.parent_bank_id IS NULL THEN 1 ELSE 0 END isParentBankInt,
       COUNT (CASE WHEN r.p_type NOT IN ('SS', 'DR') THEN 1 ELSE NULL END) postpaidPaymentCount,
       SUM (CASE WHEN r.p_type NOT IN ('SS', 'DR') THEN r.amount ELSE 0 END) postpaidPaymentAmount,
       COUNT (CASE WHEN r.p_type = 'SS' THEN 1 ELSE NULL END) prepaidPaymentCount,
       SUM (CASE WHEN r.p_type = 'SS' THEN r.amount ELSE 0 END) prepaidPaymentAmount,
       COUNT (CASE WHEN r.p_type = 'DR' THEN 1 ELSE NULL END) depositRepayCount,
       SUM (CASE WHEN r.p_type = 'DR' THEN r.amount ELSE 0 END) depositRepayAmount
  FROM (SELECT q.queue_id,
               q.amount,
               q.p_type,
               q.user_name,
               q.action_date,
               b.parent_bank_id,
               U.descr,
               b.bank_name
          FROM rbp_queue q, rbp_all_banks b, rbp_users U
         WHERE     q.user_name = U.user_name
               AND U.working_bank_id = b.bank_id
               AND q.err_code = -1000000
               AND q.action_date BETWEEN TO_DATE (#{start_date, javaType=STRING, jdbcType=VARCHAR}, 'YYYYMMDDHH24MISS')
                                     AND TO_DATE (#{end_date,   javaType=STRING, jdbcType=VARCHAR}, 'YYYYMMDDHH24MISS')
               AND U.working_bank_id IN
                    (SELECT bank_id
                       FROM rbp_all_banks
                      WHERE bank_id = #{bank_id, javaType=Integer, jdbcType=NUMERIC} OR parent_bank_id = #{bank_id, javaType=Integer, jdbcType=NUMERIC})
        UNION
        SELECT qa.queue_id,
               qa.amount,
               qa.p_type,
               qa.user_name,
               qa.action_date,
               ba.parent_bank_id,
               Ua.descr,
               ba.bank_name
          FROM sysadm.rbp_queue_arch@azis_archdb qa,
               rbp_all_banks ba,
               rbp_users Ua
         WHERE     qa.user_name = Ua.user_name
               AND Ua.working_bank_id = ba.bank_id
               AND qa.err_code = -1000000
               AND qa.action_date BETWEEN TO_DATE (#{start_date, javaType=STRING, jdbcType=VARCHAR}, 'YYYYMMDDHH24MISS')
                                      AND TO_DATE (#{end_date,   javaType=STRING, jdbcType=VARCHAR}, 'YYYYMMDDHH24MISS')
               AND Ua.working_bank_id IN
                    (SELECT bank_id
                       FROM rbp_all_banks
                      WHERE bank_id = #{bank_id, javaType=Integer, jdbcType=NUMERIC} OR parent_bank_id = #{bank_id, javaType=Integer, jdbcType=NUMERIC})) r
         GROUP BY r.bank_name,
                  r.user_name,
                  r.descr,
                  CASE WHEN r.parent_bank_id IS NULL THEN 1 ELSE 0 END
         ORDER BY isParentBankInt DESC, bankName, userName
]]>
Run Code Online (Sandbox Code Playgroud)

应用程序不使用日期类型参数,因为这种情况下的 Oracle 使用了不同的计划,并且查询运行了很长时间。因此,应用程序将查询日期作为文本传递给查询日期,然后使用 TO_DATE 函数将其转换为日期。来自 MyBatis 的日志记录:

DEBUG 2015-07-22 15:10:52,720 [http-apr-8281-exec-2] ooo Using Connection [ProxyConnection[PooledConnection[oracle.jdbc.driver.T4CConnection@344482ac]]]
DEBUG 2015-07-22 15:10:52,724 [http-apr-8281-exec-2] ==>  Preparing: SELECT r.bank_name bankName, r.user_name userName, r.descr userDescription, ...
DEBUG 2015-07-22 15:10:52,725 [http-apr-8281-exec-2] ==> Parameters: 20150601000000(String), 20150621235959(String), 31(Integer), 31(Integer), 20150601000000(String), 20150621235959(String), 31(Integer), 31(Integer)
Run Code Online (Sandbox Code Playgroud)

如这里所见,MyBatis 将日期参数(作为字符串)传递给查询,但是如果查看 Oracle 跟踪我们可以看到,日期参数的值为“”(不是 null,而是两个双引号)。

呼唤

SELECT TO_DATE ('', 'YYYYMMDDHH24MISS') FROM dual
Run Code Online (Sandbox Code Playgroud)

在 TOAD 返回空值,但

SELECT TO_DATE ('""', 'YYYYMMDDHH24MISS') FROM dual
Run Code Online (Sandbox Code Playgroud)

引发异常:ORA-01841:(完整)年份必须介于 -4713 和 +9999 之间,而不是 0。

奇怪的是,当服务器出现问题时,同时应用程序在其他计算机(例如我的工作笔记本电脑)上正常运行(创建此报告)。以下是出现问题时Oracle数据库的跟踪文件的一部分:

     Bind#0
      oacdty=01 mxl=32(28) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=02 csi=2000 siz=224 off=0
      kxsbbbfp=9fffffffbf330908  bln=32  avl=28  flg=05
      value=""
     Bind#1
      oacdty=01 mxl=32(28) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=02 csi=2000 siz=0 off=32
      kxsbbbfp=9fffffffbf330928  bln=32  avl=28  flg=01
      value=""
     Bind#2
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=02 csi=2000 siz=0 off=64
      kxsbbbfp=9fffffffbf330948  bln=22  avl=02  flg=01
      value=31
     Bind#3
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=02 csi=2000 siz=0 off=88
      kxsbbbfp=9fffffffbf330960  bln=22  avl=02  flg=01
      value=31
     Bind#4
      oacdty=01 mxl=32(28) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=02 csi=2000 siz=0 off=112
      kxsbbbfp=9fffffffbf330978  bln=32  avl=28  flg=01
      value=""
     Bind#5
      oacdty=01 mxl=32(28) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=02 csi=2000 siz=0 off=144
      kxsbbbfp=9fffffffbf330998  bln=32  avl=28  flg=01
      value=""
     Bind#6
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=02 csi=2000 siz=0 off=176
      kxsbbbfp=9fffffffbf3309b8  bln=22  avl=02  flg=01
      value=31
     Bind#7
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=02 csi=2000 siz=0 off=200
      kxsbbbfp=9fffffffbf3309d0  bln=22  avl=02  flg=01
      value=31
Run Code Online (Sandbox Code Playgroud)

以下是未发生问题时 Oracle 数据库的跟踪文件的一部分:

     Bind#0
      oacdty=01 mxl=32(14) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=01 csi=31 siz=224 off=0
      kxsbbbfp=9fffffffbf323e50  bln=32  avl=14  flg=05
      value="20150601000000"
     Bind#1
      oacdty=01 mxl=32(14) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=01 csi=31 siz=0 off=32
      kxsbbbfp=9fffffffbf323e70  bln=32  avl=14  flg=01
      value="20150621235959"
     Bind#2
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=01 csi=31 siz=0 off=64
      kxsbbbfp=9fffffffbf323e90  bln=22  avl=02  flg=01
      value=31
     Bind#3
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=01 csi=31 siz=0 off=88
      kxsbbbfp=9fffffffbf323ea8  bln=22  avl=02  flg=01
      value=31
     Bind#4
      oacdty=01 mxl=32(14) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=01 csi=31 siz=0 off=112
      kxsbbbfp=9fffffffbf323ec0  bln=32  avl=14  flg=01
      value="20150601000000"
     Bind#5
      oacdty=01 mxl=32(14) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000010 frm=01 csi=31 siz=0 off=144
      kxsbbbfp=9fffffffbf323ee0  bln=32  avl=14  flg=01
      value="20150621235959"
     Bind#6
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=01 csi=31 siz=0 off=176
      kxsbbbfp=9fffffffbf323f00  bln=22  avl=02  flg=01
      value=31
     Bind#7
      oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
      oacflg=03 fl2=1000000 frm=01 csi=31 siz=0 off=200
      kxsbbbfp=9fffffffbf323f18  bln=22  avl=02  flg=01
      value=31
Run Code Online (Sandbox Code Playgroud)

注意 binds:0, 1, 4, 5 的值。当问题是value="" 时

该问题与 MyBatis 无关,因为之前请求是在已编译的 JasperReports 文件(monthlyReport.jasper)中,并且应用程序将数据库连接传递给 JasperReports 引擎以创建报告。JasperReports 本身连接到数据库并运行查询。MyBatis 不用于创建报告,而是用于应用程序中的所有其他目的。同样的 Oracle 错误 (ORA-01841: (full) year must be between -4713 and +9999, and not be 0) 定期发出并出现。从旧日志文件:

    ERROR 2015-06-11 08:57:17,559 [http-apr-8280-exec-9] Fill 1: exception
    net.sf.jasperreports.engine.JRException: Error executing SQL statement for : monthlyReport_New32Dataset321_1432644594876_272524
            at net.sf.jasperreports.engine.query.JRJdbcQueryExecuter.createDatasource(JRJdbcQueryExecuter.java:240)
            at net.sf.jasperreports.engine.fill.JRFillDataset.createQueryDatasource(JRFillDataset.java:1087)
            at net.sf.jasperreports.engine.fill.JRFillDataset.initDatasource(JRFillDataset.java:668)
            at net.sf.jasperreports.engine.fill.JRBaseFiller.setParameters(JRBaseFiller.java:1281)
            at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:900)
            at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:845)
            at net.sf.jasperreports.engine.fill.JRFillSubreport.fillSubreport(JRFillSubreport.java:651)
            at net.sf.jasperreports.engine.fill.JRSubreportRunnable.run(JRSubreportRunnable.java:59)
            at net.sf.jasperreports.engine.fill.AbstractThreadSubreportRunner.run(AbstractThreadSubreportRunner.java:203)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
            at java.lang.Thread.run(Thread.java:722)
    Caused by: java.sql.SQLException: ORA-12801: error signaled in parallel query server P002
    ORA-01841: (full) year must be between -4713 and +9999, and not be 0

            at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
            at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
            at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:879)
            at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:450)
            at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
            at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
            at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:207)
            at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:884)
            at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1167)
            at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1289)
            at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3584)
            at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3628)
            at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1493)
            at sun.reflect.GeneratedMethodAccessor349.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
            at java.lang.reflect.Method.invoke(Method.java:601)
            at org.apache.tomcat.jdbc.pool.interceptor.AbstractQueryReport$StatementProxy.invoke(AbstractQueryReport.java:235)
            at $Proxy99.executeQuery(Unknown Source)
            at net.sf.jasperreports.engine.query.JRJdbcQueryExecuter.createDatasource(JRJdbcQueryExecuter.java:233)
            ... 11 more
Run Code Online (Sandbox Code Playgroud)

请帮助解决这个问题。

Mar*_*ber 0

您正在观察一种症状。这不是引号的问题。在跟踪中,字符串参数显示为带有双引号。IE

value="20150621235959"
Run Code Online (Sandbox Code Playgroud)

表示客户端传递了字符串“20150621235959”并且

value=""   
Run Code Online (Sandbox Code Playgroud)

表示客户端传递了一个空字符串“”。导致ORA-12801

根本原因是客户端能够将空字符串传递给数据库。

谓词

action_date BETWEEN date1 and date2
Run Code Online (Sandbox Code Playgroud)

如果一个或两个日期为 NULL,则不返回任何行。如果您不信任简单运行此查询

 -- return nothing
 SELECT * FROM dual where sysdate between to_date('','ddmmyyyy') and to_date('','ddmmyyyy');
Run Code Online (Sandbox Code Playgroud)

如果您知道不会返回任何内容,那么客户端没有必要向数据库发送空字符串。

因此,恕我直言,客户端中应该进行一些验证,强制只接受有效的字符串(至少是正确的长度)。这将解决问题。