最近生产环境上因数据库某一字段类型由VARCHAR2(4000)改为CLOB后,报如下异常:
Caused by: java.sql.SQLException: setString can only process strings of less than 32766 chararacters
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:125)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:162)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:227)
at oracle.jdbc.driver.OraclePreparedStatement.setStringInternal(OraclePreparedStatement.java:4746)
at oracle.jdbc.driver.OraclePreparedStatement.setString(OraclePreparedStatement.java:4717)
at com.jolbox.bonecp.PreparedStatementHandle.setString(PreparedStatementHandle.java:1029)
at com.ibatis.sqlmap.engine.type.StringTypeHandler.setParameter(StringTypeHandler.java:30)
at com.ibatis.sqlmap.engine.type.UnknownTypeHandler.setParameter(UnknownTypeHandler.java:69)
at com.ibatis.sqlmap.engine.mapping.parameter.BasicParameterMap.setParameter(BasicParameterMap.java:165)
at com.ibatis.sqlmap.engine.mapping.parameter.BasicParameterMap.setParameters(BasicParameterMap.java:125)
at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeUpdate(SqlExecutor.java:79)
at com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.sqlExecuteUpdate(GeneralStatement.java:200)
at com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.executeUpdate(GeneralStatement.java:78)
从异常信息观察,应该是com.ibatis.sqlmap.engine.type.StringTypeHandler.setParameterv即iBatis报出来的,通过代码跟踪,进入com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.class类中的update方法如下:
public int update(SessionScope session, String id, Object param) throws SQLException {
int rows = 0;
MappedStatement ms = getMappedStatement(id);
Transaction trans = getTransaction(session);
boolean autoStart = trans == null;
try {
trans = autoStartTransaction(session, autoStart, trans);
RequestScope request = popRequest(session, ms);
try {
rows = ms.executeUpdate(request, trans, param);
} finally {
pushRequest(request);
}
autoCommitTransaction(session, autoStart);
} finally {
autoEndTransaction(session, autoStart);
}
return rows;
}
继续跟踪到com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.class类中,请看executeUpdate方法如下:
parameterObject = validateParameter(parameterObject);
Sql sql = getSql();
errorContext.setMoreInfo("Check the parameter map.");
ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);
errorContext.setMoreInfo("Check the result map.");
ResultMap resultMap = sql.getResultMap(request, parameterObject);
注意
ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);这句 ,继续跟踪,在
com.ibatis.sqlmap.engine.mapping.sql.dynamic.DynamicSql.class中有如下方法,注意
dynSql = new SimpleDynamicSql(this.delegate, dynSql).getSql(request, parameterObject);这句
private void process(RequestScope request, Object parameterObject) {
SqlTagContext ctx = new SqlTagContext();
List localChildren = this.children;
processBodyChildren(request, ctx, parameterObject, localChildren.iterator());
BasicParameterMap map = new BasicParameterMap(this.delegate);
map.setId(request.getStatement().getId() + "-InlineParameterMap");
map.setParameterClass(((GeneralStatement)request.getStatement()).getParameterClass());
map.setParameterMappingList(ctx.getParameterMappings());
String dynSql = ctx.getBodyText();
if (SimpleDynamicSql.isSimpleDynamicSql(dynSql)) {
dynSql = new SimpleDynamicSql(this.delegate, dynSql).getSql(request, parameterObject);
}
request.setDynamicSql(dynSql);
request.setDynamicParameterMap(map);
}
继续跟踪到箭头所示
getSql(request, parameterObject)就去里,找着如下代码:
这里应该是获取对应的类型转换器,继续跟踪到com.ibatis.sqlmap.engine.type.TypeHandlerFactory.class类中,发现该类创建了所有默认的类型转换器。
register(String.class, new StringTypeHandler());
register(String.class, "CLOB", new CustomTypeHandler(new ClobTypeHandlerCallback()));
register(String.class, "LONGVARCHAR", new CustomTypeHandler(new ClobTypeHandlerCallback()));
register(BigDecimal.class, new BigDecimalTypeHandler());
register([B.class, new ByteArrayTypeHandler());
register([B.class, "BLOB", new CustomTypeHandler(new BlobTypeHandlerCallback()));
register([B.class, "LONGVARBINARY", new CustomTypeHandler(new BlobTypeHandlerCallback()));
register(Object.class, new ObjectTypeHandler());
register(Object.class, "OBJECT", new ObjectTypeHandler());
register(java.util.Date.class, new DateTypeHandler());
register(java.util.Date.class, "DATE", new DateOnlyTypeHandler());
register(java.util.Date.class, "TIME", new TimeOnlyTypeHandler());
很明显,CLOB类型调了自定义类型转换器
new CustomTypeHandler(new ClobTypeHandlerCallback())),于是继续跟踪到ClobTypeHandlerCallback.class里面,发现有如下代码:
public void setParameter(ParameterSetter setter, Object parameter) throws SQLException{
String s = (String)parameter;
if (s != null) {
StringReader reader = new StringReader(s);
setter.setCharacterStream(reader, s.length());
} else {
setter.setString(null);
}
}
看来该转换器是调用setCharacterStream以流的方式实现的,这样对于CLOB的处理应该是没有问题的,但实际项目为什么会报异常呢,经过摸索发现,可以按钮如下的sqlMap.xml中写,然后测试,问题解决,注意desc属性对应的写法 DESC = #desc,handler=com.ibatis.sqlmap.engine.type.ClobTypeHandlerCallback#
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="iBatisTest">
<typeAlias alias="testDO" type="com.shixing.domain.TestDO" />
<resultMap id="TestResult" class="testDO">
<result property="id" column="ID" />
<result property="desc" column="DESC" />
<result property="version" column="VERSION" />
</resultMap>
<update id="updateItem" parameterClass="testDO">
<![CDATA[UPDATE TEST_TABLE]]>
<dynamic prepend="SET">
<isNotEmpty prepend="," property="desc">
<![CDATA[DESC = #desc,handler=com.ibatis.sqlmap.engine.type.ClobTypeHandlerCallback#]]>
</isNotEmpty>
<isNotEmpty prepend="," property="version">
<![CDATA[VERSION = #version#]]>
</isNotEmpty>
</dynamic>
<![CDATA[WHERE id = #id#]]>
</update>
</sqlMap>
对于insert同样的方法处理,我测试的项目中select语句没有报错,如报错select中比较直观,可以按如下方法
<result property="desc" column="DESC" typeHandler="com.ibatis.sqlmap.engine.type.ClobTypeHandlerCallback"/>
本人测试环境:
iBatis: ibatis-sqlmap-2.3.0
Spring:Spring-*-3.0.5.REALEASE
Oracle: oracle11g
Oracle驱动: ojdbc14