java.sql.SQLException: ORA-01461: can bfor insert into a LONG column
1.发现系统报此问题的第一反应是插入数据时,数据长度超出数据库的字段长度。
2.而根据错误日志不难分析出是向邮件发送信息表中插入数据时,数据超过定义的最大值限制了,但到底是哪个字段,需进一步看一下代码和日志。
3.根据错误日志的时间找到跟踪日志,找到组装邮件DTO的数据,一个一个排查,最终发现只可能是邮件内容超出长度了。
但邮件内容是有长度判断与处理的(即:判断是否超过数据库字段所设置的4000长度,如果超过了,进行截取处理)
String emailContent = null; if(mailContent != null){ emailContent= mailContent.buildMailContent(args); } if(emailContent!=null&&emailContent.length()>4000){ emailContent=emailContent.substring(0, 4000); } mailDTO.setEmailContent(emailContent);
进一步分析,发现邮件内容的组装有html标签及样式的定义;而查看跟踪日志,发现’<’被替换为’<’、’>’被替换为’>’,于是我就主观的认为是在保存至数据库时,'<'、'>'标签被转义处理(即’<’变成了’<’,仅此一个’<’的长度由1变成了4)。
后来,将跟踪日志中的邮件内容拷出,将其全部’<’替换为’<’、’>’替换为’>’,并将内容中的单引号再加一个单引号的转义处理,通过PL/SQL更新开发环境里的一条记录,报超出长度错误提示。经一步步减少字符长度,最终发现:“将内容去掉6个汉字或11个英文字母,都能正常更新成功”。按上面的在保存至数据库时,'<'、'>'标签被转义处理的原因推测,减少6个汉字或11个英文字母,应该还是会超出长度。
于是,我将邮件内容中的所有’<’替换为’<’、’>’替换为’>’ ,在UltraEdit-32中发现其长度是4011(正好符合上面更新SQL 减少11个英文字母能正常更新),此时,想到原来代码扫描时的getByte()方法,有字符编码格式的问题。
进一步,我将’<’替换为’<’、’>’替换为’>’的邮件内容字符串进行了如下测试:
//全部’<’替换为’<’,全部’>’替换为’>’ String res3 = "<html><style>th , td{font-size: 13px;color: #4f5b75;font-weight: bold;border: 1px solid #95a7bf;border-collapse: collapse;padding: 4px;height: 22px;}</style><body>尊敬的XXX:<br /> 您好!<br /> ... 此处内容省略 ... </body></html>"; System.out.println("res3:" + res3.length()); //结果是:3801 byte[] b_str = res3.getBytes(); System.out.println("b_str:" + b_str.length); //结果是:4011
分析到这里,我们应该知道原因了。
原因是:
str.length()返回此字符串的长度。长度等于字符串中 Unicode 代码单元的数量。即不论是中文还是英文,都是按照1个长度来看待的,而不是根据所占的字节数来计算length长度。
而我们的ORACLE数据库存储格式应该是UTF-8或GBK之类(肯定不是JAVA中的Unicode 代码单元的数量)。
//全部’<’替换为’<’,全部’>’替换为’>’ String res3 = "<html><style>th , td{font-size: 13px;color: #4f5b75;font-weight: bold;border: 1px solid #95a7bf;border-collapse: collapse;padding: 4px;height: 22px;}</style><body>尊敬的XXX:<br /> 您好!<br /> ... 此处内容省略 ... </body></html>"; System.out.println("res3:" + res3.length()); byte[] b_str = res3.getBytes(); System.out.println("b_str:" + b_str.length); try { byte[] b_str3 = res3.getBytes("GBK"); System.out.println("GBK b_str3:" + b_str3.length); //结果:4011 byte[] b_str2 = res3.getBytes("UTF-8"); System.out.println("UTF-8 b_str2:" + b_str2.length); //结果:4221 } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); }
发现用GBK编码解析这串字符串长度正好是4011。据此反推:我们的数据库的编码格式是GBK。
简单来说,系统在组装邮件对象后对邮件内容长度进行判断(长度是3801)是满足小于4000的条件的,而到后台保存至数据库时,由于数据库是GBK编码的,按GBK编码,其长度为4011,超出数据库字段的定义长度4000。所以报“ORA-01461: can bfor insert into a LONG column”错误。
解决方法:
将原来的如下校验
if(mailContent != null){ emailContent= mailContent.buildMailContent(args); }
改为:
if(emailContent != null) { byte[] bytes = emailContent.getBytes("GBK"); if(bytes.length > 4000) { int tempLen = new String(bytes, 0, 4000, "GBK").length(); //根据tempLen长度截取原字符串 emailContent = emailContent.substring(0, tempLen); } }