Gestión de Ficheros y Flujos de Datos
Clasificación de Ficheros por Contenido
- Binarios o de Bytes:
- Caracteres ilegibles.
- Representan información (audio, imagen, vídeo).
- Utilizados por aplicaciones específicas para reconocer dicha información.
- De Caracteres o de Texto:
- Caracteres reconocibles.
- Representan texto.
- Editables con un editor de texto.
Clasificación de Ficheros por Modo de Acceso
- Secuencial: Requiere leer todo el contenido hasta llegar a la posición deseada.
- Aleatorio: Permite ir directamente a la posición deseada.
Tipos de Ficheros de Texto
.txt
(Fichero de texto plano).xml
(Fichero XML).json
(Fichero de intercambio de información).props
(Fichero de propiedades).conf
(Fichero de configuración).sql
(Script SQL).srt
(Fichero de subtítulo)
Tipos de Ficheros Binarios
Pueden contener imágenes, vídeos, ejecutables, etc.
.pdf
(Fichero PDF).jpg
(Fichero de imagen).doc
,.docx
(Fichero de Microsoft Word).avi
(Fichero de vídeo).ppt
,.pptx
(Fichero de PowerPoint)
Flujos de Datos
Acceder a un fichero implica un flujo de información. El programa interactúa con el fichero destino/origen de información. El flujo es un objeto intermediario que actúa como puente entre el programa y el origen o destino de la información.
Tipos de Acceso a Ficheros
Acceso Secuencial a Ficheros de Texto
- Leer un fichero de texto:
FileReader
: Implementa un flujo de caracteres que los lee desde un fichero de texto, lo que nos proporciona un flujo de datos.
- Escribir un fichero de texto:
FileWriter
: Escribe un flujo de caracteres para escribir datos en el archivo seleccionado.
Acceso Secuencial a Ficheros de Bytes
FileInputStream
: Lee bytes.FileOutputStream
: Escribe bytes.
Acceso Aleatorio
RandomAccessFile
: Permite el acceso aleatorio a cualquier posición del fichero.
Ejemplos de Operaciones con Ficheros de Texto
Escribir Ficheros de Texto Plano
FileWriter fichero = null;
PrintWriter escritor = null;
try {
fichero = new FileWriter("archivo.txt");
escritor = new PrintWriter(fichero);
escritor.println("Esto es una linea del fichero");
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
if (fichero != null) {
try {
fichero.close();
} catch (IOException ioe) {
// Manejo de excepción al cerrar
}
}
}
Leer Ficheros de Texto Plano
File fichero = null;
FileReader lector = null;
BufferedReader buffer = null;
try {
buffer = new BufferedReader(new FileReader(new File("archivo.txt")));
String linea = null;
while ((linea = buffer.readLine()) != null) {
System.out.println(linea);
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
if (buffer != null) {
try {
buffer.close();
} catch (IOException ioe) {
// Manejo de excepción al cerrar
}
}
}
Ficheros Binarios y Serialización
Clase Producto.java
public class Producto implements Serializable {
private static final long serialVersionUID = 1L;
private String nombre;
private float precio;
public Producto(String nombre, float precio) {
this.nombre = nombre;
this.precio = precio;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public float getPrecio() {
return precio;
}
public void setPrecio(float precio) {
this.precio = precio;
}
}
Serialización y Deserialización de Objetos
Serializar es el proceso por el cual un objeto en memoria se transforma en una estructura que puede ser almacenada en un fichero (persistencia). El proceso contrario se denomina deserializar.
Durante la serialización, cada objeto se serializa a un fichero. Si se desea almacenar todos los objetos de una aplicación, la idea más conveniente es tenerlos en una estructura compleja (y por comodidad, dinámica) para serializar o deserializar esta estructura y así guardar o cargar los objetos de la aplicación. Estructuras como ArrayList
, Set
o HashMap
son clases muy utilizadas para trabajar de esta manera.
Serializar un Objeto
ObjectOutputStream serializador = null;
try {
serializador = new ObjectOutputStream(new FileOutputStream("archivo.dat"));
serializador.writeObject(listaProductos);
} catch (IOException ioe) {
// Manejo de excepción
} finally {
if (serializador != null) {
try {
serializador.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Deserializar un Objeto
List listaProductos = null;
ObjectInputStream deserializador = null;
try {
deserializador = new ObjectInputStream(new FileInputStream("archivo.dat"));
listaProductos = (ArrayList) deserializador.readObject();
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
if (deserializador != null) {
try {
deserializador.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Resumen de Métodos de Lectura/Escritura
FileWriter
FileWriter fileWriter = new FileWriter(String ruta, boolean añadir_al_final);
FileWriter fileWriter = new FileWriter(String ruta);
fileWriter.write([String[], String]);
FileReader
FileReader fileReader = new FileReader(String ruta);
FileReader fileReader = new FileReader(File fichero);
fileReader.read();
-> Array de caracteres
FileOutputStream
- Constructor similar a
FileWriter
. - Flujo para ficheros binarios, escribe a través de arrays de bytes.
FileInputStream
- Constructor similar a
FileReader
. - Lee bytes secuencialmente.
RandomAccessFile
- Permite acceder directamente a cualquier posición del fichero.
RandomAccessFile randomReader = new RandomAccessFile(File fichero, String modo);
(Modos: «r» para lectura, «rw» para lectura/escritura).
Flujo de Trabajo para el Uso de Ficheros
- Abrir el archivo.
- Usar el fichero.
- Gestionar excepciones.
- Cerrar el archivo y liberar recursos.
Archivos XML y Parsers
Los Analizadores Sintácticos o Parsers son herramientas encargadas de leer el lenguaje XML y comprobar si es válido o no.
- Son módulos, bibliotecas o programas.
- Herramientas que validan los documentos XML:
- Comprueban que el documento esté bien-formado (well-formed).
- Comprueban que sea válido respecto al esquema XML (Ejemplo: JAXB).
- Herramientas que no validan los documentos XML:
- Comprueban que el documento esté bien-formado (well-formed).
- No necesitan un esquema XML para comprobar que sea válido (Ejemplos: DOM y SAX).
Document Object Model (DOM)
La tecnología DOM permite analizar y manipular dinámicamente el contenido, el estilo y la estructura de un documento XML.
Conceptos Fundamentales de DOM
Para trabajar con un documento XML, DOM lo almacena en memoria en forma de árbol (nodos padres, hijos, etc.). Cada estructura de datos se transforma en nodos, organizados jerárquicamente. Los métodos DOM permiten recorrer los distintos nodos del árbol y analizar el tipo de nodo al que pertenecen. En función del tipo de nodo, se ofrecen funcionalidades específicas para trabajar con la información que contiene.
DOM y Java
El principal inconveniente de DOM es que el documento XML se guarda en memoria RAM o memoria principal. Si el documento es muy grande, podría colapsar y hacerlo intratable.
JAXP (Java API for XML Processing)
Los parsers garantizan la interoperabilidad de Java. Se busca parsear una estructura jerárquica de árbol de nodos. Parsers comunes para DOM incluyen XT, Xalan y Xerces.
Paquetes de Xerces
Dentro de la librería Xerces, se utilizan los siguientes paquetes:
javax.xml.parsers
: Contiene clases comojavax.xml.parsers.DocumentBuilder
yjavax.xml.parsers.DocumentBuilderFactory
.org.w3c.dom
: Representa el modelo DOM según la W3C.- Para generar un fichero XML desde un árbol DOM,
javax.xml.transform
especifica la fuente y el resultado.
Esquema JAXP y Acceso a DOM
Desde la interfaz JAXP, se crea un DocumentBuilderFactory
. Este, a su vez, crea un DocumentBuilder
que permite cargar la estructura de árbol DOM desde un fichero XML.
Métodos de DocumentBuilderFactory
Dentro de la clase javax.xml.parsers.DocumentBuilderFactory
, se encuentran métodos para indicar qué elementos del fichero XML deben ser incluidos en el árbol DOM o si se desea validar el XML con respecto al esquema:
setIgnoringComments(boolean ignore)
: Ignora los comentarios de un fichero XML.setIgnoringElementWhitespace(boolean ignore)
: Ignora los espacios en blanco.setNamespaceAware(boolean aware)
: Interpreta el documento usando el espacio de nombres.setValidating(boolean validate)
: Valida el documento XML según el esquema.
Métodos para Recorrer Nodos
getFirstChild()
/getNextSibling()
: Permiten obtener los nodos uno a uno: los nodos descendientes (hijos) y sus adyacentes (hermanos).getNodeType()
: Devuelve una constante para distinguir entre los distintos tipos de nodos (ej:ELEMENT_NODE
,TEXT_NODE
). Útil para recorrer el árbol, ya que permite ignorar los tipos de nodo que no interesan.getAttributes()
: Devuelve un objetoNamedNodeMap
(lista con sus atributos, si el nodo es de tipo elemento).getNodeName()
/getNodeValue()
: Devuelven el nombre y el valor de un nodo. Útil para hacer búsquedas selectivas de un nodo concreto.
(*) Error típico: Creer que getNodeValue()
aplicado a un nodo de tipo elemento devuelve texto, cuando devolvería NULL
o un DomException
.
Abrir un Documento DOM desde Java
Para abrir un documento XML y crear un árbol DOM, se utilizan las clases DocumentBuilderFactory
(javax.xml.parsers
), DocumentBuilder
(javax.xml.parsers
) y Document
(org.w3c.dom
).
El proceso de apertura implica:
- Crear una instancia de
DocumentBuilderFactory
. - Preparar el parser para interpretar el fichero XML.
Algunos elementos que no se tienen en cuenta al generar el DOM son los espacios en blanco y los comentarios.
Recorrer un Árbol DOM
Se utilizan las siguientes clases:
Node
(org.w3c.dom
)NodeList
(org.w3c.dom
)
Su utilidad radica en permitir comprobar el tipo de nodo que se está tratando, ya que un DOM tiene muchos tipos de nodos que no siempre contienen información relevante para explorar.
Modificar y Serializar un Árbol DOM
Además de recorrer en modo “solo lectura” un árbol DOM, también se puede modificar y guardar en un fichero. Más adelante se verá cómo crearlo.
- Se crea y añade un elemento al árbol DOM, y este se guarda en un documento XML.
- Es importante crear nodos de texto que desciendan de los nodos elementos para proporcionar información sobre los valores.
- Una vez modificado el árbol DOM, se guarda en un fichero, logrando así la persistencia de los datos.
Proceso de Serialización (Marshalling)
Para serializar (marshalling), el árbol se convierte en una cadena de caracteres o bytes. Es el proceso de convertir el estado de un objeto en un formato que se pueda almacenar. Ejemplo: llevar los objetos que componen un árbol DOM a un fichero XML. El unmarshalling convierte el contenido de un fichero en una estructura de objetos.
Clases para Serialización: XMLSerializer
y OutputFormat
- La clase
XMLSerializer
serializa el árbol DOM y lo guarda en un fichero XML. - La clase
OutputFormat
permite asignar diferentes propiedades de formato al fichero resultante (Ejemplo: indentado, donde los elementos hijos aparecen con mayor tabulación).
Requisitos para Serializar un Árbol DOM
- Un objeto
File
para representar el fichero resultante. - La clase
OutputFormat
, que permite indicar las pautas del formato para la salida. - Un objeto
XMLSerializer
, que se construye con el objetoFile
de salida y el formato definido porOutputFormat
. - Un método
serialize()
deXMLSerializer
, que recibe como parámetro el documento que se desea guardar en el fichero (el árbol DOM) y lo escribe.
Interfaces Clave de DOM para Java
Document
: Permite crear nuevos nodos en el documento.Element
: Expone las propiedades y métodos para manipular los elementos y sus atributos.Node
: Representa cualquier nodo del documento.NodeList
: Contiene una lista con los nodos hijos de cada nodo.Attr
: Permite acceder a los atributos de un nodo.Text
: Representa los datos de caracteres de un nodo.CharacterData
: Proporciona atributos y métodos para manipular los datos de los caracteres.
Manejo de Conectores de Bases de Datos
Conectores: Definición
Los conectores son el software necesario para poder conectar un programa a una base de datos.
El Desfase Objeto-Relacional
Bases de Datos Orientadas a Objetos vs. Relacionales
- Bases de Datos Orientadas a Objetos: La información se representa mediante objetos, como en la programación orientada a objetos.
- Bases de Datos Relacionales: Permiten establecer interconexiones entre los datos. Los datos están estructurados en tablas, y las interconexiones o relaciones permiten vincular los datos de distintas tablas.
Características de las Bases de Datos Relacionales
- Una misma base de datos se compone de distintas tablas y, por ende, interconexiones.
- No podrán existir dos tablas con el mismo nombre.
- Cada tabla está compuesta por columnas (campos) y filas (registros).
Actualmente, las bases de datos orientadas a objetos van ganando más peso frente a las relacionales, ya que son más ágiles y solucionan las necesidades de aplicaciones más sofisticadas que requieren el tratamiento de elementos complejos. Ejemplos: Sistemas de información geográfica, aplicaciones para fabricación y diseño en ingeniería.
Las bases de datos relacionales no están diseñadas para almacenar objetos, ya que existe un desfase entre las construcciones típicas de las bases de datos relacionales y las proporcionadas por los ambientes de la programación basada en objetos. Esto es lo que se denomina desfase objeto-relacional o desajuste de impedancia. Se refiere a los problemas que existen debido a las diferencias entre el modelo de bases de datos relacionales (como tablas) y el de lenguajes de programación orientados a objetos. Estos dos paradigmas pueden llegar a convivir, pero cada vez que los objetos deban extraerse en una base de datos relacional, se requiere un mapeo.
Bases de Datos Embebidas
Cuando se desarrollan pequeñas aplicaciones que no van a almacenar una gran cantidad de datos, no es necesario utilizar un sistema gestor de bases de datos como Oracle o MySQL. En su lugar, se puede usar una base de datos embebida. En estas, el motor está incrustado en la misma aplicación y su uso es exclusivo para ella. La base de datos se inicia cuando arranca la aplicación y finaliza cuando esta se cierra. Generalmente son de código abierto, aunque existen algunas de tipo propietario.
SQLite
- Sistema gestor de bases de datos multiplataforma.
- Escrito en C y con un motor ligero.
- Las bases de datos se guardan como archivos, lo que facilita su traslado junto con la aplicación que las usa.
- Es un proyecto de dominio público.
- Se puede usar desde programas en C/C++, PHP, Visual Basic, Java o Perl.
Apache Derby
- Base de datos relacional de código abierto, implementada totalmente en Java.
- Disponible bajo licencia Apache 2.0.
- Ventajas: Tamaño reducido, basada en Java y soporta los estándares SQL; soporta el paradigma cliente-servidor. Fácil de usar, instalar e implementar.
HSQLDB
- Sistema gestor de bases de datos relacionales escrito en Java.
- El paquete OpenOffice la incluye desde su versión 2.0.
- Puede mantener la base de datos en su memoria o en archivos en disco.
- Se distribuye bajo licencia BSD (Berkeley Software Distribution), muy cercana al dominio público.
H2
- Sistema gestor de bases de datos relacionales programado en Java.
- Disponible como software de código abierto bajo la licencia pública de Mozilla.
DB4o
- Motor de bases de datos orientado a objetos.
- Disponible para entornos Java y .NET.
- Posee una licencia dual GPL/comercial.
Características de DB4o
- Evita el desfase objeto-relacional.
- No existe un lenguaje SQL para manipular los datos; se usan métodos delegados.
- Para instalarlo, solo es necesario añadir una librería.
- Se guarda como un único fichero de base de datos con la extensión
.yap
.
Protocolos de Acceso a Bases de Datos
- ODBC (Open DataBase Connectivity): Define una API que las aplicaciones pueden usar para abrir una conexión a una base de datos, enviar consultas, actualizaciones y obtener resultados. Las aplicaciones podrán usar esta API para conectarse a cualquier servidor de bases de datos compatible con ODBC.
- JDBC (Java DataBase Connectivity): Define una API que los programas Java pueden usar para conectarse a los servidores de bases de datos relacionales.
Acceso a Datos Mediante JDBC
JDBC proporciona una biblioteca estándar para acceder a fuentes de datos, principalmente orientada a bases de datos relacionales que usan SQL. No solo proporciona una interfaz, sino que también define una arquitectura estándar para que los fabricantes puedan crear los drivers que permitan a las aplicaciones Java el acceso a los datos. JDBC dispone de una interfaz distinta para cada base de datos, lo que se conoce como conector, driver o controlador.
JDBC consta de un conjunto de clases e interfaces que permiten escribir aplicaciones Java para gestionar ciertas tareas con una base de datos relacional:
- Conectarse a la base de datos.
- Enviar consultas e instrucciones de actualización a la base de datos.
- Recuperar y procesar los resultados recibidos de la base de datos en respuesta a las consultas.
Tipos de Conectores JDBC
Existen cuatro tipos de conectores JDBC:
- Tipo 1: JDBC-ODBC Bridge: Permite el acceso a bases de datos JDBC mediante un driver ODBC, exigiendo la instalación y configuración de ODBC en la máquina cliente.
- Tipo 2: Native (Native-API-Partially-Java Driver): Escrito parcialmente en Java y en código nativo. Traduce las llamadas a la API de JDBC de Java en llamadas propias del motor de bases de datos. Exige instalar en la máquina cliente código binario propio del cliente y del sistema operativo.
- Tipo 3: Network (JDBC-Net Pure Java Driver): Controlador de Java puro, que utiliza un protocolo de red (por ejemplo, HTTP) para comunicarse con un servidor de bases de datos. Traduce las llamadas a la API de JDBC de Java en llamadas propias del protocolo de red. No exige instalación en la máquina cliente.
- Tipo 4: THIN (Native-Protocol Pure Java Driver): Controlador de Java puro con protocolo nativo. Traduce las llamadas a la API de JDBC de Java en llamadas propias del protocolo de red usado por el motor de la base de datos. No exige instalación en la máquina cliente.
Los tipos tres y cuatro son los mejores para acceder a bases de datos JDBC. Los tipos uno y dos se usan cuando no queda más remedio, por ejemplo, si el único sistema de acceso final al gestor de bases de datos es ODBC, pero exigen instalación de software en la parte cliente. Si no existen drivers disponibles para el sistema gestor de bases de datos, normalmente se usa el tipo cuatro.
Funcionamiento de JDBC
JDBC define varias interfaces que permiten realizar operaciones con bases de datos. A partir de estas interfaces, se derivan las clases correspondientes, las cuales están definidas en el paquete java.sql
.
Clases e Interfaces Importantes de JDBC
Driver
: Permite conectarse a una base de datos. Cada gestor de base de datos requiere un driver distinto.DriverManager
: Permite gestionar todos los drivers instalados en el sistema.DriverPropertyInfo
: Proporciona diversa información sobre el driver.Connection
: Representa una conexión con una base de datos. En una aplicación puede haber más de una.DatabaseMetadata
: Proporciona información sobre la base de datos, por ejemplo, el número de tablas.Statement
: Permite ejecutar sentencias SQL sin parámetros.PreparedStatement
: Permite ejecutar sentencias SQL con parámetros de entrada.CallableStatement
: Permite ejecutar sentencias SQL con parámetros de entrada y salida, como llamadas a procedimientos almacenados.ResultSet
: Contiene las filas resultantes de ejecutar una sentenciaSELECT
.ResultSetMetadata
: Permite obtener información sobre unResultSet
, por ejemplo, el número de columnas y sus nombres.
El trabajo con JDBC comienza con la clase DriverManager
, que se encarga de establecer las conexiones con los orígenes de datos a través de los drivers JDBC.
Pasos para un Programa JDBC
A continuación, se detallan los pasos para el funcionamiento de un programa JDBC:
- Importar las clases necesarias.
- Cargar el driver JDBC.
- Identificar el origen de datos.
- Crear un objeto
Connection
. - Crear un objeto
Statement
. - Ejecutar una consulta con el objeto
Statement
. - Recuperar los datos del objeto
ResultSet
. - Liberar el objeto
ResultSet
. - Liberar el objeto
Statement
. - Liberar el objeto
Connection
.
En un programa Java de JDBC que accede a una base de datos, todos los import
necesarios para manejar la base de datos están incluidos en java.sql.*
. Es fundamental incluir un bloque try/catch
, ya que casi todos los métodos relativos a bases de datos pueden lanzar la excepción SQLException
, entre otras.
Cargar el Driver
Con el método forName()
de la clase Class
, se le pasa un objeto String
con el nombre de la clase del driver como argumento. Ejemplo: Class.forName("com.mysql.jdbc.Driver")
(nombre de la clase del driver).
Establecer la Conexión
El servidor debe estar en ejecución. Se utiliza la clase DriverManager
con el método getConnection()
. Ejemplo:
Connection conexion = DriverManager.getConnection("jdbc:mysql://localhost/ejemplo", "root", "root");
- El primer parámetro del método representa la URL de conexión a la base de datos.
jdbc:mysql
indica que se está usando un driver JDBC para MySQL.localhost
indica que el servidor de bases de datos está en la misma máquina en la que se ejecuta el programa. Se puede especificar una dirección IP o un nombre de red.ejemplo
es el nombre de la base de datos a la que se va a acceder.- El segundo parámetro es el nombre del usuario que accede a la base de datos.
- El tercer parámetro es la clave del usuario que accede.
Ejecutar Sentencias SQL
Para realizar consultas, se recurre a la clase Statement
para crear sentencias. Para obtener un objeto Statement
, se usa el método createStatement()
. La sentencia obtenida tiene el método executeQuery()
, que sirve para realizar una consulta a la base de datos. Ejemplo:
Statement sentencia = conexión.createStatement();
ResultSet resul = sentencia.executeQuery("SELECT * FROM tabla");
El resultado se devuelve como un ResultSet
, un objeto similar a una lista que contiene el resultado de la consulta. Cada elemento de la lista es uno de los registros de la tabla. Inicialmente, ResultSet
no contiene todos los datos; los va obteniendo de la base de datos a medida que se solicitan. Por ello, el método executeQuery()
puede ser un poco lento.
ResultSet
tiene internamente un puntero que apunta al primer registro de la lista. Mediante el método next()
, el puntero avanza al siguiente registro. Para recorrer la lista de registros, se usa el método next()
dentro de un bucle while
que se ejecutará mientras next()
devuelva true
. Ejemplo:
while(resul.next()) {
System.out.println(resul.getInt(1) + " " + resul.getString(2) + " " + resul.getString(3));
}
Los métodos getInt()
y getString()
devuelven los valores de los campos de dicho registro. Entre paréntesis, se puede especificar la posición de la columna en la tabla o una cadena que indica el nombre de la columna: resul.getInt("dept_no")
.
Liberar Recursos
Se liberan todos los recursos y se cierra la conexión. Ejemplo:
resul.close();
sentencia.close();
conexion.close();
JavaBeans: Componentes Reutilizables en Java
Desarrollo de Software Basado en Componentes
El desarrollo de software basado en componentes constituye una aproximación que describe, construye y emplea técnicas de software para elaborar sistemas abiertos y distribuidos, mediante el ensamblaje de partes de software reutilizables. El surgimiento de este paradigma supone una extensión a la programación orientada a objetos y viene dada por la necesidad de ensamblar módulos independientes.
Definición de Componentes de Software
Según Szyperski (1998), un componente software es una unidad de composición en formato ejecutable con interfaces especificadas contractualmente y dependencias explícitas del contexto. Un componente software se puede instalar independientemente y está sujeto a composición por terceros. Además, no tienen estado observable. El objetivo del uso de componentes es facilitar la construcción de aplicaciones mediante módulos reutilizables, diseñados previamente de forma independiente de la aplicación final en la que se ensamblan.
Características de los Componentes de Software
- Independencia de la plataforma.
- Son genéricos.
- Son identificables.
- Son autocontenidos, es decir, no necesitan utilizar otros para hacer su cometido y deben poder ser distribuidos como paquetes.
- Son reemplazables por otros componentes.
- Proporcionan acceso a través de una interfaz (y solo a través de ella).
- La implementación puede variar, pero no la interfaz.
- Necesidad de documentación adecuada.
- Reutilización en tiempo de ejecución.
La tecnología concreta de componentes que se verá aquí son los JavaBeans (basados en Java y, por tanto, multiplataforma), pero hay más: .NET, COM, COM+ (Microsoft Windows).
JavaBeans: Concepto y Reglas
Un JavaBean es un componente software que se puede reutilizar y que puede ser manipulado visualmente por una herramienta de programación en lenguaje Java. Ejemplos de JavaBeans son, por ejemplo, las librerías gráficas AWT.
En general, un bean es una clase que obedece dos reglas fundamentales:
- Un bean tiene que tener un constructor por defecto (sin argumentos).
- Un bean tiene que tener persistencia, es decir, implementar la interfaz
Serializable
.
Propiedades de los JavaBeans
Una propiedad es un atributo del JavaBean que afecta a su apariencia o a su conducta. Por ejemplo, un botón puede tener las siguientes propiedades: el tamaño, la posición, el título, el color de fondo, el color del texto, si está o no habilitado, etc.
Las propiedades de un bean pueden examinarse y modificarse mediante métodos o funciones miembro, que acceden a dicha propiedad, y pueden ser de dos tipos:
- getter method: Lee el valor de la propiedad.
- setter method: Cambia el valor de la propiedad.
Tipos de Propiedades
a) Propiedades Simples
Una propiedad simple representa un único valor.
// miembro de la clase que se usa para guardar el valor de la propiedad
private String nombre;
// métodos set y get de la propiedad denominada Nombre
public void setNombre(String nuevoNombre){
nombre = nuevoNombre;
}
public String getNombre(){
return nombre;
}
En el caso de que dicha propiedad sea booleana, se escribe:
// miembro de la clase que se usa para guardar el valor de la propiedad
private boolean conectado = false;
// métodos set y get de la propiedad denominada Conectado
public void setConectado(boolean nuevoValor){
conectado = nuevoValor;
}
public boolean isConectado(){
return conectado;
}
b) Propiedades Indexadas
Una propiedad indexada representa un array de valores.
// miembro de la clase que se usa para guardar el valor de la propiedad
private int[] numeros = {1, 2, 3, 4};
// métodos set y get de la propiedad denominada Numeros, para el array completo
public void setNumeros(int[] nuevoValor){
numeros = nuevoValor;
}
public int[] getNumeros(){
return numeros;
}
// métodos get y set para un elemento de array
public void setNumeros(int indice, int nuevoValor){
numeros[indice] = nuevoValor;
}
public int getNumeros(int indice){
return numeros[indice];
}
c) Propiedades Ligadas (Bound)
Los objetos de una clase que tiene una propiedad ligada notifican a otros objetos (listeners) interesados cuando el valor de dicha propiedad cambia, permitiendo a estos objetos realizar alguna acción. Cuando la propiedad cambia, se crea un objeto (event) que contiene información acerca de la propiedad (su nombre, el valor previo y el nuevo valor), y lo pasa a los otros objetos (listeners) interesados en el cambio.
d) Propiedades Restringidas (Constrained)
Una propiedad restringida es similar a una propiedad ligada, salvo que los objetos (listeners) a los que se les notifica el cambio de valor de la propiedad tienen la opción de vetar (veto) cualquier cambio en el valor de dicha propiedad. (Este tipo de propiedad no se estudiará en este documento).
Ventajas de los JavaBeans
- Reutilización de software.
- Disminución de la complejidad del software.
- Mejoras en el mantenimiento.
- Incremento de la calidad del software.
Manejo de Eventos en JavaBeans: Ejemplo Práctico
A continuación, se presenta un ejemplo con las clases Asalariado.java
, Hacienda.java
y ProyectoxHacienda.java
para ilustrar el manejo de eventos.
Clase Asalariado.java
package proyectoxhacienda;
import java.beans.*;
import java.io.Serializable;
import java.util.*;
public class Asalariado implements Serializable{
// Propiedades
private PropertyChangeSupport propertySupport;
private int sueldo;
// Constructor
public Asalariado(){
propertySupport = new PropertyChangeSupport(this);
sueldo = 20;
}
// Métodos
public void addPropertyChangeListener(PropertyChangeListener listener){
propertySupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener){
propertySupport.removePropertyChangeListener(listener);
}
public void setSueldo(int nuevoSueldo){
int anteSueldo = sueldo;
sueldo = nuevoSueldo;
if (anteSueldo != nuevoSueldo){
propertySupport.firePropertyChange("sueldo", anteSueldo, sueldo);
}
}
public int getSueldo(){
return sueldo;
}
}
Clase Hacienda.java
package proyectoxhacienda;
import java.beans.*;
import java.io.Serializable;
import java.util.*;
public class Hacienda implements Serializable, PropertyChangeListener{
// Constructor
public Hacienda(){}
// Métodos
public void propertyChange(PropertyChangeEvent evt){
System.out.println("Hacienda: nuevo sueldo " + evt.getNewValue());
System.out.println("Hacienda: sueldo anterior " + evt.getOldValue());
}
}
Clase ProyectoxHacienda.java
package proyectoxhacienda;
public class ProyectoxHacienda{
public static void main(String[] args){
Hacienda funcionario1 = new Hacienda();
Asalariado empleado = new Asalariado();
System.out.println("---------------------------");
empleado.addPropertyChangeListener(funcionario1);
empleado.setSueldo(50);
}
}
Introducción a MongoDB: Bases de Datos NoSQL
MongoDB es una base de datos orientada a documentos. Esto significa que, en lugar de guardar los datos en registros, los guarda en documentos. Estos documentos son almacenados en BSON, que es una representación binaria de JSON. Una de las diferencias más importantes con respecto a las bases de datos relacionales es que no es necesario seguir un esquema. Los documentos de una misma colección (concepto similar a una tabla de una base de datos relacional) pueden tener esquemas diferentes.
Conceptos Fundamentales de MongoDB
Estructuras JSON en MongoDB
En JSON, las estructuras se presentan de dos formas principales:
Objeto JSON
Podría decirse que es un conjunto no ordenado de pares clave/valor expresado entre { }
(llaves). La clave y el valor asociado se separan mediante :
(dos puntos). Cada par se separa del siguiente mediante una coma.
{
"active": true,
"formed": 2016,
"homeTown": "Metro City",
"members": [
{
"age": 29,
"name": "Molecule Man",
"powers": [
"Radiation resistance",
"Turning tiny",
"Radiation blast"
],
"secretIdentity": "Dan Jukes"
},
{
"age": 39,
"name": "Madame Uppercut",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
],
"secretIdentity": "Jane Wilson"
},
{
"age": 1000000,
"name": "Eternal Flame",
"powers": [
"Immortality",
"Heat Immunity",
"Inferno",
"Teleportation",
"Interdimensional travel"
],
"secretIdentity": "Unknown"
}
],
"secretBase": "Super tower",
"squadName": "Super hero squad"
}
Array JSON
Es una colección de valores encerrada por [ ]
(corchetes o square brackets).
{
"heroes": [
{
"age": 29,
"name": "Molecule Man",
"powers": [
"Radiation resistance",
"Turning tiny",
"Radiation blast"
],
"secretIdentity": "Dan Jukes"
},
{
"age": 39,
"name": "Madame Uppercut",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
],
"secretIdentity": "Jane Wilson"
}
]
}
Tipos de Valores JSON
Un Valor puede ser de los siguientes tipos:
- String: En UTF-8. Siempre entre dobles comillas.
- Number: Números. Al guardarse en BSON, admite los de tipo
byte
,int32
,int64
odouble
. - Boolean:
true
ofalse
. - Array: Entre corchetes
[]
y pueden contener de 1 a N elementos, que pueden ser de cualquiera de los otros tipos. - Documentos: Un documento en formato JSON puede contener otros documentos embebidos que incluyan más documentos o cualquiera de los tipos anteriormente descritos.
- Null.
Operaciones Básicas (CRUD) en MongoDB
En cualquier base de datos, el conjunto de operaciones básicas se denominan CRUD (Crear, Leer, Actualizar, Eliminar). A continuación, se muestran ejemplos de estas en MongoDB.
Comandos Iniciales
show databases
oshow dbs
: Muestra las bases de datos existentes.use nombre_base_datos
: Cambia a la base de datos especificada. Si no existe, la crea al insertar un objeto.db
: Referencia a la base de datos actual.show collections
: Muestra las colecciones de la base de datos actual.db.help()
: Para consultar la ayuda.
Crear Registros (Insertar Datos)
save()
oinsert()
: Para añadir datos.
Consultar Registros (Buscar Datos)
Para realizar consultas a la base de datos, se debe usar el comando db.nombre_de_coleccion.find()
. El comando puede recibir dos parámetros: una consulta (los filtros) y una proyección (los campos). Ambos comandos son opcionales, por lo que si se ejecuta el comando:
db.jugadores.find()
: Se obtiene una lista con los primeros 20 elementos de la colección. MongoDB no muestra todos los elementos por defecto; para la consulta, crea un cursor. Para mostrar más documentos, se debe escribirit
.
Filtrando Consultas con find()
Al añadir consultas al comando find()
, se filtran los elementos según las necesidades. Para ello, se especifica un objeto JSON como primer parámetro del comando, con los campos por los que se quiere filtrar. Por ejemplo, jugadores en activo con el dorsal 23:
db.jugadores.find( {jersey:23, isActive: true} ).pretty()
Los resultados muestran todos los campos de cada elemento, similar a usar el asterisco en una consulta SELECT
. Si se desea seleccionar solo algunos de los campos, se debe utilizar el segundo parámetro de la consulta find()
para definir una proyección:
db.jugadores.find( {jersey:23, isActive: true}, {firstName:1, lastName:1} ).pretty()
Esto devolverá solo el nombre y el apellido. Si en lugar de valor 1, se pasa 0, devuelve todos los campos excepto los indicados con el 0.
Otra función alternativa a find()
es findOne()
, que devuelve solo la primera colección que cumple con lo especificado en la consulta.
Ordenando Resultados con sort()
sort()
: Para ordenar.1
para orden ascendente,-1
para descendente.
Operadores de Comparación
$eq
: Coincide con valores iguales a un valor especificado.$gt
: Coincide con valores mayores que un valor especificado.$gte
: Coincide con valores mayores o iguales que un valor especificado.$in
: Coincide con cualquiera de los valores especificados en un array.$lt
: Coincide con valores menores que un valor especificado.$lte
: Coincide con valores menores o iguales que un valor especificado.$ne
: Coincide con todos los valores que no son iguales a un valor especificado.$nin
: Coincide con ninguno de los valores especificados en un array.
Operadores Lógicos
$and
: Une cláusulas de consulta con unAND
lógico y devuelve todos los documentos que coinciden con las condiciones de ambas cláusulas.$not
: Invierte el efecto de una expresión de consulta y devuelve documentos que no coinciden con la expresión de consulta.$nor
: Une cláusulas de consulta con unNOR
lógico y devuelve todos los documentos que no coinciden con ninguna de las cláusulas.$or
: Une cláusulas de consulta con unOR
lógico y devuelve todos los documentos que coinciden con las condiciones de cualquiera de las cláusulas.
Operadores de Tipo
$exists
: Coincide con documentos que tienen el campo especificado.$type
: Selecciona documentos si un campo es del tipo especificado.
Operaciones Avanzadas en MongoDB
Actualizaciones de Registros
update()
: Para actualizar registros.- Un ejemplo:
db.coleccion.update(filtro, cambios, { upsert: boolean, multi: boolean })
Opciones multi
y upsert
Por defecto, MongoDB solo actualiza un documento a menos que se le indique lo contrario, lo cual es una forma de evitar errores. Para actualizar todos los documentos, se debe usar la opción multi
, que debe incluirse como se ve en el ejemplo anterior.
Además de la opción multi
, está disponible la opción upsert
, que inserta el documento si este no existe. Es bastante parecido al comando save()
visto en las operaciones de inserción. En este caso, se comprueba toda la consulta en lugar de solo el _id
.
Operadores de Actualización
$set
: Para actualizar un documento o varios con nuevas propiedades.$unset
: Para eliminar propiedades de un documento o varios.$inc
: Incrementa campos numéricos en la cantidad especificada.$rename
: Para renombrar campos de documentos.
Eliminación de Registros
remove()
: Para eliminar registros.
Funciones de Resumen (Agregación)
$sum
: Suma (o incrementa).$avg
: Calcula la media.$min
: Mínimo de los valores.$max
: Máximo de los valores.
Funciones para Arrays
$each
: Utilizado junto conaddToSet
opush
para indicar que se añaden varios elementos a un array.$pop
: Para eliminar el primer o último elemento de un array.$pull
: Para eliminar los valores del array que cumplan con el filtro especificado.$push
: Inserta un valor determinado en un array.$addToSet
: Inserta valores en un array, pero solo una vez (evita duplicados).$first
: Obtiene el primer elemento del grupo, a menudo junto consort
.$last
: Obtiene el último elemento, a menudo junto consort
.$elemMatch
: Obtiene documentos que contienen campos de array con al menos un elemento que cumple un criterio de consulta especificado.
Funciones para Cadenas de Texto
Las funciones específicas para las cadenas de texto son las siguientes y su cometido queda bastante bien explicado por el propio nombre de la función:
$concat
: Concatena varias cadenas en una sola.$toUpper
: Convierte a mayúsculas.$toLower
: Convierte a minúsculas.$substr
: Devuelve un substring de una cadena existente.$strcasecmp
: Compara cadenas sin tener en cuenta mayúsculas/minúsculas.
Funciones Aritméticas
$abs
: Para obtener el valor absoluto.$add
: Suma (en el caso de fechas, añade milisegundos).$ceil
: Entero superior más cercano.$divide
: División.$exp
: e^x.$floor
: Entero inferior más cercano.$ln
: Logaritmo neperiano.$log
: Devuelve el logaritmo para el número y la base indicados.$log10
: Logaritmo en base 10.$mod
: Módulo de la división entera.$multiply
: Producto.$pow
: Potencia x^y.$sqrt
: Raíz cuadrada.$subtract
: Resta.$trunc
: Truncamiento.
Funciones sobre Fechas
$dayOfYear
: Día del año.$dayOfMonth
: Día del mes.$dayOfWeek
: Día de la semana.$dateFromString
: Convierte una cadena en fecha.$dateToString
: Convierte una fecha en cadena (admite varios formatos).$year
: Devuelve de una fecha la parte correspondiente al año.$month
: Devuelve de una fecha la parte correspondiente al mes.$week
: Devuelve de una fecha la parte correspondiente a la semana.$hour
: Devuelve de una fecha la parte correspondiente a las horas.$minute
: Devuelve de una fecha la parte correspondiente a minutos.$second
: Devuelve de una fecha la parte correspondiente a segundos.$millisecond
: Devuelve de una fecha la parte correspondiente a milisegundos.
Aggregation Pipeline en MongoDB
La agregación trabaja con grupos de valores procedentes de múltiples documentos que se pueden agrupar para obtener un resultado. Se denomina pipeline o tubería porque cada etapa de la agregación toma como entrada la salida de la anterior.
Concepto de Pipeline o Tubería
El Aggregation Pipeline está dividido en varias etapas en las que se somete a una colección a diversas operaciones (cada una se considera una etapa), que modifican los datos hasta obtener el resultado deseado. Cada etapa tiene asociada una multiplicidad, lo que hace referencia al número de documentos que se obtienen como resultado tras la etapa en cuestión. Son similares a las cardinalidades de las relaciones de bases de datos relacionales:
- 1:1: Se aplica a un documento y se obtiene un documento.
- 1:N: Se aplica a un documento y se obtienen N documentos.
- N:1: Se aplica a N documentos y se obtiene un documento.
Etapas de Aggregation Pipeline
$project
(1:1): Cambia la forma del documento, la proyección.$match
(N:1): Filtra los resultados.$group
(N:1): Agrupa los documentos por campos comunes. También se usa para las funciones de resumen.$sort
(1:1): Ordenación.$skip
(N:1): Salto de elementos.$limit
(N:1): Muestra una cantidad determinada de resultados.$lookup
: Permite establecer relaciones entre colecciones. Es parecido a unLEFT OUTER JOIN
en SQL.$unwind
(1:N): Normalización de arrays.$out
(1:1): Para enviar el resultado a una salida concreta (Ej: insertarlo en un nuevo documento).
Condiciones en la Agregación
$cond
: Es similar a la sentencia de controlif-else
. Recibe 3 elementos: la expresión a evaluar, la sentencia sitrue
y la sentencia sifalse
.$ifNull
: Recibe una expresión y otra de reemplazo. Evalúa la primera y devuelve su valor si no es nulo, o la de reemplazo si lo es.
Operaciones con Arrays y Campos Compuestos
$arrayElemAt
: Para acceder a los elementos de un array presente en un documento. Recibe el campo array y el índice que se desea consultar.$concatArrays
: Recibe varios arrays independientes y los concatena en uno solo.$filter
: Devuelve un array nuevo a partir de algunos elementos de otro array.$isArray
: Devuelve verdadero o falso si el operando recibido es o no un array.$size
: Devuelve el tamaño del array que recibe.$slice
: Devuelve un subconjunto del array que recibe.