web-dev-qa-db-fra.com

Oracle BLOB à base64 CLOB

Puis-je convertir un BLOB Oracle en Base64 CLOB en une fois?

comme:

CREATE TABLE test
(
image BLOB,
imageBase64 CLOB
);

INSERT INTO test(image)
VALUES (LOAD_FILE('/full/path/to/new/image.jpg'));

UPDATE test SET imageBase64 = UTL_ENCODE.base64_encode(image);

commit;

Je sais que je peux ajouter des fonctions/procédures stockées pour effectuer le travail. L’aspect performance est très important, je demande donc s’il est possible de dépasser la limite de 32 Ko en introduisant directement les données dans un CLOB.

6
user648026

À condition que les procédures stockées soient malgré tout une alternative viable pour vous, voici une solution possible à votre problème ...

Tout d’abord, faisons de cette fonction base64encode() de Tim Hall une procédure ...

create or replace procedure base64encode
    ( i_blob                        in blob
    , io_clob                       in out nocopy clob )
is
    l_step                          pls_integer := 22500; -- make sure you set a multiple of 3 not higher than 24573
    l_converted                     varchar2(32767);

    l_buffer_size_approx            pls_integer := 1048576;
    l_buffer                        clob;
begin
    dbms_lob.createtemporary(l_buffer, true, dbms_lob.call);

    for i in 0 .. trunc((dbms_lob.getlength(i_blob) - 1 )/l_step) loop
        l_converted := utl_raw.cast_to_varchar2(utl_encode.base64_encode(dbms_lob.substr(i_blob, l_step, i * l_step + 1)));
        dbms_lob.writeappend(l_buffer, length(l_converted), l_converted);

        if dbms_lob.getlength(l_buffer) >= l_buffer_size_approx then
            dbms_lob.append(io_clob, l_buffer);
            dbms_lob.trim(l_buffer, 0);
        end if;
    end loop;

    dbms_lob.append(io_clob, l_buffer);

    dbms_lob.freetemporary(l_buffer);
end;

Le "truc" consiste ici à utiliser directement les localisateurs persistants de LOB dans les appels à des procédures/fonctions. Pourquoi "persistant"? En effet, si vous créez une fonction qui renvoie un LOB, un LOB temporaire est créé en arrière-plan, ce qui implique une certaine utilisation de la mémoire et du disque TEMP, ainsi que la copie du contenu LOB. Pour les grands secteurs d'activité, cela peut impliquer un impact négatif sur les performances. Afin de satisfaire votre besoin de rendre cela le plus performant possible, vous devez éviter cette utilisation de l'espace TEMP. Par conséquent, pour cette approche, une procédure stockée au lieu d'une fonction doit être utilisée.

Ensuite, bien sûr, la procédure doit être alimentée avec des localisateurs LOB persistants. Vous devez le faire, encore une fois, avec une procédure stockée, par exemple, vous insérez d'abord un LOB vide (créant effectivement un nouveau localisateur de LOB) dans une table, puis transmettez ce localisateur de LOB nouvellement créé à la routine d'encodage en base64 ...

create or replace procedure load_and_encode_image
    ( i_file_name       in varchar2 )
is
    l_input_bfile       bfile := bfilename('DIR_ANYTHING', i_file_name);
    l_image_base64_lob  test.imageBase64%type;
    l_image_raw         test.image%type;
begin
    insert into test(image, imageBase64)
    values (empty_blob(), empty_clob())
    returning image, imageBase64
    into l_image_raw, l_image_base64_lob;

    begin
        dbms_lob.fileopen(l_input_bfile);
        dbms_lob.loadfromfile(
            dest_lob => l_image_raw,
            src_lob => l_input_bfile,
            amount => dbms_lob.getlength(l_input_bfile)
        );
        dbms_lob.fileclose(l_input_bfile);
    exception
        when others then
            if dbms_lob.fileisopen(l_input_bfile) = 1 then
                dbms_lob.fileclose(l_input_bfile);
            end if;
            raise;
    end;

    base64encode(
        i_blob => l_image_raw,
        io_clob => l_image_base64_lob
    );
end;

