You are on page 1of 25

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 1

MANIPULAR LOBs (Large Objects)

1. COMPRENDER LOS LOBS. Actualmente, las aplicaciones web (y el resto) necesitan adems de almacenar textos, nmeros y fechas, manejar informacin multimedia para hacer amigable al usuario su uso. Por este motivo, ahora los SGBD pueden almacenar imgenes, sonidos, vdeo, etc. Antes de Oracle 8, podas almacenar grandes bloques de texto usando el tipo LONG, y grandes bloques de datos binarios usando LONG RAW. Actualmente, hay 4 tipos de LOB en Oracle: CLOB: LOB de caracteres, para almacenar cadenas de caracteres muy largas, como descripciones, pginas web completas, etc. NCLOB Un CLOB nacional para caracteres no ingleses. BLOB Un LOB binario (archivos, etc.). BFILE para ficheros binarios. Punteros a ficheros que estn almacenados en el sistema de ficheros del sistema operativo que contiene el SGBD (no estn almacenados dentro de la BD). Las columnas creadas usando CLOB, NCLOB y BLOB tienen tres ventajas sobre los viejos tipos LONG y LONG RAW: 1. Las columnas LOB pueden almacenar hasta 128 Terabytes de datos. Una columna LONG y LONG RAW, puede almacenar 2 Gigabytes y RAW 4 Kilobytes. 2. En segundo lugar, una tabla puede tener varias columnas LOB, pero solamente una LONG o LONG RAW. 3. En tercer lugar, los datos LOB pueden accederse en orden aleatorio; LONG y LONG RAW solamente en orden secuencial. Por este motivo, vamos a utilizar nicamente los tipos BLOB y BFILE. Un LOB tiene dos partes: LOB locator: Un puntero que especifica la localizacin del contenido LOB. LOB content: La informacin almacenada del LOB. Segn el tamao del LOB content, los datos se almacenarn en la tabla donde se define o fuera de ella. Si es menor de 4 kilobytes, se deja en la tabla, si es mayor, se almacena fuera. Si se usa el tipo BFILE, solamente el locator se almacena en la BD, el LOB content ser externo a la BD (estar en el sistema de ficheros de algn sistema operativo). Crearemos una tabla con todos los tipos posibles de LOBs:
CREATE TABLE saltos_agua( nombre VARCHAR2(80) CONSTRAINT cascadas_pk PRIMARY KEY, foto BLOB, indicaciones CLOB, descripcion NCLOB, web BFILE );

COMPRENDER LOS LOCALIZADORES Un localizador de LOB es un puntero a sus datos. Veamos lo que ocurre cuando seleccionamos una columna BLOB en una variable BLOB:
DECLARE v_foto BLOB; BEGIN SELECT foto INTO v_foto FROM cascadas WHERE nombre='Dryer Hose';

Qu hay exactamente en la variable v_foto tras ejecutar la sentencia SELECT? la foto? No. Solo hay un puntero a los datos de la imagen. La figura muestra un esquema.

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 2

Esto es diferente a la forma de trabajar de otras bases de datos. Las columnas LOB almacenan localizadores LOB, que apuntan a los datos almacenados. Para trabajar con datos LOB, primero recuperas el LOB locator y luego usas un paquete predefinido llamado DBMS_LOB para recuperar/modificar los datos del LOB. Por ejemplo, para recuperar la los datos de la foto anterior: 1. Usas las sentencia SELECT para recuperar el LOB locator. 2. Abres el LOB usando una llamada a DBMS_LOB.OPEN. 3. Llamas a DBMS_LOB.GETCHUNKSIZE para obtener el tamao ptimo de bloque para leer (y escribir) los valores LOB. 4. Haces una llamada a DBMS_LOB.GETLENGTH para obtener el nmero de bytes de los datos LOB. 5. Haces varias llamadas a DBMS_LOB.READ para ir recuperando los datos del LOB. 6. Cierras el LOB. No todos los pasos son necesarios. Aunque el uso de localizadores pueda dar la impresin de mecanismo engorroso, en realidad es una buena aproximacin porque evita manipular una gran cantidad de datos cada vez que usas columnas LOB en consultas, cursores, etc. Puedes manipular localizadores con el mismo coste que manipulas un nmero o una cadena de texto, y luego acceder a los datos que realmente necesites. LOB NULOS CONTRA LOB VACOS. Un LOB vaco (empty LOB) es aquel en el que su localizador no apunta a ningn dato. Un LOB null (columna o variable PL/SQL) es el que no tiene ningn localizador. Ejemplo:
DECLARE v_direcciones CLOB; BEGIN IF v_direcciones IS NULL THEN DBMS_OUTPUT.PUT_LINE('Direcciones es NULL'); ELSE DBMS_OUTPUT.PUT_LINE('Direcciones no es NULL'); END IF; END; / Direcciones es NULL

El siguiente cdigo usa una llamada a EMPTY_CLOB en una sentencia INSERT para crear un LOB locator. A continuacin, una sentencia SELECT recupera el LOB locator de la base de datos y lo coloca en la variable v_direcciones:
DECLARE v_direcciones CLOB; BEGIN --Borra cualquier fila 'Munising Falls' DELETE FROM cascadas WHERE nombre='Munising Falls'; --Inserta una nueva fila usando EMPTY_CLOB() para crear el LOB locator INSERT INTO cascadas(nombre,indicaciones) VALUES ('Munising Falls', EMPTY_CLOB() ); --Recupera el LOB locator creado antes SELECT direcciones INTO v_direcciones FROM cascadas WHERE nombre='Munising Falls'; IF v_direcciones IS NULL THEN DBMS_OUTPUT.PUT_LINE('Indicaciones es NULL'); ELSE DBMS_OUTPUT.PUT_LINE('indicaciones no es NULL'); END IF; DBMS_OUTPUT.PUT_LINE( 'Peso = ' || DBMS_LOB.GETLENGTH(v_direcciones) ); END;

La salida ser:

Direcciones no es NULL Peso = 0

EMPTY_CLOB es una funcin que devuelve un CLOB locator. Esto es importante porque es la forma de comprobar la presencia o ausencia de datos en un LOB (no basta con saber si es NULL, no como en otros tipos de datos):

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 3

-- COMPROBACIONES EN DATOS BSICOS


IF dato IS NULL THEN --No hay dato ELSE --Hay dato END IF;

-- COMPROBACIONES EN LOB
IF dato IS NULL THEN --No hay locator ni datos ELSEIF DBMS_LOB.GETLENGTH(dato) = 0 THEN --hay locator pero no datos ELSE --Ahora hay datos END IF;

3. USAR LOBs EN PL/SQL.


Oracle aporta varios paquetes para ayudar a realizar ciertas tareas. Unos, nos ayudan a trabajar con ficheros, otros a trabajar con pginas web, otros nos ayudan a manejar los atributos LOB. El paquete DBMS_LOB, tiene un enfoque relacional, es decir usa PL/SQL para mover los campos LOB como si de otro dato se tratase. Veamos algunos de sus procedimientos y funciones. Mtodo
APPEND(lob_origen, lob_destino) CLOSE(lob) COMPARE(lob1, lob2, amount, offset1, offset2) COPY(dest_lob, src_lob, amount, dest_offset, src_offset) CREATETEMPORARY(lob, cache, duration) ERASE(lob, amount, offset) FILECLOSE(bfile) FILECLOSEALL() FILEEXISTS(bfile) FILEGETNAME(bfile, dir_alias, filename) FILEISOPEN(bfile) FILEOPEN(bfile, open_mode) FREETEMPORARY(lob) GETCHUNKSIZE(lob)

Descripcin Aade contenido de lob_origen al final de lob_destino Cierra un LOB abierto antes Compara dos LOBs completos o partes de ellos Copia todo o parte de src_LOB a dest_LOB Crea BLOB o CLOB temporal en temporary tablespace Borra todo o parte de un LOB Cierra un BFILE Cierra todos los BFILEs abiertos antes Comprueba si exixte un fichero Obtiene el alias deldirectorio y el nombre del fichero Comprueba si el fichero est abierto Abre un fichero Libera el BLOB o CLOB temporal Obtiene la cantidad de espacio usado en LOB chunk para el valor del LOB Obtener el lmite de almacenamiento de un LOB Obtiene la longitud de un LOB Posicin de la n-sima ocurrencia de pattern en LOB Comprueba si LOB fue abierto usando input locator Comprueba si el locator apunta a un LOB temporal Carga datos de BFILE a un LOB

