Introducción al acceso a datos. 1.- Introducción. 2.- Acceso a datos. Mediante ficheros. Bases de datos. Mapeo objeto relacional (ORM). Bases de datos XML. Componentes. 2.1.- Qué estrategia o método de acceso a datos usar. Dependerá de las alternativas que haya y del sistema de información en estudio.
3.- Ficheros. Para guardar poca información, es mejor usarlos que usar otro método. 3.1.- Uso ficheros en la actualidad. Por esta razón se emplea XML en tecnologías de comunicación como, por ejemplo, en WML (lenguaje de formato inalámbrico) y WAP (protocolo de aplicaciones inalámbricas).
4.- Bases de datos. 4.1.- Introducción. Las ventajas que aportan los sistemas de bases de datos respecto a los sistemas de archivos convencionales son: • • • • • • • • • •
Independencia de los datos respecto de los procedimientos. Disminución de las redundancias y en consecuencia, Disminución de la posibilidad de que se produzca inconsistencia de datos. Mayor integridad de los datos. Mayor disponibilidad de los datos. Mayor seguridad de los datos. Mayor privacidad de los datos. Mayor eficiencia en la recogida, codificación y entrada en el sistema. Lo que se suele denominar interfaz con el pasado y futuro: una base de datos debe estar abierta a reconocer información organizada físicamente por otro software. Compartición de los datos.
4.2.- Bases de datos relacionales. 4.3.- Bases de datos orientadas a objetos. una característica general es que el lenguaje de programación y el esquema de la base de datos utilizan las mismas definiciones de tipos.
4.4.- Comparativa entre bases de datos relacionales y orientadas a objetos. Podemos decir que las ventajas de un SGBDOO frente a las relacionales son: • Permiten mayor capacidad de modelado. • Extensibilidad • Disposición de un lenguaje de consulta más expresivo. • Adaptación a aplicaciones avanzadas de base de datos. • Prestaciones. • Reglas de acceso. • Clave.
4.4.1.- Desventajas de las bases de datos orientadas a objetos frente a las relacionales. Como desventajas o puntos débiles de las BBDDOO respecto a las relacionales podemos mencionar: • La reticencia del mercado • Carencia de un modelo de datos universal. • Carencia de experiencia. • Dificultades en optimización. 4.5.- Bases de datos objeto-relacionales. Con las Bases de Datos Objeto-Relacional se amplía el modelo relacional destacando las siguientes aportaciones: • Se aumentan la variedad en los tipos de datos,se pueden crear nuevos tipos de datos • Hay extensiones en el control de la Semántica de datos Objeto-Relacionales:Se pueden crear procedimientos almacenados y funciones que tengan un código en algún lenguaje de programación • Se pueden compartir varias librerías de clases ya existentes, esto es lo que conocemos como reusabilidad. 5.- Acceso a bases de datos mediante conectores. Un driver JDBC es un componente software que posibilita a una aplicación Java interaccionar con una base de datos. Mediante JDBC el programador puede enviar sentencias SQL, y PL/SQL a una base de datos relacional. JDBC permite embeber SQL dentro de código Java. 6.- Mapeo objeto relacional (ORM). El mapeo objeto-relacional (Object-Relational Mapping, o ORM) consisten en una técnica de programación para convertir datos entre el sistema de tipos utilizado en un lenguaje de programación orientado a objetos y el sistema utilizado en una base de datos relacional. Cuando se requiere almacenar la información de los objetos utilizando una base de datos relacional se comprueba que hay un problema de compatibilidad entre estos dos paradigmas, el llamado desfase objeto-relacional. 6.1.- Capa de persistencia y framework de mapeo. La capa de persistencia de una aplicación es la pieza que permite almacenar, recuperar, actualizar y eliminar el estado de los objetos que necesitan persistir en un sistema gestor de datos. La capa de persistencia traduce entre los dos modelos de datos: desde objetos a registros y desde registros a objetos. Así, si el programa quiere grabar un objeto, entonces llama al motor de persistencia, el motor de persistencia traduce el objeto a registros y llama a la base de datos para que guarde estos registros. Las comunidades open source incluyen importantes tecnologías, entre ellas Hibernate y el framework Spring. Las alternativas más importantes basadas en el estándar, son EJB 3.0 y JDO. Entre las implementaciones comerciales se puede resaltar TopLink. 7.- Bases de datos XML. Las bases de datos XML nativas permiten trabajar con XQL (eXtensible Query Language), el cuál sirve un propósito similar a SQL en una base de datos relacional. El trabajo con bases de datos XML nativas involucra dos pasos básicos: • Describir los datos mediante Definiciones de Tipos de Datos (Document Type Definitions, DTD) o esquemas XML y • Definir un nuevo esquema de base de datos XML nativa o Mapa de Datos a usar para almacenar y obtener datos.
8.- Desarrollo de componentes. 8.1.- Definición de componente. Un componente es una unidad de software que realiza una función bien definida y posee una interfaz bien definida.
8.2.- JavaBeans. El origen de los JavaBeans lo podemos encontrar en un par de necesidades que Java tenía: • Disponer de una tecnología de objetos y componentes reutilizables. • Mejorar el proceso para crear interfaces de usuario Un JavaBean es un componente software reutilizable basado en la especificación JavaBean de Sun (ahora Oracle) que se puede manipular visualmente con una herramienta de desarrollo.
Manejo de ficheros. 1.- Introducción. La librería java.io contiene las clases necesarias para gestionar las operaciones de entrada y salida con Java. 2.- Clases asociadas a las operaciones de gestión de ficheros y directorios. 2.1.- Clase File. La clase File proporciona una representación abstracta de ficheros y directorios. Dado un objeto File, podemos hacer las siguientes operaciones con él: • Renombrar el archivo, con el método renameTo(). El objeto File dejará de referirse al archivo renombrado, ya que el String con el nombre del archivo en el objeto File no cambia. • Borrar el archivo, con el método delete(). También, con deleteOnExit() se borra cuando finaliza la ejecución de la máquina virtual Java. • Crear un nuevo fichero con un nombre único. El método estático createTempFile() crea un fichero temporal y devuelve un objeto File que apunta a él. Es útil para crear archivos temporales, que luego se borran, asegurándonos tener un nombre de archivo no repetido. • Establecer la fecha y la hora de modificación del archivo con setLastModified(). Por ejemplo, se podría hacer: new File("prueba.txt").setLastModified(new Date().getTime()); para establecerle la fecha actual al fichero que se le pasa como parámetro, en este caso prueba.txt. • Crear un directorio, mediante el método mkdir(). También existe mkdirs(), que crea los directorios superiores si no existen. • Listar el contenido de un directorio. Los métodos list() y listFiles() listan el contenido de un directorio. list() devuelve un vector de String con los nombres de los archivos, listFiles() devuelve un vector de objetos File. • Listar los nombres de archivo de la raíz del sistema de archivos, mediante el método estático listRoots(). Para crear un objeto File nuevo, se puede utilizar cualquiera de los tres constructores siguientes: File miFichero; miFichero = new File( "/etc/kk" ); File f = new File("C:\texto.txt"); o miFichero = new File( "/etc","kk" ); o File miDirectorio = new File( "/etc" ); o miFichero = new File( miDirectorio,"kk" ); Nombres de fichero: String getName(); String getPath(); String getAbsolutePath(); String getParent(); boolean renameTo( File nuevoNombre ) Comprobaciones: boolean exists(); boolean canWrite(); boolean canRead(); boolean isFile(); boolean isDirectory(); boolean isAbsolute() Información general del fichero: long lastModified(); long length() Utilidades de directorio: boolean mkdir(); String[] list() import java.io.*; class InfoFichero { public static void main( String args[] ) throws IOException { if( args.length > 0 ) { for( int i=0; i < args.length; i++ ) { File f = new File( args[i] ); System.out.println( "Nombre: "+f.getName() ); System.out.println( "Camino: "+f.getPath() ); if( f.exists() ) { System.out.print( "Fichero existente " ); System.out.print( (f.canRead() ? " y se puede Leer" : "" ) ); System.out.print( (f.canWrite() ? " y se puese Escribir" : "" ) ); System.out.println( "." ); System.out.println( "La longitud del fichero son "+ f.length()+" bytes" ); } else System.out.println( "El fichero no existe." ); } } else System.out.println( "Debe indicar un fichero." ); } } 2.2.- Interface FilenameFilter. El interface FilenameFilter se puede usar para crear filtros que establezcan criterios de filtrado relativos al nombre de los ficheros. Una clase que lo implemente debe definir e implementar el método: boolean accept(File dir, String nombre) Este método devolverá verdadero en el caso de que el fichero cuyo nombre se indica en el parámetro nombre aparezca en la lista de los ficheros del directorio indicado por el parámetro dir. import java.io.File; listadeArchivos = fichero.list(new Filtrar(".txt")); import java.io.FilenameFilter; // Comprobamos el número de archivos en el listado int numarchivos = listadeArchivos.length ; public class Filtrar implements FilenameFilter { // Si no hay ninguno lo avisamos por consola String extension; if (numarchivos < 1) // Constructor System.out.println("No hay archivos que listar"); Filtrar(String extension){ // Y si hay, escribimos su nombre por consola. this.extension = extension; else } { public boolean accept(File dir, String name){ for(int conta = 0; conta < listadeArchivos.length; return name.endsWith(extension); conta++) } System.out.println(listadeArchivos[conta]); public static void main(String[] args) { } try { } // Obtendremos el listado de los archivos de ese catch (Exception ex) { directorio System.out.println("Error al buscar en la ruta indicada"); File fichero=new File("c:\\datos\\."); } String[] listadeArchivos = fichero.list(); } // Filtraremos por los de extension .txt }
2.3.- Rutas de los ficheros. Para evitar problemas en la ejecución de los programas cuando se ejecuten en uno u otro sistema operativo y, por tanto, persiguiendo que nuestras aplicaciones sean lo más portables posibles, se recomienda usar en Java: File.separator. String substFileSeparator(String ruta){ String separador = "\\"; try{ // Si estamos en Windows if ( File.separator.equals(separador) ) separador = "/" ; // Reemplaza todas las cadenas que coinciden con la expresión // regular dada oldSep por la cadena File.separator return ruta.replaceAll(separador, File.separator); }catch(Exception e){ // Por si ocurre una java.util.regex.PatternSyntaxException return ruta.replaceAll(separador + separador, File.separator); } }
2.4.- Creación y eliminación de ficheros y directorios. Cuando queramos crear un fichero, podemos proceder del siguiente modo: try { // Creamos el objeto que encapsula el fichero File fichero = new File("c:\\prufba\\miFichero.txt"); // A partir del objeto File creamos el fichero físicamente if (fichero.createNewFile()) System.out.println("El fichero se ha creado correctamente"); else System.out.println("No ha podido ser creado el fichero"); } catch (Exception ioe) { ioe.getMessage(); } Para borrar un fichero podemos usar la clase File, comprobando previamente si existe el fichero, del siguiente modo: fichero.delete(); Para crear directorios, podríamos hacer: try { // Declaración de variables String directorio = "C:\\prueba"; String varios = "carpeta1/carpeta2/carpeta3"; // Crear un directorio boolean exito = (new File(directorio)).mkdir(); if (exito) System.out.println("Directorio: " + directorio + " creado"); // Crear varios directorios exito = (new File(varios)).mkdirs(); if (exito) System.out.println("Directorios: " + varios + " creados"); }catch (Exception e){ System.err.println("Error: " + e.getMessage()); } Para borrar un directorio con Java, tendremos que borrar cada uno de los ficheros y directorios que éste contenga. Al poder almacenar otros directorios, se podría recorrer recursivamente el directorio para ir borrando todos los ficheros. Se puede listar el contenido del directorio e ir borrando con: File[] ficheros = directorio.listFiles(); Si el elemento es un directorio, lo sabemos mediante el método isDirectory(). 3.- Flujos. Un programa en Java, que necesita realizar una operación de entrada/salida (en adelante E/S), lo hace a través de un flujo o stream. Un flujo es una abstracción de todo aquello que produce o consume información. Java define dos tipos de flujos en el paquete java.io: • Byte streams (8 bits): proporciona lo necesario para la gestión de entradas y salidas de bytes y su uso está orientado a la lectura y escritura de datos binarios. dos clases abstractas que son InputStream y OutputStream, métodos implementados y, de entre todos, destacan read()y write(). • Character streams (16 bits): de manera similar a los flujos de bytes, dos clases abstractas, en este caso: Reader y Writer. los métodos read() y write().
3.1.- Flujos basados en bytes. Las clases principales que heredan de OutputStream, para la escritura de ficheros binarios son: • FileOutputStream: escribe bytes en un fichero. Si el archivo existe, cuando vayamos a escribir sobre él, se borrará. Por tanto, si queremos añadir los datos al final de éste, habrá que usar el constructor FileOutputStream(String filePath, boolean append), poniendo append a true. • ObjectOutputStream: convierte objetos y variables en vectores de bytes que pueden ser escritos en un OutputStream. • DataOutputStream, que da formato a los tipos primitivos y objetos String, convirtiéndolos en un flujo de forma que cualquier DataInputStream, de cualquier máquina, los pueda leer. Todos los métodos empiezan por "write", como writeByte(), writefloat(), etc. De InputStream, para lectura de ficheros binarios, destacamos: • FileInputStream: lee bytes de un fichero. • ObjectInputStream: convierte en objetos y variables los vectores de bytes leídos de un InputStream.
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { // Nombre del feichero donde se escribirá la información String fileName = "C:\\datosbinarios.dat" ; try { // Se declara un objeto de tipo DataOutputStream DataOutputStream fileOut = new DataOutputStream (new FileOutputStream(fileName)); // Escribir cadena de texto fileOut.writeUTF(jTextArea1.getText()); // Escribir un entero 5 fileOut.writeInt(5); } catch (IOException ioe) { System.out.print(ioe.getMessage()); } }
3.2.- Flujos basados en caracteres. Los BufferedReader, BufferedInputStream, BufferedWriter y BufferedOutputStream añaden un buffer intermedio.En el ejemplo anterior para leer caracteres: private void jButton1ActionPerformed(java.awt.event.ActionEvent jTextArea1.append(linea + "\n"); evt) { } // TODO add your handling code here: } File archivo = null; catch(Exception e){ }finally{ FileReader fr = null; // En el finally cerramos el fichero, para asegurarnos BufferedReader br = null; // que se cierra tanto si todo va bien como si salta try { // Apertura del fichero y creacion de BufferedReader para // una excepcion. poder try{ // hacer una lectura comoda (disponer del metodo readLine()). if( null != fr ){ archivo = new File ("C:\\archivo.txt"); fr.close(); fr = new FileReader (archivo); } br = new BufferedReader(fr); }catch (Exception e2){ // Lectura del fichero } String linea; } while((linea=br.readLine())!=null){ } System.out.println(linea); 4.- Formas de acceso a un fichero. Acceso aleatorio. Acceso secuencial. Concatenación (tuberías o "pipes").
4.1.- Operaciones básicas sobre ficheros de acceso secuencial. Grabar datos en fichero secuencial.
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { // Declarar un objeto de tipo archivo DataOutputStream archivo = null; // Abir, capturar y grabar datos try { int edad = 0 ; // Creando o abriendo para añadir el archivo archivo = new DataOutputStream( new FileOutputStream("c:\\secuencial.dat",true) ); // Escribir el nombre y los apellidos archivo.writeUTF( jTextField1.getText() ); archivo.writeUTF( jTextField2.getText() ); // Obtener la edad edad = Integer.parseInt(jTextField3.getText());
// Escribir la edad archivo.writeInt(edad) ; // Cerrar fichero archivo.close(); } catch(FileNotFoundException fnfe) { /* Archivo no encontrado */ } catch (IOException ioe) { /* Error al escribir */ } catch (Exception e) { /* Error de otro tipo*/ System.out.println(e.getMessage());} }
Copiar fichero. La clase espera dos argumentos desde la línea de comandos: nombre del fichero fuente y nombre del fichero donde se hará la copia. Se utilizan las clases FileInputStream y FileOutputStream para leer desde el fichero origen, y copiar al fichero destino, respectivamente. import java.io.FileInputStream; // Leer del fuente hasta llegar la fin de archivo import java.io.FileOutputStream; int i = fuente.read(); import java.io.IOException; while (i != -1) { // mientras not EOF destino.write(i); public class copiar { i = fuente.read(); } public static void main(String args[]) { // Clase para leer los datos de un fichero // Cerrar los ficheros FileInputStream fuente = null; fuente.close(); // Clase para escribir los datos a un fichero destino.close(); FileOutputStream destino = null; }catch (IOException e) { try { System.out.println("Error en operaciones de ficheros"); // El fichero fuente es el primer parámetro } fuente = new FileInputStream(args[0]); } // El fichero destino es el segundo parámetro. } destino = new FileOutputStream(args[1],true); Streams de salida con buffer // Ahora se pueden utilizar los dos streams de entrada para Creación de Streams de salida con buffer. // acceder al fichero (si se quiere) FileOutputStream miFileStream; miBufferStream.write( b ); BufferdOutpurStream miBufferStream; // Obtiene un controlador de fichero miDataStream.writeInt( i ); miFileStream = new FileOutputStream( "/tmp/kk" ); // Cierra el fichero de datos explícitamente. Siempre se cierra // Encadena un stream de salida con buffer // primero el fichero stream de mayor nivel miBufferStream = new BufferedOutputStream( miFileStream ); miDataStream.close(); miBufferStream.close(); Volcado y Cierre de Streams de salida con buffer miFileStream.close(); // Se fuerza el volcado del buffer a disco miBufferStream.flush(); Escritura en un objeto DataOutputStream // Cerramos el fichero de datos. Siempre se ha de cerrar primero el void writeBoolean( boolean b ); // fichero stream de mayor nivel void writeByte( int i ); miBufferStream.close(); void writeShort( int i ); miFileStream.close(); void writeChar( int i ); Streams DataOutput void writeInt( int i ); Apertura y cierre de objetos DataOutputStream void writeFloat( float f ); DataOutputStream miDataStream; void writeDouble( double d ); FileOutputStream miFileStream; void writeBytes( String s ); BufferedOutputStream miBufferStream; void writeChars( string s ); // Obtiene un controlador de fichero Contabilidad de la salida miFileStream = new FileOutputStream( "/tmp/kk" ); ... int numBytes = miDataStream.size() % 4; // Encadena un stream de salida con buffer (por eficiencia) miBufferStream = new BufferedOutputStream( miFileStream ); for( int i=0; i < numBytes; i++ ) // Encadena un fichero de salida de datos miDataStream.write( 0 ); ... miDataStream = new DataOutputStream( miBufferStream ); Las subclases de Writer y Reader que permiten trabajar con ficheros de texto son: • FileReader, para lectura desde un fichero de texto. Crea un flujo de entrada que trabaja con caracteres en vez de con bytes. • FileWriter, para escritura hacia un fichero de texto. Crea un flujo de salida que trabaja con caracteres en vez de con bytes. También se puede montar un buffer sobre cualquiera de los flujos que definen estas clases: • BufferedWriter se usa para montar un buffer sobre un flujo de salida de tipo FileWriter. • BufferedReader se usa para montar un buffer sobre un flujo de entrada de tipo FileReader. Veamos un ejemplo de lectura utilizando un BufferReader. public class LeeconBuffer { public static void main(String[] args) { String texto = "" ; try { // Crear el flujo de salida FileReader fichero = null; // Ruta y nombre del fichero fichero = new FileReader("c:\\texto.txt"); // Montar un buffer sobre ese flujo BufferedReader buffer = new BufferedReader(fichero); // Escribimos el texto usando el metodo readLine() while ((texto = buffer.readLine()) != null) { System.out.println(texto); } // Cerrar fichero fichero.close(); } catch (IOException e) { // Escribir el error en la consola System.err.println("Error: " + e); } } } Ahora vamos a ver cómo buscar en un archivo secuencial, usando ese mismo ejemplo. Al pinchar en buscar:Acrion Perfonmance private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { String busqueda = jTextField1.getText() ; boolean seguir = true ; try{ String nombre = "" ; // Declarar variable String apellidos = "" ; DataInputStream archivo = null; int edad = 0 ; // Abrir el archivo
archivo = new DataInputStream( new FileInputStream("c:\\secuencial.dat") ); // Leer archivo while (seguir) { // Leer el nombre nombre = archivo.readUTF(); // Si el nombre es el que buscamos if (busqueda.equals(nombre)) { System.out.println("encontrado"); seguir = false ; jLabel2.setText("¡¡Encontrado registro!!"); }
// Leer los otros campos apellidos = archivo.readUTF(); edad=archivo.readInt(); } // Cerrar fichero archivo.close(); } catch(FileNotFoundException fnfe) { /* Archivo no encontrado */ } catch (IOException ioe) { /* Error al escribir */ } }
4.2.- Operaciones básicas sobre ficheros de acceso aleatorio. Java proporciona una clase RandomAccessFile para este tipo de entrada/salida. La clase posee dos constructores: • RandomAccessFile(File file, String mode). • RandomAccessFile(String name, String mode). El modo es: "r" si se abre en modo lectura o "rw" si se abre en modo lectura y escritura. Posee métodos específicos de desplazamiento como seek(long posicion) o skipBytes(int desplazamiento) para poder movernos de un registro a otro del fichero, o posicionarnos directamente en una posición concreta del fichero. Escribir en ficheros de acceso aleatorio. public static void main(String[] args)throws IOException { RandomAccessFile miRAFile; String s = "Cadena a escribir2\n"; // Abrimos el fichero de acceso aleatorio miRAFile = new RandomAccessFile( "C:\\aleatorio.bin","rw" ); // Nos vamos al final del fichero miRAFile.seek( miRAFile.length() ); // Incorporamos la cadena al fichero miRAFile.writeBytes( s ); // Cerramos el fichero miRAFile.close(); }
5.- Trabajo con ficheros XML: analizadores sintácticos (parser) y vinculación (binding). 5.1.- Conceptos previos. 5.2.- Definiciones. ¿Qué es y para qué sirve JAXB (Java Architecture for XML Binding)? JAXB simplifica el acceso a documentos XML representando la información obtenida de los documentos XML en un programa en formato Java. Parsear un documento XML consiste en "escanear" el documento y dividirlo o separarlo lógicamente en piezas discretas. Binding: Binding o vincular un esquema (schema) significa generar un conjunto de clases Java que representan el esquema. Compilador de esquema o schema compiler: liga un esquema fuente a un conjunto de elementos de programa derivados. Binding runtime framework: proporciona operaciones de unmarshalling y marshalling para acceder, manipular y validar contenido XML usando un esquema derivado o elementos de programa. Marshalling: es un proceso de codificación de un objeto en un medio de almacenamiento, normalmente un fichero. Unmarshalling: proporciona a una aplicación cliente la capacidad de convertir datos XML a objetos Java JAXB derivados. 5.3.- Introducción a JAXB. JAXB permite almacenar y recuperar datos en memoria en cualquier formato XML, sin la necesidad de implementar un conjunto específico de rutinas XML de carga y salvaguarda para la estructura de clases del programa. 5.4.- Funcionamiento de JAXB. Para construir una aplicación JAXB necesitamos tener un esquema XML. Tras obtener el esquema XML, seguimos los siguientes pasos para construir la aplicación JAXB: 1. Escribir el esquema: es un documento XML que contiene la estructura que se tomará como indicaciones para construir las clases. 2. Generar los ficheros fuente de Java: para esto usamos el compilador de esquema, ya que éste toma el esquema como entrada de información. 3. Construir el árbol de objetos Java: con nuestra aplicación, se genera el árbol de objetos java, también llamado árbol de contenido, que representa los datos XML que son validados con el esquema. Hay dos formas de hacer esto: • Instanciando las clases generadas. • Invocando al método unmarshall de una clase generada y pasarlo en el documento. El método unmarshall toma un documento XML válido y construye una representación de árbol de objetos. 4. Acceder al árbol de contenido usando nuestra aplicación: ahora podemos acceder al árbol de contenido y modificar sus datos. 5. Generar un documento XML desde el árbol de contenido. Para poder hacerlo tenemos que invocar al método marshall sobre el objeto raíz del árbol. Ejemplo de proyecto sencillo con JAXB. Creamos fichero .xsd Vamos a suponer una estructura de un archivo que podría usarse en un almacén distribuidor de medicamentos. El fichero albaran.xsd tiene la estructura que el almacén usa cuando envía un pedido de medicamentos que le ha hecho una farmacia.
public class ModificaAlbaPed { public static void main(String[] args) { try { // Crear una instancia de la clase JAXBContext para poder manipular las clases generadas en el paquete jaxb.albaran // La clase JAXBContext proporciona al cliente un punto de entrada a la API JAXB. Facilita una abstracción para manejar la información generada para implementar las operaciones del JAXB binding framework como unmarshal y marshal // unmarshal: consiste en convertir datos XML en un árbol de objetos Java // marshal: consiste en convertir un árbol de objetos Java a datos XML JAXBContext jaxbContext = JAXBContext.newInstance("jaxb.albaran"); // Crear un objeto de tipo Unmarshaller para convertir datos XML en un árbol de objetos Java Unmarshaller u = jaxbContext.createUnmarshaller(); // La clase JAXBElement representa a un elemento de un documento XML en este caso a un elemento del documento albaran.xml JAXBElement jaxbElement = (JAXBElement) u.unmarshal(new FileInputStream("C:\\albaran.xml")); // El método getValue() retorna el modelo de contenido (content model) y el valor de los atributos del elemento PedidoType pedidoType = (PedidoType) jaxbElement.getValue(); // Obtenemos una instancia de tipo PedidoType para obtener un Objeto de tipo Direccion Direccion direccion = pedidoType.getFacturarA(); // Establecemos los datos direccion.setNombre("Jose Javier"); direccion.setCalle("Zafiro 3"); direccion.setCiudad("Molina"); direccion.setProvincia("Murcia"); direccion.setCodigoPostal(new BigDecimal("30500")); // Crear un objeto de tipo Marshaller para posteriormente convertir un el árbol de objetos Java a datos XML Marshaller m = jaxbContext.createMarshaller(); // El método setProperty(String nombrePropiedad, Object value) recibe en este caso la propiedad "jaxb.formatted.output". Esta propiedad controla si al realizar un marshal, debe formatear el resultado XML con saltos de linea e indentaciones para que las personas podamos leerlo cómodamente. Por defecto su valor es falso es decir el XML creado no está formateado // El argumento value en este caso tiene que ser concretamente de tipo Boolean para indicar si queremos que el resultado XML está formateado o no m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); // El método marshall(Object elementoJAXB, OutputStream os) recibe un objeto de tipo JAXBElement para que su contenido lo muestre en la salida estándar debido a que este método está¡ sobrecargo, si miramos la documentación de la API podemos ver como podemos mostrar o escribir el resultado XML de diferentes maneras m.marshal(jaxbElement, System.out); } catch (JAXBException je) { System.out.println(je.getCause()) ; } catch (IOException ioe) { System.out.println(ioe.getMessage()); } } }
6.- Librerías para conversión de documentos XML a otros formatos. En nuestro caso, vamos a optar por una herramienta que permite generar informes de todo tipo en Java de una forma sencilla: JasperReport. 6.1.- Introducción a JasperReport. JasperReports es una herramienta que consta de un poderoso motor para la generación de informes. Descarga de JasperReports e integración en NetBeans. 1. Descargamos y descomprimimos 2. Añadimos a librería: Tools-Libraries…….New Library…..
6.2.- Diseñar y compilar la plantilla. Las plantillas de los informes de JasperReports son sencillamente ficheros XML con la extensión .jrxml. Podemos hacer que NetBeans reconozca este tipo de ficheros como XML, para que cuando los editemos en el editor se muestren los mismos códigos de colores en las etiquetas y demás elementos de la sintaxis de XML. En la imagen se ilustra cómo conseguirlo: en NetBeans pinchamos en el menú Tools, y ahí en Options. Ahí seleccionamos Miscellaneous, luego la pestaña Files. Entonces pulsamos en el botón New... para añadir la nueva extensión.
Los pasos a seguir para trabajar con JasperReport serían: Paso 1: Diseñar la plantilla del informe: un fichero .jrxml. El documento de diseño está representado por un archivo XML que mantiene la estructura de un archivo DTD (Document Type Definition) definido por el motor de JasperReports. La generación de un diseño implica editar un archivo XML validado mediante: Estos documentos XML cuentan con una estructura similar a la de cualquier documento de texto. Fundamentalmente se siguen estas secciones: title Título del informe. pageHeader Encabezado del documento. columnHeader Encabezado de las columnas. detail Detalle del documento. Cuerpo columnFooter Pie de la columna. pageFooter Pie del documento. sumary Cierre del documento. Paso 2: Compilación: Una vez que se ha realizado el diseño, se compila antes de poder iniciar el proceso de carga de datos. La compilación se lleva a cabo a través del método compileReport(). En este proceso, el diseño se transforma en un objeto serializable de tipo net.sf.jasperreports.engine JasperReport, que luego se guarda en disco.
6.3.- Rellenar el informe con datos, exportar el informe. Paso 3: Rellenar el informe con datos: mediante los métodos fillReportXXX(), se puede realizar la carga de datos del informe, Como resultado de este proceso, se obtiene un objeto que representa un documento listo para ser impreso, un objeto serializable de tipo JasperPrint. Este objeto puede guardarse en disco para su uso posterior, o bien puede ser impreso, enviado a la pantalla o transformado en PDF, XLS, CSV, etc. Paso 4: Visualización.Ahora podemos optar por mostrar un informe por pantalla, imprimirlo, o bien obtenerlo en algún tipo específico de fichero, como PDF, etc. • Para mostrar un informe por pantalla se utiliza la clase JasperViewer, la cual, a través de su método main(), recibe el informe a mostrar. • Para imprimir el informe usaremos los métodos printReport(), printPage() o printPages(), contenidos en la clase JasperPrintManager. • Para exportar los datos a un formato de archivo específico podemos utilizar los métodos exportReportXXX(). Ejemplo con JasperReports.
Seguimos desarrollando el ejemplo. Ahora vamos a hacer que obtenga datos de una base de datos. En concreto, de la base de datos derby que se incluye al instalar el jdk.
Manejo de conectores. 1.- Introducción. Java, mediante JDBC (Java Database Connectivity), permite simplificar el acceso a bases de datos relacionales. 1.1.- El desfase objeto-relacional. consiste en la diferencia de aspectos que existen entre la programación orientada a objetos y la base de datos. impedancia Objeto-Relacional, o sea, el conjunto de dificultades técnicas que surgen cuando una base de datos relacional se usa en conjunto con un programa escrito en POO.
2.- Protocolos de acceso a bases de datos. 2.1.- Arquitectura JDBC. El API JDBC soporta dos modelos de procesamiento para acceso a bases de datos: de dos y tres capas. En el modelo de dos capas, una aplicación se comunica directamente a la fuente de datos. En el modelo de tres capas, los comandos se envían a una capa intermedia de servicios, la cual envía los comandos a la fuente de datos. El API JDBC viene distribuido en dos paquetes: java.sql, dentro de J2SE. javax.sql, extensión dentro de J2EE. 2.2.- Conectores o Drivers. Un conector o driver es un conjunto de clases encargadas de implementar los interfaces del API y acceder a la base de datos. Hay cuatro tipos de drivers JDBC: Tipo 1, Tipo 2, Tipo 3 y Tipo 4, que veremos a continuación. Código de la estructura para conectarnos, ejecutar consulta y procesar resultados. // Establece la conexión Connection con = DriverManager.getConnection ("jdbc:odbc:miBD", "miLogin", "miPassword" ); // Ejecuta la consulta Statement stmt = (Statement) con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT nombre, edad FROM Empleados"); // Procesa los resultados while (rs.next()) { String nombre = rs.getString("nombre"); int edad = rs.getInt("edad"); }
2.3.- Conectores tipo 1 y tipo 2. Los conectores tipo 1 se denominan también JDBC-ODBC Bridge (puente JDBC-ODBC). Los conectores tipo 2 se conocen también como: API nativa. Convierten las llamadas JDBC a llamadas específicas de la base de datos para bases de datos como SQL Server, Informix, Oracle, o Sybase. los drivers tipo 2 no pueden usarse para Internet.
2.4.- Conectores tipo 3 y tipo 4. Tipo 3: JDBC-Net pure Java driver. Tiene una aproximación de tres capas. Tipo 4: Protocolo nativo. En este caso se trata de conectores que convierten directamente las llamadas JDBC al protocolo de red usando por el sistema gestor de la base de datos. Un ejemplo de este tipo de conector es Oracle Thin. 3.- Conexión a una base de datos. En Java, para establecer una conexión con una base de datos podemos utilizar el método getConnection() de la clase DriverManager. La ejecución de este método devuelve un objeto Connection que representa la conexión con la base de datos. Si no se encuentra ningún conector adecuado, se lanza una SQLException
3.1.- Instalar el conector de la base de datos. Para que podamos ejecutar el código anterior, necesitamos instalar el conector de la base de datos. En la siguiente presentación vamos a ver cómo descargarnos el conector que necesitamos para trabajar con MySQL. Como verás, tan sólo consiste en descargar un archivo, descomprimirlo y desde NetBeans añadir el fichero .jar que constituye el driver que necesitamos. Instalar conector JDBC para MySQL: • En esta pagina lo descargamos: http://www.mysql.com/downloads/connector/j/
•
En la pantalla que aparece después, pinchamos en la parte de abajo para poder seleccionar los servidores de descarga donde se aloja el conector JDBC. Ahora, elegimos uno de lo servidores que aparecen y pinchamos en FTP
•
En NetBeans, situándonos en el nombre el proyecto pulsamos el botón derecho del ratón.En el menú contextual que aparece seleccionamos Properties.Seleccionamos el nodo de las Librerías del proyecto.Pinchamos en el botón Add JAR/Folder.Buscamos y elegimos el fichero comentado anteriormente, el .jar.Tan solo queda pulsar Ok y hemos acabado.
3.2.- Pool de conexiones. El proceso de creación y destrucción de una conexión a una base de datos es costoso e influye sensiblemente en el rendimiento de una aplicación. Es mejor en estos casos, por tanto, abrir una o más conexiones y mantenerlas abiertas. La versión 3.0 de JDBC proporciona un pool de conexiones que funciona de forma transparente. El código básico para conectarnos a una base de datos con pool de conexiones transparente a través de JNDI podría ser: //Initial Context es el punto de entrada para comenzar a explorar un espacio de nombres. javax.naming.Context ctx = new InitialContext(); dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/Basededatos"); Cada vez que necesitamos realizar una operación tendremos que escribir el siguiente código para obtener una conexión lógica: connection = dataSource.getConnection(); Cuando hayamos finalizado la operación entonces cerraremos la conexión lógica con el siguiente código: connection.close(); 4.- Creación de la base de datos. 5.- Operaciones: ejecución de consultas. Podemos utilizar los siguientes tipos de sentencias: Statement: para sentencias sencillas en SQL. PreparedStatement: para consultas preparadas, como por ejemplo las que tienen parámetros. CallableStatement: para ejecutar procedimientos almacenados en la base de datos. El API JDBC distingue dos tipos de consultas: Consultas: SELECT Actualizaciones: INSERT, UPDATE, DELETE, sentencias DDL.
5.1.- Ejemplo: consultas con MS-Access. En primer lugar tenemos que definir la fuente de datos ODBC.
instalar el driver JDBC. Dentro del código se carga el driver antes de acceder a la base de datos: Class.forName("jdbc:odbc:admdb"); realizar la conexión a la base de datos para comenzar a trabajar con ella. Connection con = DriverManager.getConnection("jdbc:odbc:admdb"); Las consultas que se realizan a una base de datos se realizan utilizando objetos de las clases Statement y PreparedStatement. Estos objetos se crean a partir de una conexión. Statement stmt = con.createStatement(); La clase Statement contiene los métodos executeQuery y executeUpdate para realizar consultas y actualizaciones. Así por ejemplo, para obtener los nombres de los medicamentos que tenemos en la tabla medicamentos, de la base de datos farmacia.mdb que creamos anteriormente, tendríamos que emplear la sentencia: ResultSet rs = stmt.executeQuery("SELECT nombre from medicamentos"); El método executeQuery devuelve un objeto ResultSet para poder recorrer el resultado de la consulta utilizando un cursor. El método next se emplea para hacer avanzar el cursor. Para obtener una columna del registro utilizamos los métodos get. Hay un método get para cada tipo básico Java y para las cadenas. while (rs.next()) String usuario = rs.getString("nombre"); Comentar que un método interesante del cursor es wasNull que nos informa si el último valor leído con un método get es nulo. Respecto a las consultas de actualización, executeUpdate, retornan el número de registros insertados, registros actualizados o eliminados, dependiendo del tipo de consulta que se trate. import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class Main { public static void main(String[] args) { try {
// Cargar el driver de Microsoft Access Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // Cadena de conexión para conectar con Access String connectionUrl = "jdbc:odbc:admdb" ; // Obtener la conexión Connection con = DriverManager.getConnection(connectionUrl); // La clase Statement contiene los métodos executeQuery y executeUpdate para realizar consultas y actualizaciones Statement stmt = con.createStatement(); //El método executeQuery devuelve un objeto ResultSet para poder recorrer el resultado de la consulta utilizando un cursor. // Esta consulta obtiene todos los datos, todos los campos, )debido al *), almacenados en la tabla medicamentos. ResultSet rs = stmt.executeQuery("SELECT * from medicamentos"); // Mientras queden datos while (rs.next()) { // Imprimir en la consola String codigo = rs.getString("codigo"); String nombre = rs.getString("nombre"); String precio = rs.getString("precio"); String pvp = rs.getString("pvp"); String unidades = rs.getString("unidades"); System.out.println(codigo + "---" + nombre + "--" +precio+ "--" +pvp+ "--" +unidades); } } catch (SQLException e) { System.out.println("SQL Exception: "+ e.toString()); } catch (ClassNotFoundException cE) { System.out.println("Excepción: "+ cE.toString()); } }} 5.2.- Consultas preparadas. Las consultas preparadas están representadas por la clase PreparedStatement. Son consultas precompiladas, por lo que son más eficientes, y pueden tener parámetros. Una consulta se instancia del modo que vemos con un ejemplo: PreparedStatement pstmt = con.preparedStatement("SELECT * from medicamentos"); Si hay que emplear parámetros en una consulta, se puede hacer usando el carácter ‘?’. Por ejemplo, para realizar una consulta de un medicamento que tenga un código determinado, haríamos la consulta siguiente: PreparedStatement pstmt = con.preparedStatement("SELECT * from medicamentos WHERE codigo = ? "); Establecemos los parámetros de una consulta utilizando métodos set que dependen del tipo SQL de la columna. Así, le decimos que el primer parámetro, que es el único que tiene esta consulta, es “712786”: pstmt.setString(1, "712786"); Finalmente, ejecutamos la consulta utilizando el método executeQuery() o executeUpdate(), ambos sin parámetros, dependiendo del tipo de consulta. Ejemplo: public class Actualizacion{ private PreparedStatement sentencia; public void prepararInsercion(){ String sql = "insert into personas values ( ?, ? ,? )"; sentencia = conexion.prepareStatement(sql); } public void insertarPersona(String nombre, dirección, telefono) { sentencia.setString(1, nombre); sentencia.setString(2, direccion); sentencia.setString(3, telefono); sentencia.executeUpdate(); }}
6.- Ejecución de procedimientos almacenados en la base de datos. Además, estos procedimientos suelen ser de dos clases: • Procedimientos almacenados. • Funciones, las cuales devuelven un valor que se puede emplear en otras sentencias SQL. Un procedimiento almacenado típico tiene: • Un nombre. • Una lista de parámetros. • Unas sentencias SQL.
6.1.- Ejecutando procedimentos almacenados en MySQL. A continuación, vamos a realizar un procedimiento almacenado en MySQL, que simplemente insertará datos en la tabla clientes. -Si no tenemos creada la tabla de clientes, la creamos. Por simplicidad, en este ejemplo, trabajamos sobre la base de datos que viene por defecto en MySQL, el esquema denominado: test. Para crear la tabla de clientes, el script correspondiente es: delimiter $$ CREATE TABLE `clientes` ( `Cod_Cliente` int(3) NOT NULL DEFAULT '0', `Nombre` tinytext, `Telefono` tinytext, PRIMARY KEY (`Cod_Cliente`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1$$ -Creamos el procedimiento almacenado en la base de datos. Sería tan fácil como lo que ves en el siguiente enlace: DELIMITER $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `insertaCliente`(IN Cod_Cliente INTEGER, IN Nombre TinyText,IN Telefono TinyText ) BEGIN INSERT INTO clientes VALUES (Cod_Cliente, Nombre, Telefono); END
-Crear la clase Java para desde aquí, llamar al procedimiento almacenado: public class inserConProcAlma { public static void main(String[] args) { try { // Cargar el driver de mysql Class.forName("com.mysql.jdbc.Driver"); // Cadena de conexión para conectar con MySQL en localhost,seleccionar la base de datos llamada ‘test’ con usuario y contraseña del servidor de MySQL: root y admin String connectionUrl = "jdbc:mysql://localhost/test?" + "user=root&password=admin"; // Obtener la conexión Connection con = DriverManager.getConnection(connectionUrl); // El procedimiento almacenado tendrá tres parámetros CallableStatement prcProcedimientoAlmacenado = con.prepareCall("{ call insertaCliente(?, ?,?) }"); // cargar parametros en el procedimiento almacenado prcProcedimientoAlmacenado.setInt("Cod_Cliente", 765); prcProcedimientoAlmacenado.setString("Nombre", "Antonio Pérez") ; prcProcedimientoAlmacenado.setString("Telefono", "950121314") ; // ejecutar el procedimiento prcProcedimientoAlmacenado.execute(); } catch (SQLException e) { System.out.println("SQL Exception: "+ e.toString()); } catch (ClassNotFoundException cE) { System.out.println("Exceción: "+ cE.toString()); } }} Si hemos definido la tabla correctamente, con su clave primaria, y ejecutamos el programa, intentando insertar una fila igual que otra insertada, o sea, con la misma clave primaria, obtendremos un mensaje al capturar la excepción de este tipo: SQL Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '765' for key 'PRIMARY' 7.- Transacciones. Cuando tenemos una serie de consultas SQL que deben ejecutarse en conjunto, con el uso de transacciones podemos asegurarnos de que nunca nos quedaremos a medio camino de su ejecución. 7.1.- Commit y Rollback. Una transacción tiene dos finales posibles, COMMIT o ROLLBACK. Si se finaliza correctamente y sin problemas se hará con COMMIT, con lo que los cambios se realizan en la base de datos, y si por alguna razón hay un fallo, se deshacen los cambios efectuados hasta ese momento, con la ejecución de ROLLBACK. Por defecto, al menos en MySQL o con Oracle, en una conexión trabajamos en modo autocommit con valor true. Eso significa que cada consulta es una transacción en la base de datos. Por tanto, si queremos definir una transacción de varias operaciones, estableceremos el modo autocommit a false con el método setAutoCommit de la clase Connection. Puedes ver un ejemplo sencillo de cómo se puede utilizar commit y rollback: tras las operaciones se realiza el commit, y si ocurre una excepción, al capturarla realizaríamos el rollback. BEGIN … SET AUTOCOMMIT OFF update cuenta set saldo=saldo + 250 where dni=”12345678-L”; update cuenta set saldo=saldo - 250 where dni=”89009999-L”; COMMIT; … EXCEPTION WHEN OTHERS THEN ROLLBACK ; END;
8.- Excepciones y cierre de conexiones. 8.1.- Excepciones. Cuando se produce un error durante la ejecución de un programa, se genera un objeto asociado a esa excepción. try { // Bloque de instrucciones del try }catch (FileNotFoundException fnfe){ // Bloque para excepción por fichero no encontrado }catch(IOException ioe){ // Bloque para excepción por error de entrada salida }catch(SQLException sqle){ // Bloque para excepción por error con SQL }catch(Exception e){ }finally{ //Instrucciones finales para, por ejemplo, limpieza }
8.2.- Cierre de conexiones. Resulta totalmente conveniente cerrarlas con el método close cuando ya no se utilizan.
Mapeo objeto relacional. 1.- Concepto de Mapeo objeto-relacional(ORM). Es una técnica de programación que se utiliza con el propósito de convertir datos entre el utilizado en un lenguaje de programación orientado a objetos y el utilizado en una base de datos relacional, gracias a la la persistencia. traducir los objetos a formas que puedan ser almacenadas en bases de datos preservando las propiedades de los objetos y sus relaciones; estos objetos se dice entonces que son persistentes. 2.- Herramientas ORM. Características y herramientas más utilizadas. Object Relational Mapping (ORM) es la herramienta que nos sirve para transformar representaciones de datos de los Sistemas de Bases de Datos Relacionales, a representaciones (Modelos) de objetos. En el modelo relacional, cada fila en la tabla se mapea a un objeto y cada columna a una propiedad. 2.1.- Características. Una herramienta ORM permite tomar un objeto Java y hacerlo persistente, carga el objeto de la base de datos a memoria y permite hacer consultas a las tablas de la base de datos. Ventajas de ORM. • Ayudan a reducir el tiempo de desarrollo de software. • Abstracción de la base de datos. • Reutilización. • Permiten persistir objetos a través de un método Orm.Save y generar el SQL correspondiente. • Permiten recuperar los objetos persistidos a través de un método Orm.Load. • Lenguaje propio para realizar las consultas. • Independencia de la base de datos. • Incentivan la portabilidad y escalabilidad de los programas de software. Desventajas de ORM. • Tiempo utilizado en el aprendizaje. • Menor rendimiento (aplicaciones algo más lentas). • Sistemas complejos.
2.2.- Herramientas ORM más utilizadas. Hibernate: para la plataforma Java. Utiliza archivos declarativos (XML) Java Persistence Api (JPA): es una especificación de Sun Microsystems para la persistencia de objetos Java a cualquier base de datos relacional. iBatis: iBatis es un framework de persistencia desarrollado por la Apache software Foundation. 3.- Instalación y configuración de Hibernate. El archivo de configuración de Hibernate recibe el nombre de Hibernate.cfg.xml y contiene información sobre la conexión de la base de datos y otras propiedades. 4.- Ficheros de configuración y mapeo. Estructura y propiedades. Para utilizar Hibernate necesitamos tener una base de datos relacional instalada en NetBeans. La base de datos Sakila es una muestra gratuita disponible, como plugin, en NetBeans. 4.1.- Ficheros de configuración. Propiedades. El archivo de configuración de Hibernate es el Hibernate.cfg.xml y contiene información sobre la conexión de base de datos, las asignaciones de recursos y otras propiedades de conexión.
Las propiedades más importantes del fichero Hibernate.cfg.xml son: • Hibernate.dialect: Dialecto o lenguaje empleado. Por ejemplo, MySQL. • Hibernate.connection.driver_class. Driver utilizado para la conexión con la base de datos. • Hibernate.connection.url. Dirección de la base de datos con la que se va a conectar Hibernate. • Hibernate.connection.username. Nombre del usuario que va a realizar la extracción de información. Por defecto, el nombre de usuario es root. • Hibernate.connection.password. Contraseña el root. • Hibernate.show_sql. Para mostrar la herramienta. Por defecto, su valor es true. 4.2.- Ficheros de mapeo. Estructura, elementos y propiedades. Hibernate utiliza ficheros de mapeo para relacionar tablas con objetos Java, estos ficheros están en formato XML y que tienen extensión .hbm.xml. Para extraer un tabla concreta de la base de datos, la sintaxis en el mapeo requiere definir el POJO Nombre_Clase.hbm.xml, donde nombre_clase se corresponderá con el nombre la tabla que queremos extraer y donde se describe cómo se relacionan clases y tablas y propiedades y columnas. Podemos utilizar la ingeniería inversa para crear archivos de mapeo basados en tablas de la base de datos que seleccionemos. El archivo de ingeniería inversa es Hibernate.reveng.xml. Mediante el asistente de NetBeans, seleccionamos Nuevo Mapeo en Hibernate y rellenamos los campos que nos piden, en función de la información que nos interese extraer de la base de datos. De esta manera, NetBeans genera un POJO nombre_clase.java (siendo nombre_clase aquella tabla cuya información nos interesa extraer de la base de datos) con todos los campos necesarios.
5.- Mapeo de colecciones, relaciones y herencia. 1. Mapeo de colecciones. Hay bastantes rangos de mapeos que se pueden generar para colecciones que cubran diversos modelos relacionales.
2. Mapeo de relaciones. Para mapear las relaciones, se usan los identificadores de objetos (OID). Son la llave primaria de la tabla relacionada y se agregan como una columna más en la tabla donde se quiere establecer la relación. 3. Mapeo de herencia. Para el caso de la herencia se presenta el problema que las base de datos relacionales no la soportan. Así es que somos nosotros quienes debemos modelar como se verá la herencia en el modelo relacional. 6.- Clases persistentes. Las clases persistente son clases en una aplicación que nos van a servir para representar entidades de la base de datos. Para poder indicar las clases y atributos que son persistentes, se utiliza un fichero de configuración XML, que se denomina descriptor de persistencia. 7.- Sesiones; estados de un objeto. Para poder utilizar la persistencia en Hibernate es necesario definir un objeto Session utilizando la clase SesssionFactory. Los estados en los que se puede encontrar un objetos son: • Transitorio (Transient). En este estado estará un objeto recién creado que no ha sido enlazado con el gestor de persistencia. • Persistente: Ente este caso el objeto está enlazado con la sesión. Todos los cambios que se realicen será persistentes. • Disociado (Detached): En este caso nos encontramos con un objeto persistente que sigue en memoria después de que termine la sesión. En este caso existe en Java y en la base de datos. • Borrado (Removed): En esta caso el objeto está marcado para ser borrado de la base de datos. Existe en la aplicación Java y se borrará de la base de datos al terminar la sesión. 8.- Carga, almacenamiento y modificación de objetos. Para cargar un objeto de acceso a datos en la aplicación Java, el método load() de la clase Session suministra un mecanismo para capturar una instancia persistente, si conocemos su identificador. El método load() acepta un objeto Class, y cargará el estado de una nueva instancia de esa clase, inicializada en estado persistente. El método load() lanzará una excepción irrecuperable si no existe la fila de base de datos correspondiente. Si no se está seguro de que exista una fila correspondiente, debe usarse el método get(), el cual consulta la base de datos inmediatamente y devuelve null si no existe una fila correspondiente. 8.1.- Almacenamiento y modificando de objetos persistentes. Para almacenar objetos persistentes se proceso siguiendo los siguientes pasos: 1. Se instancia un objeto nuevo (estado transitorio). 2. Se obtiene una sesión y se comienza la transacción, inicializando el contexto de persistencia. 3. Una vez obtenida da la sesión, se llama al método save(), el cual introduce el objeto en el contexto de persistencia. Este método devuelve el identificador del objeto persistido. 4. Para que los cambios sean sincronizados en las bases de datos, es necesario realizar el commit de la transacción. Dentro del objeto sesión se llama al método flush(). Es posible llamarlo explícitamente. En este momento, se obtiene la conexión JDBC a la bases de datos para poder ejecutar la oportuna sentencia. 5. Finalmente, la sesión se cierra, con el objeto de liberar el contexto de persistencia, y por tanto, devolver la referencia del objeto creado al estado disociado. Para borrar objetos persistentes, podemos ejecutar Session.delete(), que quitará el estado de un objeto de la base de datos. 9.- Consultas SQL. Usando Hibernate, la ejecución de consultas SQL nativas se controla por medio de la interfaz SQLQuery, la cual se obtiene llamando a Session.createSQLQuery().La consulta SQL más básica es para obtener a una lista de escalares (valores). sess.createSQLQuery("SELECT * FROM Personas").list(); sess.createSQLQuery("SELECT ID,NOMBRE, EDAD FROM PERSONAS").list(); Estas retornarán una lista de objetos arrays (Object[]) con valores escalares para cada columna en la tabla PERSONAS. Otro tipo de consulta más compleja, es la consulta de entidades. Para obtener los objetos entidades desde una consulta sql nativa, se utiliza por medio de addEntity(). sess.createSQLQuery("SELECT * FROM PERSONAS").addEntity(Persona.class); sess.createSQLQuery("SELECT ID,NOMBRE,EDAD FROM PERSONAS").addEntity(Persona.class); 10.- Lenguajes propios de la herramienta ORM. Hibernate utiliza un lenguaje de consulta potente (HQL) que se parece a SQL. HQL es completamente orientado a objetos y comprende nociones como herencia, polimorfismo y asociación. Entre las características más importantes de HQL.
• • • •
•
Soporte completo para operaciones relacionales: HQL permite representar consultas SQL en forma de objetos. HQL usa clases y atributos o propiedades en vez de tablas y columnas. Regresa sus resultados en forma de objetos: Las consultas realizadas usando HQL regresan los resultados de las mismas en la forma de objetos o listas de objetos, que son más fáciles de usar. Consultas Polimórficas: Podemos declarar el resultado usando el tipo de la superclase e Hibernate se encargara de crear los objetos adecuados de las subclases correctas de forma automática. Soporte para características avanzadas: HQL contiene muchas características avanzadas que son muy útiles y que no siempre están presentes en todas las bases de datos, o no es fácil usarlas, como paginación, fetch joins con perfiles dinámicos, inner y outer joins, etc. Además soporta proyecciones, funciones de agregación (max, avg), y agrupamientos, ordenamientos, y subconsultas. Independiente del manejador de base de datos: Las consultas escritas en HQL son independientes de la base de datos (siempre que la base de datos soporte la característica que estamos intentando utilizar ^
10.1.- Lenguaje HQL. Clausula from: la siguientes sería una consulta que mostraría todos los datos de una tabla de nombre Alumnos: from Alumnos Cláusula select: La cláusula select escoge qué objetos y propiedades devolver en el conjunto de resultados de la consulta. Un ejemplo de consulta podría ser select alumno.nombre from Alumnos alumno where alumno.nombre like 'A%' La cláusula where: La cláusula where nos permite refinar la lista de instancias retornadas. Si no existe ningún alias, puede referirse a las propiedades por nombre: from Alumnos where nombre='Francisco'. Si existe un alias, usaremos un nombre de propiedad calificado: from Alumnos as alumnos where alumnos.nombre='Francisco'. Esto retorna instancias de Alumnos llamados "Francisco". Funciones de agregación. Las consultas HQL pueden retornar resultados de funciones de agregación sobre propiedades: select avg(alumnos.nota), sum(alumnos.nota), max(alumnos.nota), count(alumnos) from Alumnos alumnos. Expresiones. Las expresiones utilizadas en la cláusula where incluyen lo siguiente: operadores matemáticos, operadores de comparación binarios, operadores lógicos , paréntesis ( ) que indican agrupación, funciones Java, etc. La cláusula order by. La lista retornada por una consulta se puede ordenar por cualquier propiedad de una clase retornada o componentes. La palabra asc o desc opcionales indican ordenamiento ascendente o descendente respectivamente. La cláusula group by. Una consulta que retorna valores agregados se puede agrupar por cualquier propiedad de una clase retornada o componentes: Subconsultas. Para bases de datos que soportan subconsultas, Hibernate soporta subconsultas dentro de consultas. Una subconsulta se debe encerrar entre paréntesis (frecuentemente por una llamada a una función de agregación SQL). Incluso se permiten subconsultas correlacionadas (subconsultas que se refieren a un alias en la consulta exterior).
public static String CONSULTA_BASADA_EN_NOMBRE="from Actor a where a.firstName like '"; public static String CONSULTA_BASADA_EN_APELLIDO="from Actor a where a.lastName like '";
private void ejecutaConsultaBasadaEnNombre(){ ejecutaHQLConsulta(CONSULTA_BASADA_EN_NOMBRE+txtNombre.getText()+"%'"); } private void ejecutaConsultaBasadaEnApellidos(){ ejecutaHQLConsulta(CONSULTA_BASADA_EN_APELLIDO+txtApellidos.getText()+"%'"); } private void ejecutaHQLConsulta(String hql){ try{ Session session=HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); Query consulta=session.createQuery(hql); List resultList=consulta.list(); mostrarResultado(resultList); session.getTransaction().commit(); } catch (HibernateException he){ he.printStackTrace(); } }
private void btConsultaActionPerformed(java.awt.event.ActionEvent evt) { // TODO add your handling code here: if(!txtNombre.getText().trim().equals("")){ ejecutaConsultaBasadaEnNombre(); }else if(!txtApellidos.getText().trim().equals("")){ ejecutaConsultaBasadaEnApellidos(); } }
private void mostrarResultados(List resuList){ Vector tableHeaders=new Vector(); Vector tableData=new Vector(); tableHeaders.add("Actor id"); tableHeaders.add("Nombre"); tableHeaders.add("Apellidos"); tableHeaders.add("Actualizado"); for (Object o:resuList){ Actor actor=(Actor) o; Vector oneRow=new Vector(); oneRow.add(actor.getActorId()); oneRow.add(actor.getFirstName()); oneRow.add(actor.getLastName()); oneRow.add(actor.getLastUpdate()); tableData.add(oneRow); } tbResultados.setModel(new DefaultTableModel(tableData, tableHeaders)); }
11.- Gestión de transacciones. Un transacción es un conjunto de órdenes que se ejecutan formando un unidad de trabajo, en forma indivisible o atómica. Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = null; try { tx = session.beginTransaction(); // Utilizar la Session para saveOrUpdate/get/delete/...tx.commit(); } catch (Exception e) { if (tx != null) { tx.rollback(); throw e; } } finally { session.close(); }// Al finalizar la aplicación ...HibernateUtil.shutdown( ); Cuando se crea el objeto Session , se le asigna la conexión de la base de datos que va a utilizar. Una vez obtenido el objeto Session , se crea una nueva unidad de trabajo ( Transaction) utilizando el método beginTransaction. Dentro del contexto de la transacción creada, se pueden invocarlos métodos de gestión de persistencia proporcionados por el objeto Session, para recuperar, añadir, eliminar o modificar el estado de instancias de clases persistentes. También se pueden realizar consultas. Si las operaciones de persistencia no han producido ninguna excepción, se invoca el método commit de la unidad de trabajo para confirmar los cambios realizados. En caso contrario, se realiza un rollback para deshacer los cambios producidos. Sobre un mismo objeto Session pueden crearse varias unidades de trabajo. Finalmente se cierra el objeto Session invocando su método close.
Bases de datos objeto-relacionales y orientadas a objetos. 1.- Introducción. Las Bases de Datos Orientadas a Objetos (BDOO) o Bases de Objetos se integran directamente y sin problemas con las aplicaciones desarrolladas en lenguajes orientados a objetos, ya que soportan un modelo de objetos puro y son ideales para almacenar y recuperar datos complejos permitiendo a los usuarios su navegación directa (sin un mapeo entre distintas representaciones). Las Bases de Datos Objeto-Relacionales (BDOR) son bases de datos relacionales que han evolucionado hacia una base de datos más extensa y compleja , incorporando conceptos del modelo orientado a objetos. Pero en estas bases de datos aún existe un mapeo de objetos subyacente, que es costoso y poco flexible, cuando los objetos y sus interacciones son complejos. 2.- Características de las bases de datos orientadas a objetos. La principal característica de las BDOO es que soportan un modelo de objetos puro y que el lenguaje de programación y el esquema de la base de datos utilizan las mismas definiciones de tipos. Soportan las características propias de la Orientación a Objetos. Identificador de objeto (OID). Cada objeto tiene un identificador. Jerarquía y extensión de tipos. Se pueden definir nuevos tipos basándose en otros tipos predefinidos. Objetos complejos. Acceso navegacional de datos. Gestión de versiones. 2.1.- Ventajas e inconvenientes.Ventajas: la transparencia, Gran capacidad de modelado. Flexibilidad. Soporte para el manejo de objetos complejos. Alta velocidad de procesamiento. Extensibilidad. Mejora los costes de desarrollo, Facilitar el control de acceso y concurrencia, Funcionan de forma eficiente en entornos cliente/servidor y arquitecturas distribuidas. Desventajas: Carencia de un modelo de datos universal. Falta de estándares. Complejidad. Competencia de otros modelos. Difícil optimización de consultas.
3.- Gestores de bases de datos orientadas a objetos. Db4o de Versant. Es una BDOO Open Source para Java y .NET. Se distribuye bajo licencia GPL. Matisse. Es un SGBOO basado en la especificación ODMG, proporciona lenguajes para definición y manipulación de objetos, así como interfaces de programación para C, C++, Eiffel y Java. • ObjectDB. Es una BDOO que ofrece soporte para Java, C++, y Python entre otros lenguajes. No es un producto libre, aunque ofrecen versiones de prueba durante un periodo determinado. • EyeDB. Es un SGBOO basado en la especificación ODMG, proporciona lenguajes para definición y manipulación de objetos, e interfaces de programación para C++ y Java. Se distribuye bajo licencia GNU y es software libre. • Neodatis, ObjectStore y GemStone. Son otros SGBDOO. 3.1.- Objetos simples y objetos estructurados. Un objeto de tipo simple u objeto simple es aquel que no contiene a otros objetos y por tanto posee una estructura de un solo nivel de profundidad en este sentido.Un objeto de tipo estructurado u objeto estructurado incluye entre sus componentes a otros objetos. Entre un objeto y sus componentes de cada nivel, existen dos tipos de referencia: • Referencia de propiedad. Se aplica cuando los componentes de un objeto se encapsulan dentro del propio objeto y se consideran, por tanto, parte de ese objeto. Relación es-parte-de. • Referencia de asociación. Se aplica cuando entre los componentes del objeto estructurado existen objetos independientes, pero es posible hacer referencia a ellos desde el objeto estructurado. Relación está-asociado-con. Una relación uno a muchos se representa mediante un objeto tipo colección (List, Set, etc.). 3.2.- Instalación del gestor de objetos Db4o. descargar de www.db4o.com • •
4.- El API de la base de objetos. Los principales paquetes del API de Db4o son los siguientes: • com.db4o. Paquete principal (core) de la Base de Objetos. • com.db4o.query. Paquete con funcionalidades de consulta. • om.db4o.config. Paquete con funcionalidades de configuración. La documentación del API viene en formato JavaDoc y la puedes encontrar en el directorio /doc/api del fichero .zip descargado y descomprimido. Siempre que trabajemos con bases de objetos Db4o utilizaremos el interface ObjectContainer, puesto que es quien representará a la base de objetos, sea embebida o no. 4.1.- Apertura y cierre de conexiones. En general, la conexión de una aplicación Java con una base de objetos se podrá realizar vía: • JDBC. • El API proporcionado por el propio gestor de objetos. Abrir conexión. Podemos utilizar las siguientes clases: • Db4oEmbedded. Es una clase que hereda de java.lang.Object y proporciona métodos estáticos como openFile() para abrir una instancia de la base de datos en modo embebido. • ObjectServer. Es un interfaz que permite trabajar con una base de datos db4o en modo cliente-servidor. Una vez abierta la base de datos como servidor mediante Db4o.openServer(), el método openClient() del interfaz ObjectServer permitirá abrir conexiones cliente directamente en memoria o bien mediante TCP/IP. Cerrar conexión. Para ello utilizaremos el método close() de la interfaz ObjectContainer.
package congreso; public class ponente { private String nif; private String nombre; private String email; private float cache; //constructores public ponente() { this.nif=null; this.nombre = null; this.email = null; this.cache = 0; } public ponente(String ni, String n, String e) { this.nif=ni; this.nombre = n; this.email = e; this.cache = -1; //caché no asignado } public ponente(String ni, String no, String e, float c) { this.nif=ni; this.nombre = no; this.email = e; this.cache = c; } //métodos básicos para asignar y obtener valores de atributos public void setNif(String n) { this.nif = n; } public String getNif() { return this.nif;
} public void setNombre(String n) { this.nombre = n; } public String getNombre() { return this.nombre; } public void setEmail(String e) { this.email = e; } public String getEmail() { return this.email; } public void setCache(float c) { this.cache = c; } public float getCache() { return this.cache; } @Override //comportamiento del método toString heredado de la superclase Objet //Devuelve los atributos de un objeto ponente public String toString() { if (this.cache != -1) { return this. nif+" "+this.nombre+" "+this.email+" Caché:"+this.cache; } else { return this.nif+" "+this.nombre +" "+this.email; } } }
package congreso; import com.db4o.Db4oEmbedded; import com.db4o.ObjectContainer; public class Main { public static void main(String[] args) { // TODO code application logic here ObjectContainer db = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(),"congreso.db4o"); //La base de datos física es el fichero "congreso.db4o" almacenado en la //carpeta raíz del proyecto creado try { almacenarPonentes(db); } finally { db.close(); //cerrar la conexión a la base de datos } }
//Método para almacenar datos en la Base de Objetos. public static void almacenarPonentes(ObjectContainer db) { //se crean cuatro objetos tipo alumno con valores asignados ponente p1 = new ponente("11A", "Antonio Camaco", "
[email protected]", 300); ponente p2 = new ponente("22B","Isabel Pérez", "
[email protected]", 100); ponente p3 = new ponente("33C","Ana Navarro", "
[email protected]", 200); ponente p4 = new ponente("44D","Pedro Sánchez", "
[email protected]", 90); //Persistir Objetos: almacenamos los objetos con el método store() db.store(p1); db.store(p2); db.store(p3); db.store(p4); }
4.2.- Consultas a la base de objetos. A una BDOO se podrán realizar consultas mediante: • Un lenguaje de consultas como OQL, si el gestor está basado en el estandar ODMG e incluye sentencias del tipo SQL. • El API proporcionado por el propio sistema gestor de bases de datos orientadas a objetos. Los tres sistemas de consulta que proporciona Db4o basados en el API del propio gestor, son los siguientes: • Consultas por ejemplo. Query By Example (QBE). Es la forma más sencilla y básica de realizar cosultas, pero tienen bastantes limitaciones. • Consultas nativas. Native Queries (NQ). Es la interface principal de consultas de la base de objetos. Permiten realizar un filtro contra todas las instancias de la base de objetos. • Consultas SODA. Simple Object Data Access (SODA). Permite generar consultas dinámicas.
public static void consultarPonentes(ObjectContainer db) { ponente p = new ponente(null, null, null, 0); //prototipo de búsqueda ObjectSet res = db.queryByExample(p); //realización de consulta mostrarConsulta(res);//obtención de resultados }
//Método para mostrar objetos recuperados de la Base de Objetos public static void mostrarConsulta(ObjectSet resul) { System.out.println("Recuperados " + resul.size() + " Objetos"); while (resul.hasNext()) { System.out.println(resul.next()); } }
//consulta de un ponente en concreto. Consultar ponentes de cache 200. public static void consultarPonente200(ObjectContainer db) { ponente p = new ponente(null, null, null, 200); ObjectSet res = db.queryByExample(p); mostrarConsulta(res); } //consulta de ponentes por nombre. Al método se le pasa el parámetro nomb public static void consultarPonentePorNombre(ObjectContainer db, String nomb) { ponente p = new ponente(null, nomb, null, 0);//prototipo de búsqueda ObjectSet res = db.queryByExample(p); mostrarConsulta(res); }
//Método para consultar todos los ponentes public static void consultaSODAponentes(ObjectContainer db) { Query query = db.query();//declara un objeto Query //indica la clase a la que se aplicarán restricciones query.constrain(ponente.class); ObjectSet result = query.execute(); //Ejecuta la consulta mostrarConsulta(result);//muestra los resultados de la consulta }
//Consulta SODA de todos los ponentes con caché 200 public static void consultaSODAcache200(ObjectContainer db) { Query query = db.query(); //se declara objeto tipo Query() query.constrain(ponente.class); //clase a la que se aplican restricciones //establece restricción del valor 200 para cache query.descend("cache").constrain(200); ObjectSet result = query.execute(); //ejecuta consulta mostrarConsulta(result);//muestra resultados de consulta }
//consulta SODA de ponentes con cache entre 50 y 200 public static void consultaSODAcacheEntre50_200(ObjectContainer db) { Query query = db.query(); query.constrain(ponente.class); //se declara una de las restricciones con Constraint Constraint constra1 = query.descend("cache").constrain(200).smaller(); //se enlazan las dos restricciones a aplicar query.descend("cache").constrain(50).greater().and(constra1 ); ObjectSet result = query.execute(); mostrarConsulta(result); }
//consultar los ponentes de caché 200 public static void consultarPonenteNQcache200(ObjectContainer db) { List res = db.query(new com.db4o.query.Predicate() { public boolean match(ponente p) { return p.getCache() == 200; } //método abstracto @Override public boolean match(Object et) { throw new UnsupportedOperationException("Not supported yet."); } }); mostrarConsulta((ObjectSet) res); }
//consulta SODA de ponentes ordenados por caché, de más a menos public static void consultaSODAponentesOrdenadosCache(ObjectContainer db) { Query query = db.query(); query.constrain(ponente.class); query.descend("cache").orderDescending(); ObjectSet result = query.execute(); mostrarConsulta(result); } MEtodo Main public static void main(String[] args) { System.out.println("PONENTES con caché 200: *****"); //Conexión, apertura o creación de la base de objetos congreso.db4o consultarPonenteNQcache200(db); ObjectContainer db = System.out.println("CONSULTAS SODA: Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), *****************************"); "congreso.db4o"); System.out.println("TODOS LOS PONENTES: *****"); //operaciones sobre la base de objetos congreso consultaSODAponentes(db); try { System.out.println("PONENTES con caché 200: *****"); almacenarPonentes(db); consultaSODAcache200(db); System.out.println("CONSULTAS QBE: System.out.println("PONENTES con caché entre 50 y 200: *****"); ******************************"); consultaSODAcacheEntre50_200(db); System.out.println("TODOS LOS PONENTES: *****"); System.out.println("TODOS los PONENTES de más a menos consultarPonentes(db); caché: *****"); System.out.println("PONENTES con caché 200: *****"); consultaSODAponentesOrdenadosCache(db); consultarPonente200(db); } catch (Exception e) { System.out.println("PONENTE de nombre Antonio Camaco: //código para el tratamiento de la excepción *****"); } finally { db.close(); // cerrar la base de datos antes de salir consultarPonentePorNombre(db, "Antonio Camaco"); System.out.println("CONSULTAS NQ: } *******************************"); }
4.3.- Actualización de objetos simples. Para modificar objetos almacenados debes seguir los siguientes pasos: • Cambiar los valores del objeto con los nuevos valores. • Almacenar de nuevo el objeto con el método store() de la interfaz ObjectContainer. Db4o necesita conocer previamente un objeto para poder actualizarlo. Esto significa que para poder ser actualizados los objetos, éstos deben de haber sido insertados o recuperados en la misma sesión; en otro caso se añadirá otro objeto en vez de actualizarse. Para eliminar objetos almacenados utilizaremos el método delete() de la interface ObjectContainer . //método que modifica el e-mail de un ponente cuyo nif se pasa como parámetro // y almacena en la base de objetos los nuevos valores public static void actualizarEmailPonente(ObjectContainer db, String nif, String em) { //se consulta a la base de objetos por el ponente del nif indicado
ObjectSet res = db.queryByExample(new ponente(nif, null, null, 0)); ponente p = (ponente) res.next(); //se obtiene el objeto consultado en p p.setEmail(em); //se cambia el email del objeto db.store(p); //se alamcena de nuevo el objeto poenente } public static void borrarPonenteporNif(ObjectContainer db, String nif) { //se consulta a la base de objetos por el ponente del nif indicado ObjectSet res = db.queryByExample(new ponente(nif, null, null, 0)); ponente p = (ponente) res.next(); //se obtiene el objeto consultado en p db.delete(p); //se elimina el objeto poenente de la base de objetos } Y en el Main public static void main(String[] args) { ObjectContainer db = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), "congreso.db4o"); try { almacenarPonentes(db); consultarPonentes(db); consultarPonente200(db); actualizarEmailPonente(db, "22B", "
[email protected]"); borrarPonenteporNif(db, "11A"); consultarPonentes(db); } catch (Exception e) { //código para el tratamiento de la excepción } finally { db.close(); // cerrar la base de datos antes de salir } } 4.4.- Actualización de objetos estructurados. Los objetos estructurados son objetos que contienen a su vez a otros objetos (objetos hijo u objetos miembro). Los objetos estructurados se almacenan asignando valores con set() y después persistiendo el objeto con store().La eliminación o borrado de un objeto estructurado se realiza mediante el método delete().Para eliminar objetos estructurados en cascada o de forma recursiva, eliminando los objetos miembro, habrá que configurar de modo apropiado la base de objetos antes de abrirla, mediante el paquete com.db4o.config. En el caso de modo embebido, se hará mediante la interface EmbeddedConfiguration. En la nueva configuración se debe indicar cascadeOnDelete(true). Para actualizar de forma recursiva todos los objeto miembro habrá que indicar en la configuración cascadeOnUpdate(true). Clase estructurada Charla, formada por otra clase en segundo nivel, Ponente: package actualizarcongresoestructurados; this.pl = p; //clase que implementa un charla. Cada charla tiene un título y está } asignada //Método para obtener el título de una charla public String getTitulo() { //a un ponente. CLASE ESTRUCTURADA: contiene un objeto return titulo; ponente public class charla { } private String titulo; //Método para obtener la duración de una charla private float duracion; public float getDuracion() { private ponente pl; return duracion; //constructor } public charla(String ti, float h) { //Método para asignar el ponente de una charla this.titulo = ti; public void setDuracion(float h) { this.pl = null; this.duracion = h; this.duracion=h; } } //Método para mostrar título y ponente de una charla //Método para obtener el ponente de una charla @Override public ponente getPonente() { public String toString() { return pl; return "Charla: " + titulo + ", " + duracion + " horas. } PONENTE: " +pl ; //Método para asignar el ponente de una charla } } public void setPonente(ponente p) { Clase Principal para objetos estructurados, para consultas y actualizaciones System.out.println("ELIMINACIÓN de la charla XML:"); package actualizarcongresoestructurados; //API necesaria de db4o borrarCharlaporTitulo(db, "XML"); import com.db4o.Db4oEmbedded; System.out.println("CHARLAS Y PONENTES EXISTENTES tras import com.db4o.ObjectContainer; borrar charla XML:"); import com.db4o.ObjectSet; mostrarCharlasQBE(db); import com.db4o.query.Query; mostrarPonentes(db); public class Main { System.out.println("MODIFICACIÓN de las horas de la CHARLA public static void main(String[] args) { Db4o:"); //Conexión a la base de objetos y apertura de la base de objetos actualizarHorasCharla(db, "Db4o", 8.0f); congreso.db4o consultaSODACharla_concreta(db, "Db4o"); ObjectContainer db = } catch (Exception e) { Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), //código para el tratamiento de la excepción "congreso.db4o"); } finally { //llamada a métodos para operar con la base de objetos db.close(); // cerrar la base de datos antes de salir try { } } almacenarCharlas(db); System.out.println("CHARLAS EXISTENTES EN LA BDOO:"); //Almacenar objetos estructurados mostrarCharlasQBE(db); //Método para insertar charlas en la Base de Objetos y almacenarlas System.out.println("CHARLAS EXISTENTES con título XML:"); public static void almacenarCharlas(ObjectContainer db) { consultaSODACharla_concreta(db, "XML"); //se crean 4 objetos tipo charla System.out.println("CHARLAS EXISTENTES del ponente charla c1 = new charla("Bases de Datos Orientadas a Objetos", 2); ANTONIO CAMACO:"); charla c2 = new charla("MySQL y PostGreSQL", 3); mostrarCharlasCamacoQBE(db);
charla c3 = new charla("XML", 2); charla c4 = new charla("Db4o", 3); //se crean 4 objetos ponente ponente p1 = new ponente("11A", "Antonio Camaco", "
[email protected]", 300); ponente p2 = new ponente("22B", "Isabel Pérez", "
[email protected]", 100); ponente p3 = new ponente("33C", "Ana Navarro", "
[email protected]", 200); //se le asigna un ponente a cada charla c1.setPonente(p1); c2.setPonente(p2); c3.setPonente(p3); c4.setPonente(p1);
//Consulta de objetos estructurados. Consulta QBE //Consulta de las charlas del ponenete Anotnio Camaco public static void mostrarCharlasCamacoQBE(ObjectContainer db) { //se crea objeto ponente con patrón de búsqueda (el ejemplo) ponente p = new ponente(null, "Antonio Camaco", null, 0); //se crea el objeto charla con patrón de búsqueda charla c = new charla(null, 0); c.setPonente(p); //se asocia el ponente de búsqueda a la charla ObjectSet resul = db.queryByExample(c); //Consulta las charlas con patrones indicados mostrarConsulta(resul);//método que muestra los objetos recuperados de BDOO } //Borrado de objetos estructurados. Se utiliza Consulta SODA //Se elimina la charla de título tit sin eliminar al ponente asociado
//Persistir Objetos: almacenamos los objetos con el método store() db.store(c1); db.store(c2); db.store(c3); db.store(c4);
public static void borrarCharlaporTitulo(ObjectContainer db, String tit) { Query query = db.query(); //declaración de un objeto query(). query.constrain(charla.class);//establece la clase a la que se aplicará la restricción } query.descend("titulo").constrain(tit);//establece la restricción de búsqueda ObjectSet resul = query.execute();//ejecuta consulta con //Método para mostrar objetos recuperados de la Base de Objetos public static void mostrarConsulta(ObjectSet resul) { restricción búsqueda while (resul.hasNext()) { //bucle que recupera los objetos charla y //mensaje indicando el total de objetos recuperados elimina de la BDOO System.out.println("Recuperados " + resul.size() + " Objetos"); charla c = (charla) resul.next(); while (resul.hasNext()) {//bucle que obtiene objeto a objeto System.out.println("Eliminando: " + c); System.out.println(resul.next()); db.delete(c); } } } //Consulta QBE de objetos estructurados. Consulta de todas las charlas. } //Consulta de todos los objetos ponente. Consulta QBE public static void mostrarCharlasQBE(ObjectContainer db) { //se crea el objeto charla patrón de búsqueda public static void mostrarPonentes(ObjectContainer db) { charla c = new charla(null, 0); //se crea objeto p con patrón de búsqueda (el ejemplo) //Consulta las charlas con patrones indicados ponente p = new ponente(null, null, null, 0); ObjectSet resul = db.queryByExample(c); //consulta los ponentes de patrón ponente(null, null, null, 0). Consulta mostrarConsulta(resul);//método que muestra los objetos QBE ObjectSet res = db.queryByExample(p); recuperados de BDOO mostrarConsulta(res); //método que muestra los objetos } recuperados de BDOO //Consulta SODA de objetos estructurados } //Se consulta la charla cuyo título se pasa en parámetro tit //Modificación de Objetos estructurados. Con consulta QBE public static void consultaSODACharla_concreta(ObjectContainer //Actualiza la duración de la charla de título tit en d horas db, String tit) { public static void actualizarHorasCharla(ObjectContainer db, String Query query = db.query(); //declara un objeto query tit, float d) { query.constrain(charla.class);//establece la clase a la que se //consulta la charla de patrón charla(tit,0). Consulta QBE aplicará la restricción ObjectSet res = db.queryByExample(new charla(tit, 0)); query.descend("titulo").constrain(tit);//establece la restricción de charla c = (charla) res.next(); //obtiene la charla consultada búsqueda c.setDuracion(d); //asigna la nueva duración ObjectSet resul = query.execute();//ejecuta consulta db.store(c); //almacena la charla modificada mostrarConsulta(resul);//método que muestra los objetos } recuperados de la BDOO } } Clase Principal para objetos estructurados, para eliminación de objetos package eliminacioncascada; //API necesaria de db4o //llamada a métodos para operar con la base de objetos import com.db4o.Db4oEmbedded; try { import com.db4o.ObjectContainer; almacenarCharlas(db); import com.db4o.ObjectSet; System.out.println("CHARLAS EXISTENTES EN LA import com.db4o.config.EmbeddedConfiguration; //establecer BDOO:"); configuración de conexión mostrarCharlasQBE(db); import com.db4o.query.Query; System.out.println("ELIMINACIÓN de la charla XML:"); public class Main { borrarCharlaporTitulo(db, "XML"); public static void main(String[] args) { System.out.println("CHARLAS Y PONENTES EXISTENTES //Conexión a la base de objetos y apertura de la base de objetos tras borrar charla XML:"); congreso.db4o mostrarCharlasQBE(db); //Se CONFIGURA la apertura de manera que al eliminar un objeto mostrarPonentes(db); estructurado } catch (Exception e) { //de tipo charla, se eliminen también los objeto hilo tipo ponente. //código para el tratamiento de la excepción //Se indica la nueva configuración de conexión, con borrado en } finally { cascada db.close(); // cerrar la base de datos antes de salir EmbeddedConfiguration config = } Db4oEmbedded.newConfiguration(); } //Almacenar objetos estructurados config.common().objectClass(charla.class).cascadeOnDelete(tr //Método para insertar charlas en la Base de Objetos y ue); //Se abre la conexión a la base de objetos congreso.db4o almacenarlas ObjectContainer db = Db4oEmbedded.openFile(config, public static void almacenarCharlas(ObjectContainer db) { "congreso.db4o"); //se crean 4 objetos tipo charla
charla c1 = new charla("Bases de Datos Orientadas a Objetos", 2); charla c2 = new charla("MySQL y PostGreSQL", 3); charla c3 = new charla("XML", 2); charla c4 = new charla("Db4o", 3); //se crean 4 objetos ponente ponente p1 = new ponente("11A", "Antonio Camaco", "
[email protected]", 300); ponente p2 = new ponente("22B", "Isabel Pérez", "
[email protected]", 100); ponente p3 = new ponente("33C", "Ana Navarro", "
[email protected]", 200); //se le asigna un ponente a cada charla c1.setPonente(p1); c2.setPonente(p2); c3.setPonente(p3); c4.setPonente(p1); //Persistir Objetos: almacenamos los objetos con el método store() db.store(c1); db.store(c2); db.store(c3); db.store(c4); } //Método para mostrar objetos recuperados de la Base de Objetos public static void mostrarConsulta(ObjectSet resul) { //mensaje indicando el total de objetos recuperados System.out.println("Recuperados " + resul.size() + " Objetos"); while (resul.hasNext()) {//bucle que obtiene objeto a objeto System.out.println(resul.next()); } } //Consulta QBE de objetos estructurados. Consulta de todas las charlas. public static void mostrarCharlasQBE(ObjectContainer db) { //se crea el objeto charla patrón de búsqueda
charla c = new charla(null, 0); //Consulta las charlas con patrones indicados ObjectSet resul = db.queryByExample(c); mostrarConsulta(resul);//método que muestra los objetos recuperados de BDOO } //Borrado de objetos estructurados. Se utiliza Consulta SODA //Se elimina la charla de título tit sin eliminar al ponente asociado public static void borrarCharlaporTitulo(ObjectContainer db, String tit) { Query query = db.query(); //declaración de un objeto query(). query.constrain(charla.class);//establece la clase a la que se aplicará la restricción query.descend("titulo").constrain(tit);//establece la restricción de búsqueda ObjectSet resul = query.execute();//ejecuta consulta con restricción búsqueda while (resul.hasNext()) { //bucle que recupera los objetos charla y elimina de la BDOO charla c = (charla) resul.next(); System.out.println("Eliminando: " + c); db.delete(c); } } //Consulta de todos los objetos ponente. Consulta QBE public static void mostrarPonentes(ObjectContainer db) { //se crea objeto p con patrón de búsqueda (el ejemplo) ponente p = new ponente(null, null, null, 0); //consulta los ponentes de patrón ponente(null, null, null, 0). Consulta QBE ObjectSet res = db.queryByExample(p); mostrarConsulta(res); //método que muestra los objetos recuperados de BDOO } }
5.- El lenguaje de consulta de objetos OQL. OQL (Object Query Languaje) es el lenguaje de consulta de objetos propuesto en el estándar ODMG. Las siguientes son algunas de las características más relevantes de OQL: • Es un lenguaje declarativo del tipo de SQL • Su sintaxis es similar a la de SQL • No posee primitivas para modificar el estado de los objetos • Puede ser usado como un lenguaje autónomo o incrustado dentro de otros lenguajes • Una consulta OQL incrustada en uno de estos lenguajes de programación puede devolver objetos que coincidan con el sistema de tipos de ese lenguaje. • Permite acceso tanto asociativo como navegacional: o Una consulta asociativa devuelve una colección de objetos. o Una consulta navegacional accede a objetos individuales y las interrelaciones entre objetos sirven para navegar entre objetos. 5.1.- Sintaxis, expresiones y operadores. sentencia SELECT del OQL estándar es la siguiente:
SELECT [DISTINCT] FROM [WHERE ] [ORDER BY ] SELECT p.nombre, p.email FROM p in Profesor WHERE p.ingreso , =, = max) { break; } } System.out.flush(); } //Método para mostrar el resultado private static void mostrarResultado(XMLSerializer serializer, Item result) throws QizxException { //Si no es un nodo del árbol XML, escribe el resultado como cadena if (!result.isNode()) { System.out.println(result.getString()); return; } //obtiene el nodo XML del resultado, el Item Node node = result.getNode(); //prepara la serialización de otro arbol XML serializer.reset(); //El método XMLSerializer.serializeToString se utiliza para obtener la //representación de cadena de un nodo. String xmlForm = serializer.serializeToString(node); //Imprime el nodo del resultado como cadena System.out.println(xmlForm); } //método que cierra la conexión a la base de datos private static void cerrar(Library bd, LibraryManager bdManager) throws QizxException { //si la basede datos esta inmersa en una transacción, deshacer los cambios if (bd.isModified()) { bd.rollback(); } //cerrar conexión con base de datos bd.close(); //cerrar las bases de datos del grupo dentro de 100000 ms bdManager.closeAllLibraries(10000); } //método que devuelve la consulta XQuery (almacenada en fichero file) //en una cadena private static String cargaScript(File file) throws IOException { //Obtiene el contenido del fichero
InputStreamReader in = if (count > 0) { new InputStreamReader(new FileInputStream(file), "UTF-8"); build.append(chars, 0, count); //cadena para construir la cadena resultante } } StringBuilder build = new StringBuilder(); } finally { //array de tamaño suficiente para alamcenar los caracteres del script char[] chars = new char[8192]; in.close(); int count; } try { return build.toString(); //mientras hay caracteres en el contenido del fichero } //los añade a la cadena } while ((count = in.read(chars, 0, chars.length)) != -1) {
5.8.- Actualizar documentos. Aunque dependiendo del sistema de BD XML pueden existir diferentes formas de actualizar documentos, la manera más sencilla de hacerlo es mediante Update XQuery, la extensión del lenguaje XQuery que permite inserciones, eliminaciones y modificaciones de nodos de los documentos XML. En el siguiente enlace dispones de un ejemplo detallado de actualización XQuery desde una aplicación Java. La actualización consiste en añadir dos nodos empresa al documento /Empresa.xml de la BD Cursillos. //compila la consulta de actualización package edit; Expression expr = compilaExpression(bd, script, queryRoot); import com.qizx.api.ItemSequence; import java.io.IOException; //evalúa la consulta compilada import java.io.File; evaluaExpression(expr, bd); import java.io.FileInputStream; } finally { import java.io.InputStreamReader; cerrar(bd, bdManager); import com.qizx.api.QizxException; } import com.qizx.api.Message; } import com.qizx.api.CompilationException; /**************************************************************************** import com.qizx.api.Configuration; * Compila la consulta import com.qizx.api.Expression; * import com.qizx.api.LibraryMember; * @param bd: base de datos import com.qizx.api.Library; * @param script: script con consulta de actualización import com.qizx.api.LibraryManager; * @param queryRoot: camino localización consulta * @return expression compilada /****************************************************************************** * @throws IOException * clase para ejecutar scripts de actualización XQuery * @throws QizxException * */ * @author IMCG private static Expression compilaExpression(Library bd, */ String script, LibraryMember queryRoot) public class XUpdate { throws IOException, QizxException { Expression expr; //ruta del Grupo de BD try { private static String directorioGrupoRoot = "c:\\dbxml"; expr = bd.compileExpression(script); //Base de Datos } catch (CompilationException e) { private static String bdNombre = "Cursillos"; Message[] messages = e.getMessages(); //fichero con script de actualización private static String ficheroScriptRoot = for (int i = 0; i < messages.length; ++i) { "C:\\BDCursillosXML\\cursillos_query\\12update_insert.xq"; System.out.println(messages[i].toString()); // "C:\\BDCursillosXML\\cursillos_query\\13update_delete.xq"; } // "C:\\BDCursillosXML\\cursillos_query\\14update_replace_node_value.xq"; throw e; // } "C:\\BDCursillosXML\\cursillos_query\\15update_rename_varios_nodos.x q"; if (queryRoot != null) { /**************************************************************************** expr.bindImplicitCollection(queryRoot); * } * @param args return expr; * @throws IOException } * @throws QizxException */ /**************************************************************************** public static void main(String[] args) * Evalúa la expresión compilada throws IOException, QizxException { * * @param expr: expresión compilada //variables locales * @param library: base de datos LibraryMember queryRoot=null; * @throws QizxException File scriptFile; */ //crea el objeto file apuntando a esa ruta private static void evaluaExpression(Expression expr, Library File directorioGrupo = new File(directorioGrupoRoot); library) throws QizxException { // Conexión o apertura del gestor del grupo de BD /*ItemSequence results =*/ LibraryManager bdManager = ItemSequence evaluate = expr.evaluate(); Configuration.openLibraryGroup(directorioGrupo); // confirmar cambios en BD library.commit(); //abre conexión a la BD } Library bd = bdManager.openLibrary(bdNombre); /**************************************************************************** try { * Cierra la base d edatos y el su grupo scriptFile = new File(ficheroScriptRoot); * @param bd * @param bdManager //carga el contenido del fichero de script en una cadena * @throws QizxException String script = cargarScript(scriptFile); */ System.out.println("---\n" + script + "\n---"); private static void cerrar(Library bd, LibraryManager bdManager) throws QizxException {
bd.close(); bdManager.closeAllLibraries(10000 /*ms*/); }
/**************************************************************************** * Carga el script de consulta de actualización en una cadena */ private static String cargarScript(File file) throws IOException { InputStreamReader in = new InputStreamReader(new FileInputStream(file), "UTF-8");
int count; try { while ((count = in.read(chars, 0, chars.length)) != -1) { if (count > 0) { build.append(chars, 0, count); } } } finally { in.close(); } return build.toString();
} } StringBuilder build = new StringBuilder(); char[] chars = new char[8192]; El siguiente ejemplo actualiza un documentos de la colección Authors, insertando un nuevo pseudónimo al autor que se pasa como parámetro al script. El script de actualización está almacenado dentro del directorio de instalación de Qizx: docs\samples\programming\edit\pseudonym.xq package update_2; //Para cada elemento en varName import java.io.IOException; for (int i = 0; i < length; i++) { import java.io.File; //construye un QName válido o null import java.io.FileInputStream; varQName[i] = procesaQName(bd, varName[i]); import java.io.InputStreamReader; } import com.qizx.api.QizxException; //comprueba la existencia de la colección import com.qizx.api.Message; if (coleccionNombre != null) { import com.qizx.api.CompilationException; queryRoot = bd.getMember(coleccionNombre); import com.qizx.api.Configuration; if (queryRoot == null) { import com.qizx.api.QName; cerrar(bd, bdManager); import com.qizx.api.Expression; System.out.println("'" + coleccionNombre + "no encontrada"); import com.qizx.api.LibraryMember; } import com.qizx.api.Library; } import com.qizx.api.LibraryManager; try { /****************************************************************************** scriptFile = new File(ficheroScriptRoot); //carga el script de actualización * clase para ejecutar scripts de actualización Update XQuery String script = cargarScript(scriptFile); */ public class Main { System.out.println("---\n" + script + "\n---"); //compila la expresión de consulta //ruta del Grupo de bibliotecas: db Expression expr = compilarExpresion(bd, script, queryRoot, private static String directorioGrupoRoot = "c:\\dbxml"; varQName, varValue); //Bilioteca o base de datos: Tutorial //evalúa la expresión compilada private static String bdNombre = "Tutorial"; evaluarExpresion(expr, bd); //nombre de la colección que se quiere modificar } finally { private static String coleccionNombre = "Authors"; cerrar(bd, bdManager); } //Ruta del script de actualización } //recuerda corregir, si hay algún QName con Q minúscula y poner a mayúscula private static String ficheroScriptRoot = //Método para procesar y generar un QName válido o null "C:\\qizx\\docs\\samples\\programming\\edit\\pseudonym.xq"; private static QName procesaQName(Library bd, String qName) { int length = qName.length(); //nombre de las variables requeridas por el script private static String[] varName = {"authorName", "pseudo"}; if (length == 0) { //valores de las varibales requeridas por el script return null; private static String[] varValue = {"Philip José Farmer", "Kilgort } Trout"}; //para ejecutar este ejemplo varias veces hay que poner un pseudónimo int pos; //distinto cada vez, porque no se aceptan pseudónimos duplicados; o if (qName.charAt(0) == '{' && (pos = qName.lastIndexOf('}')) > 0) { bien, if (pos + 1 == length) { //volver a colocar los datos originates ejecutando Qizx_put return null; */ } public static void main(String[] args) throws IOException, QizxException { String uri = qName.substring(1, pos); String localPart = qName.substring(pos + 1); //variables locales int length; if (uri.length() == 0) { File scriptFile; return bd.getQName(localPart); LibraryMember queryRoot = null; } else { //array de variables QName - nombre calificado return bd.getQName(localPart, uri); QName[] varQName; } } else { //crea el objeto file apuntando a esa ruta if (qName.startsWith("xml:")) { File directorioGrupo = new File(directorioGrupoRoot); String localPart = qName.substring(4); return bd.getQName(localPart, // Conexión o apertura del gestor del grupo de BD "http://www.w3.org/XML/1998/namespace"); LibraryManager bdManager = } else { Configuration.openLibraryGroup(directorioGrupo); return bd.getQName(qName); } //conexión a la BD } Library bd = bdManager.openLibrary(bdNombre); } length = varName.length; //crea tantos objetos QName como elementos tenga el array varName varQName = new QName[length];
/**************************************************************************** * Compila la expresión de actualización */
private static Expression compilarExpresion(Library bd, String script, LibraryMember queryRoot, QName[] varNames, String[] varValues) throws IOException, QizxException { Expression expr; try { expr = bd.compileExpression(script); } catch (CompilationException e) { Message[] messages = e.getMessages(); for (int i = 0; i < messages.length; ++i) { System.out.println(messages[i].toString()); }
library.commit(); } /**************************************************************************** * Cierra la base de datos y todas las del grupo */ private static void cerrar(Library bd, LibraryManager bdManager) throws QizxException { bd.close(); bdManager.closeAllLibraries(10000 /*ms*/); }
/**************************************************************************** * Carga el script de actualización */ private static String cargarScript(File file) throws IOException { InputStreamReader in = new InputStreamReader(new FileInputStream(file), "UTF-8");
throw e; } if (queryRoot != null) { expr.bindImplicitCollection(queryRoot); }
StringBuilder buffer = new StringBuilder(); char[] chars = new char[8192]; int count;
if (varNames != null) { for (int i = 0; i < varNames.length; ++i) { expr.bindVariable(varNames[i], varValues[i], /*type*/ null); } }
try { while ((count = in.read(chars, 0, chars.length)) != -1) { if (count > 0) { buffer.append(chars, 0, count); } } } finally { in.close(); }
return expr; } /**************************************************************************** * Evalúa la consulta de actualización compilada */ private static void evaluarExpresion(Expression expr, Library library) throws QizxException { /*ItemSequence results =*/ expr.evaluate(); //confirma transacción
return buffer.toString(); } }
6.- Indexación. De manera predeterminada, Qizx indexa la mayor parte de la información disponible en documentos XML: elementos, atributos, nodos, y texto completo. Esto se realiza automáticamente. Qizx soporta los siguientes tipos de índices: • Índices elemento. Dado un nombre de elemento, este índice devuelve todos los elementos XML en todos los documentos de una BD con este nombre. • Índices de atributos. Dado un nombre de atributo y un valor, este índice devuelve todos los elementos que tienen un atributo con este nombre y valor. Hay tres tipos de índices de atributos, de acuerdo con el tipo del valor del atributo: texto, numéricos y de fecha / hora. o Texto. Por defecto, todos los valores de los atributos están indexados como cadenas o texto. o Numérico. Si el valor se puede convertir al tipo double, entonces se añade como índice atributo numérico. o Fecha/Hora. Si el valor se puede convertir al tipo date-time, entonces se añade como índice date/time. • Índices de contenido simple. Dado un nombre de elemento y un valor, este índice devuelve todos los elementos que tienen un contenido simple correspondiente a este valor. • Índices Full-text. Dada una palabra, este índice devuelve todos los nodos de texto que contienen una ocurrencia de esta palabra. 6.1.- Especificaciones de indexado. Una especificación de indexado es un documento XML con las siguiente estructura: • El elemento raíz tiene el nombre indexing. • El elemento indexing contiene los atributos que definen las condiciones generales. • Contiene una lista de reglas aplicables a los elementos o atributos. Ejemplo de xml de endexacion:
6.2.- Re-Indexar una BD desde Java. Para reindexar una BD XML de Qizx será necesario un objeto de la clase com.qizx.api.Indexing. Cada objeto Indexing es una especificación de indexado con las reglas y parámetros necesarios para construir índices en una BD o Library. El método Indexing.parse(InputSource source) analiza la especificación de indexado contenida en el texto XML del parámetro source. Además se necesitan los siguientes métodos de Library: Library.setIndexing(Indexing ind). Define las reglas de indexado para esa BD o Library, especificadas en el parámetro ind. Normalmente va seguida de un reindexado, que se realiza mediante el método reIndex(). Library.reIndex(). Reconstruye y reorganiza los índices de una BD package reindex; } finally { shutdown(lib, libManager); import java.io.IOException; } import java.io.File; } import org.xml.sax.SAXException; import org.xml.sax.InputSource; private static void usage() { import com.qizx.api.QizxException; System.err.println( import com.qizx.api.Indexing; "usage: java ReIndex libraries_storage_dir library_name" + import com.qizx.api.Library; " indexing_spec\n" + import com.qizx.api.LibraryManager; " libraries_storage_dir Directory containing libraries.\n" + import com.qizx.api.LibraryManagerFactory; " library_name Name of library being queried.\n" + " indexing_spec File containing the indexing specification."); public class ReIndex { System.exit(1); public static void main(String[] args) } throws IOException, SAXException, QizxException { if (args.length != 3) { private static Indexing loadIndexing(File file) usage(); throws IOException, SAXException, QizxException { } Indexing indexing = new Indexing(); File storageDir = new File(args[0]); String libName = args[1]; String systemId = file.toURI().toASCIIString(); File indexingFile = new File(args[2]); indexing.parse(new InputSource(systemId)); LibraryManagerFactory factory = LibraryManagerFactory.getInstance(); LibraryManager libManager = factory.openLibraryGroup(storageDir); Library lib = libManager.openLibrary(libName); try { verbose("Loading indexing specifications from '" + indexingFile + "'..."); Indexing indexing = loadIndexing(indexingFile); lib.setIndexing(indexing); verbose ("Re-indexing library '" + libName + "'..."); lib.reIndex();
return indexing; } private static void shutdown(Library lib, LibraryManager libManager) throws QizxException { lib.close(); libManager.closeAllLibraries(10000 /*ms*/); } private static void verbose(String message) { System.out.println(message); } }
7.- Gestión de transacciones. 7.1.- Los métodos commit() y rollback(). Los métodos para gestionar transacciones en Qizx los proporciona la interface Library y son: commit(). Confirma la transacción actual. Si este método se completa sin errores, las actualizaciones realizadas en la transacción garantizan la persistencia, y se hacen visibles a otras sesiones. rollback(). Cancela la transacción actual. En Qizx, todas las operaciones de actualización sobre una BD o Library, para que tengan efecto permanente, deben ir seguidas de una confirmación o commit(). Por ejemplo, después de realizar una consulta de actualización, para que permanezcan los cambios es necesario realizar un commit(), tal y como refleja el siguiente código. private static void evaluaExpression(Expression expr, Library library) throws QizxException { /*ItemSequence results =*/ ItemSequence evaluate = expr.evaluate(); // confirmar cambios en BD library.commit(); } Por otra parte, al cerrar una conexión a una BD es necesario comprobar si hay alguna transacción en curso, de manera que, si hay alguna en curso, para poder cerrar la BD habrá que cancelar la transacción actual. El código que refleja de forma clara este hecho es el siguiente: //Si la base de datos esta inmersa en una transaccion if (bd.isModified()) { //deshace los cambios realizados por la transaccion bd.rollback(); } //cierra la conexion a la base de datos bd bd.close(); 8.- Tratamiento de excepciones. En Qizx, la clase que contiene los diferentes tipos de excecpiones es QizxException del paquete com.qizx.api. Esta clase extiende a Exception, superclase de todas las excepciones del API y sus constructores son: QizxException(String message). Construye un mensaje con la razón o causa de excepción. El código del error es indefinido. QizxException(String message, Throwable cause). Construye un mensaje con la razón o causa de excepción, y la excepción. El código del error es indefinido. QizxException(QName errorCode, String message). Construye un mensaje con la razón de la excepción y un código de error XQuery. private static void cerrar(Library bd, LibraryManager bdManager) throws QizxException { bd.close();
bdManager.closeAllLibraries(10000 /*ms*/); } Tres subclases directas de QizxExcepction son: CompilationException. Excepciones debidas a errores en la compilación de consultas a la BD. DataModelException. Excepciones debidas a manipulación de documentos XML en la BD. EvaluationException. Excepciones debidas a errores en la ejecución de una consulta. 8.1.- Excepciones en el procesamiento de consultas. Estas excepciones las puedes gestionar mediante dos subclases directas de la clase QizxException: • CompilationException. Se lanza cuando hay errores de compilación en una expresión de consulta. Los mensajes asociados pueden ser: el error, una advertencia o warning, y la descripción o detalle del error. Podemos gestionar esos errores mediante los siguientes métodos o getErrorCount() devuelve el número actual de errores. o getMessages() devuelve una lista de errores o warnings • EvaluationException. Se lanza cuando hay errores en la ejecución de una expresión XQuery. Por ejemplo, el siguiente código muestra un método que compila una expresión de consulta y controla posibles errores, almacenando en un array de tipo Message los posibles errores de compilación, Observa que en este caso, el tratamiento de la excepción se realiza mediante un bloque trycatch, y que en caso de que se produzca se captura la lista de errores. //Metodo para compilar la consulta controlando errores private static Expression compileExpression(Library bd, String consultaXquery) throws IOException, QizxException { Expression expr; try { expr = bd.compileExpression(consultaXquery); } catch (CompilationException e) { //mensajes de error devueltos tras la compilacion Message[] messages = e.getMessages(); for (int i = 0; i < messages.length; ++i) { System.out.println(messages[i].toString()); } throw e; } //devuelve consulta compilada return expr; } Otro Ejemplo package photoassociation; public void doGet(HttpServletRequest import java.io.*; httpservletrequest,HttpServletResponse httpservletresponse) import java.net.MalformedURLException; throws IOException { import java.net.URL; doPost(httpservletrequest, httpservletresponse); import javax.servlet.*; } import javax.servlet.http.*; import com.qizx.api.CompilationException; public void doPost(HttpServletRequest httpservletrequest, import com.qizx.api.Configuration; HttpServletResponse httpservletresponse) throws IOException { import com.qizx.api.Expression; httpservletresponse.setContentType("text/xml; import com.qizx.api.Item; UTF-8"); import com.qizx.api.ItemSequence; PrintStream out = new import com.qizx.api.Library; PrintStream(httpservletresponse.getOutputStream()); import com.qizx.api.LibraryManager; XQuerySessionManager manager = null; import com.qizx.api.LibraryManagerFactory; XQuerySession session = null; import com.qizx.api.LibraryMember; String date = import com.qizx.api.Message; httpservletrequest.getParameter("date"); import com.qizx.api.Node; String text = import com.qizx.api.QName; httpservletrequest.getParameter("text"); import com.qizx.api.QizxException; try { import com.qizx.api.XQuerySession; import com.qizx.api.XQuerySessionManager; Configuration.set(Configuration.ALLOWED_CLASSES, import com.qizx.api.util.DefaultModuleResolver; import com.qizx.api.util.XMLSerializer; "java.io.StringReader," + "java.io.StringWriter," public class XQueryServlet extends HttpServlet { + "java.io.Reader," + private static final long serialVersionUID = "java.net.URL," + 9186875057311859285L; "java.util.Map," + "java.util.List," + { "java.util.HashMap," + "java.lang.String," + System.setProperty("javax.xml.parsers.DocumentBuilderF actory", "photoassociation.qizx.UtilityFunctions,"+ "java.lang.Math"); "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); manager = System.setProperty("javax.xml.parsers.SAXParserFactory" Configuration.createSessionManager("http://aphoteg.googlecode.co , m/hg/src/xquery/"); "org.apache.xerces.jaxp.SAXParserFactoryImpl"); manager.setModuleResolver(new } MyModuleResolver()); session = manager.createSession(); public void init(ServletConfig servletconfig) throws ServletException { session.getContext().declarePrefix("flickr", super.init(servletconfig); "http://www.flickr.com/services/api/"); }
session.getContext().declarePrefix("geoplanet", "http://developer.yahoo.com/geo/");
} catch (Exception ex) { out.println("\n "); if ( ex instanceof com.qizx.api.CompilationException) { for ( com.qizx.api.Message m : ((com.qizx.api.CompilationException)ex).getMessages() ) {
session.getContext().declarePrefix("photoAssociation", "http://web.tagus.ist.utl.pt/~rui.candeias/"); BufferedReader input = new BufferedReader(new out.println(m.getLineNumber() + ":" + InputStreamReader(this.getServletContext().getResourceAsStream(" m.getColumnNumber() + " -> " + m.getText()); /WEB-INF/classes/xquery/xquery2compile.xq"))); out.println(); String line = null; out.flush(); StringBuffer sBuffer = new } StringBuffer(); } while ( (line = input.readLine()) != null ) { sBuffer.append(line); sBuffer.append("\n"); } if (session != null && manager != null && String xqy = sBuffer.toString(); manager instanceof LibraryManager) try { shutdown( (Library)session, QName[] varNames = { (LibraryManager)manager); session.getQName("text") , session.getQName("date") }; } catch (Exception e2) { } String[] varValues = { text , date }; ex.printStackTrace(out); Expression expr = out.println("\n "); session.compileExpression(xqy); out.flush(); for (int i = 0; varNames != null && i < } varNames.length; ++i) { } expr.bindVariable(varNames[i], varValues[i], null); private static void shutdown(Library lib, LibraryManager } libManager) throws QizxException { ItemSequence results = expr.evaluate(); lib.close(); XMLSerializer serializer = new libManager.closeAllLibraries(10000); XMLSerializer(out, "UTF-8"); } serializer.setIndent(2); serializer.setOmitXMLDeclaration(true); class MyModuleResolver extends DefaultModuleResolver { out.println(""); public MyModuleResolver ( ) throws out.println(""); MalformedURLException { while (results.moveToNextItem()) try { super ( new URL( Item result = "http://aphoteg.googlecode.com/hg/src/xquery/" ) ); results.getCurrentItem(); }; if (!result.isNode()) { out.println( "" + result.getString() + "" ); } public URL[] resolve (String Node node = result.getNode(); moduleNamespaceURI, String[] locationHints) throws serializer.reset(); MalformedURLException { String xmlForm = if serializer.serializeToString(node); (moduleNamespaceURI.equals("http://www.flickr.com/services/api/") out.println(xmlForm); ) out.flush(); return new URL[]{ new } catch ( Exception ex ) { URL("http://aphoteg.googlecode.com/hg/src/xquery/flickr.xqy") }; out.println(""); else if if (session != null && manager (moduleNamespaceURI.equals("http://developer.yahoo.com/geo/")) != null && manager instanceof LibraryManager) try { return new URL[]{ new shutdown( URL("http://aphoteg.googlecode.com/hg/src/xquery/geoplanet.xqy") (Library)session, (LibraryManager)manager); }; } catch (Exception e2) { } else if ex.printStackTrace(out); (moduleNamespaceURI.equals("http://web.tagus.ist.utl.pt/~rui.cande out.println(""); ias/")) out.flush(); return new URL[]{ new return; URL("http://aphoteg.googlecode.com/hg/src/xquery/photoAssociatio } n.xqy") }; out.println(""); else return out.flush(); super.resolve(moduleNamespaceURI,locationHints); if (session != null && manager != null && } manager instanceof LibraryManager) { } shutdown( (Library)session, (LibraryManager)manager); } }