Remarque: Bien sûr, si vous encodez en base64 uniquement de petits fichiers (la taille réelle dépend de vos paramètres PGA, je devine ; une question pour un administrateur de base de données, c'est le cas), l'approche basée sur les fonctions peut être tout aussi performant que celui basé sur la procédure. Encoder en base64 un fichier de 200 Mo sur mon ordinateur portable a pris 55 secondes avec l’approche fonction + mise à jour, et 14 secondes avec l’approche procédure. Pas exactement un démon de la vitesse, alors choisissez ce qui convient à vos besoins.

Remarque: Je pense que cette approche basée sur la procédure peut être encore accélérée en lisant le fichier dans des fragments immémés en boucle, codant en base64 les morceaux en un autre et en les ajoutant tous les deux aux objets LOB persistants cibles. De cette façon, vous devriez rendre la charge de travail encore plus facile en évitant de relire le contenu LOB complet de test.image par la procédure base64encode().

5
nop77svk

Cette fonction a à partir d'ici devrait faire le travail.

CREATE OR REPLACE FUNCTION base64encode(p_blob IN BLOB)
  RETURN CLOB
-- -----------------------------------------------------------------------------------
-- File Name    : http://Oracle-base.com/dba/miscellaneous/base64encode.sql
-- Author       : Tim Hall
-- Description  : Encodes a BLOB into a Base64 CLOB.
-- Last Modified: 09/11/2011
-- -----------------------------------------------------------------------------------
IS
  l_clob CLOB;
  l_step PLS_INTEGER := 12000; -- make sure you set a multiple of 3 not higher than 24573
BEGIN
  FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_blob) - 1 )/l_step) LOOP
    l_clob := l_clob || UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(DBMS_LOB.substr(p_blob, l_step, i * l_step + 1)));
  END LOOP;
  RETURN l_clob;
END;
/

Ensuite, la mise à jour peut ressembler à

UPDATE test SET imageBase64 = base64encode(image);

Notez que la fonction devrait peut-être être optimisée avec la fonction DBMS_LOB.APPEND au lieu de cet opérateur de concaténation. Essayez cela si vous avez des problèmes de performances.

7
dimm

J'ai résolu ce même problème au travail en utilisant une procédure stockée Java. Il n’existe pas de fragmentation/contaténation de VARCHAR2 impliquée dans une telle approche, car la capacité à encoder/décoder en base64 est intégrée de manière native à Java, le simple fait d’écrire une fonction Oracle qui enveloppe la méthode Java fonctionne bien et est performant vous l'avez exécuté plusieurs fois, la machine virtuelle HotSpot compile le processus Java en code de bas niveau (hautes performances, tout comme une fonction stockée en C). Je modifierai cette réponse plus tard et ajouterai les détails concernant ce code Java.

Mais pour faire un pas en arrière, demandez pourquoi vous stockez ces données à la fois en tant que BLOB et codé en base64 (CLOB)? Est-ce parce que vous avez des clients qui veulent consommer les données dans ce dernier format? Je préférerais vraiment ne stocker que le format BLOB. Une des raisons est que la version encodée en base64 peut être le double de la taille du BLOB binaire original, donc les stocker tous les deux signifie éventuellement 3 fois le stockage.

Une solution, celle que j’ai implémentée au travail, pour créer votre propre fonction stockée Java base64_encode() qui code binaire -> base64, puis l’utiliser pour coder base64 à la volée au moment de la requête (ce n’est pas cher). Du côté de l’application/du client, vous interrogez quelque chose comme SELECT base64_encode(image) FROM test WHERE ...

Si le code de l'application ne peut pas être touché (par exemple, une application COTS) ou si vos développeurs ne sont pas enthousiastes à l'idée d'utiliser une fonction, vous pouvez le résumer pour eux (puisque vous utilisez 11g +) en utilisant une colonne VIRTUAL (calculée) dans la liste table qui contient la base64_encode(image) calculée. Il fonctionnerait comme une vue, en ce sens qu'il ne stockerait pas physiquement les CLOB codés, mais les générerait au moment de la requête. Pour aucun client, ils ne pourraient pas dire qu'ils ne lisent pas une colonne physique. L’autre avantage est que si vous mettez à jour le jpg (BLOB), le CLOB virtuel est immédiatement et automatiquement mis à jour. Si vous deviez éventuellement insérer/mettre à jour/supprimer un grand nombre d'objets BLOB, vous épargneriez 66% du volume de restauration/archivage sans avoir à traiter tous les objets CLOB.

