Professional Documents
Culture Documents
Pgina 1
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.
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:
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):
Pgina 3
-- 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;
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)
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 );
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.
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? .
Pgina 7
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
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
Pgina 9
Pgina 10
Pgina 11
Pgina 12
Pgina 13
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
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)
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.
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") ;
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:
Pgina 18
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
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); } };
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); } };
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;
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.
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 ) /
Pgina 23
IN
VARCHAR2);
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"
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;