1. 程式人生 > >Mybatis呼叫儲存過程異常-Non supported SQL92 token at position: 1

Mybatis呼叫儲存過程異常-Non supported SQL92 token at position: 1

最近遇到一個奇怪的異常,使用Mybatis呼叫一個oracle的package時出現一個異常:

Mybatis中xxDAO.xml

<select id="getxxByUserId" statementType="CALLABLE" parameterType="xx.xx.xx.xxInput" resultMap="xxResult">
{ call #{output, mode=OUT, jdbcType=CURSOR, resultMap=xxResult} := XX.do_xxSearch( #{xxx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN}, #{xx, javaType=java.lang.String, mode=IN} ) }
</select>

異常資訊如下:

16:09:32.399 [http-apr-8080-exec-6] ERROR c.xx.xx.xxService - Error occur when getting xx By xx.
org.springframework.jdbc.UncategorizedSQLException: 
### Error querying database.  Cause: java.sql.SQLException: Non supported SQL92 token at position: 1: 
### The error may exist in com/xx/xx/dao/xxDAO.xml
### The error may involve com.xx.xx.dao.xxDAO.getxxByUserId-Inline
### The error occurred while setting parameters
### SQL: {         call ? := XX.do_xxSearch(                 ?,                 ?,                 ?,                 ?,                 ?,                 ?,                 ?,                 ?,                 ?,                 ?,                 ?,                 ?             )         }
### Cause: java.sql.SQLException: Non supported SQL92 token at position: 1: 
; uncategorized SQLException for SQL []; SQL state [99999]; error code [17034]; Non supported SQL92 token at position: 1: ; nested exception is java.sql.SQLException: Non supported SQL92 token at position: 1: 
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84) ~[spring-jdbc-4.1.4.RELEASE.jar:4.1.4.RELEASE]
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-4.1.4.RELEASE.jar:4.1.4.RELEASE]
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-4.1.4.RELEASE.jar:4.1.4.RELEASE]
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73) ~[mybatis-spring-1.2.1.jar:1.2.1]
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:368) ~[mybatis-spring-1.2.1.jar:1.2.1]
	at com.sun.proxy.$Proxy478.selectList(Unknown Source) ~[na:na]
	at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:198) ~[mybatis-spring-1.2.1.jar:1.2.1]
	at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:114) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:58) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:43) ~[mybatis-3.2.3.jar:3.2.3]
	at com.sun.proxy.$Proxy532.getAppWorkQueueByUserId(Unknown Source) ~[na:na]
	....
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_31]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_31]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_31]
	at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_31]
	at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:205) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:326) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271) [jersey-common-2.22.1.jar:na]
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267) [jersey-common-2.22.1.jar:na]
	at org.glassfish.jersey.internal.Errors.process(Errors.java:315) [jersey-common-2.22.1.jar:na]
	at org.glassfish.jersey.internal.Errors.process(Errors.java:297) [jersey-common-2.22.1.jar:na]
	at org.glassfish.jersey.internal.Errors.process(Errors.java:267) [jersey-common-2.22.1.jar:na]
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317) [jersey-common-2.22.1.jar:na]
	at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154) [jersey-server-2.22.1.jar:na]
	at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:471) [jersey-container-servlet-core-2.22.1.jar:na]
	at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:425) [jersey-container-servlet-core-2.22.1.jar:na]
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:383) [jersey-container-servlet-core-2.22.1.jar:na]
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:336) [jersey-container-servlet-core-2.22.1.jar:na]
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:223) [jersey-container-servlet-core-2.22.1.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.42]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
	xx
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.42]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.42]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.42]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [catalina.jar:7.0.42]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.42]
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953) [catalina.jar:7.0.42]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.42]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [catalina.jar:7.0.42]
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023) [tomcat-coyote.jar:7.0.42]
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) [tomcat-coyote.jar:7.0.42]
	at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:1852) [tomcat-coyote.jar:7.0.42]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_31]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_31]
	at java.lang.Thread.run(Thread.java:745) [na:1.8.0_31]
