APEX:从临时表下载BLOB

时间:2014-04-08 14:46:22

标签: oracle plsql oracle-apex

我正在尝试使用Oracle APEX 4.1.1构建一个简单的查看应用程序。要显示的信息位于不同数据库的表中,然后是包含APEX应用程序访问的模式的数据库。使用View( RemoteTableView )和Database Link访问此远程表。 View按预期工作,包括Oracle通过数据库链接选择LOB列项目的残疾。

在APEX应用程序中,我根据Oracle Application Express Advanced Tutorials中的说明定义了一个程序( DownloadFiles ),只要需要下载View中的BLOB,就会运行该程序。 p>

当APEX应用程序构建在包含BLOB项目的现有表上时,这是完美的,没有问题。

但是,在 RemoteTableView 上,该过程略有不同。其他代码行添加到 DownloadFiles 过程中,每当调用视图中的项目进行下载时,将 RemoteTableView 中的实际BLOB插入临时表(< EM>不是Temptable )。然后在 TempTable 上调用 DownloadFile 以下载(现在本地存储的)BLOB。 (这样做是为了绕过通过DB-Link直接选择LOB项目)。没有COMMIT。

不幸的是,无论何时调用项目下载时,APEX应用程序都会失败,并且#34;找不到此网页。没有找到网址的网页:... / f?p = 101:7:1342995827199601 :: NO :: P7_DOC_ID:3001&#34;。

对这个问题的研究已证明毫无结果。插入过程按预期工作(在PL / SQL Developer中),任何其他本地表中的任何其他BLOB都可以轻松下载。

因此问题是,为什么APEX应用程序无法处理这种情况。使用临时表或插入我应该注意的语句时是否有限制?此外,下载LOB对象的最佳做法是什么。

详细说明插入行和下载BLOB的过程。 (我尝试过不同的方法)。这个PL / SQL块在标题&#39;之前被加载调用:P2_BLOB_ID用标识符列值填充到BLOB列。

DECLARE
  v_mime      VARCHAR2(48);
  v_length    NUMBER(38);
  v_file_name VARCHAR2(38);
  Lob_loc     BLOB;
BEGIN
  DELETE FROM [TemporaryTable];
  --
  INSERT INTO [TemporaryTable]( [attr1]
                              , [attr2]
                              , [blob]
                              , [mime] )
  SELECT [attr1]
  ,      [attr2]
  ,      [blob]
  ,      [mime]
  FROM   [RemoteTableView]
  WHERE  [attr1] = :P2_BLOB_ID
  AND    ROWNUM  = 1;
  --
  SELECT [mime]
  ,      [blob]
  ,      [attr1]
  ,      DBMS_LOB.GETLENGTH( [blob] )
  INTO   v_mime
  ,      lob_loc
  ,      v_file_name
  ,      v_length
  FROM   [TemporaryTable]
  WHERE  [attr1] = :P2_BLOB_ID;
  --
  owa_util.mime_header( nvl(v_mime,'application/octet'), FALSE );
  htp.p('Content-length: ' || v_length);
  htp.p('Content-Disposition:  attachment; filename="'||replace(replace(substr(v_file_name,instr(v_file_name,'/')+1),chr(10),null),chr(13),null)|| '"');
  owa_util.http_header_close;
  wpg_docload.download_file( Lob_loc );
END;

3 个答案:

答案 0 :(得分:3)

尝试在wpg_docload电话后添加apex_application.stop_apex_engine。这将避免进一步输出HTTP标头,从而可能会导致下载,因为会生成更多的顶点代码。

  owa_util.mime_header( nvl(v_mime,'application/octet'), FALSE );
  htp.p('Content-length: ' || v_length);
  htp.p('Content-Disposition:  attachment; filename="'||replace(replace(substr(v_file_name,instr(v_file_name,'/')+1),chr(10),null),chr(13),null)|| '"');
  owa_util.http_header_close;
  wpg_docload.download_file( Lob_loc );
  apex_application.stop_apex_engine;

此外,详细说明:

  

使用临时表或插入时是否存在限制   我应该注意哪些陈述?

是。但不一定在你的情况下。重要的是要记住apex在数据库会话方面的工作原理。 Apex是无状态的,可以使用连接池。顶点会话通常与1个数据库会话不匹配,并且您永远不能保证,例如,在渲染和处理之间使用相同的数据库会话。在Understanding Session State Management的文档中也简要提到了这一点,为方便起见而复制了

  

