技术开发 频道

在ORACLE里调用JAVA程序解决 utl_file 错误



    【IT168 专稿】本文描述在一次工作中,遇上输出文本时,需要去掉UTL_FILE包输出时带回车换行符的问题。在经过多次思考努力后,最后找到在ORACLE里调用JAVA来替代UTL_FILE包输出数据的方法。完满地解决多出的回车换行符问题的过程。

    那是几年前的事了,当时我新进一家新成立的港资公司工作,公司的主营业务是面向香港,承接外包服务。

    刚进公司不久,领导就交给我们(几个搞ORACLE的)一任务:从一接口库获取数据,按业务要求对数据进行筛选过滤后,生成文本输出到指定的目录。 分析清楚用户需求后,感觉很简单,没花多少工夫,便把程序写好,输出时使用了ORACLE自带的UTL_FILE包; 按流程,让另外几位同事作了测试后连源码一起提交给客户。 但没过一个礼拜,客户便反馈,程序没问题,但输出/生成的文本的最后一行自动带了回车符,文件的结尾不是最后一个字符,而是新起一空行,他们不接受这样的结果,要求我们删除最后一个换行符,一个礼拜后交货。  接到反馈后,我便和几个同事研究,除了反复测试输出过程UTL_FILE. PUT_LINE外,还测试了NEW_LINE和PUT 过程,但结果都PUT_LINE一样,无法避免回车符。于是上网寻求解决方法,当时网络上关于ORACLE的技术论坛还不是很热,同事在ORACLE的TOM论坛查到类似的需求,结果,我记得TOM当时的答复是:ORACLE承认那是一个BUG,但还没修复。当时我是公司ORCLE技术的主力,解决问题的重任落在我身上。既然在网上找不到解决方法,唯有自己思考:

    回忆起2000年初研究ORACLE8新特性时,我发现该版本有一新功能,叫外部过程,即,可在ORACLE里调用操作系统层的共享库。大概流程是,监听器里配置调用外部动态库的监听配置,在数据库里定义好和外部共享库接口的存储过程,当运行存储过程时,其将驱动监听分裂出(EXPORTC)进程去调用该共享库。看到该功能后,我尝试在WINDOWS环境测试该功能:请了同事帮忙用VC写了个共享库(动态链接库),然后在库里测试调用,经过几轮调试,果然可以按照指定的目录,文件名,生成输出文件内容:HELLO WORLD!这是我当时首先考虑到的可采用的技术。当时想着:文件输出结束后,紧接着调用动态库来复制该文件的内容,末尾截掉文件的回车换行符,生成新的文件,应该可行。但再思考时,感觉方法不是很爽,好像心口有东西被堵,具体又说不出来哪不对劲,于是放下这想法,先忙别的,让这思想酝酿酝酿。

    下午忙完了,又思考回这个问题,过了一会想明白了,是复制过程太浪费,没必要;而且初始文件在命名时可能会遇上如何保证唯一文件名的问题。想想,何必复制,若能用程序把最后那个回车符给删除调,不更快捷!随即问了旁边写C的同事,能否删除指定文本的最后的回车符?他问清楚我的想法后想了想,肯定答复。

    至此,我想,技术问题已经解决,正要整理思路和小组成员讲解,看看他们的意见。突然想起,客户说过,他们使用的是HP服务器,而我们没有此测试环境,无法编写测试;紧接着又发现我遗漏了一个重点:客户是香港公司,我们无法远程连接给客户的数据库服务器配置监听器,也不太可能为了采用这个方法,跑到客户公司去配置监听器,何况,他们不一定允许我们操作。想到这里,明白刚产生的思路不切可行。

    第2天工作之余继续思考该问题,不知过了多久,也不知是苦尽甘来,还是百无聊赖之际,突然想起:ORACLE不是到处大肆宣扬,从ORACLE8I开始(客户的数据库环境817),Oracle Java虚拟机是全面兼容Java运行环境。也就是说,JAVA程序可以放到ORACLE数据库里运行。嗯,能否这样:用JAVA编写个程序,用于删除指定文件最后的回车换行符,装载到数据库中,在数据输出完后调用该程序来允许即可。想到这里,我喜出望外,立即把想法和写JAVA的同事说明,同事表示这操作很简单。和小组成员解释,他们也表示可以尝试一下。喜庆之余,立即开始着手处理技术问题:

    首先遇上的问题是,如何判断ORACLE数据库里安装了JVM(由于删除字符操作很简单,我想没必要询问需要安装第几版本才能支持此操作)?其次,如何装载和卸载JAVA程序?很快,从ORACLE的官方文档上找到这两个问题的帮助信息,测试一下,没问题; 再就是,如何创建存储过程,使之和指定的JAVA程序关联起来,即调用该存储过程来运行JAVA程序。在ITPUB上询问,果然,有热心人士指点,和另外一位同事一起,经过几番测试,思考,询问,再测试,也获成功;紧接着是要操作的文件路径的问题。大家知道,ORACLE在操作系统的文件时,需要事先设置参数UTL_FILE_DIR为要操作的文件路径,才能操作该目录下的文件,不知道JAVA在读写文件时,是不是也得这样设置?再次询问,也得到了热心人士的帮助。

    用JAVA程序删除字符的要求很简单,同事早已写好程序并且在WINDOWS 环境下测试成功,只待我解决上述的技术问题。忙碌了一个下午后,在逐步实现ORACLE里调用JAVA的过程中,也逐步解决了实现该应用需要准备的技术细节问题。万事具备后,把程序LOAD进数据库后,运行发现报错:

//一段代码样例 import java.io.*; public class DeleteCRLF { public DeleteCRLF() { } public static void delete(String fileName) throws java.io.IOException{ String errMsg = ""; try { File f = new File(fileName); if (!f.exists()) throw new java.io.IOException("File not found!"); if (!f.canRead()) throw new java.io.IOException("File can not be read!"); if (!f.canWrite()) throw new java.io.IOException("File can not be write!"); RandomAccessFile raf = new RandomAccessFile(f, "rw"); if (raf.length() >= 2) { //throw new java.IOException("File greater than 2."); raf.seek(raf.length() - 2); byte b1 = raf.readByte(); byte b2 = raf.readByte(); //short s = raf.readShort(); //if (s == 0x0D0A) // raf.setLength(raf.length() - 2); if (b1 == 0x0D && b2 == 0x0A) { raf.setLength(raf.length() - 2); throw new java.io.IOException("The last byte is: " + b1 + ", " + b2); } throw new java.io.IOException("The last two byte is: " + b1 + ", " + b2); } raf.close(); } catch (SecurityException se) { throw new IOException("Security violation for access the file!"); } catch (IOException ie) { throw new IOException(ie.getMessage()); } catch (Exception e) { throw new IOException(e.getMessage()); } } } ==程序编译正常,如: D:\Oracle\Ora81\Apache\jdk\bin>javac d:\tool\DeleteCRLF.java 执行正常 ==已经删除掉文件末端的两个字符 D:\Oracle\Ora81\Apache\jdk\bin>java -cp d:\tool DeleteCRLF The last byte is: 13, 10 D:\Oracle\Ora81\Apache\jdk\bin> ==LOAD入数据库正常 D:\Oracle\Ora81\bin>loadjava -user scott/tiger d:\tool\DeleteCRLF.class D:\Oracle\Ora81\bin> SQL> begin 2 DeleteCRLF('D://TEMP//AE_TXT.TXT'); 3 end; 4 / begin ERROR at line 1: ORA-29532: Java call terminated by uncaught Java exception: java.io.IOException: The last byte is: 13, 10 ORA-06512: at "SCOTT.DELETECRLF", line 0 ORA-06512: at line 2 }



    再上网请教,等了近一个下午,也没见有人解答,大家很郁闷,我也很着急,眼看就要成功了,却迈不过最后这关键的一步! 过了一会,静下心想,在ORACLE里调用JAVA程序是个新技术,本来懂得应用的人就不多,再加上可能是这个问题太具体了,不代表广泛的应用,意义不大,别人也不愿意花精力去研究;而自己对该应用也是新手,急切之间,也没法解决!!!再思考吧!

    不知什么时候,恍惚间,突然醒悟,为什么得用程序去删除回车符呢,问题源头在于UTL_FILE输出后自带了回车换行符;既然JAVA可以删除文件的字符,当然也可以输出数据,生成文件;这样,不用该包,改用JAVA程序来输出,不就可以避开该BUG了吗!再仔细想想,数据筛选过滤完后,可以事先保存到一临时表中,待此作完成后,用JAVA程序读取输出,生成指定的文件。想到这里,我立即和同事说了该想法,同事了解后立即表示,这很简单!

    真是苦尽甘来,之后事情进展很顺利:同事很快改写了程序,测试后,果然一切OK。转发给客户,和他说明,为克服该BUG,采用JAVA方法输出的方法,多建了个临时表。客户检查后,表示认可。至此,问题在客户反馈的第4个工作日后完全解决!

    回顾这次解决问题的经历:用JAVA实现该功能,很简单。问题的关键在于:如何思考到,采用该方法,绕开UTL_FILE包产生的”BUG”,来解决输出问题。看似简单的思路,其实每往前走一步,都是在上一步骤失败后苦苦思考的结果。用爱迪生的话说,天才就是99%的汗水加1%的灵感也不为过!很庆幸自己在“紧张”时刻,能够获得这份灵感,由原来动态连接库,思考到改用JAVA方法,再改思路,跳出了固定思维:用UTL_FILE包来输出数据的局限,改用JAVA程序输出,最终成功。由于思考过程辛苦,思路连连转下,给我留下了深刻的影响,所以事情即使过了几年,我依旧清晰记得当时这个冥思苦想,以及苦尽甘来的结果。

0
相关文章