Enfin, pour des performances optimales, veillez à utiliser les fichiers LOB SecureFile (pour les objets BLOB et les objets CLOB). Ils sont vraiment beaucoup plus rapides et meilleurs à peu près de toutes les manières.

UPDATE- J'ai trouvé mon code, du moins la version qui utilise une procédure stockée Java pour effectuer l'inverse (conversion d'un CLOB codé en base64 en sa version binaire BLOB). Il ne serait pas si difficile d’écrire l’inverse.

--DROP FUNCTION base64_decode ;
--DROP Java source base64;

-- This is a PLSQL Java wrapper function
create or replace
FUNCTION base64_decode (
   myclob clob)
   RETURN blob
AS LANGUAGE Java
   NAME 'Base64.decode (
            Oracle.sql.CLOB) 
            return Oracle.sql.BLOB';
/

-- The Java code that base64 decodes a clob and returns a blob.
create or replace and compile Java source named base64 as
import Java.sql.*;
import Java.io.*;
import Oracle.sql.*;
import Sun.misc.BASE64Decoder;
import Oracle.jdbc.driver.*;

public class Base64 {

public static Oracle.sql.BLOB decode(Oracle.sql.CLOB  myBase64EncodedClob)
  {

    BASE64Decoder base64   = new BASE64Decoder();
    OutputStream  outstrm  = null;
    Oracle.sql.BLOB myBlob = null;
    ByteArrayInputStream instrm = null;

    try
    {
      if (!myBase64EncodedClob.equals("Null"))
      {
        Connection conn = new OracleDriver().defaultConnection();
        myBlob = Oracle.sql.BLOB.createTemporary(conn, false,Oracle.sql.BLOB.DURATION_CALL);
        outstrm = myBlob.getBinaryOutputStream();
        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();

        InputStream in = myBase64EncodedClob.getAsciiStream();
        int c;
        while ((c = in.read()) != -1)
        {
          byteOutStream.write((char) c);
        }

        instrm = new ByteArrayInputStream(byteOutStream.toByteArray());

        try  // Input stream to output Stream
        {
          base64.decodeBuffer(instrm, outstrm);
        }
        catch (Exception e)
        {
          e.printStackTrace();
        }

        outstrm.close();
        instrm.close();
        byteOutStream.close();
        in.close();
        conn.close();
      }

    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    return myBlob;

  }  // Public decode

} // Class Base64
;
/
7
Joshua Huber

Le moyen le plus simple que j'ai trouvé, qui fonctionne avec des caractères spéciaux (dans votre cas, vous n'avez pas ce problème), utilise dbms_lob.converttoclob.

Créer une procédure encapsulée:

CREATE OR REPLACE FUNCTION blob2clob(blob_i IN BLOB) RETURN CLOB IS
  l_clob         CLOB;
  l_dest_offset  NUMBER := 1;
  l_src_offset   NUMBER := 1;
  l_amount       INTEGER := dbms_lob.lobmaxsize;
  l_clob_csid    NUMBER := nls_charset_id('WE8ISO8859P15'); --dbms_lob.default_csid;
  l_lang_context INTEGER := dbms_lob.default_lang_ctx;
  l_warning      INTEGER;
BEGIN
  ---------------------------
  -- Create Temporary BLOB --
  ---------------------------
  dbms_lob.createtemporary(lob_loc => l_clob,
                           cache   => TRUE);
  --------------------------
  -- Convert CLOB to BLOB --
  --------------------------
  dbms_lob.converttoclob(dest_lob     => l_clob,
                         src_blob     => blob_i,
                         amount       => l_amount,
                         dest_offset  => l_dest_offset,
                         src_offset   => l_src_offset,
                         blob_csid    => l_clob_csid,
                         lang_context => l_lang_context,
                         warning      => l_warning);
  --
  RETURN l_clob;
END blob2clob;

Ensuite, vous pouvez utiliser:

blob2clob(utl_encode.base64_encode(image))
1
Manuel Vidigal