JDBC - Mapping Objeto/Relacional
Objetivos del capítulo
-
Desarrollar aplicaciones que accedan a una base de datos relacional.
-
Desarrollar las clases necesarias para el mapping objeto-relacional (se trata de establecer una correspondencia entre las tablas de una base de datos relacional y los objetos utilizados para acceder a la base).
Definición
El API JDBC (Java DataBase Connectivity) es un conjunto de clases e interfaces Java, que permiten el acceso a los datos.
Aunque el API JDBC permita el acceso a cualquier fuente de datos, principalmente se utiliza para acceder a las bases de datos relacionales.
Comunicación Java - Base de datos
1. Controladores (drivers) JDBC
Para conectarse a una base de datos, JDBC necesita clases particulares escritas por el editor de la base de datos. Estas clases se agrupan en un controlador (driver). Es un archivo .jar (Java archivo).
El API JDBC proporciona interfaces, que los controladores deben implementar obligatoriamente.
Esto tiene dos ventajas:
-
Las interfaces exponen los métodos que podemos utilizar.
-
Es posible utilizar estos métodos sin conocer el nombre de la clase correspondiente del controlador. Este nombre es diferente según el editor básico de datos.
2. Esquema de comunicación
Trabajar con JDBC
1. Ejecución de una consulta SQL de tipo SELECT
Escribir un programa Java que acceda a una base de datos implica:
-
Cargar el controlador de la base de datos utilizada.
-
Establecer una conexión a la base. Hay que cerrar la conexión después de su utilización.
-
Crear un objeto Statement (objeto de tratamiento). Hay que cerrar el Statement después de su utilización.
-
Ejecutar los consultas SQL.
-
Si la consulta es una SELECT, devuelve un objeto ResultSet, que contiene las filas y las columnas seleccionadas. Hay que cerrar el ResultSet después de su utilización.
2. Estructura general del programa para una consulta SELECT
El «esqueleto» del siguiente programa muestra cómo cerrar con seguridad la conexión, el Statement y el ResultSet. Cada apertura o creación exitosa se sigue por un bloque try con un finally de cierre de los objetos:
Cambiar el controlador de la base de datos
Establecer una conexión a la base (objeto Connection)
try
{
Crear un objeto Statement
try
{
Ejecutar una SELECT: devuelve un objeto ResultSet
try
{
Utilizar el ResultSet
}
finally
{
Cerrar el ResultSet
}
}
finally
{
cerrar el Statement
}
}
finally
{
Cerrar la conexión
}
Cuando cierra la conexión, el Statement y el ResultSet siguen estando referenciados, pero no son utilizables. Es importante cerrar el ResultSet y el Statement para recuperar el espacio ocupado por estos objetos que ya no son útiles.
3. Ejemplo: base de datos utilizada
-
El sistema de gestión básico de datos utilizado es MySQL.
-
La base de datos se llama «gnmi».
-
El nombre de usuario y la contraseña...
Presentación de las clases herramientas
En el paquete herramientasMG.jdbc se encuentran las clases que pueden facilitar el desarrollo.
1. JuegoResultado
a. Clase JuegoResultado
El método executeQuery() de la interfaz Statement devuelve un objeto de tipo ResultSet.
Un ResultSet está unido a la conexión. Cuando se cierra la conexión, se pierde el ResultSet. Normalmente es interesante conservar los resultados leídos después de haber cerrado la conexión.
La clase JuegoResultado permite volver a copiar la información del ResultSet. A continuación podemos cerrar la conexión, intentando preservar los valores leídos.
Esta copia organiza los datos leídos en dos colecciones:
-
Una colección de objetos Columna (la clase Columna es la del paquete herramientasMG.varios).
-
Una colección de filas (Vector<Vector<Object>>).
Uno de los constructores de la clase JuegoResultado recibe como argumento un ResultSet, que vuelve a copiar las dos colecciones.
Gracias a esta organización de datos, la utilización de la clase JuegoResultado es más agradable y sencilla que la del ResultSet. ¡Pero tenemos derecho a que nos gusten los next()!
public class JuegoResultado implements java.io.Serializable
{
private Vector<Columna> columnas;
private Vector<Vector<Object>> filas;
public JuegoResultado()
{
}
public JuegoResultado(ResultSet rs) throws SQLException
{
. . .
}
. . .
public Vector<Columna> getColumnas()
{
return columnas;
}
public Vector<Vector<Object>> getFilas()
{
return filas;
}
. . .
}
b. Ejecución de una consulta SQL de tipo SELECT con JuegoResultado
public class VerificaJdbcSqlJuegoResultado
{ ...
Trabajo práctico: Proyecto GestionContactoJdbc - Versión 1
1. Objetivo
-
Utilizar JDBC para acceder a una base de datos.
-
Modificar el Modelo de la aplicación GestionContactoLocal para permitir este acceso. Adaptar el Controlador. Conservar la Vista sin modificación.
2. Arquitectura del proyecto: 2 tiers
3. Tema
Retomar la aplicación GestionContactoLocal del capítulo JTable - DAO - MVC.
-
Modificar la clase ContactoDAO para leer los datos «Contacto» en las tablas CONTACTO de la base de datos gnmi (usuario UTIL_BIP, contraseña x).
-
Si la lectura de la base de datos provoca una excepción, mostrar el mensaje de error en una caja de diálogo (JOptionPane).
-
Durante el cierre de la ventana interna contacto, no cambiar nada respecto al proyecto GestionContactoLocal. No reportar las modificaciones a la base de datos.
4. Diseño
Completar el diagrama de secuencia de la aplicación GestionContactoLocal.
5. Programación
Modificar las clases ContactoDAO y Controlador.
Todas las clases de la vista se utilizan sin modificación.
6. GestionContactoJdbc - Versión 1: propuesta de corrección
a. Diagrama de secuencia
Tratamiento de excepciones:
La lectura de datos en la base de datos puede fallar por varias razones, técnicas o de software (problemas de autorizaciones, consulta SQL incorrecta, servidor fuera de servicio, etc.). El método executeQuery() puede desencadenar una excepción de tipo SQLException.
¿A quién se debe informar de estos errores? Es al usuario de la aplicación, debido a que el controlador prevé una alternativa en respuesta a la petición del usuario. El método muestraContactos() envía la lista de los contactos y de las columnas para que se visualicen en la ventana...
JDBC: complementos
1. Tabla utilizada en los ejemplos
a. Script SQL de creación de la tablas EMPLEADO
drop tablas EMPLEADO;
create tablas EMPLEADO
(
EMPNUM smallint primary key not null,
EMPNOM varchar(20) null,
EMPSAL dec(9,2) null,
EMPDAT datetime null
);
create unique index EMPLEADO_PK on EMPLEADO(EMPNUM);
b. Script SQL de proceso de llenado de la tabla EMPLEADO
delete from empleado;
insert into empleado values(1, ‘PEDRO', NULL, ‘2016/04/25');
insert into empleado values(4, ‘Alcaudón', 2214, NULL);
insert into empleado values(10, ‘Pivert', 1811, ‘2015/11/05');
select * from empleado;
2. Ejecución de consultas INSERT y DELETE
public class VerificaInsert
{
public static void main(String args[]) throws IOException
{
. . .
try
{
Class.forName("com.mysql.jdbc.Driver");
base = new BaseDeDatos(
"jdbc:mysql://localhost/gnmi?user=util_bip&password=x");
accesoBase = new AccesoBase(base);
try
{
accesoBase.getConnection();
try
{
...
El Mapping Objeto/Relacional
1. Exposición del problema que hay que resolver
Se trata de realizar las entradas/salidas entre una base de datos relacional y los objetos ubicados en memoria.
A continuación se muestra un esquema general de las entradas/salidas (lecturas/escrituras):
-
Leer la información consiste en transferir la información del disco a una zona de memoria prevista para recibirlos. Esta zona de memoria debe ser tan grande como para contener la información leída.
-
Escribir la información consiste en volver a copiar la información contenida en la zona de memoria, en el disco duro.
En el caso de una base de datos relacional, los datos almacenados en el disco se organizan en forma de tablas. Cada tabla está formada por filas y columnas.
Ejemplo
Tabla CONTACTO
Estas tablas contienen cinco columnas: NUMERO, NOMBRE, DIRECCION, CODIGO_POSTAL Y CIUDAD.
Contiene tres filas correspondientes a los contactos: 100, 101, 102.
Leer una fila de la tabla hace necesario reservar un espacio en memoria. En el caso de un lenguaje-objeto, hay que instanciar un objeto de una clase.
El siguiente esquema UML indica que la clase Contacto tiene propiedades correspondientes a cada columna de la tabla CONTACTO: numero, nombre, dirección, codigoPostal y ciudad.
El numero, que identifica de manera única al contacto, se llama clave primaria.
A continuación se muestra el código Java de la clase Contacto:
public class Contacto
{
...
Trabajo práctico: Proyecto Mapping
1. Objetivo
Encontrar un enfoque sistemático de diseño y de escritura de las clases, necesarias para el mapping objeto/relacional.
2. Tema
-
Diseñar y escribir las clases Contacto, ContactoDAO, Sector, SectorDAO, Pago, PagoDAO necesarias para leer y escribir los objetos Java a partir de una base de datos relacional, cuya descripción se da por su MCD.
-
Podemos comenzar diseñando las clases Contacto y ContactoDAO del proyecto, y después generalizar a los sectores y pagos.
-
En la clase Contacto, se trata de leer (get) y modificar (set) todas las propiedades de la clase.
-
En la clase ContactoDAO, se trata de prever todos los accesos a la base (select, update, insert, delete) y algunos métodos de generación de las listas de objetos Contacto.
3. Documentos adjuntos
Las siguientes secciones presentan:
-
El MCD del proyecto Mapping.
-
El diagrama de clases del proyecto Mapping.
-
El controlador de una aplicación de comprobación que utiliza las clases que se han de desarrollar.
En el paquete herramientasMG.varios, existe una clase Conversion que tiene un método cadenaSQL() que permite convertir una cadena de caracteres al formato SQL (problema de comillas simples). También hay un método dateSQL().
a. Modelo Conceptual de Datos (MCD) del proyecto Mapping
-
Un sector puede tener varios contactos.
-
Un contacto pertenece a un sector y puede tener varios pagos.
-
Un pago pertenece a un contacto.
b. Diagrama de clases (UML) del proyecto Mapping
Este diagrama representa las mismas reglas de gestión que el MCD. Atención, entre Merise y UML, porque las cardinalidades están invertidas.
El modelo conceptual de datos de Merise representa las tablas de la base de datos. El diagrama UML representa los objetos en memoria. Esta diferenciación es práctica y visual. Por supuesto, es posible representar todo en UML. Pero como tenemos las dos representaciones, podemos aprovecharlas...
c. Controlador de la aplicación de verificación
public class Controlador
{
private static BaseDeDatos base;
private static AccesoBase accesoBase;
public static void main(String args[])
{
Contacto contacto;
Pago pago; ...
Trabajo práctico: Proyecto GestionContactoJdbc
1. Objetivos
-
Utilizar los paquetes de las clases desarrolladas en el TP Mapping.
-
Guardar las modificaciones en la base de datos.
2. Tema
-
Modificar la aplicación «GestionContactoJdbc - versión 1» para permitir el registro de las modificaciones de los contactos en la base de datos durante el cierre de la ventana interna de los contactos.
-
Sustituir las clases Contacto y ContactoDAO por las de los paquetes businessMapping y daoJdbcMapping.
-
El paquete businessMapping contiene las clases Contacto, Sector, Pago.
-
El paquete daoJdbcMapping contiene las clases ContactoDAO, SectorDAO y PagoDAO.
-
Transmitir la lista de los Sectores a la ventana interna de los contactos para adaptar la lista desplegable de introducción de datos del código de sector.
3. GestionContactoJdbc: Proposición de corrección
a. Clase Controlador
public class Controlador
{
. . .
public static void main(String args[])
{
. . .
contactoDAO = new ContactoDAO(accesoBase);
sectorDAO = new SectorDAO(accesoBase);
. . .
}
public static void solicitaContactos()
{
Vector<Contacto> listaContactos;
Vector<Columna> listaColumnas;
Vector<Sector> listaSectores;
try
{
accesoBase.getConnection();
try
{
listaContactos = contactoDAO.leerLista();
listaColumnas = contactoDAO.getListaColumnas();
...