GET_STORAGE_LIMIT() GETLENGTH(lob) INSTR(lob, pattern, offset, n) ISOPEN(lob) ISTEMPORARY(lob) LOADFROMFILE(dest_lob, src_bfile, amount, dest_offset, src_offset)

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 4

Mtodo
LOADBLOBFROMFILE(dest_lob, src_bfile, amount, dest_offset, src_offset) LOADCLOBFROMFILE(dest_lob, src_bfile, amount, dest_offset, src_offset, src_csid, lang_context, warning) OPEN(lob, open_mode) READ(lob, amount, offset, buffer) SUBSTR(lob, amount, offset) TRIM(lob, newlen) WRITE(lob, amount, offset, buffer) WRITEAPPEND(lob, amount, buffer)

Descripcin Carga un BFILE en un BLOB Carga datos BFILE en CLOB Abre un LOB (interno, externo o temporal) Lee datos en buffer desde LOB Lee parte de LOB Recorta el LOB Escribe datos del buffer al LOB Aade datos del buffer al final del LOB

EJERCICIO 0. PREPARAR EL ENTORNO DE LA PRCTICA. Vamos a crear un script llamado alumno_iniciar.sql que prepare el entorno para crear la prctica.
connect SYS AS SYSDBA; set serveroutput on set echo on -- Necesitas privilegios SYSDBA para borrar un usuario. DROP USER multimedia CASCADE; DROP DIRECTORY carpeta; DROP TABLESPACE multimedia INCLUDING CONTENTS; -- Crear un tablespace: un fichero separado. CREATE TABLESPACE multimedia DATAFILE 'multimedia.dbf' size 100M MINIMUM EXTENT 64K DEFAULT STORAGE (INITIAL 64K NEXT 128K) LOGGING; -- Crear el usuario multimedia CREATE USER multimedia IDENTIFIED BY multimedia DEFAULT TABLESPACE multimedia TEMPORARY TABLESPACE temp; GRANT CONNECT, RESOURCE, CREATE LIBRARY TO multimedia; GRANT CREATE ANY DIRECTORY TO multimedia; GRANT EXECUTE ON UTL_FILE TO multimedia; -- Conectarnos como multimedia CONNECT multimedia/multimedia; -- Crear el directorio de carga de datos multimedia. -- Cambia la ruta a la que tu quieras (el directorio debe existir en el SO) CREATE OR REPLACE DIRECTORY carpeta AS 'd:\carpeta'; GRANT READ ON DIRECTORY carpeta TO PUBLIC; -- Creamos una secuencia para rellenar claves primarias de la tabla archivos DROP SEQUENCE archivo; CREATE SEQUENCE archivo START WITH 1 INCREMENT BY 1 NOCACHE; -- Creamos la tabla donde almacenaremos los archivos CREATE TABLE tablaArchivos( ID INTEGER CONSTRAINT pk_tablaArchivos PRIMARY KEY, Nombre VARCHAR2(100), ALTA DATE DEFAULT SYSDATE, BIN BLOB );

EJERCICIO 1: INSERTAR EN LA BD UN FICHERO DESDE UN DIRECTORIO DEL SERVIDOR.

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 5

Te conectas como usuario multimedia. Los datos binarios nos van a permitir guardar en la base de datos archivos, imgenes, sonidos, etc. Lo que vamos a hacer es cargar un fichero existente en el servidor en un campo BLOB de una tabla. El siguiente bloque de PL/SQL nos va a permitir cargar una imagen, llamada "imagen.gif" en la tabla. Es importante tener claro que el archivo "imagen.gif" debe existir fsicamente y tambin el directorio al que se asocia el objeto CARPETA (d:\carpeta) que hemos creado anteriormente. Nota: Por experiencia, Oracle guarda los nombres de los objetos en maysculas, as que si utilizas el objeto 'carpeta' en vez de 'CARPETA' te dar un mensaje de error como: ORA-22285: no existe el directorio o el archivo para la operacin FILEOPEN
DECLARE L_bfile BFILE; -- Usaremos el BFILE para cargar el BLOB L_blob BLOB; BEGIN INSERT INTO tablaArchivos(ID, NOMBRE, BIN, ALTA) VALUES(1,'calamardo.gif', EMPTY_BLOB(), SYSDATE) Ojo!! RETURN BIN INTO l_blob; l_bfile := BFILENAME('CARPETA', 'calamardo.gif'); DBMS_LOB.fileopen(l_bfile, DBMS_LOB.File_Readonly); DBMS_LOB.loadfromfile(l_blob, l_bfile, DBMS_LOB.getlength(l_bfile) ); DBMS_LOB.fileclose(l_bfile); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; DBMS_OUTPUT.PUT_LINE('Error al importar fichero imagen.gif. ' || SQLERRM ); RAISE; END;

Hay varios aspectos a comentar de este cdigo: El uso de RETURN en la sentencia INSERT nos permite capturar el LOB locator sin usar otra sentencia SQL. La funcin EMPTY_BLOB.Nos permite crear un LOB locator vaco. La funcin BFILENAME. Devuelve un objeto BFILE que representa la ruta del fichero "imagen.gif" que queremos almacenar en la tabla. El uso del paquete predefinido de ORACLE DBMS_LOB. Utilizamos las siguientes funciones: o fileopen: Abre el archivo definido por BFILE (l_bfile) en el modo indicado (en nuestro caso solo lectura Dbms_Lob.File_Readonly) o loadfromfile: Lee un determinado nmero de bytes (en nuestro caso todos) del fichero definido por BFILE(l_bfile) en un objeto de tipo BLOB (l_blob). o getlength:Devuelve el tamao del archivo en bytes. o fileclose:Cierra el archivo Llama la atencin de este cdigo que a pesar de haber insertado el campo BIN vaco con la funcin EMPTY_BLOB, finalmente queda cargado sin ejecutar ninguna sentencia UPDATE. Esto ocurre porque estamos utilizando RETURN en la sentencia INSERT y guardando una referencia al campo BIN que posteriormente asignamos al leer el archivo con DBMS_LOB.LoadFromFile. El resultado de todo esto, es que la imagen se almacena en la base de datos. Tu Turno: modifica el bloque PL/SQL anterior y convirtelo en una funcin almacenada denominada InsertaFichero que acepte de parmetros de entrada: El nombre del fichero. El nombre del objeto directorio donde se encuentra. La funcin debe usar la secuencia para insertar en la tabla tablaArchivos y debe devolver la clave de la fila que tiene el blob. Adems, almacenar el nombre del fichero en la tabla y tratar las excepciones posibles. EJERCICIO 2: EXTRAER OBJETOS BLOB DEDE LA BD HACIA UN FICHERO.

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 6

Usamos la tabla archivos, que contiene una columna BLOB del ejercicio anterior. Tenemos una carpeta creada en el sistema de ficheros, en la que tenemos permisos para acceder. Con un usuario DBA, damos permisos de escritura sobre el directorio.
GRANT WRITE ON DIRECTORY carpeta TO PUBLIC;

