天天看點

【spring boot】JdbcTemplate druid ClobProxyImpl cannot be cast to oracle.sql.CLOB一·異常資訊:二·背景描述三·異常分析四·解決方案

一·異常資訊:

===2018-01-24 10:36:10.256 ERROR org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler Line:37  - Unexpected error occurred invoking async method 'public void com..service.SmsCallBackListenner.callback(com..pojo.SendInterfaceListEvent)'.
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [INSERT INTO SEND (..) VALUES(?,?,?,?,?,?,?,?,?,?,?);]; SQL state [null]; error code [0]; Error; nested exception is java.sql.SQLException: Error
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:90)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:82)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:82)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:655)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:668)
	at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:956)
	at com..service.SmsCallBackListenner.insertSms(SmsCallBackListenner.java:44)
	at com..service.SmsCallBackListenner.callback(SmsCallBackListenner.java:40)
	at com..service.SmsCallBackListenner$$FastClassBySpringCGLIB$$7d92c1dc.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.sql.SQLException: Error
	at com.alibaba.druid.pool.DruidDataSource.handleConnectionException(DruidDataSource.java:1392)
	at com.alibaba.druid.pool.DruidPooledConnection.handleException(DruidPooledConnection.java:136)
	at com.alibaba.druid.pool.DruidPooledStatement.checkException(DruidPooledStatement.java:71)
	at com.alibaba.druid.pool.DruidPooledPreparedStatement.setClob(DruidPooledPreparedStatement.java:607)
	at com..dao.InsertSmsSendBatchPreparedStatementSetter.setValues(InsertSmsSendBatchPreparedStatementSetter.java:29)
	at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:966)
	at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:956)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:639)
	... 13 common frames omitted
Caused by: java.lang.ClassCastException: com.alibaba.druid.proxy.jdbc.ClobProxyImpl cannot be cast to oracle.sql.CLOB
	at oracle.jdbc.driver.OraclePreparedStatement.setClob(OraclePreparedStatement.java:6805)
	at oracle.jdbc.driver.OraclePreparedStatementWrapper.setClob(OraclePreparedStatementWrapper.java:162)
	at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_setClob(FilterChainImpl.java:3054)
	at com.alibaba.druid.filter.FilterAdapter.preparedStatement_setClob(FilterAdapter.java:1195)
	at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_setClob(FilterChainImpl.java:3051)
	at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.setClob(PreparedStatementProxyImpl.java:297)
	at com.alibaba.druid.pool.DruidPooledPreparedStatement.setClob(DruidPooledPreparedStatement.java:605)
	... 17 common frames omitted
           

二·背景描述

oracle 資料庫,查詢帶有clob類型字段的A表,并存儲到 實體類,實體類對應的字段 類型也為 oracle.sql.CLOB。 經過業務處理後 把該字段的值插入到 到 B表 的也為clob類型字段時  報出 如上異常。

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.1</version>
</dependency>      
<dependency>
   <groupId>com.oracle</groupId>
   <artifactId>ojdbc6g</artifactId>
   <version>11.2.0.4.0</version>
</dependency>      
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>      

三·異常分析

主要是 使用的druid 連接配接池導緻的,JdbcTemplate本身沒有直接關系。 我們看到錯誤日志 ,有一個類型轉換異常。

Caused by: java.lang.ClassCastException: com.alibaba.druid.proxy.jdbc.ClobProxyImpl cannot be cast to oracle.sql.CLOB
           

oracle 資料庫驅動 clob 使用的是oracle.sql.CLOB 而  Druid 我們 從  ClobProxyImpl  和 DruidPooledPreparedStatement 可以看到  使用的是 java.sql.Clob 而 這并沒有關系 ,會在 驅動包裡的 OraclePreparedStatement 裡把clob 強轉成 CLOB

繼續看源碼 :在FilterChainImpl 裡的connection_createClob 方法:

@Override
    public Clob connection_createClob(ConnectionProxy connection) throws SQLException {
        if (this.pos < filterSize) {
            return nextFilter().connection_createClob(this, connection);
        }

        Clob clob = connection.getRawObject().createClob();

        return wrap(connection, clob);
    }
           

最後 return的 是 FilterChainImpl 下的 wrap:

public ClobProxy wrap(ConnectionProxy conn, Clob clob) {
        if (clob == null) {
            return null;
        }

        if (clob instanceof NClob) {
            return wrap(conn, (NClob) clob);
        }

        return new ClobProxyImpl(dataSource, conn, clob);
    }
           

導緻 類型轉換異常的原因就是這樣。 至于為什麼這麼做,并不清楚,可能我們使用的姿勢并不對,druid 可能并不允許我們這麼做。

四·解決方案

從資料庫讀取的clob字段的時候 ,實體類 相應字段 使用String 類型存儲,讀取的時候 也 通過getSting 去 擷取clob的值:

@Override
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        SendInterface sendInterface = new SendInterface();
        sendInterface.setSendId(resultSet.getString("SEND_ID"));
        sendInterface.setMobileTo(resultSet.getString("MOBILE_TO"));
        sendInterface.setSendMsg(resultSet.getString("SEND_MSG"));
        sendInterface.setExtendNumber(resultSet.getString("EXTEND_NUMBER"));
        sendInterface.setSysTime(resultSet.getDate("SYS_TIME"));
        sendInterface.setPreSendTime(resultSet.getDate("PRE_SEND_TIME"));
        return sendInterface;
    }
           

存儲 字元串 在 表 clob字段的時候 也不需要你把 String 轉為 clob 再存 ,因為Druid 都 替你做了,

DruidLobCreator:

@Override
    public void setClobAsAsciiStream(PreparedStatement ps, int paramIndex, InputStream asciiStream, int contentLength)
                                                                                                                      throws SQLException {
        if (asciiStream != null) {
            Clob clob = ps.getConnection().createClob();

            OutputStream out = clob.setAsciiStream(1);

            final int BUFFER_SIZE = 4096;
            try {
                byte[] buffer = new byte[BUFFER_SIZE];
                int bytesRead = -1;
                while ((bytesRead = asciiStream.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
                out.flush();
            } catch (Exception e) {
                throw new SQLException("setClob error", e);
            } finally {
                JdbcUtils.close(asciiStream);
                JdbcUtils.close(out);
            }

            ps.setClob(paramIndex, clob);
        } else {
            ps.setClob(paramIndex, (Clob) null);
        }
    }
           
@Override
    public void setClobAsString(PreparedStatement ps, int paramIndex, String content) throws SQLException {
        Clob clob = ps.getConnection().createClob();
        clob.setString(1, content);
        ps.setClob(paramIndex, clob);
    }
           

繼續閱讀