Estás en:   ArielOrtiz.info > Estructura de datos > Persistencia en Java

Persistencia en Java

Introducción

La persistencia es la propiedad de un objeto a través del cual su existencia trasciende en el tiempo y/o espacio1. Esto significa que un objeto presistente sigue existiendo después de que ha finalizado el programa que le dio origen y que además puede ser movido de la localidad de memoria en la que fue creado.

Gabriel2 explica de manera muy pictórica el porqué es necesaria la persistencia en un ambiente de programación orientada a objetos:

Supongamos que usted tuviera un perro al cual estuviera enseñándole a dar vueltas y a hacerse el muertito. Supongamos además que al perro se le olvida todo lo que le enseña cuando lo deja solo. Mientras usted esté frente al perro, éste aprende y recuerda todo lo que le ha enseñado, incluso si esto dura muchos meses; pero al momento en que usted se va, el perro lo olvida todo. Usted pensaría que hay algo raro en este perro porque no puede recordar las cosas, y quizás intentaría deshacerse de él.

Muchos programas se comportan de esta misma manera. Cuando se ejecutan, construyen información respecto a la tarea siendo realizada; mas al finalizar, la información se pierde. Afortunadamente, muchos programas pueden ser así de simples. Sin embargo, en aplicaciones del mundo real (por ejemplo, un sistema de nómina), este comportamiento es inaceptable porque los datos almacenados representan a personas u objetos que existen a través del tiempo, y las representaciones de éstos deben persistir también.

En la programación orientada a objetos, nos enfrentamos más seguido con este problema que bajo los esquemas tradicionales por el hecho de que los objetos creados y mantenidos en programas orientados a objetos tienen más parecido a personas que a estructuras de datos; los objetos [al igual que las personas] tienen estado y comportamiento, y un programa típico crea objetos y los manipula.

El atributo de persistencia solamente debe estar presente en aquellos objetos que una aplicación requiera mantener entre corridas, de otra forma se estarían almacenando una cantidad probablemente enorme de objetos innecesarios. La persistencia se logra almacenando en un dispositivo de almacenamiento secundario (disco duro, memoria flash) la información necesaria de un objeto para poder restaurarlo posteriormente. Típicamente la persistencia ha sido dominio de la tecnología de base de datos, por lo que esta propiedad no ha sido sino hasta recientemente que se ha incorporada en la arquitectura básica de los lenguajes orientados a objetos.

El lenguaje de programación Java permite serializar objetos en un flujo de bytes. Dicho flujo puede ser escrito a un archivo en disco y posteriormente leído y deserializado para reconstruir el objeto original. Con esto se logra lo que se llama "persistencia ligera" (lightwigth persistence en inglés). Se puede consulta la Especificación de Serialización de Objetos de Java para obtener más detalles de los procesos que se resumen a continuación.

Escribiendo un objeto a un archivo

Para escribir un objeto a un archivo se requiere:

  1. Crear una instancia de la clase FileOutputStream mandando al constructor una cadena de caracteres como argumento. Dicha cadena es el nombre del archivo que se desea crear. Si el archivo ya existe, el archivo original se borra y se crea uno nuevo con el mismo nombre. El constructor arroja una instancia de la clase IOException si el archivo no pudo ser creado por alguna razón.
  2. Crear una instancia de la clase ObjectOutputStream enviando al constructor un argumento: la instancia de la clase FileOutputStream creada en el punto anterior. Dicho constructor arroja una instancia de la clase IOException en caso de producirse algún error.
  3. Invocar el método writeObject() sobre la instancia de la clase ObjectOutputStream del punto anterior, y enviando como argumento el objeto que se desea escribir en el archivo. Dicho objeto debe ser una instancia de alguna clase que implemente la interfaz Serializable (ver detalles más abajo). El método writeObject() arroja las siguientes excepciones:

    • InvalidClassException: existe algún error con la clase que se desea serializar.
    • NotSerializableException: el objeto a ser serializado no implementa la interfaz Serializable.
    • IOException: se produjo algún error al escribir al archivo.
  4. Cerrar el objeto de la clase ObjectOutputStream para garantizar que toda la información quede almacenada en disco. Esto se puede hacer de manera implícita si se usa la instrucción try-con-recursos, o explícitamente invocando el método close(), el cual arroja una instancia de la clase IOException en caso de detectarse algún error al momento de cerrar el archivo.