Ahora, crearemos el procedimiento ExtraerFichero, al que pasamos la clave del fichero y la carpeta donde queremos dejarlo.
CREATE OR REPLACE PROCEDURE ExtraerFichero(pFichero IN INTEGER, pDirectorio IN VARCHAR2) IS vblob BLOB; PosInicio NUMBER := 1; bytelen NUMBER := 32000; vlongitud NUMBER; buffer RAW(32000); x NUMBER; vnombre VARCHAR2(100); lv_str_len NUMBER; l_output UTL_FILE.FILE_TYPE; BEGIN -- Averiguar el nombre del fichero SELECT nombre INTO vnombre FROM tablaArchivos WHERE ID=pFichero; -- Crear el fichero de salida (una escritura escribe hasta 32000Bytes) l_output := UTL_FILE.FOPEN( UPPER(pdirectorio), 'Vengo del SGBD_'||vnombre, 'WB', 32000); -- Obtener la longitud del BLOB SELECT dbms_lob.getlength(bin) INTO vlongitud FROM tablaarchivos WHERE ID=pFichero; -- Guardar la longitud del blob x := vlongitud; -- seleccionar el lob locator del blob en una variable blob SELECT bin INTO vblob FROM tablaarchivos WHERE ID = pFichero; IF vlongitud <= 32000 THEN -- Si es ms pequeo solo una nica escritura utl_file.put_raw(l_output, vblob); utl_file.fflush(l_output); ELSE - Hay que escribirlo en piezas (chunks) posInicio:= 1; WHILE posInicio < vlongitud LOOP dbms_lob.read(vblob, bytelen, posInicio, buffer); -- En bytelen devuelve bytes leidos utl_file.put_raw(l_output, buffer); utl_file.fflush(l_output); posInicio:= posInicio + bytelen; -- Modificar posicin de inicio x := x bytelen; -- Comprobar que posicin final < 32000 bytes IF x < 32000 THEN bytelen := x; END IF; END LOOP; END IF; utl_file.fclose(l_output); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('ERROR al extraer fichero ' || pFichero || ' en directorio ' || pDirectorio ); END ExtraerFichero;

PREGUNTA: Prueba a insertar la imagen 'calamardo.gif' (menor de 32Kb). Luego la extraes Funciona? . Prueba a insertar la imagen 'bobesponja.jpg' (mayor de 32Kb). Luego la extraes Funciona? .

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 7

3. USANDO LOBS CON INTERMEDIA (opcional).


Intermedia es una especie de librera que permite trabajar con contenido multimedia (imagen, sonido y vdeo). Tiene dos interfaces, una relacional (para mover datos como un todo) y otra orientada a objetos (modelo objeto-relacional) que adems da la posibilidad al SGBD de modificar los objetos (cambiarles el formato, editarlos, ampliarlos, cambiar el nmero de colores, etc.). Tienes ejemplos en $ORACLE_HOME/D\ord para trabajar con diferentes tipos de material (imagen, sonido, vdeo, etc.). Nota: Oracle Intermedia necesita que el SGBD soporte Java. Una de las limitaciones de Oracle XE es que no soporta Java. Por tanto, para trabajar con Intermedia, debemos usar otra versin. Si te interesa el tema, puedes instalarte la versin Oracle 9i, 10g, 11g en edicin personal o enterprise. EJERCICIO 3.1 (opcional): Crear funciones usando Intermedia, para importar una imagen desde fichero y otra para importar imagen desde una web.
-- Importa imgenes desde el directorio carpeta en el sistema de archivos local (srcType=file). CREATE OR REPLACE PROCEDURE ImportaImagen( FicheroDesde in VARCHAR2 ) AS obj ORDSYS.ORDIMAGE; ctx RAW(64):= NULL; Clave imagenes.ID%Type; BEGIN SELECT imagen.nextval INTO clave FROM DUAL; INSERT INTO imagenes VALUES(clave, ORDSYS.ORDImage.init()); SELECT imagen INTO obj FROM imagenes WHERE id = clave FOR UPDATE; obj.setSource( 'file', 'MULTIMEDIA', FicheroDesde ); obj.import(ctx); UPDATE imagenes SET imagen = obj WHERE id = clave; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20000, 'Error al importar imagen desde fichero ' || Ficherodesde); END; -- Importa imagen desde una pgina web CREATE OR REPLACE PROCEDURE ImportaImagenWeb( URLDesde IN VARCHAR2 DEFAULT 'localhost', FicheroDesde IN VARCHAR2 ) AS obj ORDSYS.ORDIMAGE; ctx RAW(64) := NULL; Clave imagenes.ID%Type; BEGIN -- Importa el fichero Ficherodesde que es alcanzable desde -- la URL URLDesde (srcType=HTTP) SELECT imagen.nextval INTO clave FROM DUAL; INSERT INTO imagenes VALUES( clave, ORDSYS.ORDImage.init( 'http', URLDesde, Ficherodesde) ); SELECT imagen INTO obj FROM imagenes WHERE Id = clave FOR UPDATE; obj.import(ctx); UPDATE imagenes SET imagen = obj WHERE id = clave; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20000, 'Error al importar imagen desde web ' || URLDesde || '/' || Ficherodesde); END;

EJERCICIO 3.2 (opcional). Hacer una aplicacin web que permita implementar un lbum de fotos con Intermedia y PLSQL web Toolkit. Es una demostracin sacada de Oracle interMedia PL/SQL Web Toolkit. Vamos a realizar una aplicacin PL/SQL que muestre como usar PL/SQL Tookit para cargar (upload), recuperar y procesar datos multimedia

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 8

almacenados usando el tipo de dato interMedia ORDImage. Los usuarios que accedan a esta aplicacin, vern los contenidos de un lbum de fotos, incluyendo versiones en miniatura (thumb-nail) de cada foto que genera la propia aplicacin y que permiten enlazar con las fotografas a tamao real. PASO 1. Creamos la tabla que contendr el lbum de fotos y una secuencia para su clave primaria. Crearemos adems la tabla de upload de documentos que necesita el web Toolkit. As que te conectas como usuario multimedia y construyes un script SQL llamado alumno_creaAlbum.sql, que contendr:
-- Crea la tabla FOTOS CREATE TABLE fotos( id NUMBER CONSTRAINT pk_fotos PRIMARY KEY, descripcion VARCHAR2(40) CONSTRAINT pk_nnDesc_fotos NOT NULL, localizador VARCHAR2(40), imagen ORDSYS.ORDIMAGE, thumb ORDSYS.ORDIMAGE ); -- Crea la secuencia FOTOS_SEQUENCE CREATE SEQUENCE secuencia_fotos; -- Crea la tabla document upload que permitir almacenar las imgenes subidas al -- servidor desde un cliente. El formato de esta tabla lo impone el Web Toolkit. CREATE TABLE fotos_upload( name VARCHAR2(256) UNIQUE NOT NULL, mime_type VARCHAR2(128), doc_size NUMBER, dad_charset VARCHAR2(128), last_updated DATE, content_type VARCHAR2(128), blob_content BLOB );

PASO 2. Creamos la cabecera del paquete que gestionar el lbum de fotos. Escogemos el esquema multimedia para hacerlo.
---------------------------------------------------------------------------- Package name: album -- Descripcin: -El paquete ALBUM es un PL/SQL package que implementa un sencillo -lbum de fotos usando interMedia ORDImage y PL/SQL Web Toolkit. ---------------------------------------------------------------------------- Especificacin o cabecera del Package CREATE OR REPLACE PACKAGE album AS PROCEDURE ver_album; PROCEDURE ver_entrada( entrada_id IN VARCHAR2 ); PROCEDURE entrega_foto( entrada_id IN VARCHAR2, tipo IN VARCHAR2 ); PROCEDURE ver_formulario_upload; PROCEDURE inserta_nueva_foto( nueva_descripcion IN VARCHAR2, nuevo_localizador IN VARCHAR, nueva_foto IN VARCHAR2 ); FUNCTION dame_formato_preferido( formato IN VARCHAR2 ) RETURN VARCHAR2; PROCEDURE imprime_formulario_upload; PROCEDURE imprime_pagina_cabecera( tag_cabecera IN VARCHAR2 DEFAULT NULL ); PROCEDURE imprime_pagina_pie( muestra_url_devuelta IN BOOLEAN ); PROCEDURE imprime_encabezado( mensaje IN VARCHAR2 ); PROCEDURE imprime_enlace( enlace IN VARCHAR2, mensaje IN VARCHAR2 ); END album; / SHOW ERRORS;

PASO 3. Creamos el cuerpo del paquete que gestionar el lbum de fotos. Escogemos el esquema multimedia para hacerlo.
-Cuerpo del paquete

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


