利用ORACLE JAVA存储过程实现BLOB图片导出

原文地址:http://fengwen.iteye.com/blog/580323

问题:一个XX收单系统,需要的交易情况统计来自另外一套系统,以图片方式进行存储和导出查看.图

 

片以BLOB类型直接存在数据库中.本来以为就是个JAVA操作LOB的问题,但是因为是2次开发,所以必须遵循以前的规定,即所

 

有业务由存储过程实现.他们的选择是用DBMS_LOB和UTL_FILE包,导入图片到数据库没问题,但是在导出时始终不能正确

 

显示,发现导出后文件的大小与导入的图片不一致.

 

其实这又是9i的一个老问题了(为什么我又要说又呢),在Oracle DBA Tips Corner 中说明如下:

It should be noted that in Oracle9i this PL/SQL procedure does not work with all binary files. This is due to Oracle bug (BUG#: 2883782). The PL/SQL procedure that I use to write binary (raw) data out is UTL_FILE.PUT_RAW. This procedure, along with UTL_FILE.GET_RAW, was introduced in Oracle 9i Release 2 as previous versions of UTL_FILE only worked with TEXT files.

In Oracle9i there is currently a restriction of a maximum of 32k that can be written with PUT_RAW unless you insert new line characters in between the data. In Oracle10g there is a new binary mode. When files are opened with this mode, "wb", any amount of raw data can be written without the need for new lines. In short, this is a bug that can bite you if your binary files do not have a new line character within the RAW data in your MAX_LINESIZE buffer. If you do get bit by this bug in Oracle9i, there is no solution that I have found other than writing it in Java.

When I originally started writing and testing the procedure, I was using a fairly small image file (~ 1KB). This worked fine. I later tested a large PDF file (~ 3MB) and it failed (after writing only a small portion of the file) with the following exception:

BEGIN Write_BLOB_To_File; END;
*
ERROR at line 1:
ORA-29285: file write error
ORA-06512: at "SYS.UTL_FILE", line 18
ORA-06512: at "SYS.UTL_FILE", line 1007
ORA-06512: at "SCOTT.WRITE_BLOB_TO_FILE", line 74
ORA-06512: at line 1

Again, using the new binary write mode in Oracle10g when opening binary files should fix this.

 

大意如下:fopen如果用w模式打开,put_raw会自动加回车,哪怕只写一个字符,系统都会把换行符0A转换为0D0A,因为windows是以0D0A为换行符的,也就是说会自动加回车.而在10g中用wb模式打开,是以二进制方式打开,这种方式不会进行”回车符”和”换行符”的转换.也就是说,在9i中,读写2进制文件存在着问题....

 

  很可惜的是:

   1.不可能因为这1个功能就进行数据库地升级,9i也确实是一个BUG满天飞的版本......

   2.由于是2次开发,限制使用存储过程实现,因此无法使用纯JDBC或者spring的OracleLobHandler

 

  考虑了下,我的解决方案如下:

  1.利用一些外部工具,比如lobs_win32.exe(http://www.dbatools.net/software/lobs.zip)实现LOB操作,操作简单,但是不利于与当前项目集成,并且也有基于OCI方式的限制

  2.实际上PL/SQL对IO操作的支持并不好(9i还有个版本会因为参数utl_file_dir设置无效而导致pl/sql无法访问文件系统),而JAVA的IO操作则丰富得多.那么,我们为何不取长补短呢?也就是编写JAVA存储过程,利用简单易用的IO操作来代替PL/SQL中的程序包.

 

=====================我是分割线========================

 

  首先创建测试表,触发器以及对应的DIR:

Sql代码 复制代码  收藏代码
  1. CREATE TABLE IMAGE_LOB    
  2. (I_ID NUMBER PRIMARY KEY NOT NULL,   
  3. I_IMG BLOB NOT NULL);   
  4.   
  5. CREATE OR REPLACE TRIGGER tri_img   
  6. BEFORE INSERT ON fw.image_lob   
  7. FOR EACH ROW   
  8. BEGIN  
  9.   SELECT fw.se_test.NEXTVAL INTO :NEW.I_ID FROM dual;   
  10. END;   
  11.   
  12. CREATE OR REPLACE DIRECTORY DIR_IMAGES AS 'C:\picture';  

 

  接下来编写导入图片的过程:

Pl/sql代码 复制代码  收藏代码
  1. CREATE OR REPLACE PROCEDURE P_IMG_INSERT (v_filename VARCHAR2)   
  2. IS   
  3.   v_bfile BFILE;--文件指针   
  4.   v_blob BLOB;   
  5.   DIR CONSTANT VARCHAR2(20) := 'DIR_IMAGES';--文件存放DIRECTORY   
  6. BEGIN   
  7.   /*通过empty_blob()函数将类型为blob的列初始化为空以便以后填充*/   
  8.   INSERT INTO fw.image_lob (I_IMG)   
  9.   VALUES (EMPTY_BLOB ()) RETURN I_IMG INTO v_blob;   
  10.   
  11.   v_bfile:= BFILENAME (DIR, v_filename);--获得定位器指向的目录和文件   
  12.   IF (dbms_lob.fileexists(v_bfile)!=0) THEN --如果文件定位器指向的文件存在   
  13.     dbms_lob.fileopen(v_bfile,dbms_lob.file_readonly); --打开目标文件   
  14.   
  15.     /*将文件字数据加载到指定的LOB类型变量*/   
  16.     dbms_lob.loadfromfile(v_blob,v_bfile,dbms_lob.getlength(v_bfile));   
  17.   
  18.     dbms_lob.fileclose(v_bfile);--关闭文件   
  19.     COMMIT;   
  20.     dbms_output.put_line('已经从'||DIR||'目录中读取了图片'||v_filename||'向表中插入');   
  21.   
  22.   ELSE--如果文件定位器指向的文件不存在   
  23.     dbms_output.put_line('文件没找到');   
  24.   END IF;   
  25.   EXCEPTION WHEN OTHERS THEN   
  26.   dbms_output.put_line(SQLERRM);   
  27. END;  

 

  测试插入:

Sql代码 复制代码  收藏代码
  1. SQL> set serveroutput on  
  2. SQL> exec fw.p_img_insert(v_filename => '1.JPG');   
  3.   
  4. 已经从DIR_IMAGES目录中读取了图片1.JPG向表中插入   
  5.   
  6. PL/SQL procedure successfully completed  

 

  现在表格中数据如下:

Sql代码 复制代码  收藏代码
  1. SQL> select * from fw.image_lob;   
  2.   
  3.       I_ID I_IMG   
  4. ---------- -----   
  5.         21 <BLOB  

  

 

  接下来是重点,加载并编译AVA程序,很简单地IO操作:

Pl/sql代码 复制代码  收藏代码
  1. CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "sp_exp_blob" AS   
  2. package util;   
  3.   
  4. import java.io.FileOutputStream;   
  5. import java.io.OutputStream;   
  6.   
  7. import oracle.sql.BLOB;   
  8.   
  9. public class OracleBlobUtil {   
  10.        
  11.         public static void exp(BLOB blob,String expDir) throws Exception{   
  12.             byte[] bt = blob.getBytes(1, (int)blob.length());   
  13.             OutputStream os = new FileOutputStream(expDir);   
  14.             os.write(bt);   
  15.             os.flush();   
  16.             os.close();   
  17.     }   
  18. }  

 

  创建对应的过程:

 

Pl/sql代码 复制代码  收藏代码
  1. CREATE OR REPLACE PROCEDURE p_exp_blob(v_blob BLOB,v_exp_dir VARCHAR2)   
  2. AS LANGUAGE JAVA NAME 'util.OracleBlobUtil.exp(oracle.sql.BLOB,java.lang.String)';  

 

  测试导出功能:

Pl/sql代码 复制代码  收藏代码
  1. DECLARE   
  2.   v_blob fw.image_lob.i_img%TYPE;   
  3. BEGIN   
  4.   SELECT i_img INTO v_blob FROM fw.image_lob WHERE i_id=21;   
  5.   <STRONG><SPAN style="COLOR: #0000ff">dbms_java.grant_permission( 'FW''SYS:java.io.FilePermission''c:/picture/1_exp.jpg''write' );</SPAN></STRONG>   
  6.   fw.p_exp_blob(v_blob => v_blob,v_exp_dir => 'c:/picture/1_exp.jpg');   
  7. END;  

 

   成功的话将在c:/picture下看到1_exp.jpg.

   注意兰色这行代码,是设置对文件的写的权限,如果没有的话,会出现java.security.AccessControlException: the Permission.....,可以参看老外的这篇帖子http://cn.forums.oracle.com/forums/thread.jspa?threadID=832298&tstart=0&messageID=3149561#3149561.更进一步的信息,可以查询OracleJava安全方面的文档.

  

  当然也可以单独地对用户进行权限授予

  

 

Sql代码 复制代码  收藏代码
  1. SQL> connect sys/is311027@feng as sysdba;   
  2. Connected to Oracle9i Enterprise Edition Release 9.2.0.1.0    
  3. Connected as SYS   
  4.   
  5. SQL> call dbms_java.grant_permission( 'FW''SYS:java.io.FilePermission''c:/picture/1_exp.jpg''write' );   
  6.   
  7. Method called  

 

 

  最后总结如下,利用从8i开始就引入的JVM,我们可以利用JAVA和PL/SQL进行互补.利用JAVA的各种优点来扩展数据库应用

 

的功能,并且充分利用高效的内存回收技术和线程管理方面的能力,而不再是局限于PL/SQL.而正如本文中的例子,PL/SQL

 

Java 可以在同一个应用中和谐共处,2者是可以共存的.同时使用两个世界最好的方法,让我们能够开发出更好的数据库应

 

用.

你可能感兴趣的:(java,oracle,exception,存储,insert,oracle10g)