HTTP,最经常传递HTML页面的协议   无国籍协议。 Web浏览器仅连接到服务器   只要下载一个完整的页面。另外,每个   页面请求被服务器视为独立事件,   与先前或可能发生的任何页面请求无关   发生在将来。访问在a上的一个页面上输入的表单值   后续页面,值必须存储为会话状态。神谕   Application Express透明地维护会话状态并提供   能够从中获取和设置会话状态值的开发人员   应用程序中的任何页面。

     

2.4.1什么是会话?

     

会话是一种建立持久性的逻辑结构(或   跨页面视图的有状态行为。每个会话都分配了一个   唯一标识符。 Application Express引擎使用此标识符   (或会话ID)来存储和检索应用程序的工作集   每个页面视图之前和之后的数据(或会话状态)。

     

因为会话完全相互独立,任何数字   会话可以同时存在于数据库中。用户可以   同时运行应用程序的多个实例   不同的浏览器。

     

会话在逻辑上和物理上与Oracle数据库不同   用于为页面请求提供服务的会话。用户在中运行应用程序   从登录到注销的单个Oracle Application Express会话   以分钟或小时计算的典型持续时间。 每页   在该会话期间请求导致Application Express   引擎创建或重用Oracle数据库会话来访问   数据库资源。这些数据库会话通常只是一小部分   一秒钟。

对于全局临时表,这意味着在许多情况下使用它是没有意义的,因为数据将仅存在于当前数据库会话中。这方面的一个例子是,人们可以在onload中的某个位置加载GTT中的数据,并且意味着在提交后的进程或ajax调用中使用它。桌子空置的可能性很大。
然而,Apex提供了apex_collection形式的替代方案,它将在给定的顶点会话中临时保存数据。

答案 1 :(得分:1)

使用Oracle PL / SQL远程查询数据库链接中的CLOB或BLOB数据

在Oracle 11gR2实例上开发并测试了以下解决方案。 APEX仅用于通过不同的中间对象存储或传递的数据值的可视化。对于具有远程共享连接(例如 Oracle DBLinks )的APEX数据库实例的应用程序开发,此解决方案仍然有用。

  

INTRO: OP的解释中有一些漏洞,但我假设使用术语,例如&#34; Remote Table&#34;和&#34;数据库链接&#34;表示OP正试图绕过已知的Oracle RDBMS限制。

     

此限制是无法跨远程dblink连接查询LOB类型的数据。尝试此操作时遇到的常见错误是:ORA-22992: cannot use LOB locators selected from remote tables

研究与参考

  • 关于以下内容的博文:Querying CLOB or BLOB Data Across DBLinks这是我用来开发漫游的主要参考资料......这只是我努力验证此链接上的代码是否真正有用的回顾(它确实!)。

  • 来自PSOUG.org的参考文献:Reference on the DBMS_LOB Oracle package。用于验证我对CLOB和LOB数据类型处理的理解。

  • 特别提及:watchout4snakes。这是一个在线随机字典工具。我在这里使用它来制作大块的文字,这些文字并没有让人眼前一亮,例如:lorum ipsum locati quorum...:)

  

如果您查看此列表中的第一个引用,您可能会注意到我的代码非常相似。 我在2010年重新展示此案例时添加的价值是创建一个更大但更有趣的测试数据集。我还添加了一些原始调整帮助验证方法的解决方案。

再注意一下:在深入了解示例之前,请务必映射(至少在您的脑海中)每个&#34;远程&#中创建的对象的架构/所有者34;和#34;本地&#34;你的演示实例...它将缓解这种非常直接的解决方案所遇到的悲伤程度。

在REMOTE SERVER上设置演示

以下是设置REMOTE DATABASE INSTANCE

的源代码

远程实例的数据库对象类型

 CREATE TYPE object_row_type AS OBJECT (
    MY_ORDER   NUMBER,
    MY_ID      NUMBER,
    MY_CLOB_AS_VARCHAR   VARCHAR2(4000));

 CREATE TYPE object_table_type AS TABLE OF object_row_type;

