iBatis操作CLOB类型字段时报setString can only process strings of less than 32766 chararacters

最近生产环境上因数据库某一字段类型由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)就去里,找着如下代码:

iBatis操作CLOB类型字段时报setString can only process strings of less than 32766 chararacters

这里应该是获取对应的类型转换器,继续跟踪到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