CREATE OR REPLACE PACKAGE BODY album AS ---------------------------------------------------------------------------- Procedure: ver_album -- Descripcin: Muestra el contenido del lbum con imgenes thumb-nail. --------------------------------------------------------------------------PROCEDURE ver_album IS CURSOR cursor_album IS SELECT id,descripcion,localizador,thumb FROM fotos ORDER BY descripcion; contador_filas INTEGER := 0; localizador fotos.localizador%TYPE; ampersand VARCHAR2(1) := CHR( 38 ); BEGIN -- Cabecera de pgina comn imprime_pagina_cabecera; -- Muestra las imgenes thumb-nail en una tabla htp.print( '<p><table border="1" cellpadding="3" cellspacing="0"' || 'width="100%" summary="Tabla de Im&aacute;genes en miniatura">' ); htp.print( '<tr bgcolor="#336699">' ); htp.print( '<th id="descripcion"><font color="#ffffff">Descripcin</font></th>' ); htp.print( '<th id="localizador"><font color="#ffffff">Localizador</font></th>' ); htp.print( '<th id="imagen"><font color="#ffffff">Foto</font></th></tr>' ); -- Para cada entrada... FOR entrada IN cursor_album LOOP -- Incrementa el contador de filas contador_filas := contador_filas + 1; htp.print( '<tr>' ); htp.print( '<td headers="descripcion">' || entrada.descripcion || '</td>' ); IF entrada.localizador IS NOT NULL THEN htp.print( '<td headers="localizador">' || entrada.localizador || '</td>' ); ELSE htp.print( '<td headers="localizador">' || ampersand || 'nbsp;</td>' ); END IF; -- Muestra la imagen thumb-nail como enlace a su tamao real. htp.print( '<td headers="imagen"><a href="ALBUM.VER_ENTRADA' || '?entrada_id=' || entrada.id || '">' ); IF entrada.thumb.contentLength > 0 THEN htp.print( '<img src="ALBUM.ENTREGA_FOTO?tipo=thumb' || ampersand || 'entrada_id=' || entrada.id || '"' || ' height=' || entrada.thumb.height || ' width=' || entrada.thumb.width || ' alt="' || entrada.descripcion || '"' || ' border=1></a></td>' ); ELSE htp.print( '[Ver imagen]</a></td>' ); END IF; htp.print( '</tr>' ); END LOOP; -- Muestra un mensaje si el album est vaco htp.print('<tr><td scope="col" colspan="3" align="center">' || '<font color="#336699">'); IF contador_filas = 0 THEN htp.print( '<b><i>El &aacute;lbum de fotos est&aacute; vac&iacute;o</i></b>' ); ELSE htp.print( '<b><i>Selecciona la miniatura (thumb-nail) para ver la foto' || ' a tamao completo</i></b>' ); END IF; htp.print( '</font></td></tr>' ); htp.print( '</table></p>' ); -- Muestra un tag anchor que permita mostrar el formulario de nueva foto imprime_enlace( 'ALBUM.VER_FORMULARIO_UPLOAD', 'Subir una nueva foto' );

Pgina 9

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


-- Pie comn imprime_pagina_pie( FALSE ); END ver_album; ---------------------------------------------------------------------------- Procedure: ver_entrada -- Descripcin: Ver la versin a tamao real de una imagen. --------------------------------------------------------------------------PROCEDURE ver_entrada( entrada_id IN VARCHAR2 ) IS CURSOR cursor_fotos IS SELECT descripcion, localizador, imagen FROM fotos WHERE id = entrada_id; ampersand VARCHAR2( 1 ) := CHR( 38 ); BEGIN imprime_pagina_cabecera; FOR foto IN cursor_fotos LOOP htp.print( '<p><table summary="Ver imagen de entrada">' ); htp.print( '<tr><td scope="col"><b>Descripci&oacute;n:</b></td><td scope="col">' || foto.descripcion || '</td></tr>' ); htp.print( '<tr><td scope="col"><b>Localizador:</b></td><td scope="col">' || foto.localizador || '</td></tr>' ); -- Construir una URL para acceder a la imagen en tamao completo. -- Si el formato de imagen no se reconoce, no genera los atributos HTML -- height y width para la etiqueta img. Indicarlos siempre es mejor -- para el navegador, que puede dibujar la pgina sin esperar a que se -- descargue toda la imagen. htp.prn( '<tr><td scope= "col" valign="top"><b>Foto:</b></td><td scope="col">' || '<img src="ALBUM.ENTREGA_FOTO?tipo=imagen' || ampersand || 'entrada_id=' || entrada_id || '"' ); htp.prn( ' alt="' || foto.descripcion || '"' ); IF foto.imagen.height > 0 AND foto.imagen.width > 0 THEN htp.prn( ' height="' || foto.imagen.height || '"' || ' width=' || foto.imagen.width || '"' ); END IF; htp.print( ' border="1"></td></tr>' ); htp.print( '</table></p>' ); EXIT; END LOOP; imprime_pagina_pie( TRUE ); END ver_entrada; ---------------------------------------------------------------------------- Procedure: entrega_foto -- Descripcin: Entregar una imagen thumb-nail o a tamao completo al browser. --------------------------------------------------------------------------PROCEDURE entrega_foto( entrada_id IN VARCHAR2, tipo IN VARCHAR2 ) IS imagen_local ORDSYS.ORDIMAGE; BEGIN IF tipo = 'thumb' THEN SELECT thumb INTO imagen_local FROM fotos WHERE id = entrada_id; ELSE SELECT imagen INTO imagen_local FROM fotos WHERE id = entrada_id; END IF; -- Fijar el tipo MIME y devolver la imagen al browser owa_util.mime_header( imagen_local.mimeType, TRUE ); wpg_docload.download_file( imagen_local.source.localData ); END entrega_foto; ---------------------------------------------------------------------------- Procedure: ver_formulario_upload -- Descripcin: Muestra el formulario de upload. --------------------------------------------------------------------------PROCEDURE ver_formulario_upload

Pgina 10

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


IS BEGIN imprime_pagina_cabecera; imprime_formulario_upload; imprime_pagina_pie( TRUE ); END ver_formulario_upload; ---------------------------------------------------------------------------- Procedure: inserta_nueva_foto -- Descripcin: permite subir a la BD un fichero desde el cliente --------------------------------------------------------------------------PROCEDURE inserta_nueva_foto( nueva_descripcion IN VARCHAR2, nuevo_localizador IN VARCHAR, nueva_foto IN VARCHAR2 ) IS new_desc VARCHAR2( 128 ) := nueva_descripcion; upload_mime_type VARCHAR2( 128 ); upload_blob BLOB; new_id NUMBER; new_image ORDSYS.ORDIMAGE; new_thumb ORDSYS.ORDIMAGE; pos INTEGER; image_blob BLOB; upload_size INTEGER; BEGIN -- Si no se indica un nombre de fichero, se muestra un error IF nueva_foto IS NULL OR LENGTH( nueva_foto ) = 0 THEN imprime_pagina_cabecera; imprime_encabezado( 'Mensaje de Error' ); htp.print( '<p>Por favor, indica un nombre de fichero.</p>' ); imprime_formulario_upload; imprime_pagina_pie( TRUE ); return; END IF; -- Obtener longitud, tipo MIME y BLOB de la nueva foto desde la tabla upload SELECT doc_size, mime_type, blob_content INTO upload_size, upload_mime_type, upload_blob FROM fotos_upload WHERE name = nueva_foto; -- Comprobar que el fichero es una imagen vlida. IF upload_size = 0 THEN imprime_pagina_cabecera; imprime_encabezado( 'Mensaje de Error' ); htp.print( '<p>Por favor, indique un fichero de imagen vlido.</p>' ); imprime_formulario_upload; imprime_pagina_pie( TRUE ); return; END IF; -- Si la descripcin est en blanco, usaremos el nombre del fichero IF new_desc IS NULL OR LENGTH( new_desc ) = 0 THEN new_desc := nueva_foto; pos := INSTR( new_desc, '/' ); IF pos > 0 THEN new_desc := SUBSTR( new_desc, pos + 1 ); END IF; END IF; -- Insertamos una nueva fila en la tabla, usando la secuencia new_image := ORDSYS.ORDIMAGE.INIT(); new_thumb := ORDSYS.ORDIMAGE.INIT(); INSERT INTO fotos ( id, descripcion, localizador, imagen, thumb ) VALUES(secuencia_fotos.nextval,new_desc, nuevo_localizador, new_image, new_thumb ) RETURN id INTO new_id; SELECT imagen, thumb INTO new_image, new_thumb FROM fotos WHERE id = new_id

Pgina 11

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