表格DDL和测试数据脚本

 CREATE TABLE REMOTE_CLOB_TABLE (
    ID       NUMBER      NOT NULL,
    MY_CLOB  CLOB
 );


 INSERT INTO REMOTE_CLOB_TABLE(id, my_clob) 
    VALUES(1001, 'When can the expenditure enter behind a shock recovery? The strategy fishes underneath the sugar. An after wrap masters a slim moron. The twenty dish hunts an aunt opposite the credible zone. The phoenix copes. The diagonal flours a bag against the positive fan.');

 INSERT INTO REMOTE_CLOB_TABLE(id, my_clob) 
    VALUES(2001, 'A traveled concept hides a removed skin. A liquid steers whatever understandable heart. A curve strips away an assembly. A wartime freezes the outcome.');

 INSERT INTO REMOTE_CLOB_TABLE(id, my_clob) 
    VALUES(3001, 'A lonely genre bolts beside the obliging prisoner. The freedom stamps! Its game fluid dictates. How will her power imagine the quantum?');

 INSERT INTO REMOTE_CLOB_TABLE(id, my_clob) 
    VALUES(4001, 'The young office caps the travelled temper. A forum husbands the family. The detail peers. Her jammed agenda experiments against the regarding obstruction.');

 INSERT INTO REMOTE_CLOB_TABLE(id, my_clob) 
    VALUES(5001, 'The incredible drivel suspects. A vehicle reads. A cardboard jacket shares the insult above the baking constitutional. Outside this effort composes the invited jest.');

 INSERT INTO REMOTE_CLOB_TABLE(id, my_clob) 
    VALUES(6001, 'The vegetarian strength marches underneath an opposing intellectual. The ringed lifestyle bends the archaic thirst. A saga escapes with the ego. The acorn escapes against the old lark.');

 INSERT INTO REMOTE_CLOB_TABLE(id, my_clob) 
    VALUES(7001, 'Whatever bear furthers a mania. The norm contours a ruin. How can the reasoned composite cough? With a unimportant timetable reasons the sorry frog. Can the key jacket toss an author?');

 COMMIT;

功能对象脚本

 CREATE or REPLACE FUNCTION CONVERT_CLOB_TO_VARCHAR RETURN object_table_type PIPELINED
    IS 

 PRAGMA AUTONOMOUS_TRANSACTION;

    v_clob_length   number;
    v_loops         number;
    v_varchar_size  number := 100; 
    -- purposely set to a small value to see the looping step work
    -- normally set to max varchar2 size (4000 BYTE).

 BEGIN

 FOR cur IN (SELECT id, my_clob from remote_clob_table)  
 LOOP

       v_clob_length := dbms_lob.getlength (cur.my_clob);
       v_loops := trunc(v_clob_length/v_varchar_size) + 
          sign (mod(v_clob_length, v_varchar_size )) - 1;


    FOR i IN 0..v_loops
    LOOP

       -- This chunks the CLOB/LOB file from beginning to end in chunks
       --   of pre-designated size.

       PIPE ROW(object_row_type(i+1, cur.id, dbms_lob.substr(cur.my_clob,
          v_varchar_size, v_varchar_size * i + 1 )));

    END LOOP;


 END LOOP;

 COMMIT;
 RETURN;

 END;

用于呈现可远程访问的转换CLOB数据的VIEW对象:

 CREATE or REPLACE VIEW myremotedata AS
    SELECT * FROM TABLE(CONVERT_CLOB_TO_VARCHAR) a;

在LOCAL SERVER上设置演示

以下是设置LOCAL DATABASE INSTANCE

的源代码

建立到远程服务器的数据库链接:

 CREATE PUBLIC DATABASE LINK MY_REMOTE_CONNECTION
    CONNECT TO REMOTE_USER
    IDENTIFIED BY <PWD>
    USING <TNS or DIRECT CONNECTION STRING>

声明自定义对象类型:

 CREATE OR REPLACE TYPE MY_TABLE_TYPE AS TABLE OF VARCHAR2(4000);

用于将VARCHAR片段重新组合为CLOB格式的PL / SQL函数

 CREATE OR REPLACE FUNCTION F_VARCHAR_TO_CLOB (input_table_of_varchar  my_table_type)
    RETURN CLOB IS
    PRAGMA AUTONOMOUS_TRANSACTION;

    v_clob   clob;

 BEGIN
    FOR i in 1..input_table_of_varchar.COUNT
    LOOP

    v_clob := v_clob || input_table_of_varchar(i);

    END LOOP;

 RETURN v_clob;

 END;

查看从数据库链接收集结果的对象

 CREATE OR REPLACE VIEW MY_REMOTE_DATA AS

    SELECT a.id,
      f_varchar_to_clob( 
         CAST( MULTISET( SELECT b.MY_CLOB_AS_VARCHAR
                           FROM remote_user.myremotedata@my_remote_connection b
                          WHERE a.id = b.my_id
                          ORDER BY b.my_id ASC, b.my_order ASC ) 
                                     as my_table_type)

                     ) MY_CLOB

 FROM REMOTE_CLOB_TABLE@my_remote_connection a;

