Oracle BLOB 到 base64 CLOB
Oracle BLOB to base64 CLOB
我可以一次将 oracle BLOB 转换为 Base64 CLOB 吗?
喜欢:
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;
我知道我可以添加 functions/Stored proc 来完成这项工作。性能方面非常重要,所以我问是否有办法通过直接将数据推入 CLOB 来克服 32K 的限制。
这个函数得到了 from here 应该完成的工作。
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;
/
然后更新可以看起来像
UPDATE test SET imageBase64 = base64encode(image);
请注意,也许应该使用函数 DBMS_LOB.APPEND 而不是连接运算符来优化该函数。如果您遇到性能问题,请尝试这样做。
我在工作中使用 Java 存储过程解决了同样的问题。这种方法不涉及 chunking/contatenation 的 VARCHAR2,因为 encode/decode base64 的能力原生内置于 Java,只需编写一个 Oracle 函数来薄薄地包装 Java方法运行良好并且是高性能的,因为只要你执行了几次,HotSpot JVM 就会将 Java proc 编译成低级代码(高性能就像 C 存储函数)。我稍后会编辑此答案并添加有关该 Java 代码的详细信息。
但退一步说,为什么要将这些数据存储为 BLOB 和 base64 编码 (CLOB)? 是因为您有客户想要使用后一种格式的数据?我真的更喜欢只存储 BLOB 格式。原因之一是 base64 编码版本的大小可以是原始二进制 BLOB 的两倍,因此存储它们意味着可能是存储空间的 3 倍。
一个解决方案,我在工作中实现的那个,创建你自己的 Java 编码二进制的存储函数 base64_encode()
--> base64 然后使用该函数在运行时对 base64 进行编码查询时间(不贵)。从 application/client 端,您会查询类似 SELECT base64_encode(image) FROM test WHERE ...
的内容
如果无法触及应用程序代码(即 COTS 应用程序)或者如果您的开发人员对使用函数不感兴趣,您可以通过使用 VIRTUAL ( table 上的 computed) 列,其中包含计算的 base64_encode(image)
。它的功能类似于视图,因为它不会物理存储编码的 CLOB,而是会在查询时生成它们。对于任何客户来说,他们都无法分辨出他们不是在阅读实体专栏。另一个好处是,如果您更新 jpg (BLOB),虚拟 CLOB 会立即自动更新。如果您需要 insert/update/delete 大量的 BLOB,您将节省 66% 的 redo/archivelog 容量,因为不必处理所有 CLOB。
最后,为了提高性能,请确保您使用的是 SecureFile LOB(包括 BLOB 和 CLOB)。他们确实在几乎所有方面都更快更好。
UPDATE - 我找到了我的代码,至少是使用 Java 存储过程执行相反操作的版本(将 base64 编码的 CLOB 转换为其二进制 BLOB版本)。反写也不是那么难
--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
;
/
尽管存储过程对您来说是一个可行的替代方案,但这里有一个可能的解决方案...
首先,让我们把 Tim Hall 的那个很好的 base64encode()
函数变成一个过程...
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;
这里的"trick"是直接在对procedures/functions的调用中使用持久性LOB定位器。为什么 "persistent"?因为如果您创建一个 returns LOB 的函数,则会在后台创建一个临时 LOB,这意味着涉及一些 TEMP disk/memory 使用和 LOB 内容复制。对于大型 LOB,这可能意味着性能下降。为了满足使它成为最佳性能的要求,您应该避免这种 TEMP space 用法。因此,对于这种方法,必须使用存储过程而不是函数。
然后,当然,必须为该过程提供持久性 LOB 定位器。您必须再次使用存储过程来执行此操作,例如首先将空 LOB(有效地创建一个新的 LOB 定位器)插入 table,然后将新创建的 LOB 定位器提供给 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;
注意:当然,如果你只对小文件进行base64编码(实际大小取决于你的PGA设置,我猜测 ; DBA 的一个问题,这是),那么基于功能的方法可能与基于过程的方法性能相同。在我的笔记本电脑上对一个 200MB 的文件进行 Base64 编码,使用函数+更新方法需要 55 秒,使用过程方法需要 14 秒。不完全是速度恶魔,所以选择适合您的需求。
注意:我相信这种基于过程的方法可以通过循环读取文件到内存块,base64 编码块到另一个内存块并附加它们都发送到目标持久性 LOB。这样,您应该避免通过 base64encode()
过程重新读取完整的 test.image
LOB 内容,从而使工作量更加轻松。
我发现最简单的处理特殊字符的方法(在你的情况下你没有这个问题)是使用 dbms_lob.converttoclob.
创建封装过程:
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;
那么你可以使用:
blob2clob(utl_encode.base64_encode(image))
我可以一次将 oracle BLOB 转换为 Base64 CLOB 吗?
喜欢:
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;
我知道我可以添加 functions/Stored proc 来完成这项工作。性能方面非常重要,所以我问是否有办法通过直接将数据推入 CLOB 来克服 32K 的限制。
这个函数得到了 from here 应该完成的工作。
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;
/
然后更新可以看起来像
UPDATE test SET imageBase64 = base64encode(image);
请注意,也许应该使用函数 DBMS_LOB.APPEND 而不是连接运算符来优化该函数。如果您遇到性能问题,请尝试这样做。
我在工作中使用 Java 存储过程解决了同样的问题。这种方法不涉及 chunking/contatenation 的 VARCHAR2,因为 encode/decode base64 的能力原生内置于 Java,只需编写一个 Oracle 函数来薄薄地包装 Java方法运行良好并且是高性能的,因为只要你执行了几次,HotSpot JVM 就会将 Java proc 编译成低级代码(高性能就像 C 存储函数)。我稍后会编辑此答案并添加有关该 Java 代码的详细信息。
但退一步说,为什么要将这些数据存储为 BLOB 和 base64 编码 (CLOB)? 是因为您有客户想要使用后一种格式的数据?我真的更喜欢只存储 BLOB 格式。原因之一是 base64 编码版本的大小可以是原始二进制 BLOB 的两倍,因此存储它们意味着可能是存储空间的 3 倍。
一个解决方案,我在工作中实现的那个,创建你自己的 Java 编码二进制的存储函数 base64_encode()
--> base64 然后使用该函数在运行时对 base64 进行编码查询时间(不贵)。从 application/client 端,您会查询类似 SELECT base64_encode(image) FROM test WHERE ...
如果无法触及应用程序代码(即 COTS 应用程序)或者如果您的开发人员对使用函数不感兴趣,您可以通过使用 VIRTUAL ( table 上的 computed) 列,其中包含计算的 base64_encode(image)
。它的功能类似于视图,因为它不会物理存储编码的 CLOB,而是会在查询时生成它们。对于任何客户来说,他们都无法分辨出他们不是在阅读实体专栏。另一个好处是,如果您更新 jpg (BLOB),虚拟 CLOB 会立即自动更新。如果您需要 insert/update/delete 大量的 BLOB,您将节省 66% 的 redo/archivelog 容量,因为不必处理所有 CLOB。
最后,为了提高性能,请确保您使用的是 SecureFile LOB(包括 BLOB 和 CLOB)。他们确实在几乎所有方面都更快更好。
UPDATE - 我找到了我的代码,至少是使用 Java 存储过程执行相反操作的版本(将 base64 编码的 CLOB 转换为其二进制 BLOB版本)。反写也不是那么难
--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
;
/
尽管存储过程对您来说是一个可行的替代方案,但这里有一个可能的解决方案...
首先,让我们把 Tim Hall 的那个很好的 base64encode()
函数变成一个过程...
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;
这里的"trick"是直接在对procedures/functions的调用中使用持久性LOB定位器。为什么 "persistent"?因为如果您创建一个 returns LOB 的函数,则会在后台创建一个临时 LOB,这意味着涉及一些 TEMP disk/memory 使用和 LOB 内容复制。对于大型 LOB,这可能意味着性能下降。为了满足使它成为最佳性能的要求,您应该避免这种 TEMP space 用法。因此,对于这种方法,必须使用存储过程而不是函数。
然后,当然,必须为该过程提供持久性 LOB 定位器。您必须再次使用存储过程来执行此操作,例如首先将空 LOB(有效地创建一个新的 LOB 定位器)插入 table,然后将新创建的 LOB 定位器提供给 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;
注意:当然,如果你只对小文件进行base64编码(实际大小取决于你的PGA设置,我猜测 ; DBA 的一个问题,这是),那么基于功能的方法可能与基于过程的方法性能相同。在我的笔记本电脑上对一个 200MB 的文件进行 Base64 编码,使用函数+更新方法需要 55 秒,使用过程方法需要 14 秒。不完全是速度恶魔,所以选择适合您的需求。
注意:我相信这种基于过程的方法可以通过循环读取文件到内存块,base64 编码块到另一个内存块并附加它们都发送到目标持久性 LOB。这样,您应该避免通过 base64encode()
过程重新读取完整的 test.image
LOB 内容,从而使工作量更加轻松。
我发现最简单的处理特殊字符的方法(在你的情况下你没有这个问题)是使用 dbms_lob.converttoclob.
创建封装过程:
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;
那么你可以使用:
blob2clob(utl_encode.base64_encode(image))