FOR UPDATE; -- Cargar la foto desde la tabla documents upload en los objetos imagen DBMS_LOB.COPY( new_image.source.localData, upload_blob, upload_size ); new_image.setLocal(); BEGIN new_image.setProperties(); EXCEPTION WHEN OTHERS THEN new_image.contentLength := upload_size; new_image.mimeType := upload_mime_type; END; -- Algunos formatos son soportados por interMedia pero no pueden ser mostrador -- in-line por un navegador (formato BMP). En este caso convertimos la imagen a un -- GIF o JPEG segn el nmero de colores en la imagen. IF new_image.contentFormat IS NOT NULL AND ( new_image.mimeType = 'image/bmp' OR new_image.mimeType = 'image/x-bmp' ) THEN BEGIN new_image.process('fileFormat=' || dame_Formato_preferido( new_image.contentFormat ) ); EXCEPTION WHEN OTHERS THEN NULL; END; END IF; -- Intentar copiar la imagen a tamao real y procesarla para crear su thumb-nail. -- Si el formato no se reconoce, no ser posible. BEGIN new_image.processCopy( 'maxScale=50,50', new_thumb ); EXCEPTION WHEN OTHERS THEN new_thumb.deleteContent(); new_thumb.contentLength := 0; END; -- Actualizar la imagen a tamao real y su thumb-nail en nuestra tabla UPDATE fotos SET imagen = new_image, thumb = new_thumb WHERE id = new_id; -- Borrar la fila en la tabla upload de documentos. DELETE FROM fotos_upload WHERE name = nueva_foto; -- Redireccionar al browser para que muestre de nuevo todo el album. imprime_pagina_cabecera('<meta http-equiv="refresh" ' || 'content="2;url=ALBUM.VER_ALBUM">' ); imprime_encabezado( 'Foto satisfactoriamente subida al &aacute;lbum' ); htp.print( '<p>Por favor, haz clic en el siguiente enlace o ' || 'espera a que el browser refresque la p&aacute;agina.</p>' ); imprime_pagina_pie( TRUE ); END inserta_nueva_foto; -------------------------------------------------------------------------------------- Function: dame_formato_preferido -- Descripcin: Devuelve otro formato de fichero si el browser no soporta el actual ------------------------------------------------------------------------------------FUNCTION dame_formato_preferido( formato IN VARCHAR2 ) RETURN VARCHAR2 IS num_digits INTEGER; ch CHAR(1); BEGIN -- El formato del contenido de una imagen es de la forma: -<#bits><format> -MONOCHROME -- El nmero de dgitos representa el nmero de colores num_digits := 0; LOOP ch := SUBSTR( formato, num_digits + 1, 1 ); IF ch >= '0' AND ch <= '9' THEN num_digits := num_digits + 1; ELSE

Pgina 12

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


EXIT; END IF; END LOOP; -- Imagenes con ms de 8 bits de colores pueden convertirse a JPEG -- sin prdida significativa de calidad IF num_digits > 0 THEN IF TO_NUMBER( SUBSTR( formato, 1, num_digits ) ) > 8 THEN RETURN 'JFIF'; END IF; END IF; -- Imagenes con 8 bits o menos de color se convierten a GIF RETURN 'GIFF'; END dame_formato_preferido; ---------------------------------------------------------------------------- Procedure: imprime_formulario_upload -- Descripcin: Genera el formulario upload --------------------------------------------------------------------------PROCEDURE imprime_formulario_upload IS nbsp VARCHAR2( 6 ) := CHR( 38 ) || 'nbsp;'; BEGIN -- Simplemente genera el formulario imprime_encabezado( 'Subir Nueva Foto' ); htp.print( '<p><form action="ALBUM.INSERTA_NUEVA_FOTO"' ); htp.print( ' method="post" enctype="multipart/form-data">' ); htp.print( '<table border="0" width="100%" cellspacing="0" ' || 'summary="Formulario Upload">' ); htp.print( ' <tr bgcolor="#f7f7e7">' ); htp.print( ' <td scope="col" colspan="2">' || nbsp || '</td>' ); htp.print( ' </tr><tr bgcolor="#f7f7e7">' ); htp.print( ' <td scope="col" valign="top">' || ' '<label for="descripcion_id"><b>Descripci&oacute;n:</b>' ); htp.print( ' <br><font size="-1">(Opcional)</font></label></td>' ); htp.print( ' <td scope="col" ><input id="descripcion_id" type="text" ' || 'name="nueva_descripcion" length="40">' ); htp.print( ' <br><font size="-1">(ejemplo, Mis vacaciones)</font></td>' ); htp.print( ' </tr><tr bgcolor="#f7f7e7">' ); htp.print( ' <td scope="col" valign="top"> ' || ' <label for="localizador_id"><b>Localizador:</b>' ); htp.print( ' <br><font size="-1">(Opcional)</font></label></td>' ); htp.print( ' <td scope="col"><input id="localizador_id" type="text" ' || 'name="nuevo_localizador" length="40">' ); htp.print( ' <br><font size="-1">(ejemplo: algo divertido)</font></td>' ); htp.print( ' </tr><tr bgcolor="#f7f7e7">' ); htp.print( ' <td scope="col" valign="top"><label for="filename_id">' || '<b>Nombre del Fichero:</b></label></td>' ); htp.print( ' <td scope="col" ><input id="filename_id" type="file" ' || ' name="nueva_foto">' ); htp.print( ' <br><font size="-1">(ejemplo: islandia.jpg)</td>' ); htp.print( ' </tr><tr bgcolor="#f7f7e7">' ); htp.print( ' <td colspan="2">' || nbsp || '</td>' ); htp.print( ' </tr><tr bgcolor="#f7f7e7">' ); htp.print( ' <td colspan="2"><input type="submit" value="Subir foto" ></td>' ); htp.print( ' </tr><tr bgcolor="#f7f7e7">' ); htp.print( ' <td colspan="2">' || nbsp || '</td></tr>' ); htp.print( '</table>' ); htp.print( '</form></p>' ); END imprime_formulario_upload; ---------------------------------------------------------------------------- Procedure: imprime_pagina_cabecera -- Descripcin: Saca una cabecera comn ---------------------------------------------------------------------------

Pgina 13

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


PROCEDURE imprime_pagina_cabecera( tag_cabecera IN VARCHAR2 DEFAULT NULL ) IS BEGIN htp.print( '<html lang="ES">' ); htp.print( '<head>' ); htp.print( '<title>Album de Fotos con InterMedia PL/SQL Web Toolkit</title>' ); IF tag_cabecera IS NOT NULL THEN htp.print( tag_cabecera ); END IF; htp.print( '</head>' ); htp.print( '<body>' ); htp.print( '<table border="0" width="100%">' ); htp.print( '<tr><td bgcolor="#f7f7e7" align="center"><font size="+2">' ); htp.print('<i>Inter</i>Media PL/SQL Web Toolkit Album de Fotos</font></td></tr>' ); htp.print( '</table>' ); END imprime_pagina_cabecera; ---------------------------------------------------------------------------- Procedure: imprime_pagina_pie -- Descripcin: Genera un pie de pgina comn --------------------------------------------------------------------------PROCEDURE imprime_pagina_pie( muestra_url_devuelta IN BOOLEAN ) IS BEGIN IF muestra_url_devuelta THEN imprime_enlace( 'ALBUM.VER_ALBUM', 'Volver al Album' ); END IF; htp.print( '</body></html>' ); END imprime_pagina_pie; ---------------------------------------------------------------------------- Procedure: imprime_encabezado -- Descripcin: Genera una lnea con formato de titulo --------------------------------------------------------------------------PROCEDURE imprime_encabezado( mensaje IN VARCHAR2 ) IS BEGIN htp.print( '<p><font size=3 color="#336699">' ); htp.print( '<b>' || mensaje || '</b></font><hr size=1></p>' ); END imprime_encabezado; ---------------------------------------------------------------------------- Procedure: imprime_enlace -- Descripcin: genera un tag anchor (enlace) --------------------------------------------------------------------------PROCEDURE imprime_enlace( enlace IN VARCHAR2, mensaje IN VARCHAR2 ) IS BEGIN htp.print( '<p><table width="100%">' ); htp.print( '<tr bgcolor="#f7f7e7"><td colspan="3" align="center">' ); htp.print( '<a href="' || enlace || '">' || mensaje || '</a>' || '</td></tr></table></p>' ); END imprime_enlace; END Album; / SHOW ERRORS;

Pgina 14

PASO 4. Crear un Database Access Descriptor (DAD) para ejecutar la demo. Haz estos pasos: a) Teclea la URL de la configuracin del PL/SQL Gateway Configuration en tu navegador. Por ejemplo: http://localhost/pls/admin_/gateway.htm b) Selecciona el enlace "Gateway Database Access Descriptor Settings". c) Selecciona el enlace "Add Default (blank configuration)". d) Teclea la siguiente informacin dentro de las secciones del formulario "Create DAD Entry":
Add Database Access Descriptor Database Access Descriptor Name: ALBUM_FOTOS_DAD

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