输出结果

从本地实例远程查询的数据显示在两个报告中。第一部分展示了CLOB数据类型如何被消化成更小的部分。每件的最大尺寸实际上是可变的。我选择了比最大4,000字节varchar2更小的尺寸,这样你就可以看到一个稍微小一点的CLOB数据值,它被分成许多部分。

从远程实例的视图输出:MYREMOTEDATA

Remote DB Link Query for Converted CLOB Data

这是使用CONVERT_CLOB_TO_VARCHAR PL / SQL函数创建的。

从本地实例的视图输出:(以CLOB格式重新组合数据)

select id, my_clob from my_remote_data where id between 1000 and 5000

ID MY_CLOB

1001 When can the expenditure enter behind a shock recovery? The strategy fishes unde rneath the sugar. An after wrap masters a slim moron. The twenty dish hunts an a unt opposite the credible zone. The phoenix copes. The diagonal flours a bag aga inst the positive fan.
2001
A traveled concept hides a removed skin. A liquid steers whatever understandable heart. A curve strips away an assembly. A wartime freezes the outcome.
3001
A lonely genre bolts beside the obliging prisoner. The freedom stamps! Its game fluid dictates. How will her power imagine the quantum?
4001
The young office caps the travelled temper. A forum husbands the family. The det ail peers. Her jammed agenda experiments against the regarding obstruction.

4 rows selected.

查询VARCHAR数据后,这是将它们全部重新连接回CLOB格式的视图。

<强>结论:

这是一种概念验证方法,说明了在尝试通过数据库链接进行远程查询时如何绕过自定义用户类型,集合,CLOB和BLOB之间的Oracle RDBMS限制。

可能需要进行额外分析,以评估与更具体的本地和远程实例(即您自己的预期环境)之间查询的数据的大小,数量和传输速率相关的任何可能的性能瓶颈或问题。

答案 2 :(得分:1)

我能够使用APEX_COLLECTIONS和相应的程序来实现我想要的结果。

DECLARE
  v_mime      VARCHAR2(48);
  v_length    NUMBER(38);
  v_file_name VARCHAR2(38);
  Lob_loc     BLOB;
BEGIN
  DELETE FROM [TemporaryTable];
  --
  INSERT INTO [TemporaryTable]( [attr1]
                              , [attr2]
                              , [blob]
                              , [mime] )
  SELECT [attr1]
  ,      [attr2]
  ,      [blob]
  ,      [mime]
  FROM   [RemoteTableView]
  WHERE  [attr1] = :P2_BLOB_ID
  AND    ROWNUM  = 1;
  --
  SELECT [mime]
  ,      [blob]
  ,      [attr1]
  ,      DBMS_LOB.GETLENGTH( [blob] )
  INTO   v_mime
  ,      lob_loc
  ,      v_file_name
  ,      v_length
  FROM   [TemporaryTable]
  WHERE  [attr1] = :P2_BLOB_ID;
  --
  APEX_COLLECTION.CREATE_OR_TRUNCATE_COLLECTION('COLLECTION1');
  --
  apex_collection.add_member( p_collection_name => 'COLLECTION1'
                            , p_c001 => v_mime
                            , p_c002 => v_file_name
                            , p_n001 => v_length
                            , p_blob001 => lob_loc
                            );
  --
  DELETE FROM [TemporaryTable];
  --
  SELECT c001, blob001, c002, n001
  INTO   v_mime,lob_loc,v_file_name,v_length
  FROM   apex_collections
  WHERE  collection_name = 'COLLECTION1'
  AND    c002            = :P2_BLOB_ID;
  --
  owa_util.mime_header( nvl(v_mime,'application/octet'), FALSE );
  htp.p('Content-length: ' || v_length);
  htp.p('Content-Disposition:  attachment; filename="'||replace(replace(substr(v_file_name,instr(v_file_name,'/')+1),chr(10),null),chr(13),null)|| '"');
  owa_util.http_header_close;
  wpg_docload.download_file( Lob_loc );
END;

从远程表插入BLOB后,仍然从临时表中调用BLOB。但随后从APEX_COLLECTIONS中的临时表中存储,一旦调用它就被最终检索到。