El siguiente código ejemplifica todos los puntos anteriores.

try (FileOutputStream fos = new FileOutputStream("datos.bin");
     ObjectOutputStream oos = new ObjectOutputStream(fos)) {

    // Objeto que se desea hacer persistente.
    Integer x = new Integer(5);
    oos.writeObject(x);

} catch (Exception e) {
    e.printStackTrace();    
}

Leyendo un objeto de un archivo

Para leer un objeto de un archivo se requiere:

  1. Crear una instancia de la clase FileInputStream mandando al constructor una cadena de caracteres como argumento. Dicha cadena es el nombre del archivo que se desea abrir. El constructor arroja una instancia de la clase FileNotFoundException si el archivo no existe.
  2. Crear una instancia de la clase ObjectInputStream enviando al constructor un argumento: la instancia de la clase FileInputStream creada en el punto anterior. Dicho constructor arroja una instancia de la clase StreamCurruptedException en caso de que el archivo de entrada esté corrupto o no corresponda a un archivo de objetos serializados por Java.
  3. Invocar el método readObject() sobre la instancia de la clase ObjectInputStream del punto anterior. El método devuelve el objeto leído. El método readObject() arroja las siguientes excepciones:

    • ClassNotFoundException: no se encuentra la clase de un objeto serializado.
    • InvalidClassException: existe algún error con la clase serializada.
    • StreamCurruptedException: el archivo de entrada contiene información inconsistente.
    • OptionalDataException: se encontraron datos primitivos en el archivo de entrada en lugar de objetos.
    • IOException: se produjo algún error al leer el archivo.
  4. Cerrar, de manera implícita o explícita, el objeto de la clase ObjectInputStream.

El siguiente código ejemplifica todos los puntos anteriores.

try (FileInputStream fis = new FileInputStream("datos.bin");
     ObjectInputStream ois = new ObjectInputStream(fis)) {

    // Se requiere hacer un cast porque readObject() regresa un Object.
    Integer x = (Integer) ois.readObject();

} catch (Exception e) {
    e.printStackTrace();
}

Interfaz Serializable

Para que una instancia de una clase pueda escribirse a un archivo, se requiere que la clase implemente la interfaz Serializable. Varias clases de la biblioteca estándar de Java ya lo hacen (por ejemplo: Integer, Boolean, String, etc.). Para que una nueva clase implemente la interfaz Serializable, basta indicarlo al momento de declararla. Así mismo se debe declarar en dicha clase una variable static y final de tipo long llamada serialVersionUID inicializada con el número de versión de la clase. Esto último sirve para que evitar leer accidentalmente objetos de clases con números distintos de versión.

A continuación se tiene un ejemplo:

public class MiClasePersistente implements Serializable {

    private static final long serialVersionUID = 1;

    // Resto del código...
}
NOTA IMPORTANTE: si un objeto de la clase A contiene una variable de instancia de la clase B, para serializar a A se requiere serializar también a B. Dicho de otra forma: ambas clases deben implementar la interfaz Serializable. Esto también aplica a clases anidadas.

Un Ejemplo

La clase PersistentObject.java demuestra cómo se puede integrar en un programa la lectura y escritura de un objeto persistente relativamente sencillo.

Referencias

1 Grady Booch, Object-Oriented Analysis and Design With Applications. 2nd Edition. Benjamin Cummings. Redwood City, California. 1994.

2 Richard Gabriel, Persistence in a Programming Environment, Dr. Dobb’s Journal. (No. 195, December, 1992) p. 46.