Schema Name: Database Connectivity Information Oracle User Name: Oracle Password: Oracle Connect String: Authentication Mode Authentication Mode: Session Cookie Session Cookie Name: Session State Create a Stateful Session? Connection Pool Parameters Enable Connection Pooling? Default(Home)Page Default (Home) Page: Document Access Information Document Table: Document Access Path: Document Access Procedure: Extensions uploaded as Long Raw: Path Aliasing Path Alias: Path Alias Procedure: [lo dejamos en blanco] [teclea el nombre de usuario de multimedia] [teclea password del usuario multimedia] [deja en blanco o teclea un TNS name] Basic [en blanco] No Yes ALBUM.VER_ALBUM FOTOS_UPLOAD [en blanco] [en blanco] [en blanco] [en blanco] [en blanco]

Pgina 15

Nota: si usas el servidor embebido, debes crear el DAD equivalente (consulta la prctica 12) y la URL para conectarte es http://servidor:8080/album_fotos 5). Ejecutar la aplicacin. Para usar la aplicacin, ejecuta la URL del album de fotos en un navegador web. Por ejemplo:
<HOST>[:<PUERTO>]/pls/album_fotos_dad (sin pls con servidor embebido)

4. Recuperar BLOBs de ORACLE con Programa Cliente en Java.


Como requisito previo deberamos conocer el lenguaje de programacin Java, pero en fin, es solamente un ejemplo. Para la realizacin de este programa vamos a usar los siguientes packages: java.io , java.sql, oracle.jdbc.driver. Debido a que necesitamos el paquete de oracle.jdbc.driver vamos a necesitar incluir la librera classes12.jar al ClassPath. El programa que vamos a realizar formar parte del package Mipaquete.Oracle.Blob. Est compuesto de las siguientes clases: GestorDeConexiones RecuperadorBLOB Main Las sentencias import que vamos a necesitar son las siguientes:
package Mipaquete.Oracle.Blob; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.sql.ResultSet; import java.sql.Blob; import oracle.jdbc.driver.OracleConnection; import oracle.jdbc.driver.OracleDriver;

La primera clase que vamos a comentar es la clase GestorDeConexiones, encargada de establecer y gestionar las conexiones con ORACLE. Esta clase realiza las siguientes tareas: El constructor GestorDeConexiones, que recibe el nombre de usuario de la base de datos y su clave de acceso.

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 16

