一·异常信息:
===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()
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类型字段时 报出 如上异常。
com.alibaba
druid-spring-boot-starter
1.1.1
com.oracle
ojdbc6g
11.2.0.4.0
org.springframework.boot
spring-boot-starter-jdbc
三·异常分析
主要是 使用的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);
}