Insertar y leer clases java en una Base de Datos MySQL

De ChuWiki

He metido todos los artículos de esta wiki y de http://chuidiang.org relativos a Java y JDBC en un pdf Libro JDBC Java. A través del enlace puedes comprarlo si tienes interés.

Inserción y lectura de objetos java en base de datos[editar]

Vamos a ver un ejemplo de cómo insertar y luego recuperar una clase java completa en base de datos. Puedes ver el ejemplo completo en ObjectEnBaseDatos.java


Creación de la tabla en base de datos[editar]

En primer lugar, en nuestra base de datos, creamos una tabla que nos sirva para nuestro ejemplo. Tendrá una columna id y una columna Blob, que nos sirva para guardar nuestro objeto java.

mysql> create table objetos (int id auto_increment, primary key (id), objeto blob);

La clase java[editar]

La clase java que queremos insertar en base de datos debe ser Serializable, es decir, debe implementar la interface Serializable, tanto ella como todas las clases que sean atributos de ella. Para nuestro ejemplo, usaremos una clase DatoGordo con varios atributos, uno de ellos será a su vez otra clase Dato también Serializable y con varios atributos.

package com.chuidiang.ejemplos.mysql;

import java.io.Serializable;
import java.util.Date;

public class Dato implements Serializable {
    /** Un campo fecha */
    private Date fecha;

    /** Un campo int */
    private int valor;

    /** Un campo String */
    private String cadena;

    ...
}
package com.chuidiang.ejemplos.mysql;

import java.io.Serializable;

import javax.swing.JButton;

public class DatoGrande implements Serializable {
    /** Otra clase serializable */
    private Dato dato;

    /** Un valor double */
    private double valor;

    /** Una clase también Serializable */
    private JButton unBoton;

   ...
}


Conexión con la base de datos[editar]

La conexión con la base de datos MySQL se establece de la forma habitual, suponiendo que hay una base de datos de nombre hibernate, con usuario hibernate y password hibernate.

try {
   Class.forName("com.mysql.jdbc.Driver");
   Connection conexion = DriverManager.getConnection(
      "jdbc:mysql://localhost/hibernate", "hibernate",
      "hibernate");
   } catch (Exception e) {
      e.printStackTrace();
   }


Inserción en base de datos[editar]

Supongamos que hemos instanciado un DatoGrande, rellenado sus campos y guardado en una variable datoGrande

datoGrande = new DatoGrande();
Dato dato = new Dato();
dato.setCadena("la cadena");
dato.setFecha(new Date());
dato.setValor(33);
datoGrande.setDato(dato);
datoGrande.setUnBoton(new JButton("El botón"));
datoGrande.setValor(11.22);

para insertarlo en base de datos, lo primero que hay que hacer es convertirlo en un array de bytes. Para ello, usaremos la clase ObjectOutputStream que escribirá sobre un ByteArrayOutputStream.

ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArray);
oos.writeObject(datoGrande);

Una vez que tenemos dentro del ByteArrayOutputStream los bytes que representan la clase, no tenemos más que insertarlos en base de datos.

PreparedStatement ps = conexion.prepareStatement("insert into objetos values (null, ?)");

// Se inserta en bd
ps.setBytes(1, byteArray.toByteArray());
ps.execute();


Lectura de la base de datos[editar]

Primero hacemos la consulta y el bucle correspondiente para tratar los resultados

// La consulta y bucle para recorrer resultados
PreparedStatement ps = conexion.prepareStatement("select * from objetos");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
   ...

Ahora, en cada iteración del bucle, obtendremos el Blob de la columna objeto. De él podemos obtener un InputStream que nos da los bytes que escribimos en el paso anterior y de ahí debemos reconstruir el DatoGrande. Para ello, usaremos la clase ObjectInputStream de la siguiente forma:

while (rs.next()) {
   // Se obtiene el campo blob
   Blob blob = rs.getBlob("objeto");

   // Se reconstruye el objeto con un ObjectInputStream
   ObjectInputStream ois = new ObjectInputStream(blob.getBinaryStream());
   DatoGrande dato = (DatoGrande) ois.readObject();
}


Unas consideraciones sobre ObjectOutputStream y ObjectInputStream[editar]

Este ejemplo funciona bien si guardamos un solo dato y leemos un solo dato. Para que funcione bien para muchas inserciones y lecturas, debemos tener muy en cuenta una característica de ObjectOutputStream y ObjectInputStream.

El constructor de ObjectOutputStream escribe unos bytes de cabecera en el array de bytes. Luego, cada llamada a writeObject() escribe los bytes del objeto que pasemos. Si hacemos varias inserciones usando el mismo ObjectOutputStream, la primera inserción llevará los bytes de cabecera y el objeto, mientras que las demás sólo llevarán bytes del objeto.

El constructor de ObjectInputStream busca esos bytes de cabecera. Luego, cada readObject() leerá un objeto. Si usamos el mismo ObjectInputStream para leer varios objetos, el primero que leamos debe ser obligatoriamente el que insertamos primero y que tiene los bytes de cabecera. Esto, por supuesto, no parece muy adecuado para guardar objetos en una base de datos. De alguna forma, debemos tratar de evitar este pequeño problema de los bytes de cabecera.

La forma sencilla de evitar los bytes de cabecera es usarlos siempre. Es decir, cada vez que queramos insertar un objeto en base de datos, debemos hacer un new de un ObjectOutputStream nuevo. De esta forma, todos los objetos que insertemos en base de datos, llevarán sus bytes de cabecera.

Al leer, de la misma forma, debemos hacer un new de un ObjectInputStream nuevo cada vez. De esta forma, todas las lecturas leerán primero los bytes de cabecera y no tendremos problemas.

Esta solución de hacer un new de un ObjectOutputStream cada vez que queramos hacer un insert y un ObjectInputStream cada vez que queramos leer es una solución sencilla que funciona, aunque no es elegante y guarda bytes innecesarios en base de datos. Otra solución menos simple consiste en heredar de estas clases y sobreescribir los métodos writeStreamHeader() y readStreamHeader() para que NO escriban ni lean absolutamente nada. De esta forma, NO habrá bytes de cabecera.


Enlaces[editar]