conectar, mtodo privado encargado de realizar la conexin. closeConnection, cierra la conexion getConnection, devuelve una referencia a la conexin abierta. El cdigo de la clase GestorDeConexiones se muestra a continuacin. La conexion se realiza a travs del driver oci por lo que ser necesario que tengamos instalado el cliente de Oracle. Si no disponemos del driver de Oracle podemos usar el driver thin. En todo caso la informacin de la url de conexin depender de nuestra configuracin
class GestorDeConexiones { private String user; private String password; private Connection conn = null; private boolean conectado = false;

public GestorDeConexiones(String usr, String pwd){ user = usr; password = pwd; } public void closeConnection() throws SQLException{ if (conectado) conn.close(); } private void conectar() throws SQLException { String url; DriverManager.registerDriver(new OracleDriver()); // url = "jdbc:oracle:oci:@<TNS_NAME>"; // url = "jdbc:oracle:thin:@<server>:<port=1521>:<SID>"; url = "jdbc:oracle:oci:@ORACLEBD"; conn = DriverManager.getConnection(url,user, password); System.out.println("Conexion correcta"); conectado = true; } public Connection getConnection() throws SQLException { if (!conectado) conectar(); return conn; }

La clase que realmente realiza el trabajo del programa es RecuperadorBLOB, que ejecuta una consulta a la base de datos y vuelca al sistema de archivos cliente el contenido de nuestro campo BLOB. El metodo RecuperarBLOB es esttico por lo que no necesitaremos instanciar la clase, tan solo indicar el id del BLOB en la tabla y la ruta donde queramos guardar el resultado. Todo el trabajo se realiza en el mtodo esttico RecuperarBLOB, que ejecuta una consulta a la tabla "archivos" y con el resultado de la consulta guarda los datos en el sistema de archivos. La forma de ejecutar la consulta es la clsica de jdbc, es decir, construir la sql, crear un Statement y ejecutarlo para conseguir el ResultSet. Lo nico que varia es almacenaremos el contenido del campo en una variable de tipo Blob (java.sql.Blob), y que con ella obtendremos el InputStream a travs del mtodo getBinaryStream(). A partir de ah usaremos un FileOutputStream para guardar el archivo. Cualquier archivo puede representarse como un array de bytes. Por esos usamos la variable byte buffer[]. El cdigo de la clase RecuperadorBLOB se muesta a continuacin.
class RecuperadorBLOB { public static void RecuperarBLOB(Connection cn, String idBLOB, String path) throws SQLException, IOException { FileOutputStream fos = null; Statement st = null; ResultSet rs = null; String sql ="select CO_ARCHIVO, NOMBRE_ARCHIVO, BIN, FX_ALTA " + "from archivos " + "WHERE CO_ARCHIVO = '" + idBLOB + "' "; try{ st = cn.createStatement(); rs = st.executeQuery(sql); if (rs.next()) { String pathname= path + "\\" + rs.getString("NOMBRE_ARCHIVO") ;

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


File file = new File(pathname); fos = new FileOutputStream(file); Blob bin = rs.getBlob("BIN"); InputStream inStream = bin.getBinaryStream(); int size = (int)bin.length(); byte[] buffer = new byte[size]; int length = -1; while ((length = inStream.read(buffer)) != -1){ fos.write(buffer, 0, length); } } } catch (IOException ioe) { throw new IOException(ioe.getMessage()); } finally { if (fos != null)fos.close(); if (rs != null) rs.close(); rs = null; st = null; }

Pgina 17

} }

Bien, pues con esto nicamente nos falta la clase Main, el punto de entrada del programa. Esta clase realiza las siguiente tareas: Crea una instancia de GestorDeConexiones, y establece una conexion con nuestro servidor ORACLE, llama al mtodo esttico RecuperarBLOB de la clase RecuperadorBLOB, por tres veces (los tres archivos que, en mi caso, tengo guardos en la base de datos y cierra la conexion con la base de datos con el GestorDeConexiones.
public class Main { public static void main(String[] args) { System.out.println("Inicializando programa ..."); Connection conn = null; GestorDeConexiones gc = null; try{ gc = new GestorDeConexiones("app1", "password"); conn = gc.getConnection(); String path = "c:\\javaout"; RecuperadorBLOB.RecuperarBLOB(conn,"000001",path); RecuperadorBLOB.RecuperarBLOB(conn,"000002",path); RecuperadorBLOB.RecuperarBLOB(conn,"000003",path); } catch (SQLException sqle) { System.out.println("Error de acceso a BD:" + sqle.getMessage()); sqle.printStackTrace(); } catch (IOException ioe){ System.out.println("Error de acceso a disco:" + ioe.getMessage()); ioe.printStackTrace(); } try{ if (gc != null && conn != null) gc.closeConnection(); } catch (SQLException sqle) { System.out.println("Error de acceso a BD:" + sqle.getMessage()); sqle.printStackTrace(); conn = null; gc = null; } System.out.println("Finalizando programa ...");

} }

Como habis podido ver el cdigo es bastante fcil. El cdigo completo es el que sigue a continuacin:

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


/* * Main.java */ package Mipaquete.Oracle.Blob; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.sql.ResultSet; import java.sql.Blob; import oracle.jdbc.driver.OracleConnection; import oracle.jdbc.driver.OracleDriver; public class Main { public static void main(String[] args) { System.out.println("Inicializando programa ..."); Connection conn = null; GestorDeConexiones gc = null; try{ gc = new GestorDeConexiones("app1", "password"); conn = gc.getConnection(); String path = "c:\\javaout"; RecuperadorBLOB.RecuperarBLOB(conn,"000001",path); RecuperadorBLOB.RecuperarBLOB(conn,"000002",path); RecuperadorBLOB.RecuperarBLOB(conn,"000003",path); } catch (SQLException sqle) { System.out.println("Error de acceso a BD:" + sqle.getMessage()); sqle.printStackTrace(); } catch (IOException ioe){ System.out.println("Error de acceso a disco:" + ioe.getMessage()); ioe.printStackTrace(); } try{ if (gc != null && conn != null) gc.closeConnection(); } catch (SQLException sqle) { System.out.println("Error de acceso a BD:" + sqle.getMessage()); sqle.printStackTrace(); conn = null; gc = null; } System.out.println("Finalizando programa ..."); } } class RecuperadorBLOB { public static void RecuperarBLOB (Connection cn, String idBLOB, String path) throws SQLException, IOException { FileOutputStream fos = null; Statement st = null; ResultSet rs = null; String sql ="select CO_ARCHIVO, " + " NOMBRE_ARCHIVO, " + " BIN, " + " FX_ALTA " + "from archivos " + "WHERE CO_ARCHIVO = '" + idBLOB + "' "; try { st = cn.createStatement(); rs = st.executeQuery(sql); if (rs.next())

Pgina 18

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


{

Pgina 19

String pathname= path + "\\" + rs.getString("NOMBRE_ARCHIVO") ; File file = new File(pathname); fos = new FileOutputStream(file); Blob bin = rs.getBlob("BIN"); InputStream inStream = bin.getBinaryStream(); int size = (int)bin.length(); byte[] buffer = new byte[size]; int length = -1; while ((length = inStream.read(buffer)) != -1) { fos.write(buffer, 0, length); }

} }

} } catch (IOException ioe) { throw new IOException(ioe.getMessage()); } finally { if (fos != null) fos.close(); if (rs != null) rs.close(); rs = null; st = null; }

class GestorDeConexiones { private String user; private String password; private Connection conn = null; private boolean conectado = false; public GestorDeConexiones(String usr, String pwd){ user = usr; password = pwd; } public void closeConnection() throws SQLException{ if (conectado) conn.close(); } private void conectar() throws SQLException { String url; DriverManager.registerDriver(new OracleDriver()); // url = "jdbc:oracle:oci:@<TNS_NAME>"; // url = "jdbc:oracle:thin:@<server>:<port=1521>:<SID>"; url = "jdbc:oracle:oci:@ORACLEBD"; conn = DriverManager.getConnection(url,user, password); System.out.println("Conexion correcta"); conectado = true; } public Connection getConnection() throws SQLException { if (!conectado) conectar(); return conn; } }

5. PL/SQL y Java
Otra de la virtudes de PL/SQL es que permite trabajar conjuntamente con Java. PL/SQL es un excelente lenguaje para la gestin de informacin pero en ocasiones, podemos necesitar de un lenguaje de programacin ms potente. Por ejemplo, podramos necesitar consumir un servicio Web, conectar a otro servidor, trabajar con Sockets, etc. Para

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 20

estos casos, podemos unir la potencia de Java con PL/SQL. Para poder trabajar con Java y PL/SQL debemos realizar los siguientes pasos: Crear el programa Java y cargarlo en la base de datos. Crear un programa de recubrimiento1 (Wrapper) con PL/SQL. Creacin de Objetos Java en la base de datos ORACLE. ORACLE incorpora su propia versin de mquina virtual Java y de JRE 2. Esta versin de Java se instala conjuntamente con ORACLE. Para crear objetos Java en la base de datos podemos utilizar la utilidad LoadJava de ORACLE desde la lnea de comandos o bien crear objetos JAVA SOURCE en la propia base de datos. La sintaxis para la creacin de JAVA SOURCE en ORACLE es la siguiente.
CREATE [OR REPLACE] AND COMPILE JAVA SOURCE NAMED <nombre> AS public class <className> { <instrucciones java> ... };

El siguiente ejemplo crea y compila una clase Java OracleJavaClass en el interior de JAVA SOURCE FuentesJava. Un aspecto muy a tener en cuenta es que los mtodos de la clase java que queramos invocar desde PL/SQL deben ser estticos.
CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED FuentesJava AS public class OracleJavaClass { public static String Saluda(String nombre) { return ("Hola desde Java " + nombre); } };

Un mismo JAVA SOURCE puede contener varias clases de Java.

La otra opcin sera guardar nuestro cdigo java en el archivo OracleJavaClass.java, compilarlo y cargarlo en ORACLE con LoadJava. A continuacin se muestran ejemplos del uso de la utilidad LoadJava, loadJava -help loadJava -u usario/password -v -f -r OracleJavaClass.class loadJava -u usario/password -v -f -r OracleJavaClass.java Ejecucin de programas Java con PL/SQL. Una vez que tenemos listo el programa de Java debemos integrarlo con PL/SQL. Esto se realiza a travs de subprogramas de recubrimiento llamados Wrappers. No podemos crear un Wrapper en un bloque annimo. La sintaxis general es la siguiente:
CREATE [OR REPLACE] FUNCTION|PROCEDURE <name> [(<params>,...)] [RETURN <tipo>] IS|AS LANGUAGE JAVA NAME '<clase>.<metodo> [return <tipo>]' ;

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED FuentesJava AS public class OracleJavaClass { public static String Saluda(String nombre){ return ("Hola desde Java" + nombre); } } public class OracleJavaMejorada { public static String SaludoMejorado(String nombre) { return ("Saludo mejorado desde Java para: " + nombre); } };

El siguiente ejemplo muestra el Wrapper para nuestra funcin Saludo.


1 Un wrapper es algo que envuelve a otra cosa. En este caso, un programa PL/SQL envolver a otro java. Por fuera creemos que llamamos a PL/SQL, aunque internamente es java lo que se ejecuta. 2 JRE: Java Run-time Environment.

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


CREATE OR REPLACE FUNCTION Saluda_wrap (nombre VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'OracleJavaClass.Saluda(java.lang.String) return java.lang.String';

Pgina 21

Una vez creado el wrapper, podremos ejecutarlo como cualquier otra funcin o procedure de PL/SQL. Debemos crear un wrapper por cada funcin java que queramos ejecutar desde PL/SQL. Cuando ejecutemos el wrapper, es decir, la funcin "Saluda_wrap", internamente se ejecutar la clase java y se invocar el mtodo esttico "OracleJavaClass.Saluda". Un aspecto a tener en cuenta es que es necesario proporcionar el nombre del tipo java completo, es decir, debemos especificar java.lang.String en lugar de nicamente String.
SELECT SALUDA_WRAP('DEVJOKER') FROM DUAL;

La ejecucin de este ejemplo en SQL*Plus genera la siguiente salida:


SQL> SELECT SALUDA_WRAP('DEVJOKER') FROM DUAL; SALUDA_WRAP('DEVJOKER') ---------------------------------------------Hola desde Java DEVJOKER

Una recomendacin de diseo sera agrupar todos los Wrapper en un mismo paquete. En el caso de que nuestro programa Java necesitase de packages Java adicionales, deberamos cargarlos en la base de datos con la utilidad LoadJava.

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.

Pgina 22

A ENTREGAR
1. En el esquema empresa, modifica la tabla empleados para que tenga una columna que le permita almacenar una fotografa (Anota el SQL que necesites para realizar esta modificacin). 2. Modifica la aplicacin web de la prctica 12, para que al listar los empleados aparezca una pequea fotografa (si la tiene de tamao 30x60). Le edicin de un cliente debe permitir subir la foto. 3. En la Insercin/modificacin de los datos de un empleado Aade la posibilidad de que los clientes puedan subir un archivo de su sistema como foto de un empleado. EJERCICIO VOLUNTARIO: 4. Usando el siguiente cdigo (adaptndolo) para mostrar y descargar contenidos en la web, crea un nuevo esquema llamado personajes, que tenga una tabla de carga y descarga y que permita aadir datos de varios personajes (nombre, edad, mote, una fotografa y un chiste contado por ellos). Debes hacer una aplicacin web que mantenga seguridad a nivel de aplicacin (una tabla de usuarios/contraseas) donde las contraseas deben estar protegidas. Usando PL/SQL debe permitir: Al usuario personajes, realizar el mantenimiento de la tabla, que incluye insertar, modificar y borrar personajes adems de subir y bajar contenido (fotos y sonido). Al resto de usuarios ver y descargar el contenido. Oracle tiene un mecanismo para hacer upload y download ficheros directamente desde la BD usando DAD. Primero debes crear el DAD (el ejemplo supone que ests usando Apache, sino es as, debes adaptarlo): Accede a http://servidor:puerto/. El puerto lo consultas en el fichero $ORACLE_HOME/Apache/Apache/setupinfo.txt. Clic en "Mod_plsql Configuration Menu". Clic en "Gateway Database Access Descriptor Settings". Clic en "Add Default (blank configuration)". Teclea PERSONAJES como nombre del DAD. Esto se usar en la URL de los clientes. Teclea username (PUBLIC), password (PUBLIC) y connect string si es necesario. Selecciona "Basic" como authentication mode. Teclea "documentos" para Document Table. Teclea "docs" como Document Access Path. Teclea "document_api.download" para Document Access Procedure. Clic el botn OK. Para subir bien, la tabla documentos debe tener la siguiente estructura:
CREATE TABLE documentos ( name VARCHAR2(256) UNIQUE NOT NULL, mime_type VARCHAR2(128), doc_size NUMBER, dad_charset VARCHAR2(128), last_updated DATE, content_type VARCHAR2(128), blob_content BLOB ) /

Luego, creamos un package specification en el esquema:


CREATE OR REPLACE PACKAGE document_api AS PROCEDURE upload; PROCEDURE upload (file IN VARCHAR2);

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


PROCEDURE download; PROCEDURE download (file END; / SHOW ERRORS

Pgina 23

IN

VARCHAR2);

Luego un package body:


CREATE OR REPLACE PACKAGE BODY document_api AS PROCEDURE upload AS l_real_name VARCHAR2(1000); BEGIN HTP.htmlopen; HTP.headopen; HTP.title('Comprobar Upload'); HTP.headclose; HTP.bodyopen; HTP.header(1, 'Comprobar Upload'); HTP.print('<form enctype="multipart/form-data" ' || 'action="document_api.upload" method="post">'); HTP.print(' Fichero a Subir: <input type="file" name="file"><br />'); HTP.print(' <input type="submit" value="Upload">'); HTP.print('</form>'); HTP.bodyclose; HTP.htmlclose; END upload; PROCEDURE upload (file IN VARCHAR2) AS l_real_name VARCHAR2(1000); BEGIN HTP.htmlopen; HTP.headopen; HTP.title('File Uploaded'); HTP.headclose; HTP.bodyopen; HTP.header(1, 'Estado de la subida'); l_real_name := SUBSTR(file, INSTR(file, '/') + 1); BEGIN -- Borrar cualquier documento para permitir la modificacin. DELETE FROM documentos WHERE name = l_real_name; -- Modificar el nombre prefijado con el nombre del fichero real. UPDATE documentos SET name = l_real_name WHERE name = file; HTP.print( l_real_name || ' Subido satisfactoriamente.'); EXCEPTION WHEN OTHERS THEN HTP.print('Fallo al subir ' || l_real_name); HTP.print(SQLERRM); END; HTP.br; -- Crea algunos enlaces para demostrar la descarga con una URL HTP.br; HTP.print('Descargas con URL:'); HTP.br; FOR cur_rec IN (SELECT name FROM documentos) LOOP HTP.anchor('docs/' || cur_rec.name, 'docs/' || cur_rec.name); HTP.br; END LOOP; -- Crea enlaces para mostrar descargas directas HTP.br; HTP.print('Descargas Directas:'); HTP.br; FOR cur_rec IN (SELECT name FROM documentos) LOOP

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


HTP.anchor('document_api.download?file=' || cur_rec.name, 'document_api.download?file=' || cur_rec.name); HTP.br; END LOOP; HTP.bodyclose; HTP.htmlclose; END upload; PROCEDURE download IS l_filename VARCHAR2(255); BEGIN l_filename := SUBSTR(OWA_UTIL.get_cgi_env('PATH_INFO'), 2); WPG_DOCLOAD.download_file(l_filename); EXCEPTION WHEN OTHERS THEN HTP.htmlopen; HTP.headopen; HTP.title('Descargando Fichero'); HTP.headclose; HTP.bodyopen; HTP.header(1, 'Estado de descarga'); HTP.print('Fallo en descarga de ' || l_filename ); HTP.print(SQLERRM); HTP.bodyclose; HTP.htmlclose; END download; PROCEDURE download (file IN VARCHAR2) AS l_blob_content documents.blob_content%TYPE; l_mime_type documents.mime_type%TYPE; BEGIN SELECT blob_content, mime_type INTO l_blob_content, l_mime_type FROM documentos WHERE name = file; OWA_UTIL.mime_header(l_mime_type, FALSE); HTP.p('Content-Length: ' || DBMS_LOB.getlength(l_blob_content)); OWA_UTIL.http_header_close; WPG_DOCLOAD.download_file(l_blob_content); EXCEPTION WHEN OTHERS THEN HTP.htmlopen; HTP.headopen; HTP.title('Descarga de Ficheros'); HTP.headclose; HTP.bodyopen; HTP.header(1, 'Estado de la descarga'); HTP.print(SQLERRM); HTP.bodyclose; HTP.htmlclose; END download; END; / SHOW ERRORS

Pgina 24

Puedes subir usando la url http://servidor:puerto/pls/personajes/document_api.upload o ajustando el valor action de un formulario como en el siguiente ejemplo:
<html> <head> <title>Comprobar subida</title> </head> <body> <form enctype="multipart/form-data"

2ASI. SGBD. PL/SQL Prctica 14. LOBS Objetos Grandes.


action="http://servidor:7777/pls/DAD/document_api.upload" method="post"> Fichero a subir: <input type="file" name="file"><br> <input type="submit" value="Upload"> </form> </body>

Pgina 25

</html>

La segunda sobrecarga del procedure upload realmente no realiza la carga porque esto lo hace automticamente el DAD. Simplemente te permite realizar procesamiento adicional si fuese necesario. Oracle prefija el nombre dl fichero con un nombre de carpeta prefijado, para reducir la posibilidad de conflictos de nombres. En este ejemplo los ficheros se renombran a sus nombres originales. Adems el cdigo en el procedure upload ofrece dos tipos de links. El primero, usa el "document access path" y el otro usa el "document access procedure" definidos en el DAD. Esto permite acceder a los ficheros usando una URL normal, ocultando el procedimiento que devuelve el fichero. Usando document access path en la URL se provoca que el DAD llame al procedure document access. El nombre del fichero solicitado debe recuperarse de la URL y usar el procedure WPG_DOCLOAD.download_file. El segundo tipo de enlace llama a un procedure alternativo de download directamente. En este caso, el nombre del fichero debe indicarse como parmetro. Aqu tienes un procedure que permite visualizar una imagen y descargarla.
GRANT execute ON muestra_imagen TO public; CREATE OR REPLACE PROCEDURE muestra_imagen( p_id NUMBER ) AS s_mime_type VARCHAR2(48); n_length NUMBER; s_filename VARCHAR2(400); lob_image BLOB; BEGIN SELECT mime_type, dbms_lob.getlength( blob_content ), filename, blob_content INTO s_mime_type, n_length, s_filename, lob_image FROM documentos WHERE image_id = p_id; -- Fija el tamao para que el browser sepa cuanto debe descargar. owa_util.mime_header(NVL( s_mime_type, 'application/octet' ), FALSE ); htp.p( 'Content-length: ' || n_length ); -- El filename ser usado por el browser si el usuario hace "Guardar como" htp.p( 'Content-Disposition: filename="' || s_filename || '"' ); owa_util.http_header_close; -- Descargar el BLOB wpg_docload.download_file( lob_image ); END display_easy_image;

You might also like