Caused by: java.sql.SQLException: Non supported SQL92 token at position: 1: 
	at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:70) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:133) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:199) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:263) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:271) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OracleSql.handleToken(OracleSql.java:1245) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OracleSql.handleODBC(OracleSql.java:1136) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OracleSql.parse(OracleSql.java:1053) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OracleSql.getSql(OracleSql.java:310) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OracleSql.getSqlBytes(OracleSql.java:604) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.T4CCallableStatement.doOall8(T4CCallableStatement.java:177) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.T4CCallableStatement.executeForRows(T4CCallableStatement.java:950) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1222) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3387) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:3488) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OracleCallableStatement.execute(OracleCallableStatement.java:3857) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at oracle.jdbc.driver.OraclePreparedStatementWrapper.execute(OraclePreparedStatementWrapper.java:1374) ~[ojdbc6.jar:Oracle JDBC Driver version - "11.1.0.7.0-Production"]
	at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172) ~[tomcat-dbcp.jar:7.0.42]
	at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172) ~[tomcat-dbcp.jar:7.0.42]
	at org.apache.ibatis.executor.statement.CallableStatementHandler.query(CallableStatementHandler.java:63) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:70) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:57) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:259) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:132) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:115) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:104) ~[mybatis-3.2.3.jar:3.2.3]
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98) ~[mybatis-3.2.3.jar:3.2.3]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_31]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_31]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_31]
	at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_31]
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:358) ~[mybatis-spring-1.2.1.jar:1.2.1]
	... 53 common frames omitted
Feb 21, 2017 4:09:32 PM org.glassfish.jersey.filter.LoggingFilter log

這個異常實際上原因很簡單,是因為xxDAO.xml中的大括號換行引起,解決方法也簡單,升級oracle驅動,或是去掉換行。

  • 但是這個看似簡單的原因,實際調查起來就沒那麼順利,有興趣的同學可以看一下我是怎麼定位問題

因為這段程式碼在其他環境都是執行正常,就只有我電腦會有問題,更奇怪的是在我電腦上通過unit testing呼叫又是正常,一開始我是懷疑是程式碼不一致引起:unit testing和我部署到tomcat的程式碼是不一樣的,但是通過我再三確認調查,程式碼肯定是同一份。

所以猜想是不是Mybatis的問題,由於Mybatis只是對jdbc進行封裝,最終的底層還是通過jdbc執行資料庫操作,會不會是在呼叫jdbc時傳入的引數由於某種原因不一致導致?

  • Jdbc 呼叫oracle package的方法如下:
public class ProcedureCallTest {
    public static void main(String args[]) throws Exception {
        DriverManager.registerDriver(new sun.jdbc.odbc.JdbcOdbcDriver());
        Connection conn = DriverManager.getConnection("jdbc:odbc:mydata", "sa", "");
        CallableStatement c = conn.prepareCall("{call getsum(?)}");
        c.setInt(1, 100);
        c.execute();
        conn.close();
    }
}

所以有可能不一致的地方有兩個地方,conn.prepareCall引數不一樣或是c.setInt設定引數有問題,跟蹤Mybatis原始碼:

  • conn.prepareCall在Mybatis原始碼中位置是:

org.apache.ibatis.executor.statement.CallableStatementHandler.java

protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getResultSetType() != null) {
      return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareCall(sql);
    }
  }

c.setInt設定引數是程式碼是:

public void parameterize(Statement statement) throws SQLException {
    registerOutputParameters((CallableStatement) statement);
    parameterHandler.setParameters((CallableStatement) statement);
  }

會根據引數型別呼叫不同的xxxhander.java設定引數,比如如果是引數型別是String,則呼叫org.apache.ibatis.type.StringTypeHandler中的ps.setString(i, parameter);設定

最終分析結果讓我很困惑,unit testing和tomcat專案傳入的引數值都是一樣的,所以問題就不應該是Mybatis,而是更底層,就可能是oracle的jdbc驅動不一樣引起,這回找到了問題:

unit testing使用的oracle驅動是通過maven配置的:

<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc7</artifactId>
    <version>12.1.0.2.0</version>
    <scope>provided</scope>
</dependency>

scope是provided,所以jar是不會加入部署後的專案中,找了一下專案並沒有發現有加入oracle驅動,最後是在tomcat的lib中找到,發現竟然是用ojdbc6,和其他執行環境對比了一下,原來他們都用了ojdbc7,升級一下這個包,異常消失,進一步分析結論如下:

是由於xxDAO.xml中的大括號換行引起(高亮的括號),在ojdbc7是沒問題,但是在ojdbc6會引起上述異常,如果把換行去掉,改為:

<select id="getxxByUserId" statementType="CALLABLE" parameterType="xx.xx.xx.xxInput" resultMap="xxResult">
    {call #{output, mode=OUT, jdbcType=CURSOR, resultMap=xxResult} := XX.do_xxSearch(
            #{xxx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN},
            #{xx, javaType=java.lang.String, mode=IN}
        )}
</select>

這樣在ojdbc6也正常,所以解決方案有兩種:

1. 升級驅動
2. 大括號不要換行