0% encontró este documento útil (0 votos)
8 vistas58 páginas

Programación Eficiente con SQL y JDBC

El documento es un recurso de aprendizaje sobre programación mediante SQL, escrito por David Fíguls i Massot, que abarca técnicas para operar con bases de datos relacionales utilizando SQL programado. Se enfoca en el uso de JDBC y otros métodos de acceso a bases de datos, así como en la importancia de una comunicación eficiente entre aplicaciones y bases de datos. El módulo incluye objetivos de aprendizaje, conceptos comunes, y ejemplos prácticos relacionados con la gestión de datos en aplicaciones.
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
8 vistas58 páginas

Programación Eficiente con SQL y JDBC

El documento es un recurso de aprendizaje sobre programación mediante SQL, escrito por David Fíguls i Massot, que abarca técnicas para operar con bases de datos relacionales utilizando SQL programado. Se enfoca en el uso de JDBC y otros métodos de acceso a bases de datos, así como en la importancia de una comunicación eficiente entre aplicaciones y bases de datos. El módulo incluye objetivos de aprendizaje, conceptos comunes, y ejemplos prácticos relacionados con la gestión de datos en aplicaciones.
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Programación

mediante SQL
PID_00275642

David Fíguls i Massot

Tiempo mínimo de dedicación recomendado: 5 horas


© FUOC • PID_00275642 Programación mediante SQL

David Fíguls i Massot

Licenciado en Informática, profesor


asociado del Departamento de In-
formática y Matemática Aplicada
de la Universidad de Girona desde
1995, donde ha impartido asignatu-
ras de iniciación a la programación,
estructuras de datos, ingeniería del
software, bases de datos e informá-
tica gráfica; desde 1998 es consul-
tor de los Estudios de Informática y
Multimedia de la UOC. Ha realizado
trabajos de investigación en el ám-
bito de la informática gráfica y par-
ticipado en artículos y proyectos na-
cionales e internacionales. Desde el
2002 es profesor de formación pro-
fesional en el Área de Informática.

La revisión de este recurso de aprendizaje UOC ha sido coordinada


por la profesora: Cristina Pérez Solà

Segunda edición: septiembre 2020


© de esta edición, Fundació Universitat Oberta de Catalunya (FUOC)
Av. Tibidabo, 39-43, 08035 Barcelona
Autoría: David Fíguls i Massot
Producción: FUOC
Todos los derechos reservados

Ninguna parte de esta publicación, incluido el diseño general y la cubierta, puede ser copiada,
reproducida, almacenada o transmitida de ninguna forma, ni por ningún medio, sea este eléctrico,
mecánico, óptico, grabación, fotocopia, o cualquier otro, sin la previa autorización escrita
del titular de los derechos.
© FUOC • PID_00275642 Programación mediante SQL

Índice

Introducción............................................................................................... 5

Objetivos....................................................................................................... 6

1. Necesidad de SQL en las aplicaciones........................................... 7


1.1. Bibliotecas de acceso a BD .......................................................... 7
1.2. BD de referencia .......................................................................... 8
1.3. Conceptos comunes .................................................................... 8
1.4. Tecnologías de acceso a BD ........................................................ 11
1.4.1. SQL hospedado .............................................................. 11
1.4.2. SQL/CLI .......................................................................... 13
1.4.3. ODBC ............................................................................. 14
1.4.4. OLE DB .......................................................................... 14
1.4.5. DAO / RDO / ADO / [Link] ..................................... 14
1.4.6. El JDBC .......................................................................... 15
1.5. Comparativa ................................................................................ 15
1.5.1. Nivel de programación .................................................. 15
1.5.2. Flexibilidad y eficiencia de sentencias SQL ................... 15
1.5.3. Portabilidad en SGBD .................................................... 16
1.5.4. Lenguajes de programación soportados ........................ 16

2. API JDBC y drivers............................................................................. 18


2.1. Drivers JDBC ................................................................................ 19
2.2. Tipos de drivers............................................................................. 20
2.2.1. Puente JDBC-ODBC (tipo 1) .......................................... 20
2.2.2. Puente JDBC-libSGBD (tipo 2) ...................................... 21
2.2.3. Directo JDBC-SGBD (tipo 4) .......................................... 22
2.2.4. JDBC-intermediario (tipo 3) .......................................... 22
2.3. Comparativa ................................................................................ 23

3. Programación con JDBC.................................................................. 26


3.1. Conexión a una BD .................................................................... 26
3.2. Consultas y modificaciones básicas ............................................ 28
3.2.1. Ejecución de sentencias SQL: la clase Statement......... 29
3.2.2. Tratamiento de resultados: la clase ResultSet............. 31
3.3. Consultas y modificaciones avanzadas ....................................... 32
3.3.1. ResultSet con libertad de movimientos ..................... 33
3.3.2. Modificaciones por medio de ResultSet..................... 34
3.4. Consultas y modificaciones con PreparedStatement.............. 36
3.5. Consideraciones adicionales sobre consultas y
modificaciones ............................................................................ 37
© FUOC • PID_00275642 Programación mediante SQL

3.5.1. Tratamiento de valores nulos ........................................ 37


3.5.2. Combinaciones de tablas ............................................... 39
3.5.3. Consultas anidadas ........................................................ 40
3.5.4. Consultas recursivas ...................................................... 41
3.5.5. Claves primarias autogeneradas .................................... 42
3.5.6. Inyección SQL (SQL injection) ........................................ 44
3.6. Procedimientos almacenados ...................................................... 45
3.6.1. Parámetros de entrada ................................................... 46
3.6.2. Parámetros de salida ...................................................... 46
3.6.3. Parámetros de entrada y salida ...................................... 46
3.7. Gestión de errores ....................................................................... 46
3.7.1. Tipos de errores y cómo se reportan ............................. 47
3.7.2. Obtención de información sobre un SQLException ...... 47
3.7.3. Gestión del error desde nuestros programas ................. 48
3.8. Gestión de transacciones ............................................................ 52
3.8.1. Duración de una transacción ........................................ 53

Resumen....................................................................................................... 54

Actividades.................................................................................................. 55

Glosario........................................................................................................ 56

Bibliografía................................................................................................. 57
© FUOC • PID_00275642 5 Programación mediante SQL

Introducción

En este módulo estudiaremos diversas técnicas para operar con bases de datos
(BD) relacionales desde nuestras aplicaciones. Es lo que se denomina SQL pro-
gramado o SQL inmerso.

Hemos visto la manera de interaccionar con una BD mediante la creación y


la ejecución de sentencias SQL. Este método de trabajo se conoce como SQL
interactivo o SQL directo y es utilizado principalmente por los administradores
de BD.

La mayor parte de los usuarios de una BD no tienen conocimientos de SQL y


acceden a ella mediante aplicaciones desarrolladas a medida. Estas aplicacio-
nes contienen sentencias SQL que en tiempo de ejecución se envían a la BD,
se procesan y, si hay resultados, se devuelven a la aplicación. Este segundo
método de trabajo se conoce como SQL programado.

La comunicación entre la aplicación y la BD, lejos de ser irrelevante, a menudo


condiciona de lleno el rendimiento general de la aplicación. Una utilización
incorrecta de las técnicas de SQL programado puede enlentecer innecesaria-
mente o incluso bloquear la mejor BD y las aplicaciones que dependen de ella.
Así, el objetivo final del módulo es aportar los conocimientos necesarios para
conseguir una comunicación eficiente entre la aplicación y la BD.

Podemos utilizar SQL programado desde un amplio abanico de lenguajes de


programación y sistemas gestores de bases de datos (SGBD). En este módulo
presentaremos las soluciones más habituales y nos centraremos en el lenguaje
de programación Java y en SGBD PostgreSQL.
© FUOC • PID_00275642 6 Programación mediante SQL

Objetivos

Los materiales didácticos que integran este módulo permitirán al estudiante


alcanzar los objetivos siguientes:

1. Conocer las técnicas principales de SQL programado: SQL hospedado,


SQL/CLI, ODBC, OLE DB, ADO y JDBC.

2. Comprender las diferencias más importantes que hay entre las diversas
técnicas de SQL programado y las ventajas y los inconvenientes principales
de cada una.

3. Aprender las diversas estrategias de trabajo que nos ofrece JDBC.

4. Combinar correctamente los mecanismos que nos ofrece JDBC para desa-
rrollar aplicaciones que operen eficientemente con una BD.
© FUOC • PID_00275642 7 Programación mediante SQL

1. Necesidad de SQL en las aplicaciones

Todas las aplicaciones manipulan datos mediante variables de tipos básicos


o estructuras de datos más o menos complejas. Al final de la ejecución de
la aplicación, todos estos datos se borran si no es que utilizamos algún tipo
de almacenaje permanente. En algunos casos basta con guardar los datos en
ficheros, pero mayoritariamente, habrá que utilizar una BD.

Si el volumen de datos que manipula la aplicación es bastante elevado, la utili-


zación de ficheros es contraproducente, ya que enlentece la ejecución o com-
plica la programación. Este motivo ya nos bastaría para optar por una BD, si
bien no es el único.

Cuando enlazamos una aplicación con una BD, podemos aprovechar cual-
quiera de las funcionalidades propias de los SGBD que hemos ido estudiando
a lo largo del curso.

El punto clave es la elección de un lenguaje de programación y de un SGBD


que hagan posible la interacción entre la aplicación y la BD. Afortunadamente,
podemos encontrar un amplio abanico de soluciones que tienen un rasgo en
común: utilizan SQL para comunicar la aplicación con la BD (de ahí el nombre
de SQL programado).

A lo largo de este módulo haremos un repaso de las soluciones principales:

• SQL hospedado (en C y Java).


• SQL/CLI.
• ODBC.
• OLE DB.
• ADO.
• JDBC.

1.1. Bibliotecas de acceso a BD

Todas las soluciones tendrán como elemento común la utilización de una bi-
blioteca encargada de enlazar las aplicaciones con las BD. SQL hospedado es
la solución que más esconde la utilización de una biblioteca, pero en último
término, hace llamadas a funciones de una biblioteca utilizando una sintaxis
diferente.
© FUOC • PID_00275642 8 Programación mediante SQL

Un objetivo básico de todas las soluciones es la portabilidad, es decir, la posi-


bilidad de llevar el código de un SGBD a otro con el mínimo de cambios. Con
este fin y a lo largo del tiempo, se han ido definiendo diferentes estándares.
Paralelamente, los fabricantes de SGBD han ido publicando bibliotecas para
acceder a sus BD siguiendo los diversos estándares.

Todos los estándares permiten hacer prácticamente lo mismo. La diferencia no


está en lo que permiten hacer, sino más bien en la manera de llevarlo a cabo.
La evolución que han hecho ha estado en la línea de simplificar el trabajo de
programación.

1.2. BD de referencia

A lo largo del módulo, iremos viendo diversos ejemplos de programas que


utilizan una BD para gestionar los mensajes de diferentes fórums o foros. La
BD tendrá las tablas siguientes:

a)�Usuarios. En ella guardaremos los datos de los diferentes usuarios (colum-


nas: nombre de usuario –clave primaria–, contraseña, nombre y apellidos).

b)�Fórums. Será el punto de entrada de cada fórum (columnas: código fórum


–clave primaria– y nombre).

c)�Mensaje. En ella guardaremos cada uno de los mensajes enviados (colum-


nas: código mensaje –clave primaria–, código fórum –que identifica el fórum
al que pertenece el mensaje–, autor –que identifica al usuario que ha escrito el
mensaje–, título, texto e hilo –que identifica el mensaje al que da respuesta–).

d)�Lecturas. Contendrá un registro de las lecturas que los usuarios hacen de los
mensajes (columnas: código mensaje, nombre de usuario –estas dos primeras
columnas definirán la clave–, fecha y hora).

USUARIOS (nombre_usuario, contrasena, nombre, apellidos)


FORUMS (codigo_forum, nombre)
MENSAJES (codigo_mensaje, codigo_forum, autor, titulo, texto, hilo)
{codigo_forum} es clave foránea que referencia FORUMS(codigo_forum)
{autor} es clave foránea que referencia USUARIOS(nombre_usuario)
{hilo} es clave foránea que referencia MENSAJES(codigo_mensaje)
LECTURAS (codigo_mensaje, nombre_usuario, fecha_hora)
{codigo_mensaje} es clave foránea que referencia MENSAJES(codigo_mensaje)
{nombre_usuario} es clave foránea que referencia USUARIOS(nombre_usuario)

1.3. Conceptos comunes

A pesar de la ausencia de una solución única de SQL programado, todas las


alternativas tienen un común divisor, que estudiamos a continuación.
© FUOC • PID_00275642 9 Programación mediante SQL

1)�Conexión�y�desconexión

Partiendo de la base de que las aplicaciones acceden a una BD para hacer unas
cuantas operaciones y no una sola, todas las soluciones optan por separar la
conexión o la desconexión de las operaciones. La razón es amortizar el tiempo
que se tarda en ponerse en contacto con la BD entre las diversas operaciones.
Hemos de tener en cuenta que este tiempo puede ser significativo, especial-
mente si la BD está en una máquina diferente de la de la aplicación.

La estrategia habitual para programar aplicaciones ha sido, durante mucho


tiempo, iniciar una conexión al arrancar la aplicación cliente y mantenerla
hasta el final.

Con la aparición de las aplicaciones web, esta estrategia ha cambiado radi-


calmente. El número de usuarios (clientes) que pueden ejecutar la aplicación
simultáneamente hace inviable mantener una conexión independiente para
cada uno.

Teniendo presente que un usuario sólo utiliza la conexión puntualmente en


el momento de solicitar una página, podemos pensar en dos soluciones:

• Reaprovechar conexiones entre clientes diferentes.


• Abrir y cerrar conexiones en cada página.

De las dos, la segunda es más simple pero menos eficiente a causa de la len-
titud de la conexión. En este sentido, en los últimos años han salido biblio-
tecas que permiten hacer conexiones más ligeras y rápidas, optimizadas para
aplicaciones web.

Sea como fuere, durante la conexión a una BD hay que especificar unos cuan-
tos parámetros, entre los cuales los más importantes son:

• Anfitrión (host) en el que localizar el SGBD.


• Puerto (port) en el que escucha.
• nombre de la BD a la que nos queremos conectar (recordemos que un
SGBD puede gestionar más de una BD).
• Nombre de usuario y contraseña con los que nos queremos conectar.

2)�Sentencias�SQL�estáticas�o�dinámicas

Una vez conectados a la BD, podemos interaccionar con ella mediante senten-
cias SQL. Antes de la ejecución de una sentencia SQL, el SGBD ha de analizar
su texto, verificar que es sintácticamente correcto, transformarlo, optimizarlo,
etc. Según el momento en el que se realiza este proceso, distinguimos entre:
© FUOC • PID_00275642 10 Programación mediante SQL

• SQL�estático. Efectuamos este proceso en tiempo de compilación. En prin-


cipio, es una estrategia más rápida, pero más rígida, ya que hemos de tener
definidas todas las sentencias que ejecutará la aplicación.

• SQL�dinámico. Llevamos a cabo este proceso en tiempo de ejecución, jus-


to en el momento de efectuar cada una de las sentencias SQL que contie-
ne la aplicación. En principio, esta estrategia enlentece la ejecución de la
aplicación, pero permite la creación de sentencias SQL en tiempo de eje-
cución.

3)�Sentencias�SQL,�parámetros�y�variables

Incluso en SQL estático, la mayor parte de las sentencias tienen una parte va-
riable que se define en tiempo de ejecución. Nos referimos a valores de colum-
nas, utilizados en sentencias de modificación o de consulta, que no impiden
que el análisis de la sentencia se haga en tiempo de compilación. Por ejemplo,
en nuestra BD de referencia:

• Para añadir un nuevo mensaje, hemos de hacer INSERT e indicar los valo-
res de las columnas. Una aplicación pediría los datos al usuario, los alma-
cenaría temporalmente en variables y, de alguna manera, estas variables
se vincularían con la sentencia SQL.

• Para obtener los mensajes de un fórum, hemos de hacer SELECT e indicar,


en la cláusula WHERE, el código del fórum del que queremos los mensajes.
Una aplicación pediría al usuario el código del fórum, lo almacenaría en
una variable y se vincularía con la sentencia SQL.

4)�Sentencias�SQL�con�resultados,�iteraciones�y�variables

Las sentencias SQL consultoras (SELECT ) devuelven resultados que, de alguna


manera, se han de almacenar en variables para que la aplicación los pueda
tratar posteriormente. Estos resultados pueden ser un valor, un conjunto de
valores (correspondientes a una fila de una sentencia SELECT ) o un conjunto
de filas (todos los datos provenientes de una sentencia SELECT ).

A menudo, las sentencias que devuelven un conjunto de filas se tratan de


una manera iterativa, accediendo a los valores de una fila en cada iteración.
Con este fin tendremos operaciones para consultar los valores de la fila actual,
avanzar a la fila siguiente y detectar el final. Los SGBD ofrecen una estructura
de control para poder hacer efectivos estos recorridos. Es lo que se conoce con
el nombre de cursor.

5)�Transacciones
© FUOC • PID_00275642 11 Programación mediante SQL

Podemos definir transacciones agrupando diferentes sentencias SQL que, con-


ceptualmente, constituyan una unidad indivisible de ejecución. Las operacio-
nes disponibles son las habituales: iniciar transacción, confirmar transacción
o deshacer transacción.

6)�Tratamiento�de�errores

Todas las soluciones ofrecen alguna vía para capturar los errores que se pueden
producir al ejecutar una sentencia SQL. La manera de hacerlo, sin embargo,
cambia mucho dependiendo de la solución que escojamos.

1.4. Tecnologías de acceso a BD

A continuación, presentamos las soluciones del SQL programado más habi-


tuales: el SQL hospedado, el SQL/CLI, el ODBC, el OLE DB, el ADO y el JDBC.
Entre todas éstas, el SQL hospedado es la más antigua y la que se desmarca
más de las otras porque:

• Utiliza una sintaxis propia, alejada de la sintaxis de los lenguajes de pro-


gramación. Las otras usan llamadas a funciones.
• No permite la ejecución de sentencias SQL dinámicas. Las otras sí.
• Utiliza un tipo de variables especiales para comunicarse con la BD. Las
otras usan los tipos de variables habituales que ofrecen los lenguajes de
programación.

1.4.1. SQL hospedado

Antes de la aparición de SQL hospedado, la comunicación con BD se tenía


que hacer por medio de funciones de bibliotecas suministradas por cada uno
de los fabricantes de SGBD. El trabajo directo con estas bibliotecas no era lo
bastante ágil y motivó la aparición de SQL hospedado con el fin de:

• Simplificar el envío y la recepción de datos de las sentencias SQL.


• Verificar sintácticamente las sentencias SQL en tiempo de compilación.
• Permitir la portabilidad del código entre diferentes SGBD (con este fin, el
SQL hospedado fue incluido dentro del estándar SQL).

La característica más visible de SQL hospedado es la introducción de sentencias


SQL en medio del código de la aplicación, que utilizan una sintaxis al margen
de la propia del lenguaje de programación anfitrión. Este código híbrido no
puede estar compilado normalmente y requerirá un tratamiento previo: una
precompilación.

Ejemplo

Veamos un ejemplo, escrito en el lenguaje de programación anfitrión C, que abre y cierra


una conexión en la base de datos de referencia (llamada bdMail ) con el usuario jmarti
y la contraseña 1234.
© FUOC • PID_00275642 12 Programación mediante SQL

int main() {
EXEC SQL CONNECT TO bdMail USER jmarti/1234;
EXEC SQL DISCONNECT;
}

Durante la precompilación, se analizan las sentencias SQL y se sustituyen por


llamadas a funciones de una biblioteca encargadas del acceso a la BD. A partir
de ese momento, el código ya se puede compilar normalmente. El resultado
de la precompilación del ejemplo anterior sería:

/* Processed by ecpg (4.5.0) */


/* These include files are added by the preprocessor */
#include <ecpglib.h>
#include <ecpgerrno.h>
#include <sqlca.h>
/* End of automatic include section */

#line 1 "[Link]"
int main() {
{
ECPGconnect(__LINE__, 0, "bdMail" , "jMarti" ,
"1234" , NULL, 0);
}
#line 3 "[Link]"
{ ECPGdisconnect(__LINE__, "CURRENT");}
#line 7 "[Link]"
}

Como las sentencias SQL se analizan en tiempo de compilación, el SQL hospe-


dado sólo permite el SQL estático (aunque en alguna versión es posible cierto
grado de dinamismo).

Aunque la técnica de SQL hospedado se podría aplicar a cualquier lenguaje


de programación, en la práctica, la elección está muy restringida. Es necesario
que el SGBD ofrezca una biblioteca y un precompilador para el lenguaje de
programación que nos interese. A continuación veremos dos casos concretos.

1)�El�SQL�hospedado�en�C. El lenguaje más habitual para utilizar SQL hos-


pedado es C. Puede sorprender que el lenguaje escogido sea C, en vez de un
lenguaje más actual, pero no olvidemos que C es el lenguaje con el que se
codifican los principales sistemas operativos y SGBD.

2)�SQL�hospedado�en�Java:�SQLJ. Java ofrece la posibilidad de acceder a una


BD mediante SQL estático (llamado SQLJ). A pesar de todo, pocos SGBD ofre-
cen el precompilador para hacerlo posible.

Ejemplo
Nota
Veamos un ejemplo que muestra los mensajes de un usuario. Está escrito en SQL hospe-
dado en C y, de entrada, podemos apreciar cómo se combinan las líneas de C y de SQL. El código del ejemplo es-
Para recorrer los mensajes de un usuario utilizamos un cursor que nos permite movernos tá disponible en el fiche-
ro ex002SelectMensajes
a la fila siguiente y acceder a los valores de sus columnas (lo que conseguimos con la
[Link].
operación FETCH...INTO ). El vínculo entre las sentencias SQL y el código C son las
variables puente, declaradas en la sección DECLARE SECTION y, en el ejemplo, asignadas
cada vez que hacemos un FETCH...INTO (fijémonos en los dos puntos ":" que preceden a
las variables puente). Finalmente, para detectar el final del recorrido, hemos de consultar
© FUOC • PID_00275642 13 Programación mediante SQL

si hay algún error ([Link] ). Cuando el error nos indica que no hay más filas
por leer (error con código "02000"), acabamos el recorrido.

EXEC SQL WHENEVER SQLERROR SQLPRINT;


EXEC SQL WHENEVER SQLWARNING SQLPRINT;
//Declaracion de variables puente
EXEC SQL BEGIN DECLARE SECTION;
char usuario[20];
char password[20];
int codigo_mensaje;
char titulo[40];
char texto[250];
char autor[10];
EXEC SQL END DECLARE SECTION;

//Conectamos con la base de datos de referencia


//con usuario y contraseña.
printf("Usuario:"); scanf("%s",usuario);
printf("Password:"); scanf("%s",password);
EXEC SQL CONNECT TO bdMail@localhost
USER :usuario USING :password;

//cursor para poder leer las filas de la consulta


printf("Autor:"); scanf("%s",autor);
EXEC SQL DECLARE cMensajes CURSOR FOR
SELECT codigo_mensaje,título,texto FROM Mensajes
WHERE autor=:autor;

EXEC SQL OPEN cMensajes;


//Obtenemos la primera fila de la consulta
EXEC SQL FETCH cMensajes
INTO :codigo_mensaje, :titulo, :texto;
bool final=strcmp([Link],"02000")==0;

while (!final) {
//Mostramos las variables puente
printf("%d -- %s -- %s\n",codigo_mensaje,titulo,texto);

//Obtenemos la fila siguiente


EXEC SQL FETCH cMensajes
INTO :codigo_mensaje, :titulo, :texto;
final=strcmp([Link],"02000")==0;
}
printf("final\n");
EXEC SQL CLOSE cMensajes;
EXEC SQL DISCONNECT;

1.4.2. SQL/CLI

El grupo formado por X/Open y SQL Access Group (SAG) desarrolló la especi-
ficación de una interfaz para permitir SQL programado denominada Call Le-
vel Interface (CLI). Tenían como objetivo incrementar la portabilidad de apli-
caciones gracias a la definición de una interfaz a partir de la que se pudiera
acceder a cualquier SGBD. La mayor parte de la especificación de la interfaz
fue añadida al estándar SQL:1992 en el año 1995.
© FUOC • PID_00275642 14 Programación mediante SQL

1.4.3. ODBC

Microsoft desarrolló la interfaz Open Database Connectivity (ODBC) basán-


dose en una versión preliminar de la interfaz SQL/CLI. Aunque al principio
ODBC fue desarrollado para el sistema operativo de Microsoft, posteriormente
se ha extendido a otros sistemas operativos.

La aportación de ODBC fue la definición del entorno que permite cargar di-
námicamente los drivers de un SGBD concreto a partir del nombre de la BD.

Esto da paso a la creación de aplicaciones y su compilación sin asociarlas a


ningún SGBD concreto, sólo al nombre de una BD. En tiempo de ejecución,
el driver manager tiene la función de cargar el driver necesario para acceder a
la BD y asociarlo a la aplicación.

1.4.4. OLE DB

La segunda interfaz de acceso a datos propuesta por Microsoft es Object Lin-


king and Embedding Database (OLE DB). Mientras que ODBC permitía el ac-
ceso a BD relacionales, OLE DB está diseñado para acceder a cualquier origen
de datos, como BD orientadas a objetos, hojas de cálculo, correo, etc.

Podemos encontrar drivers OLE DB, llamados proveedores, para acceder a un


amplio abanico de orígenes de datos.

1.4.5. DAO / RDO / ADO / [Link]

Después de la definición de las interfaces ODBC y OLE DB, Microsoft ha ido


publicando diferentes bibliotecas orientadas a objetos. La finalidad de estas
bibliotecas es simplificar la utilización de las interfaces, desde el antiguo Vi-
sualBasic (lo que fue el lenguaje de programación estrella de Microsoft) y, ac-
tualmente, desde .NET.

Inicialmente apareció DAO (Data Access Objects), que permitía acceder a BD


Access o, por medio de ODBC, a cualquier otro SGBD. Era una interfaz simple
y orientada a aplicaciones pequeñas.

La segunda biblioteca fue RDO (Remote Data Objects), que permitía acceder
a prácticamente todas las funciones de ODBC, de manera que aumentaba el
potencial de DAO. Iba orientada a aplicaciones grandes.

Más reciente es la biblioteca ADO (ActiveX Data Objects), que simplifica el


modelo de objetos de las anteriores y amplía el acceso a datos por medio de
OLE DB. La última biblioteca de Microsoft es [Link], que sería la versión
de ADO para ser utilizada desde lenguajes de programación .NET.
© FUOC • PID_00275642 15 Programación mediante SQL

1.4.6. El JDBC

El lenguaje de programación Java utiliza la biblioteca llamada Java Database


Connectivity (JDBC) para acceder a datos. Por medio de JDBC podemos acce-
der a BD y otros orígenes de datos, como hojas de cálculo, ficheros de texto,
etc. JDBC sería el equivalente a [Link] en el entorno Java y, por lo tanto,
será nuestro centro de interés a lo largo de este módulo didáctico.

1.5. Comparativa

Ante todas estas alternativas, hay que hacer un punto y aparte para reflexionar
sobre las ventajas y los inconvenientes de cada solución y, sobre todo, para
sacar conclusiones que nos guíen en la elección de la solución óptima en cada
situación. Enfocaremos la comparativa desde perspectivas diferentes:

• Nivel de programación.
• Flexibilidad y eficiencia de sentencias SQL.
• Portabilidad.
• Lenguajes de programación soportados.

1.5.1. Nivel de programación

Las diferentes soluciones expuestas utilizan técnicas de programación clara-


mente diferenciadas:

• SQL�hospedado: usa una sintaxis diferente y requiere un precompilador


que ofrecen diversos SGBD para algunos lenguajes de programación.

• Bibliotecas�de�funciones: SQL/CLI, ODBC y OLE DB contienen funciones


simples y, por lo tanto, eficientes de ejecutar, pero de programación pesa-
da. Normalmente se utilizan desde lenguaje C.

• Bibliotecas�orientadas�a�objetos: DAO, RDO, ADO y JDBC son bibliotecas


que están por encima de las anteriores y que simplifican su utilización.
Están orientadas a objetos, con funciones de nivel superior, y permiten
una programación más agradable, aunque pueden no ser tan eficientes
como las anteriores.

1.5.2. Flexibilidad y eficiencia de sentencias SQL

SQL hospedado es la única solución que permite SQL estático. El resto de solu-
ciones permiten SQL dinámico, pero pueden simular el SQL estático mediante
procedimientos almacenados.
© FUOC • PID_00275642 16 Programación mediante SQL

Simplificando la explicación, podemos considerar que SQL hospedado es la


solución menos flexible, pero la más eficiente. La realidad es más compleja y,
en algunos casos, utilizando SQL dinámico se puede conseguir una eficiencia
similar a la de SQL estático.

1.5.3. Portabilidad en SGBD

Aunque todas las soluciones que hemos visto tienen por objeto la portabilidad,
ésta no siempre se consigue de la misma manera.

Tanto SQL hospedado como SQL/CLI han de ser compilados con la biblioteca
de acceso al SGBD. A pesar de que podemos encontrar bibliotecas para la gran
mayoría de SGBD, debemos hacer la elección en el momento de compilar la
aplicación y, por lo tanto, antes de distribuirla. Cambiar de SGBD comportaría
la recompilación de la aplicación.

A partir de ODBC, todas las soluciones se compilan utilizando una interfaz. En


tiempo de ejecución se cargan los drivers adecuados según lo que determine la
aplicación, y esto a menudo queda definido en un fichero de configuración
de la aplicación. Por lo tanto, para cambiar el SGBD, sólo hay que modificar
el fichero de configuración de la aplicación, sin recompilar nada.

En realidad, ninguna de las soluciones garantiza una portabilidad total. Las


aplicaciones a menudo utilizan extensiones SQL propias de un SGBD que son
incompatibles con otros SGBD.

Adicionalmente, para ejecutar aplicaciones desarrolladas con ODBC u otras


posteriores, hay que tener instalados los drivers para el SGBD que escojamos.
Estos drivers han de estar instalados en todos los ordenadores en los que se
haya de ejecutar la aplicación, lo que condiciona su distribución.

1.5.4. Lenguajes de programación soportados

El SQL hospedado se podría aplicar a cualquier lenguaje, pero necesita un pre-


compilador específico para cada uno. En la práctica, son pocos los lenguajes
que ofrecen SQL hospedado, pero la aparición de SQLJ demuestra que esta so-
lución, a pesar de sus años de existencia, continúa estando vigente.

Las soluciones basadas en bibliotecas pueden ser utilizadas desde cualquier


lenguaje de programación capaz de enlazarse con la biblioteca. Todo es posible,
pero en la práctica algunas combinaciones son complicadas.

Entre otras, algunas de las combinaciones más habituales son las siguientes:

• SQL/CLI, ODBC, OLE DB: C, C++.


• DAO, RDO, ADO: VBasic 6.0.
• [Link]: .NET.
© FUOC • PID_00275642 17 Programación mediante SQL

• JDBC, SQLJ: Java.

Como las aplicaciones que necesitan acceso a BD son mayoritariamente apli-


caciones de gestión, las combinaciones más frecuentes son las dos últimas. Los
lenguajes Java y .NET son las opciones más interesantes para desarrollar tanto
aplicaciones de gestión tradicionales como aplicaciones web.
© FUOC • PID_00275642 18 Programación mediante SQL

2. API JDBC y drivers

En este apartado iniciamos el tema principal de este módulo didáctico hacien-


do una introducción a la interfaz de programación de aplicaciones (application
program interface) de JDBC (API JDBC), la biblioteca de clases e interfaces que
nos permitirá acceder a BD desde aplicaciones escritas en Java.

Para acceder a BD, JDBC sigue el mismo patrón utilizado en todas las solucio-
nes vistas en el apartado anterior:

• Permite que nos conectemos a una BD y que, mediante sentencias SQL,


consultemos su contenido o lo modifiquemos.
• Define una interfaz común a todos los SGBD, tal como proponía SQL/CLI.
• Permite escoger el SGBD en tiempo de ejecución (no en tiempo de com-
pilación), siguiendo la propuesta de ODBC.

En el momento de escribir este material, la especificación actual del API JDBC


es la 4.0. A pesar de esto, los conceptos básicos para programar utilizando JDBC
ya estaban definidos en la versión 1.0 y se han mantenido a lo largo de las
versiones. Así pues, este material está basado principalmente en la versión 1.0
de JDBC, y puntualmente en cambios introducidos en versiones posteriores.

Veamos ahora un resumen de las versiones de JDBC y los cambios que han
ido introduciendo:

1)�JDBC�1.0�(JDK�1.1). Definido en el package [Link], contiene las siguien-


tes clases principales: DriverManager y Driver (para gestionar los drivers),
Connection (un objeto para cada una de las BD con las que conectemos),
Statement (un objeto para definir cada una de las sentencias SQL que quera-
mos ejecutar en la BD) y ResultSet (que recibe los resultados de una consulta
y ofrece métodos para poderlos consultar con desplazamientos de cursor hacia
delante). Asimismo, define los tipos de datos (enteros, reales, cadenas, binario
y fecha y hora) que se pueden pasar entre la aplicación y la BD.

También incluye otras clases, como PreparedStatement (una alternativa pa-


ra ejecutar sentencias SQL), definición de transacciones, StoredProcedures
(ejecución de procedimientos almacenados) y SQLException y SQLWarning
(para el tratamiento de errores).
© FUOC • PID_00275642 19 Programación mediante SQL

2)�JDBC�2.0�(JDK�1.2). Introduce mejoras en la clase ResultSet. Se amplían


los tipos de desplazamiento de cursor y se permiten también desplazamientos
hacia atrás y a una posición concreta. Asimismo, ofrece la posibilidad de hacer
actualizaciones de la fila apuntada por el cursor mediante métodos (sin utilizar
sentencias SQL).

Además, permite hacer actualizaciones en batch en la BD e incorpora los tipos


de datos previstos en el SQL:1999 (BLOB, CLOB, ARRAY...).

Incorpora un nuevo package de extensión estándar llamado [Link]. Con-


tiene la clase RowSet (variante de ResultSet para integrar en componentes
JavaBeans ), Java Naming Directory Interface (JNDI, que permite que nos
conectemos a una BD a partir de un nombre, sin tener que codificar ni el driver
ni la URL de la BD), pool de conexiones (conjuntos de conexiones permanen-
temente abiertas que se comparten entre diferentes clientes) y transacciones
distribuidas.

3)�JDBC�3.0�(JDK�1.4). Implementa el estándar SQL:1999, posibilita el acce-


so a metadatos de la BD, amplía los métodos para tipos de datos SQL:1999,
incorpora nuevos tipos de datos (DATALINK y BOOLEAN ), facilita la consulta
de columnas autoincrementadas después de hacer una inserción, permite ob-
tener múltiples ResultSets para cada Statement, mejora los pools de cone-
xiones, incorpora pools de PreparedStatement y permite transacciones con
savePoints (puntos de recuperación intermedios).

4)�JDBC�4.0�(JDK�1.6). Simplifica la carga de drivers, mejora los ResultSets JDBC 3.0 y JDBC 4.0
(RowSet, WebRowSet ), incorpora novedades SQL:2003 (NCHAR, NVARCHAR,
A modo de ejemplo, mientras
LONGNVARCHAR, NCLOB ) para poder trabajar con textos de cualquier alfabeto, se elabora este material, el dri-
da apoyo para excepciones encadenadas y facilita el trabajo con tipos de datos ver de PostgreSQL ofrece una
implementación razonable del
XML. JDBC 3.0 y alguna extensión
propia. La versión JDBC 4.0
también se ofrece, pero no es
completa.
2.1. Drivers JDBC

El API JDBC está formado por dos packages, [Link] y [Link]. Estos
packages contienen las clases y las interfaces necesarias para acceder a diferen-
tes orígenes de datos, pero no son completos. Faltan las clases que implemen-
tan las interfaces JDBC para acceder a cada uno de los SGBD con los que que-
ramos trabajar. Es lo que denominamos drivers JDBC. Entre otras, en el package
[Link] se define la interfaz Driver, que es la interfaz principal que un
driver ha de implementar.

Los drivers se deberán instalar en todos los equipos en los que queramos eje-
cutar aplicaciones que utilicen JDBC. Como los drivers son implementaciones
hechas en Java, como tales, serán portables a cualquier plataforma que acepte
Java y, por lo tanto, no tendríamos que tener ningún problema. Más adelante
ya veremos que, desgraciadamente, esto no siempre es así.
© FUOC • PID_00275642 20 Programación mediante SQL

Por ejemplo, para ejecutar una aplicación que use PostgreSQL, tendremos que
instalar los drivers JDBC de PostgreSQL. Sin estos drivers podríamos compilar la
aplicación, pero se generaría un error en tiempo de ejecución (recordemos que,
siguiendo el principio de ODBC, los drivers se cargan en tiempo de ejecución).

Para instalar manualmente un driver en un equipo, hay que descargar los fi-
cheros necesarios (normalmente un fichero .jar). A continuación, hay que
modificar el CLASSPATH para que incluya los ficheros que hemos descargado.

2.2. Tipos de drivers

Podemos hallar drivers JDBC para acceder a un amplio abanico de SGBD. Nos
puede sorprender encontrar más de un driver para acceder a un mismo SGBD,
especialmente porque todos permiten hacer lo mismo. ¿Qué sentido puede
tener eso?

Los diferentes drivers que permiten acceder a un mismo SGBD ofrecen las mis-
mas clases y métodos, pero utilizan caminos diferentes para comunicarse con
la BD. Podemos encontrar cuatro tipos de acceso diferentes:

• Tipo�1: por medio de ODBC, mediante un puente JDBC-ODBC.


• Tipo�2: por medio de una biblioteca de SGBD (que llamamos libSGBD),
mediante un puente JDBC-libSGBD.
• Tipo�3: de JDBC a SGBD pasando por un intermediario. Este intermediario
acostumbra a utilizar drivers de tipo 1, 2 o 4 para comunicarse con SGBD.
• Tipo�4: directo de JDBC a SGBD.

2.2.1. Puente JDBC-ODBC (tipo 1)

En este caso, se accede a la BD por medio de un driver ODBC. Es el único


que está incluido en el API JDBC y, por lo tanto, no requiere la instalación de
ningún driver JDBC adicional.
© FUOC • PID_00275642 21 Programación mediante SQL

Driver JDBC de tipo 1.

2.2.2. Puente JDBC-libSGBD (tipo 2)

En este caso, accedemos a la BD por medio de una biblioteca específica para


cada SGBD, normalmente escrita en lenguaje C. Los drivers JDBC sólo hacen
de puente entre Java y esta biblioteca.

Driver JDBC de tipo 2.


© FUOC • PID_00275642 22 Programación mediante SQL

2.2.3. Directo JDBC-SGBD (tipo 4)

Accedemos a la BD exclusivamente mediante un driver JDBC que se comunica Nota


de forma directa con el SGBD por la red.
Los drivers JDBC de Post-
greSQL sólo se ofrecen de tipo
4.

Driver JDBC de tipo 4.

2.2.4. JDBC-intermediario (tipo 3)

Nos comunicamos exclusivamente con un intermediario por la red. Este in-


termediario se encarga de comunicarse con el SGBD y, normalmente, tanto
el intermediario como el SGBD están instalados en el mismo equipo o en la
misma red local.

El intermediario puede estar implementado en cualquier lenguaje de progra-


mación. Si está implementado en Java (lo que no es imprescindible), normal-
mente utilizará drivers JDBC de tipo 1, 2 o 4 para acceder al SGBD concreto.
© FUOC • PID_00275642 23 Programación mediante SQL

Driver JDBC de tipo 3.

2.3. Comparativa

Analicemos los cuatro tipos de drivers desde diferentes puntos de vista:

1)�Portabilidad

Los drivers de tipo 1 y 2 aparecieron en los orígenes del JDBC con la intención
de aprovechar los drivers ODBC y las bibliotecas de acceso al SGBD que ya
había implementadas. Eso garantizaba la introducción del JDBC sin ningún
esfuerzo por parte de los fabricantes de SGBD, pero limitaba su portabilidad.

Para poder utilizar drivers de tipo 1 o 2, se necesita que el equipo en el que se


ejecuta la aplicación tenga instalados drivers ODBC o bibliotecas de acceso a
SGBD. Tanto los unos como los otros son dependientes de la plataforma. Este
hecho los desautoriza y los restringe a casos experimentales o de inexistencia
de drivers JDBC.

Los drivers de tipo 3 y 4 también aparecieron en los orígenes del JDBC, aun-
que inicialmente había pocos SGBD que los ofrecieran. De forma gradual, los
fabricantes de SGBD han ido publicando drivers de tipo 3 y 4 y, actualmente,
son las opciones recomendadas.
© FUOC • PID_00275642 24 Programación mediante SQL

A diferencia de los drivers de tipo 1 y 2, los de tipo 3 y 4 garantizan la porta-


bilidad de una aplicación a todas las plataformas que soportan Java. En este
caso, los drivers están escritos íntegramente en Java y, por lo tanto, son plena-
mente portables.

Adicionalmente, los drivers de tipo 3 permiten ocultar el SGBD a los clientes


(ya que éstos se comunican exclusivamente con el intermediario). Por lo tanto,
podemos llevar la aplicación de un SGBD a otro modificando tan sólo el código
del intermediario. Las aplicaciones cliente quedan inalteradas.

2)�Tipos�y�versiones�de�JDBC�implementados

No todos los fabricantes de SGBD ofrecen drivers de todos los tipos, y los que
se ofrecen no siempre implementan la última versión de JDBC.

Los drivers de tipo 1 quedan un poco al margen de esta incertidumbre. Recor-


demos que están basados en drivers ODBC y que, por lo tanto, están limitados
a las funcionalidades de ODBC. Por esta razón sólo implementan JDBC2.0.

Para el resto de casos, hay que averiguar qué tipos de driver ofrece el fabricante
de SGBD que nos interesa y qué versión JDBC implementa. Además, podemos
encontrar drivers que implementen parcialmente alguna versión.

En resumen, aunque en principio los drivers de tipo 3 y 4 garantizan portabi-


lidad, en la práctica podemos tener algunas sorpresas.

3)�Nivel�de�seguridad

Las aplicaciones no siempre se ejecutan en el propio equipo de la BD. Normal-


mente se han de conectar a la BD por la red. En escenarios simples se conectan
por la red local, pero en otros por Internet.

En escenarios de Internet se considera que los drivers de tipo 1, 2 y 4 tienen un


nivel de seguridad bajo. Para que las aplicaciones se puedan comunicar con el
SGBD, hay que exponer el SGBD a Internet, de manera que se abre la puerta
a posibles ataques.

Los drivers de tipo 3 permiten que la aplicación se conecte a un intermediario


y no al SGBD. Esto posibilita ocultar el SGBD de Internet y, por lo tanto, au-
mentar el nivel de seguridad (siempre que el intermediario esté implementado
correctamente).

Así, con respecto a la seguridad, basta con utilizar drivers de tipo 4 en escenarios
de red local (o de tipo 1 o 2 si no hay alternativa). En escenarios de Internet
se recomienda el uso de drivers de tipo 3.
© FUOC • PID_00275642 25 Programación mediante SQL

En conclusión, los drivers de tipo 3 ofrecen las mejores prestaciones,


tanto de portabilidad, como de seguridad e incluso de eficiencia. En
contrapartida, la oferta es escasa (a menudo hay que implementar el
intermediario) y tienen una arquitectura más compleja (necesitan que
el intermediario se ejecute en un servidor).

Los drivers de tipo 4 tienen un nivel de prestaciones correcto. Además, a


diferencia de los de tipo 3, los podemos encontrar en una amplia ofer-
ta en el mercado. Permiten obtener buenos resultados sin demasiadas
complicaciones.

Los drivers de tipo 1 y 2 serían la alternativa en caso de no encontrar


drivers de tipo 3 o 4 y en situaciones experimentales.
© FUOC • PID_00275642 26 Programación mediante SQL

3. Programación con JDBC

Ahora nos centraremos en la vertiente más práctica del módulo didáctico. Su-
pongamos que queremos desarrollar una aplicación en Java y que ya hemos
decidido qué SGBD y qué driver utilizaremos. Veamos cómo se usa la API JDBC,
es decir, cómo se programa mediante las clases de esta biblioteca.

Empezaremos viendo cómo se establece conexión con una BD. A continua-


ción, aprenderemos la manera de enviar consultas, tratar los resultados, hacer
modificaciones y trabajar con procedimientos almacenados. Finalmente, ve-
remos cómo gestionar las transacciones y el tratamiento de errores.

3.1. Conexión a una BD

El primer paso para trabajar con una BD es la conexión. Para poder conectar- Nota
nos, hemos de seguir una serie de pasos:
Tenéis disponible el códi-
go del ejemplo en el fichero
1)�Importar�los�paquetes�informáticos�necesarios. Tal como se ha comen- [Link].

tado, API JDBC está formada por dos paquetes informáticos, [Link] y
[Link]. El primero contiene las clases y las interfaces esenciales (incluye
las clases Driver, Connection, Statement, ResultSet, PreparedState-
ment y CallableStatement, principalmente) y lo tendremos que importar
siempre. El segundo es la extensión estándar y contiene clases más especiali-
zadas que se escapan de los objetivos de este módulo didáctico (RowSet, Da-
taSource y PooledConnection, entre otras).

2)�Cargar�el�driver�adecuado. La manera tradicional de cargar un driver es for-


zando la carga del driver a partir de su nombre, utilizando el método forName
de la clase Class. Por ejemplo, para cargar el driver de PostgreSQL haríamos
lo siguiente:

[Link]( "[Link]" );

Este nombre identifica la clase Driver del package [Link] (recor-


demos que es la clase que implementa la interfaz [Link] ). Si que-
remos utilizar otro driver, tendremos que hacer un poco de investigación para
averiguar el nombre del package y el de la clase que implementa la interfaz
[Link].

Cargar una clase a partir de su nombre puede fallar si el classloader (objeto res-
ponsable de cargar las clases necesarias para la ejecución de un programa) no
es capaz de encontrar ninguna clase con este nombre. Por lo tanto, nos tene-
© FUOC • PID_00275642 27 Programación mediante SQL

mos que asegurar de que el CLASSPATH apunte al fichero (normalmente .jar


) que contiene el driver. En caso de no encontrarla, se genera una excepción
de tipo ClassNotFoundException.

En la versión JDBC 4.0 se propone delegar la responsabilidad de cargar el driver


al DriverManager, que será el encargado de buscar el driver en los directorios
o ficheros jar definidos en el CLASSPATH cuando sean necesarios.

3)�Abrir�la�conexión. Aunque el concepto de "abrir conexión" sea simple, es-


conde cierta complejidad cuando hemos de indicar la BD que queremos abrir.

String dbURL="jdbc:postgresql:bdMail";
Connection conn = [Link]( dbURL,
"usuario","contraseña");

El encargado de abrir una conexión con una BD es el DriverManager, por


medio del método getConnection, y requiere tres parámetros:

a) El primero es la denominada URL e identifica la BD a la que nos queremos


conectar. Es una cadena formada por tres partes: la primera siempre es jdbc
y el resto, variables:

jdbc:<subprotocol>:<subname>

El subprotocol es el nombre del driver que utilizaremos para conectarnos.


Otra vez hay que hacer un poco de investigación.

El subname sirve para identificar la BD propiamente. Su formato depende del


driver que utilicemos y, por lo tanto, no tiene un formato estándar. Éste es un
tercer punto de investigación.

En los casos más explícitos, identifica el servidor (en el que está el SGBD),
el puerto en el que escucha el SGBD y el nombre de la BD. Si no indicamos
servidor, se entiende que es el mismo ordenador (localhost ) y, si tampoco
indicamos puerto, se entiende que es el puerto por defecto del SGBD.

En el ejemplo anterior nos estaríamos conectando a una BD PostgreSQL que


hay en el mismo ordenador, escuchando el puerto por defecto, y que se llama
bdMail.

b) El segundo y el tercer parámetros corresponden al nombre de usuario de la


BD y a la contraseña correspondiente. Hay que asegurarse de que nos conec-
tamos con un usuario que tenga suficientes privilegios para ejecutar las sen-
tencias SQL que vengan a continuación.
© FUOC • PID_00275642 28 Programación mediante SQL

4)� Cerrar� la� conexión. Sin duda, es la operación más sencilla de las vistas
hasta ahora. Simplemente hay que llamar al método close de la conexión
que queramos cerrar.

[Link]();

Si no cerramos la conexión lo hará el garbage collector cuando destruya el objeto


conexión.

En todo caso, en aplicaciones cliente es muy importante cerrar las co-


nexiones cuando ya no las queremos utilizar; así conseguimos que el
servidor libere recursos y que los pueda dedicar a otro cliente.

Llegados en este punto, nos podemos plantear si los pasos que vamos siguien-
do son elegantes. Hemos indicado el nombre del driver a la hora de cargarlo,
y lo tenemos que volver a indicar a la hora de conectarnos. ¿Es necesaria esta
redundancia? En la versión JDBC 4.0 se delega la carga de drivers al Driver-
Manager, de manera que sólo hay que indicar el driver en el momento de crear
la conexión.

También nos podemos plantear si es conveniente indicar el equipo, el puerto,


el nombre de la BD, el nombre de usuario y la contraseña en el código fuente.
¿Lo es? No demasiado, especialmente si queremos distribuir la aplicación sin
tener que recompilarla cada vez. De hecho, a partir de JDBC 2.0 ya se propone
utilizar una interfaz llamada DataSource para desvincular el código fuente
de los detalles de conexión.

3.2. Consultas y modificaciones básicas

Entramos ahora en la parte más interesante de la programación en JDBC o,


como mínimo, la que nos ocupará más tiempo. Veremos la manera de hacer
consultas a la BD y de modificar los datos por medio de sentencias SQL.

Empezaremos enviando consultas o modificaciones mediante la clase State-


ment y tratando los resultados de las consultas con la clase ResultSet. Con
esto tendremos una idea básica de comunicación entre aplicación y BD.

Clase Statement

Realmente, la clase Statement no envía consultas o modificaciones. De hecho, son las


instancias de esta clase, es decir, los objetos, las que las envían. Aplicamos este abuso de
lenguaje aquí y al resto del material para mejorar la claridad del texto.
© FUOC • PID_00275642 29 Programación mediante SQL

3.2.1. Ejecución de sentencias SQL: la clase Statement

El proceso para hacer una consulta o una modificación arranca de ahí mismo.
Hemos de crear un objeto de tipo Statement que contendrá la sentencia SQL
de consulta (SELECT), modificación de datos (INSERT, UPDATE y DELETE) o
modificación de la estructura de BD (CREATE TABLE, DROP TABLE, ALTER
TABLE, etc.).

La responsabilidad de crear nuevos objetos de tipo Statement es de la cone-


xión a la que queremos enviar la sentencia SQL.

Statement st = [Link]();

Una vez que tenemos el objeto de tipo Statement creado, utilizaremos el mé-
todo executeQuery para las consultas y el método executeUpdate para las
modificaciones. En el caso de las consultas, el método executeQuery devuel-
ve un objeto ResultSet que nos permitirá acceder a los datos consultados.
Este aspecto lo trataremos en el subapartado siguiente.

ResultSet rs;
rs = [Link]("SELECT * FROM Usuarios");

En el caso de modificaciones, el método executeUpdate devuelve un entero. Nota


Este entero indica el número de filas afectadas. En el caso de que se modifique
Tenéis disponible el códi-
la estructura de la BD (ejecución de sentencias SQL de tipo DDL), devuelve go del ejemplo en el fichero
un 0. [Link].

[Link]("DROP TABLE Usuarios");


[Link]("CREATE TABLE Usuarios ("+
"nombre_usuario VARCHAR(10) PRIMARY KEY, "+
"contrasena VARCHAR(10), nombre VARCHAR(20), "+
"apellidos VARCHAR(40))");
[Link]("INSERT INTO Usuarios "+
"(nombre_usuario,contrasena,nombre,apellidos) VALUES "+
"('mPalau','1234','Manuel','Palau Roca')");

Las tres sentencias SQL de este ejemplo siempre hacen lo mismo. En algunos
casos, con esto ya hay bastante (por ejemplo, cuando creamos la tabla Usua-
rios ), pero normalmente no será así.

La última sentencia del ejemplo añade al usuario Manuel a la tabla Usuarios,


pero éste es un caso poco habitual. Normalmente, cuando se añaden filas en
una BD, los valores de las columnas los introduce el usuario de la aplicación
en tiempo de ejecución o se cargan desde un fichero. En todo caso, son valores
que no se conocen en tiempo de compilación.
© FUOC • PID_00275642 30 Programación mediante SQL

Para conseguir ejecutar una sentencia SQL con valores cambiantes, concate- Nota
naremos las partes fijas de la sentencia con las partes cambiantes (que se sus-
Tenéis disponible el códi-
tituirán por variables). El código queda un poco ilegible, pero con el tiempo go del ejemplo en el fichero
nos acabaremos acostumbrando. [Link].

String nombreUsuario,contrasena,nombre,apellidos;
...
[Link]("INSERT INTO Usuarios "+
"(nombre_usuario,contrasena,nombre,apellidos) VALUES "+
"('"+nombreUsuario+"','"+contrasena+"','"+nombre+"','"+
apellidos+"')");

Podemos aplicar este mismo patrón para hacer consultas SQL utilizando la Nota
cláusula WHERE. Por ejemplo, nos puede interesar consultar los mensajes del
¡Fijémonos en que las comillas
fórum que el usuario de la aplicación seleccione. Fijémonos en que la parte simples para definir textos en
cambiante corresponde a una columna numérica y que, por lo tanto, no está SQL se mantienen como parte
fija de la instrucción!
rodeada de comillas simples.

int codigoForum;
...
ResultSet rs = [Link]("SELECT * "+
"FROM Mensajes WHERE codigo_forum="+codigoForum);

En el caso de columnas de tipo fecha, hemos de prestar una atención especial


a la interpretación que hará el SGBD. Dependiendo de la configuración local,
el SGBD puede interpretar las fechas en formato dd/mm/yyyy o mm/dd/yyyy.
Para asegurar la interpretación correcta, usaremos funciones del SGBD para
hacer la conversión. En el caso de PostgreSQL, por ejemplo, utilizaremos la
función to_timestamp.

//La interpretación dependerá de la config. del SGBD


[Link]("INSERT INTO Lecturas "+
"VALUES(1,'mPalau','3/4/2010 16:19')");
//¡aseguramos que el SGBD lo interprete correctamente!
[Link]("INSERT INTO Lecturas "+
"VALUES(1,'cMas', to_timestamp('3/4/2010 16:19'"+
",'DD/MM/YYYY HH24:MI'))");

Para definir sentencias SQL dinámicas con parámetros de tipo tiempo, debe-
remos tener en cuenta el formato de las fechas en Java. Por ejemplo, el método
toString de la clase Timestamp devuelve una fecha en formato YYYY-MM-
DD hh:mm::ss. El código necesario sería el siguiente:

[Link]("INSERT INTO Lecturas "+


"VALUES("+codigoMensaje+",'"+nombreUsuario+"',"+
"to_timestamp('"+ts+"','YYYY-MM-DD HH24:MI'))");
© FUOC • PID_00275642 31 Programación mediante SQL

3.2.2. Tratamiento de resultados: la clase ResultSet

Como ya hemos dicho, la clase ResultSet nos permitirá acceder a los resul-
tados de las consultas. Este acceso, sin embargo, no es libre, por lo que nos
tendremos que ceñir a las restricciones siguientes:

• Simultáneamente sólo podemos acceder a una sola fila. Para poder acceder
a todas las filas, deberemos hacer un recorrido y, en cada iteración, acceder
a una fila.

• El recorrido, por defecto, sólo puede ir hacia delante.

• Durante el recorrido, de entrada, sólo podemos consultar las filas. No las


podemos modificar.

Por lo tanto, la clase ResultSet nos ofrecerá métodos para poder hacer un
recorrido por las filas de la consulta y, en cada iteración, consultar el valor de
las columnas de la fila actual teniendo en cuenta que:

• Podemos consultar el valor de las columnas a partir del nombre correspon-


diente o a partir de un entero que representa la posición de la columna
dentro de la tabla (empezando por 1).

• Disponemos de diferentes métodos para cada tipo de datos de las columnas


que se quiere consultar.

Tipos�estándar�SQL Método�getTipus

CHAR getString

VARCHAR getString

SMALLINT getShort

INTEGER getInt

FLOAT getFloat / getDouble

DOUBLE getDouble

DECIMAL getBigDecimal

DATE getDate

ESTAFI getTime

MONEY getDouble
© FUOC • PID_00275642 32 Programación mediante SQL

Ejemplo
Nota
En el ejemplo siguiente, consultamos los datos de los usuarios (almacenados en la tabla
Usuarios de nuestra BD de referencia), hacemos un recorrido y, en cada iteración, mos- Tenéis disponible el códi-
tramos la columna número 1 (que corresponde a la columna nombre_usuario ) y los go del ejemplo en el fichero
[Link].
apellidos.

rs = [Link]("SELECT * FROM Usuarios");


while ([Link]()) {
[Link]([Link](1)+"--"+
[Link]("apellidos"));
}
[Link]();

Se intuye que detrás de un ResultSet hay un cursor que apunta a la fila


actual. Cuando se crea un objeto ResultSet, el cursor apunta a la posición
anterior a la primera fila y, cada vez que ejecutamos el método next, el cursor
avanza. Cuando el método next no encuentra ninguna fila más, devuelve
"falso" y el recorrido finaliza. Pero, en realidad, las cosas no son exactamente
así.

Cada vez que JDBC pide datos a la BD (lo que se conoce como fetch), no recibe
una fila y ya está. Por cuestiones de rendimiento, la BD envía unas cuantas
filas. Además, el número de filas que se envían depende de cada driver.

En el caso del driver JDBC de PostgreSQL, por defecto, se envían todos los datos Consultas ineficientes
de la consulta de golpe. Mientras no son tratados, estos datos se guardan en
Para mejorar la legibilidad de
el equipo cliente, en una memoria caché. Hemos de vigilar que el volumen los ejemplos, en este material
de datos de la consulta no sea demasiado grande; si no, podemos agotar la hemos optado por utilizar con-
sultas que utilizan *. Debéis te-
memoria del equipo cliente. ner presente que en un caso
práctico lo tenéis que evitar,
porque, en general, pueden
ser ineficientes.
En relación con este tema, hemos de tener cuidado de hacer consultas de los
datos que nos sean estrictamente necesarios. En el caso anterior, tenemos un
ejemplo claro de consulta ineficiente. No tiene ningún sentido hacer una con-
sulta de todas las columnas de la tabla Usuarios si después sólo mostramos
dos de ellas. El mismo código refinado sería el siguiente:

rs = [Link]("SELECT nombre_usuario,apellidos "+


"FROM Usuarios");
while ([Link]()) {
[Link]([Link](1)+"--"+
[Link]("apellidos"));
}
[Link]();

3.3. Consultas y modificaciones avanzadas

Tal como hemos visto, en la versión JDBC 2.0 se introdujo la posibilidad de


hacer recorridos mejorados (hacia delante, hacia atrás y desplazamientos di-
rectos a cualquier posición), y también la de modificar las filas mediante mé-
todos (sin utilizar SQL).
© FUOC • PID_00275642 33 Programación mediante SQL

Para permitir estas nuevas funcionalidades, hay que crear el objeto State-
ment con el mismo método createStatement, pero con dos parámetros que
determinan el tipo de movimiento y el tipo de operaciones permitidas (ved
la tabla siguiente):

Tipo�de�movimiento TYPE_FORWARD_ONLY. Es el tipo de movimiento asignado por defecto. Sólo permite hacer un
recorrido hacia delante.

TYPE_SCROLL_INSENSITIVE. Permite libertad de movimientos (hacia delante, hacia atrás)


tantas veces como haga falta.

TYPE_SCROLL_SENSITIVE. Es como el anterior, pero refleja los cambios que se van produ-
ciendo en la BD mientras está activo. Se entiende que estos cambios normalmente son genera-
dos por la ejecución simultánea de otras aplicaciones en otros equipos.

Tipo�de�operaciones�admitidas CONCUR_READ_ONLY. Sólo permite hacer consultas y es la opción por defecto.

CONCUR_UPDATABLE. Permite hacer consultas y modificaciones.

En total, tenemos seis combinaciones posibles, pero en la práctica no todos los


drivers las permiten. Por ejemplo, el driver de PostgreSQL (y no es el único) no
posibilita el tipo de movimiento sensitivo y, por lo tanto, ofrece cuatro combi-
naciones. Para solucionar esta limitación, se recomienda repetir las consultas
cada vez que se quiera tener los datos actualizados.

3.3.1. ResultSet con libertad de movimientos

Para trabajar con ResultSet libres de movimientos, hay que escoger la op-
ción TYPE_SCROLL_INSENSITIVE o la opción TYPE_SCROLL_SENSITIVE (si
el driver lo permite). Así, podremos hacer recorridos:

a)�Hacia�delante. Con los métodos beforeFirst() y next(). El primero no


es necesario, ya que es la posición inicial del ResultSet, y el segundo ya lo
hemos visto.

b)�Hacia�atrás. Con los métodos afterLast() y previous(). Nota

Ejemplo Tenéis disponible el códi-


go del ejemplo en el fichero
ex006ConsultaStatementScroll
El ejemplo siguiente hace un recorrido hacia atrás por las filas de la tabla Usuarios. En [Link].
cada iteración muestra la clave primaria y los apellidos de los usuarios.

st=[Link](
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);

rs = [Link]("SELECT * FROM Usuarios");


[Link]();
while ([Link]())
[Link]([Link](1)+"--"+
[Link]("apellidos"));
[Link]();
© FUOC • PID_00275642 34 Programación mediante SQL

c)� Aleatorios. Con los métodos first(), last(), absolute(int n) y Nota


relative(int n). Permiten movernos a la primera posición, a la última, a
Tenéis disponible el códi-
una posición concreta o a una relativa, respectivamente. Si intentamos mo- go del ejemplo en el fichero
vernos fuera de rango, se genera un error. ex007ConsultaStatementScroll
[Link].

Ejemplo

A continuación, presentamos un ejemplo para ver cómo se utilizan los diversos métodos
de posicionamiento.

st=[Link](
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs = [Link]("SELECT * FROM Usuarios");
[Link]();
...
[Link](-1);
...
[Link]();
...
[Link](3);
...

3.3.2. Modificaciones por medio de ResultSet

Con la opción CONCUR_UPDATABLE tenemos la posibilidad de actualizar la BD


a medida que vamos recorriendo el ResultSet y sin utilizar sentencias SQL.

En la práctica, sin embargo, pueden aparecer problemas. El motivo de fondo


es que el SGBD ha de poder propagar automáticamente la modificación des-
de los datos leídos (y que se encuentran en el ResultSet) hacia las tablas
que almacenan estos datos. Esta propagación automática de los cambios, en
general, sólo se puede hacer cuando la sentencia SELECT que está ligada al
ResultSet está basada en una única tabla y, entre los datos leídos, se incluye
la clave primaria.

Una vez superado este problema, los cambios posibles son los siguientes.

Modificación

Una vez situados en la fila que queremos modificar, disponemos de métodos


para cambiar el valor de las columnas. Son los métodos updateXXX y son
antagónicos a los getXXX.

Después de modificar las columnas de una fila, hay que enviar los cambios Nota
a la BD con el método updateRow(). Si nos movemos de fila sin ejecutar
Tenéis disponible el códi-
este método, los cambios normalmente se perderán (depende del driver que go del ejemplo en el fichero
utilicemos). Al contrario, si queremos descartar los cambios hechos, tenemos [Link].

el método cancelRowUpdates().

Ejemplo

El ejemplo siguiente añade "." al final de cada nombre de usuario.


© FUOC • PID_00275642 35 Programación mediante SQL

st = [Link](ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE);
rs = [Link]("SELECT * FROM Usuarios");
while ([Link]())
{
[Link]("nom",[Link]("nombre")+".");
[Link]();
}
[Link]();

También podemos hacer modificaciones sobre un ResultSet con libertad de Nota


movimientos.
Tenéis disponible el códi-
go del ejemplo en el fiche-
Ejemplo ro ex009ResultSetUpdatable
[Link].
El ejemplo siguiente se sitúa en la última fila y obtiene el número de fila con getRow().
Así sabemos el número total de filas consultadas. A continuación, se sitúa sobre una
posición aleatoria entre la primera y la última fila y añade "." al final del nombre.

st = [Link](
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
rs = [Link]("SELECT * FROM Usuarios");
[Link]();
int nFilas=[Link]();
int pos=(int)([Link]()*nFilas)+1;
[Link](pos);
[Link]("nombre",[Link]("nombre")+".");
[Link]();

Inserción

Para poder insertar filas, primero nos hemos de mover a una nueva fila me-
diante el método moveToInsertRow(). A partir de aquí, asignamos el valor
de las diversas columnas con updateXXX y, para añadir filas, llamamos a in-
sertRow().

[Link]();
[Link]("nombre_usuario","root");
[Link]("contrasena","super");
[Link]("nombre","administrador");
[Link]("apellidos","");
[Link]();

Borrados

Los borrados son el caso más simple. Sólo hace falta situarse en la fila adecuada
y llamar al método deleteRow().

Ejemplo

El ejemplo siguiente se sitúa en la última fila y la borra.

//Eliminamos la última fila


[Link]();
[Link]();
© FUOC • PID_00275642 36 Programación mediante SQL

3.4. Consultas y modificaciones con PreparedStatement

La clase PreparedStatement es una alternativa a la clase Statement. Ambas


permiten hacer lo mismo, consultas y modificaciones en la BD, pero cambia
la manera de hacerlo.

De entrada, un PreparedStatement no se puede reutilizar para ejecutar una


segunda sentencia SQL. Cada sentencia irá ligada a un objeto PreparedSta-
tement diferente.

Las sentencias SQL que definimos en un PreparedStatement normalmente


son incompletas, en el sentido de que pueden tener algunos valores indefini-
dos. Utilizaremos el signo ? para cada valor indefinido de la sentencia.

"SELECT * FROM USUARIOS WHERE nombre_usuario=?"

Una sentencia incompleta no se puede ejecutar, pero sí que se puede enviar al


SGBD y hacer algunos de los pasos necesarios para llevarla a cabo (lo que se
puede entender como una precompilación). El objetivo está claro. Tan pronto
como sepamos los valores que completan la sentencia, ésta se podrá ejecutar
rápidamente, ya que una parte del trabajo ya se habrá hecho con antelación.

El escenario ideal de los PreparedStatement es la situación en la que una


sentencia se ha de ejecutar repetidamente cambiando sólo algunos valores; por
ejemplo, si queremos añadir unas cuantas filas a una tabla. Se entiende que el
rendimiento es superior que si utilizamos clases Statement independientes,
ya que el SGBD analiza una sola vez la sentencia SQL y la ejecuta tantas veces
como haga falta.

Si una sentencia sólo se ha de ejecutar una vez, utilizar PreparedStatement Ved también
deja de ser tan beneficioso y el rendimiento se equipara a la utilización de
Más adelante, en el punto
clases Statement independientes. 3.5.6, veremos la inyección
SQL (SQL injection) y otros be-
neficios derivados de utilizar
Para asignar los valores de un PreparedStatement tenemos una serie de mé- PreparedStatement.

todos setXXX, de funcionamiento idéntico al updateXXX de los ResultSets


modificables, con la excepción de que los parámetros sólo se pueden indexar
a partir de la posición, y no con el nombre de la columna.
© FUOC • PID_00275642 37 Programación mediante SQL

Ejemplo
Nota
Veamos un ejemplo de cómo podemos añadir unos cuantos fórums a la BD de referencia.
Para este ejemplo, supongamos que tenemos una clase Forum implementada con los Tenéis disponible el códi-
métodos suficientes. Fijémonos también en el método clearParameters, que borra el go del ejemplo en el fiche-
ro ex010PreparedStatement
valor de los parámetros y nos prepara para la ejecución siguiente.
[Link].
Forum[] datos={ new Forum(3,"PostgreSQL"),
new Forum(4,"ODBC"), new Forum(5,"Oracle") };

sql="INSERT INTO FORUMS (codigo_forum,nombre) VALUES (?,?)";


PreparedStatement pst = [Link](sql);
for(Forum f:dades)
{
[Link]();
[Link](1,[Link]());
[Link](2,[Link]());
[Link]();
}

3.5. Consideraciones adicionales sobre consultas y


modificaciones

Hasta ahora hemos aprendido cómo consultar o modificar datos de una BD


mediante sentencias SQL. Ahora nos dedicaremos a ver la manera de resolver
situaciones particulares que aparecen con más o menos frecuencia.

3.5.1. Tratamiento de valores nulos

En los ejemplos que hemos visto, no hemos hecho ninguna consideración


especial para los valores nulos, y de vez en cuando hay que hacerlas. Todas
las columnas que no tienen la restricción NOT NULL ocasionalmente pueden
contener un valor nulo.

Hay cuatro situaciones en las que pueden aparecer complicaciones debidas a


los valores nulos. Veámoslas.

Lectura de valores nulos de un ResultSet

Cada vez que hacemos una consulta a la BD, nuestra aplicación ha de vigilar
de no recibir valores nulos y ha de protegerse si esto sucede.

En la mayoría de casos, no hay que usar ningún método especial para detec-
tar si un valor es nulo. Los métodos getString, getBigDecimal, getBy-
tes, getDate, getTime, getTimestamp, getAsciiStream, getCharacte-
rStream, getUnicodeStream, getBinaryStream, getObject, getArray,
getBlob, getClob y getRef devuelven un objeto. Si el valor consultado en
la BD es nulo, el objeto Java también lo será.

Hay que poner una atención especial en los métodos que no devuelven Nota
objetos. En caso de valor nulo, los métodos getByte, getShort, getInt,
Tenéis disponible el códi-
getLong, getFloat y getDouble devuelven un 0. Y el método getBoolean go del ejemplo en el fichero
[Link].
© FUOC • PID_00275642 38 Programación mediante SQL

devuelve "falso". En estos casos hemos de utilizar un método adicional, was-


Null(), para averiguar si el 0 y el valor falso corresponden realmente a un 0
y a un valor falso respectivamente, o a un valor nulo.

Ejemplo

El ejemplo siguiente obtiene los mensajes de los fórums que inician nuevos hilos, es decir,
de los que tienen el hilo nulo.

rs=[Link]("SELECT * FROM Mensajes");


while ([Link]())
{
int fil=[Link]("hilo");
if ([Link]()) {
[Link]([Link]("codigo_forum")+
"--"+[Link]("titulo"));
}
}
[Link]();

Uso de valores nulos en sentencias SQL dinámicas

Para añadir un mensaje en un fórum, podríamos tener un código parecido a


éste:

int codigoForum, orden, hilo;


String autor,titulo,texto;
...
[Link]("INSERT INTO Mensajes "+
"VALUES("+codigoMensaje+","+codigoForum+",'"+autor+
"','"+titulo+"','"+texto+"',"+hilo+")");

Este código es correcto, pero no permite tratar valores nulos. Por ejemplo: Ved también

En el punto 3.2.1 ya hemos


• No permite abrir un hilo nuevo. O, dicho de otra manera, ningún valor visto cómo definir una sen-
de la variable "hilo" se convertirá en un NULL en la BD. Este problema es tencia SQL con valores cam-
biantes concatenando cade-
común a todos los tipos básicos. nas (que contienen la parte fi-
ja) con variables.

• No permite dejar el texto nulo. Incluso haciendo que el texto valiera "NULL
", este NULL quedaría cerrado entre comillas simples y, por lo tanto, la BD
lo interpretaría como un texto que contiene literalmente NULL.

La solución al problema de los tipos básicos pasa o bien por trabajar con sus
objetos equivalentes, o bien por determinar un valor que represente el nulo.
Por ejemplo, el hilo ha de identificar un mensaje y, por lo tanto, será un valor
entero > 0. Podríamos reservar el –1 para identificar los valores nulos.

Para solucionar el segundo problema, tendríamos que sacar las comillas sim- Nota
ples de la parte estática de la sentencia SQL y añadirla cuando el objeto fuera
Tenéis disponible el códi-
necesario. go del ejemplo en el fichero
[Link].
[Link]("INSERT INTO Mensajes VALUES("+
codigoMensaje+","+codigoForum+",'"+autor+"','"+
titulo+"',"+
(texto==null?"NULL":"'"+texto+"'")+","+
(hilo==-1?"NULL":hilo)+")");
© FUOC • PID_00275642 39 Programación mediante SQL

Uso de valores nulos en la cláusula WHERE

La sentencia patrón que se define en un PreparedStatment está abierta a la


posibilidad de valores nulos, pero sólo funcionará si los nulos no están en las
condiciones WHERE de la consulta SQL.

Los nulos que aparecen en las condiciones WHERE tienen una sintaxis diferen-
te, xxx IS NULL, en vez de xxx=valor. Este cambio de sintaxis impide tener
un PreparedStatement que sea válido para condiciones con valores nulos
y valores no nulos.

Para el resto de casos, a la hora de definir los valores es cuando habrá que Nota
tener en cuenta los valores nulos. Cuando los valores sean objetos, podemos
Tenéis disponible el códi-
continuar utilizando el método setXXX, pero en los tipos básicos será impres- go del ejemplo en el fichero
cindible la utilización del método setNull. ex013NullsPreparedStatement.
java.

...
[Link]();
[Link](1,codigoMensaje);
[Link](2,codigoForum);
[Link](3,autor);
[Link](4,titulo);
[Link](5,texto);
if (hilo==-1) [Link](6,[Link]);
else [Link](6,hilo);
[Link]();

Modificación de un ResultSet con valores nulos

En este último caso, vuelve a pasar lo mismo. Tenemos un método especial Nota
para asignar nulos que será imprescindible para los tipos básicos, y opcional
Tenéis disponible el códi-
para el resto. go del ejemplo en el fichero
ex014NullsUpdatableResultSet.

[Link]();
[Link](1,codigoMensaje);
[Link](2,codigoForum);
[Link](3,autor);
[Link](4,titulo);
[Link](5,texto);
if (hilo==-1) [Link](6);
else [Link](6,hilo);
[Link]();
[Link]();

3.5.2. Combinaciones de tablas

Hasta ahora hemos visto ejemplos con consultas simples basadas en una sola
tabla; sin embargo, esta situación no es la más habitual. Una gran parte de las
consultas que una aplicación hace a una BD están basadas en combinaciones
(en inglés, joins) de más de una tabla.
© FUOC • PID_00275642 40 Programación mediante SQL

Con combinaciones todo continúa funcionando tal y como ya hemos expli-


cado, pero hemos de tener presente que:

• Posiblemente los ResultSet modificables no funcionarán.

• En caso de nombres de columnas coincidentes en tablas diferentes, es in-


teresante utilizar alias. Hay drivers que permiten solucionar el problema
especificando [Link], pero otros que no. Por ejemplo, el driver
de PostgreSQL no lo posibilita y, por lo tanto, deberemos definir un alias
en las columnas repetidas.

Podemos encontrar un caso en la BD del ejemplo con las columnas "nombre" Nota
de las tablas Forums y Usuarios. Para solucionar esta ambigüedad, hay que
Tenéis disponible el códi-
hacer la consulta con alias. go del ejemplo en el fichero
[Link].

rs=[Link]("SELECT [Link] as nombre_forum, "+


"[Link] as nombre_usu,* "+
"FROM Mensajes,Forums,Usuarios "+
"WHERE Mensajes.codigo_forum=Forum.codigo_forum "+
"AND [Link]=Usuarios.nombre_usuario");
while ([Link]())
[Link]([Link]("nombre_forum")+
"--"+[Link]("codigo_mensaje")+"--"+
[Link]("titulo")+"--"+
[Link]("nombre_usu"));

3.5.3. Consultas anidadas

Las consultas anidadas son consultas que se ejecutan mientras recorre-


mos las filas de otra consulta.

En algunos casos, este tipo de consultas se pueden evitar mediante combina-


ciones, con lo que se consigue un mejor rendimiento. En otros, la lógica de
la consulta puede ser lo bastante complicada como para no poder expresarse
en una única consulta SQL y hacer imprescindible la utilización de consultas
anidadas.

El único detalle que hemos de tener en cuenta es que un Statement sólo


puede tener un ResultSet activo. Por lo tanto, cada consulta anidada deberá
tener un Statement independiente.

Ejemplo
Nota
El ejemplo siguiente muestra a cada usuario y, para cada usuario, los mensajes correspon-
dientes. Para ilustrar la situación sin utilizar un ejemplo excesivamente complicado, se Tenéis disponible el códi-
ha resuelto emplear una consulta anidada, pero la solución también podría ser una sola go del ejemplo en el fichero
ex016ConsultaAnidadaStatement.
consulta que combinara los datos de las tablas Usuarios y Mensajes. En una situación
java.
real, sin embargo, tendríamos que escoger la solución con combinaciones, ya que ofrece
un mejor rendimiento.
© FUOC • PID_00275642 41 Programación mediante SQL

st1 = [Link]();
st2 = [Link]();
rsUsuarios = [Link]("SELECT * FROM Usuarios");
while ([Link]())
{
String nombreUsuario=[Link]("nombre_usuario");
rsMensajes=[Link]("SELECT * FROM "+
"Mensajes WHERE autor='"+nombreUsuario+"'");
while ([Link]())
{
[Link](
[Link]("nombre")+" "+
[Link]("apellidos")+"--"+
[Link]("titulo"));
}
[Link]();
}
[Link]();
[Link]();
[Link]();

Aunque el ejemplo es correcto, las consultas anidadas son una buena ocasión
para poner en práctica los PreparedStatement. Efectivamente, la consulta
interior siempre tiene el mismo patrón, y se ejecuta repetidamente.

Nota

Tenéis disponible el código del ejemplo en el fichero ex017ConsultaAnidadaPrepared


[Link].

st = [Link]();
pst = [Link]("SELECT * FROM "+
"Mensajes WHERE autor=?");
rsUsuarios = [Link]("SELECT * FROM Usuarios");
while ([Link]())
{
String nombreUsuario=[Link]("nombre_usuario");
[Link](1,nombreUsuario);
ResultSet rsMensajes=[Link]();
while ([Link]())
{
[Link](
[Link]("nombre")+" "+
[Link]("apellidos")+"--"+
[Link]("título"));
}
[Link]();
}

[Link]();
[Link]();
[Link]();

3.5.4. Consultas recursivas

A consecuencia de una interrelación recursiva entre tablas, pueden aparecer


consultas recursivas. Sería un caso de consulta anidada o, más bien, muy anida-
da.
© FUOC • PID_00275642 42 Programación mediante SQL

En la BD del ejemplo tenemos una interrelación recursiva en la tabla Mensa-


jes. De cada mensaje sabemos el hilo, es decir, el mensaje al cual responde.
Gracias a esta interrelación, podemos hacer una lista de los mensajes de un
fórum por orden de hilo, no por orden de redacción.

La solución más elegante sería utilizar un único PreparedStatement que se


fuera ejecutando de una manera recursiva: en un primer nivel, consultaríamos
los mensajes que inician hilos (que tienen hilo nulo); a continuación, y por
cada mensaje que inicia hilo, consultaríamos sus respuestas, y después, las
respuestas de las respuestas... Sin embargo, esta solución no es posible porque:

• Un PreparedStatement no permite nulos en la condición WHERE, y la


primera consulta pide los mensajes cuyo hilo es nulo.

• Tal y como planteamos la consulta, hemos de visitar las respuestas del pri-
mer hilo antes de finalizar el recorrido de los hilos iniciales. Esto significa
que necesitamos tantos ResultSet simultáneos como profundidad ten-
ga la recursividad. Y recordemos que un Statement, y por extensión un
PreparedStatement, sólo permite un ResultSet simultáneo.

En resumen, la solución pasa por crear uno nuevo Statement en cada llamada Nota
recursiva. En el ejemplo, consideramos que hilo=-1 significa que el hilo es
Tenéis disponible el códi-
nulo. Hemos añadido un parámetro tab para tabular la salida por pantalla. go del ejemplo en el fichero
[Link].

void listaRec(Connection conn, int codigoForum,


int hilo, String tab) throws Exception
{
Statement st = [Link]();
ResultSet rs=[Link]("SELECT * FROM "+
"Mensajes WHERE hilo"+
(hilo==-1?" IS NULL":"="+hilo));
while ([Link]())
{
[Link](tab+[Link]("titulo")+
"--"+[Link]("autor")+
"--"+[Link]("codigo_mensaje"));
listaRec(conn,codigoForum,
[Link]("codigo_mensaje"), tab+" ");
}
[Link]();
[Link]();
}

3.5.5. Claves primarias autogeneradas

De una manera o de otra, los SGBD permiten la definición de columnas au-


togeneradas. Son columnas cuyo valor se asigna automáticamente cada vez
que se inserta una nueva fila. Los valores autogenerados por estas columnas
normalmente son valores numéricos y correlativos.
© FUOC • PID_00275642 43 Programación mediante SQL

Las columnas autogeneradas se utilizan en las claves primarias, pero tienen un


problema a la hora de codificar las aplicaciones. En el momento en el que se
inserta una nueva fila, la aplicación envía los datos de las columnas, sin definir
el valor de la clave primaria. El SGBD recibe estos datos, calcula la nueva clave
primaria y la asigna.

Si la aplicación no necesita saber el valor de la clave primaria autogenerada,


no hay ningún problema. Sin embargo, en caso afirmativo, ¿cómo lo hace la
aplicación para saber cuál ha sido el valor asignado?

JDBC ofrece un mecanismo que simplifica el trabajo. En el momento de eje-


cutar la sentencia INSERT con el método executeUpdate de la clase Sta-
tement, añadimos un segundo parámetro, RETURN_GENERATED_KEYS, para
indicar que nos devuelva los valores de las columnas autogeneradas (lo que
normalmente será la clave primaria). Recibimos los valores autogenerados por
medio de un ResultSet que obtenemos con el método getGeneratedKeys,
también de la clase Statement.

Ejemplo
Nota
En el ejemplo siguiente, creamos una tabla Grupos con una clave primaria entera auto-
generada. Es importante tener presente que la sintaxis para definir columnas autogene- Tenéis disponible el códi-
radas depende del SGBD. En el ejemplo, hemos utilizado la sintaxis de PostgreSQL. Hay go del ejemplo en el fichero
[Link].
que destacar también que en cuanto a los valores autogenerados, sólo habrá uno, y que,
por eso, podemos recorrer el ResultSet sin iterar.

[Link]("CREATE TABLE Grupos " +


"(codigo_grupo SERIAL PRIMARY KEY, nombre VARCHAR(20))");
String nombre=...
[Link]("INSERT INTO Grupos ('"+nombre+"')",
Statement.RETURN_GENERATED_KEYS);
ResultSet rs=[Link]();
if ([Link]()) [Link]([Link](1));
[Link]();

Desgraciadamente, el driver de PostgreSQL no soporta esta funcionalidad, y Nota


por lo tanto, el ejemplo anterior generaría un error en tiempo de ejecución.
Tenéis disponible el códi-
Para conseguir el mismo efecto, PostgreSQL propone una extensión del SQL go del ejemplo en el fichero
estándar añadiendo la cláusula RETURNING al final de la sentencia INSERT. [Link].

Ejemplo

El ejemplo siguiente muestra el código necesario para obtener el valor de la clave primaria
autogenerada de la tabla Grupos en el caso de PostgreSQL.

ResultSet rs=[Link]("INSERT INTO Grupos " +


"(nombre) VALUES ('"+nombre+"') RETURNING codigoGrupo");
if ([Link]()) [Link]([Link](1));
else [Link]("ERROR");
[Link]();
© FUOC • PID_00275642 44 Programación mediante SQL

3.5.6. Inyección SQL (SQL injection)

Las instrucciones SQL enviadas a la BD por medio de Statement pueden te-


ner un problema de seguridad conocido como inyección SQL (en inglés, SQL
injection). A continuación, os proponemos un ejemplo para que entendáis la
base del problema.

Es muy habitual que las aplicaciones identifiquen a los usuarios antes de em-
pezar a trabajar. Gracias a eso, pueden ofrecer más o menos operaciones a los
usuarios, según el rol que tengan.

Un sistema de identificación habitual es pedir el nombre de usuario y la con- Nota


traseña. Una vez que lo hemos introducido, el sistema valida que el usuario y
Tenéis disponible el códi-
la contraseña son correctos. Y para eso lo más razonable es hacer una consulta go del ejemplo en el fichero
a la BD que contenga la tabla con los datos de los usuarios (como la de nuestra [Link].

BD de referencia).

Hay muchas maneras de hacer esta validación; por ejemplo, haciendo una
consulta como la siguiente:

String nombreUsuario,contrasena;
//leemos nombreUsuario y contrasena
...
ResultSet rs = [Link]("SELECT * "+
"FROM Usuarios WHERE nombre_usuario='"+nombreUsuario+
"' AND contrasena='"+contrasena+"'");
if ([Link]()) [Link]("CORRECTO");
else [Link]("ERROR");

Como quien no quiere la cosa, con un código como éste, tenemos un agujero
de seguridad importante. Fijémonos en lo que pasa si un usuario introduce el
nombre siguiente y cualquier contraseña:

' OR 1=1 OR 'A'='

Esto no es el nombre de ningún usuario y, a pesar de todo, nuestro sistema Seguridad de las
da como resultado CORRECTO. El problema radica en la construcción de la contraseñas

sentencia SQL, que concatena partes estáticas con el contenido de variables. El ejemplo de la contraseña ex-
Si alguna variable, en vez de contener un valor, tiene el código SQL adecuado, hibe una mala práctica de se-
guridad muy habitual: almace-
permite modificar la sentencia SQL que se está ejecutando. nar contraseñas planas. Es pre-
ferible almacenar la contraseña
en la BD utilizando alguna téc-
nica de encriptación de datos.
Este problema tiene solución, tanto si se refuerza el código como si se utiliza
PreparedStatement.
Nota
• Las soluciones en código se basan en escapar los apóstrofos, es decir, en
Tenéis disponible el códi-
marcar los apóstrofos de los valores de tipo texto para que el SGBD no go del ejemplo en el fichero
los confunda con los apóstrofos de la sentencia SQL. Cada SGBD ofrece [Link].

alguna alternativa para escapar apóstrofos, como por ejemplo, \' o ''.
© FUOC • PID_00275642 45 Programación mediante SQL

• Las sentencias ejecutadas con PreparedStatement y los ResultSet modifica-


bles están protegidos de inyección SQL, ya que el contenido de los valores
no interfiere en la sentencia que se está ejecutando.

Por ejemplo, PostgreSQL escapa los apóstrofos duplicándolos todos. El ejemplo


anterior se podría corregir utilizando el método replace de la clase String.

//Leemos nombreUsuario y contraseña


...

//Escapamos los apóstrofos


contrasena=[Link]("'","''");

//Consultamos si el nombreUsuario y la contraseña son válidos


ResultSet rs = [Link]("SELECT * FROM "+
"Usuarios WHERE nombre_usuario='"+nombreUsuario+"' AND "+
"contrasena='"+contrasena+"'");
if ([Link]()) [Link]("CORRECTO");
else [Link]("ERROR");

3.6. Procedimientos almacenados

JDBC ofrece el objeto CallableStatement para poder ejecutar procedimien-


tos almacenados. La sintaxis utilizada se aparta de SQL y se llama escape syntax.
Además, tendremos métodos para definir los parámetros de entrada y recoger
los de salida. La forma general de una llamada será la siguiente:

{? = call nombreProcedimientoAlmacenado(?, ?, ...)}

en la que los signos ? corresponden a los parámetros de entrada, de salida, y


de entrada y salida.

Ejemplo

El siguiente es un ejemplo de un procedimiento almacenado que recibe un parámetro de


entrada (importe) y devuelve el importe con IVA.

CREATE FUNCTION conIva(importe FLOAT) RETURNS FLOAT AS $$


BEGIN
RETURN importe * 1.18;
END;
$$ LANGUAGE plpgsql;

La creación del CallableStatement para este procedimiento almacenado sería la si-


guiente:

CallableStatement cst =
[Link]("{?=call conIva(?)}");

Los procedimientos almacenados también se pueden crear desde JDBC, me-


diante un Statement, pero es una práctica poco habitual. La diferencia prin-
cipal es que toda la sentencia quedará definida en una sola línea de texto.

[Link]("CREATE OR REPLACE FUNCTION "+


"conIva(importe FLOAT) RETURNS FLOAT AS $$ "+
© FUOC • PID_00275642 46 Programación mediante SQL

"BEGIN RETURN importe * 1.18; END; "+


"$$ LANGUAGE plpgsql;");

3.6.1. Parámetros de entrada

Para pasar parámetros de entrada utilizaremos métodos setXXX, una vez crea-
do el CallableStatement. El funcionamiento de estos métodos es idéntico
al de los métodos setXXX de los ResultSet modificables.

3.6.2. Parámetros de salida

Los parámetros de salida se han de registrar antes de hacer la llamada al pro- Nota
cedimiento almacenado. Durante el registro, indicamos el tipo de parámetro
Tenéis disponible el códi-
que recibiremos. Después de ejecutar el procedimiento almacenado, se pueden go del ejemplo en el fichero
recoger los resultados con los métodos getXXX. ex023StoredProcedureIN_OUT.

CallableStatement cst =
[Link]("{?=call conIva(?)}");
[Link](1, [Link]);
[Link](2,10.0f);
[Link]();
[Link]([Link](1));

3.6.3. Parámetros de entrada y salida

Para conseguir parámetros de entrada y salida, haremos una combinación de Nota


los dos anteriores. Utilizaremos el método setXXX para dar el valor de entra-
Tenéis disponible el códi-
da y, antes de ejecutar el procedimiento almacenado, también lo registrare- go del ejemplo en el fichero
mos. Una vez acabado el proceso, obtendremos los parámetros de salida con ex024StoredProcedureINOUT.

getXXX.

[Link]("CREATE OR REPLACE FUNCTION "+


"conIva2(INOUT importe FLOAT) AS $$ "+
"BEGIN importe:= importe*1.18; END; "+
"$$ LANGUAGE plpgsql;");

cst = [Link]("{call conIva2(?)}");


[Link](1, [Link]);
[Link](1,10.0f);
[Link]();
[Link]([Link](1));

3.7. Gestión de errores

Todas las aplicaciones están expuestas a errores durante la ejecución. Sin em-
bargo, si además están conectadas a una BD, las probabilidades aumentan.
La razón la podemos encontrar en el hecho de que cooperan con un sistema
complejo (el SGBD), al que nos conectamos por red y que, simultáneamente,
da servicio a otras aplicaciones. Por si no fuera bastante, las sentencias que
enviamos a menudo son dinámicas y, por lo tanto, susceptibles de errores sólo
detectables en tiempo de ejecución.
© FUOC • PID_00275642 47 Programación mediante SQL

3.7.1. Tipos de errores y cómo se reportan

JDBC expone los errores mediante excepciones, lo que facilita de forma con-
siderable el desarrollo de aplicaciones. Como es habitual en Java, se define
una jerarquía de excepciones que derivan de Exception. En lo alto de esta
jerarquía está la clase SQLException.

Una gran parte de las excepciones que genera JDBC son de la clase SQLExcep-
tion o derivan de ella, aunque podremos explotar poco la jerarquía. Las ex-
cepciones que nos interesará capturar son sobre todo SQLException y, pun-
tualmente, alguna otra derivada. Así pues, raramente podremos tratar las ex-
cepciones en bloques try-catch, y lo tendremos que hacer a partir de la in-
formación que nos facilitarán los objetos SQLException.

3.7.2. Obtención de información sobre un SQLException

Los objetos SQLException exponen el tipo de error generado mediante tres


métodos:

• getMessage(). Devuelve una cadena con información textual del error. Nota
Es el método estándar heredado de la clase Exception.
Tenéis disponible el códi-
go del ejemplo en el fichero
• getSQLState(). Devuelve una cadena de cinco caracteres con un código [Link].

de error definido en XOPEN SQLState o en SQL:2003. Deberemos investi-


gar qué estándar sigue el SGBD que utilizamos y los códigos de los errores
que nos interesa detectar.

• getErrorCode(). Devuelve un entero que corresponde al código de error.


El significado de este entero dependerá del SGBD al que estemos conecta-
dos.

int codigoMensaje;
String nombreUsuario;
...
try
{
[Link]("INSERT INTO Lecturas "+

"VALUES("+codigoMensaje+",'"+nombreUsuario+"',"+
"to_timestamp('"+ts+"','YYYY-MM-DD HH24:MI'))");
}
catch (SQLException e)
{
[Link]("Mensaje:"+[Link]());
[Link]("SQLState:"+[Link]());
[Link]("ErrorCode:"+[Link]());
}
© FUOC • PID_00275642 48 Programación mediante SQL

En este ejemplo, la sentencia INSERT puede generar tres tipos de error. Si el


SGBD utilizado es PostgreSQL, los resultados que podemos obtener son los
siguientes:

1) La clave foránea codigo_mensaje no identifica ningún mensaje.

ERROR: insert or update on table "lecturas" violates foreign key


constraint "lecturas_mensajes_fkey"
Detail: Key (codigo_mensaje)=(455) is not present in table
"mensajes".

SQLState:23503
ErrorCode:0

2) La clave foránea nombre_usuario no identifica ningún usuario.

ERROR: insert or update on table "lecturas" violates foreign key


constraint "lecturas_usuarios_fkey"
Detail: Key (nombre_usuario)=(sadf) is not present in table
"usuarios".

SQLState:23503
ErrorCode:0

3) Este usuario ya había leído el mensaje: clave primaria duplicada.

ERROR: duplicate key value violates unique constraint "lecturas_pkey"

SQLState:23505
ErrorCode:0

Podemos comprobar que el driver de PostgreSQL no devuelve ErrorCode, só- Enlace de interés
lo SQLState. Esto no presenta ningún problema, ya que desde el estándar
Podéis encontrar la lista
SQL:1992 se propuso reportar los errores por medio de códigos SQLState. Du- de códigos SQLState de
rante unos cuantos años, se han mantenido las dos versiones de códigos, pero PostgreSQL en el anexo
A de la documentación:
la versión actual del driver JDBC de PostgreSQL ha prescindido del ErrorCode. [Link]
docs/8.1/inter active/errco-
[Link]
3.7.3. Gestión del error desde nuestros programas

Analizando los SQLState obtenidos en los tres ejemplos anteriores, podemos


distinguir entre errores provocados por claves foráneas y errores provocados
por claves primarias. Sin embargo, para discriminar cuál de las claves foráneas
ha provocado el error, no nos queda más remedio que analizar el texto obte-
nido en getMessage.

Ahora ya podemos definir el patrón que seguiremos para el tratamiento de


errores:
© FUOC • PID_00275642 49 Programación mediante SQL

1) Definir el bloque try-catch del conjunto de sentencias SQL de las que


queremos tratar los errores. Cuanto mayor sea el conjunto, más difícil nos será
determinar el error exacto. Si con un solo bloque no podemos tratar bien los
errores, tendremos que definir más de uno, a menudo anidados.

2) Capturar los distintos tipos de errores a partir de la clase de excepción gene-


rada. Esto nos permitirá diferenciar entre errores de drivers, de conexión, etc.,
pero no entre los tipos de error SQL.

3) Para discriminar el tipo de error SQL, nos basaremos en el código SQLState.

4) Para acabar de determinar la causa exacta del error, deberemos analizar la


cadena obtenida con el método getMessage buscando alguna palabra clave
que nos ayude.

Una vez detectada la causa exacta del error, la aplicación tendrá que decidir
entre reintentar la operación, abandonarla silenciosamente (es decir, ignorar-
la) o detenerla con una excepción. Exceptuando el reintento, en las otras situa-
ciones es muy importante cerrar todos los objetos (como por ejemplo, Resul-
tSetStatement ) que podamos tener abiertos. Así permitimos que el SGBD
libere recursos rápidamente y que pueda atender a otros usuarios.

Ejemplo

Pongamos un ejemplo concreto para registrar la lectura de un mensaje por parte de un


usuario.

[Link]( "[Link]" );
String dbURL="jdbc:postgresql:bdMail";
Connection conn = [Link](
dbURL, "usuario","usuario");
Statement st = [Link]();
[Link]("INSERT INTO Lecturas VALUES("+
códigoMensaje+",'"+nombreUsuario+"',"+
"to_timestamp('"+ts+"','YYYY-MM-DD HH24:MI'))");

Nota

Tenéis disponible el código del ejemplo en el fichero [Link].

Las diversas líneas de este código pueden generar diferentes tipos de excepcio-
nes:

a)�[Link](...) : ClassNotFoundException. No encontramos el


driver. No está instalado o no hemos definido el CLASSPATH.

b)� [Link](...) : SQLException. Hay un error


de conexión con la BD: la URL, el puerto, el usuario, la contraseña, etc. son
incorrectos.

c)�[Link]() :
© FUOC • PID_00275642 50 Programación mediante SQL

• SQLException. La conexión está cerrada o los parámetros para definir el


tipo de ResultSet son incorrectos.

• SQLFeatureNotSupportedException. El driver no soporta esta opera-


ción, normalmente debida al tipo de pedido ResultSet.

d)�[Link](...) :

• SQLException. Hay un error en la instrucción SQL. Será la situación más


habitual.

• SQLFeatureNotSupportedException. Se dará en el caso de que el driver


no soporte alguna funcionalidad; por ejemplo, en el caso de PostgreSQL,
la imposibilidad de devolver las columnas autogeneradas.

Si nos interesa tratar todas las excepciones de forma particular, no tendremos


bastante con un solo bloque try-catch. Ahora bien, exceptuando los errores
provocados por executeUpdate, el resto normalmente no se deberían pro-
ducir. Son errores del sistema más que de la aplicación y, si se producen, poca
cosa podremos hacer para solucionarlos.

Una opción correcta es no hacer nada especial en los errores de sistema. El


ejemplo anterior, tal como está, sería insuficiente, ya que, en caso de error, no
se cerrarían los objetos abiertos.

Ejemplo
Nota
En este ejemplo mostramos una posible estructuración del código para garantizar que,
tanto en caso de éxito como de error, todos los objetos abiertos quedarán cerrados. La Tenéis disponible el códi-
utilización del bloque finally simplifica la codificación. Asimismo, dentro del bloque go del ejemplo en el fichero
[Link].
finally, todos los métodos están protegidos con un bloque try-catch para garantizar
que, aunque se produzca un error, el bloque finally se continúa ejecutando hasta el
final.

try
{
[Link]( "[Link]" );
String dbURL="jdbc:postgresql:bdMail";
conn = [Link]( dbURL,
"usuario","usuario");
st = [Link]();
//...
}
catch (Exception e)
{
throw e;
}
finally
{
//cerramos lo que haga falta
if (st!=null)
{
try [Link]();
catch(SQLException e)
{
//...tratamos el error
© FUOC • PID_00275642 51 Programación mediante SQL

}
}
if (conn!=null)
{
try [Link]();
catch(SQLException e)
{
//...tratamos el error
}
}
}

Los errores provocados por la ejecución de sentencias SQL sí que los tratare-
mos. Si el problema es provocado por datos erróneos que el usuario ha intro-
ducido, es muy interesante dar un mensaje de error fácil de entender por parte
del usuario final. Y, sobre todo, hay que dar una segunda oportunidad.

Ejemplo
Nota
En este ejemplo leemos el código de un mensaje y el nombre de un usuario, y añadimos
una fila en la tabla Lecturas para reflejar que el usuario en cuestión ha leído el mensaje Tenéis disponible el códi-
en la fecha y la hora actuales. go del ejemplo en el fichero
[Link].
boolean correct=false;
while (!correct)
{
try
{
//leemos los datos
[Link]("CódigoMensaje:");

codigoMensaje=[Link]([Link]());
[Link]("NombreUsuario:");
nombreUsuario=[Link]();
ts = new Timestamp([Link]());

[Link]("INSERT INTO Lecturas "+


"VALUES("+codigoMensaje+",'"+nombreUsuario+"',"+
"to_timestamp('"+ts+"','YYYY-MM-DD HH24:MI'))");

correcto=true;
}
catch (SQLException e)
{
if([Link]().equals(FK_ERROR))
if([Link]().indexOf(FK_MENSAJES)!=-1)
[Link]("codigo de Mensaje no existe");
else if([Link]().indexOf(FK_USUARIOS)!=-1)
[Link]("nombre del usuario no existe");
else [Link]("Inesperado:"+[Link]());
else if ([Link]().equals(PK_ERROR))
[Link]("Error: mensaje ya leído");
else [Link]("Inesperado:"+[Link]());
}
catch (Exception e)
{
[Link]("Error leyendo los datos");
}
}
© FUOC • PID_00275642 52 Programación mediante SQL

3.8. Gestión de transacciones

Cada conexión JDBC puede mantener una sola transacción activa. Cuando
creamos una conexión, por defecto, se trabaja en modo autocommit. Esto sig-
nifica que cada sentencia SQL que ejecutemos constituye una transacción que
se inicia antes de llevar a cabo la sentencia y que, si no se genera ningún error,
acaba en commit al finalizar (de ahí el nombre autocommit).

Trabajar en modo autocommit es suficiente cuando cada transacción sólo in-


corpora una sentencia SQL o cuando sabemos que no pueden aparecer proble-
mas de concurrencia (por ejemplo, cuando estamos seguros de que sólo noso-
tros estamos trabajando con la BD o cuando sabemos que todas las operacio-
nes que se ejecutan sobre la misma son operaciones de sólo lectura). Cuando
tengamos que trabajar con transacciones que incorporen más de una sentencia
SQL o cuando tengamos dudas en relación con las operaciones que se ejecutan
de forma concurrente con las operaciones que llevan a cabo nuestras aplica-
ciones es recomendable que trabajemos con el modo autocommit desactivado.

Con el modo autocommit desactivado, las transacciones se acaban explícita-


mente llamando a los métodos commit() o rollback() y empiezan implí-
citamente al ejecutar la primera sentencia después de un commit o un roll-
back. Eso permite incluir diferentes sentencias SQL dentro de una transacción,
echarlas todas hacia atrás con un rollback o aceptarlas todas con un commit.

[Link](false);
...
try
{
... sentencias SQL que forman la transacción
[Link]();
}
catch (SQLException e)
{
[Link]();
}

En nuestra BD de referencia, tenemos una tabla para registrar los mensajes que
han leído los usuarios. Cada vez que un usuario lea un mensaje, deberemos
actualizar la tabla Lecturas con un código parecido al siguiente:

[Link]("INSERT INTO Lecturas "+


"VALUES("+codigoMensaje+",'"+nombreUsuario+"',"+
"to_timestamp('"+ts+"','YYYY-MM-DD HH24:MI'))");
rs=[Link]("SELECT * FROM Mensajes "+
"WHERE codigo_mensaje="+codigoMensaje);
if ([Link]())
[Link]([Link]("titulo")+
"--"+[Link]("texto"));

Este código parece correcto y, de hecho, lo es siempre que no se produzca nin-


gún error. Ahora bien, si nunca se produce un error después de haber insertado
la fila y antes de leer el mensaje, dejaremos la BD en un estado incorrecto.
© FUOC • PID_00275642 53 Programación mediante SQL

Para recuperar el estado, deberíamos tratar el error y hacer un DELETE de la


fila, pero este DELETE también podría fallar, y entonces se complicaría la re-
cuperación del estado.

La solución óptima para este escenario es la utilización de transacciones.

Ejemplo
Nota
En este ejemplo definimos una transacción que comprende las operaciones de registrar
la lectura del mensaje y de leer el mensaje en sí. Si conseguimos registrar la lectura, leer Tenéis disponible el códi-
el mensaje y mostrarlo por pantalla finalizamos la transacción con commit. En caso con- go del ejemplo en el fiche-
ro ex027MensajesLeidos
trario, deshacemos la transacción y volvemos al estado inicial.
[Link].
[Link](false);
...
try
{
[Link]("INSERT INTO Lecturas "+
"VALUES("+codigoMensaje+",'"+nombreUsuario+"',"+
"to_timestamp('"+ts+"','YYYY-MM-DD HH24:MI'))");
rs=[Link]("SELECT * FROM Mensajes "+
"WHERE codigo_mensaje="+codigoMensaje);
if ([Link]()) [Link](
[Link]("titulo")+ "--"+[Link]("texto"));
else throw new SQLException();
[Link]();
[Link]();
}
catch (SQLException e)
{
[Link]();
}

3.8.1. Duración de una transacción

Cuando trabajemos con el modo autocommit desactivado hemos de tener pre-


sente que la duración de una transacción conviene que sea tan corta como
fuere posible. O mejor dicho, interesa que no sea más larga de lo estrictamente
necesario.

Las sentencias SQL que se ejecutan durante una transacción pueden afectar a
diversas filas de diferentes tablas de la BD. Dependiendo de la gestión interna
que haga el SGBD, estas filas o tablas pueden quedar bloqueadas. Hasta el final
de la transacción el SGBD no libera las filas o las tablas del bloqueo. Mientras
tanto, sentencias SQL que se estén ejecutando de forma concurrente y que
afecten a alguna fila o tabla bloqueadas pueden quedar en pausa y, hasta que
no se acabe la transacción, no continúan la ejecución.

Es decir, cuanto más larga sea la ejecución de una transacción, más probabili-
dades habrá de que se detenga temporalmente la ejecución de otras aplicacio-
nes que accedan simultáneamente a la BD. Por esto es tan importante que las
transacciones duren estrictamente el tiempo necesario.
© FUOC • PID_00275642 54 Programación mediante SQL

Resumen

En este módulo didáctico, hemos presentado las principales tecnologías de


SQL programado que incorporan la mayoría de los SGBD relacionales del mer-
cado:

• SQL hospedado, que permite introducir sentencias SQL en medio del có-
digo y requiere una precompilación antes de ser compilado.
• SQL/CLI, interfaz estándar que permite acceder a la BD a partir de funcio-
nes.
• ODBC, evolución de SQL/CLI que permite cargar el driver en tiempo de
ejecución.
• OLE DB, evolución de ODBC que permite acceder a otros orígenes de datos,
no sólo a SGBD.
• DAO, RDO, ADO y [Link], evolución de OLE DB orientada a objetos
para sistemas operativos Microsoft.
• JDBC, evolución de OLE DB orientada a objetos y abierta a cualquier pla-
taforma en el entorno de programación Java.

Nos hemos centrado en JDBC y hemos visto sus diferentes versiones y sus
diversos tipos de drivers (implementaciones de la API JDBC de los diferentes
SGBD). También hemos estudiado la estructura básica de una aplicación: co-
nexión, ejecución de sentencias SQL, tratamiento de los resultados y desco-
nexión.

Asimismo, hemos presentado posibles alternativas tanto para la ejecución de


sentencias SQL como para el tratamiento de los resultados. Hemos visto la po-
sibilidad de hacer recorridos hacia atrás o aleatorios, de actualizar la BD sin
sentencias SQL, de definir sentencias SQL parametrizadas, de ejecutar procedi-
mientos almacenados, de gestionar los errores y de trabajar con transacciones.

Adicionalmente, hemos planteado una solución a situaciones habituales, co-


mo los valores nulos, las combinaciones, las claves autogeneradas, las consul-
tas anidadas y las consultas recursivas, así como la manera de prevenir ataques
por inyección SQL.
© FUOC • PID_00275642 55 Programación mediante SQL

Actividades
1.�Os proponemos una serie de actividades basadas en los ejemplos de Java del módulo. Veréis
que se ha utilizado el SGBD PostgreSQL en todos los ejemplos. Recomendamos que utilicéis
los drivers de tipo 4 que podéis encontrar en [Link]

Antes de empezar, acordaos de crear una BD en PostgreSQL y un usuario con suficientes


privilegios. Los ejemplos están hechos con una BD llamada bdMail y un usuario de nombre
usuario y contraseña 1234.
© FUOC • PID_00275642 56 Programación mediante SQL

Glosario
API f Sigla correspondiente a application program interface ('interfaz de programación de
aplicaciones').

BD f Sigla correspondiente a base de datos.

cursor m Estructura de SQL que permite que una aplicación sea capaz de recorrer las filas
del resultado de una consulta de SQL.

JDBC m (Java Database Connectivity). Marca comercial de Sun Microsystems que define
una API estándar para manipulación de SQL desde Java.

JDK m (Java Development Kit). Intérprete y entorno para desarrollos Java realizado por Sun
Microsystems.

programa driver m Programa de implementación particular del JDBC.

SAG-X/Open m Sigla correspondiente a SQL Access Group-X/Open.

SQL m Sigla correspondiente a structured query language.

SQL/CLI m Técnica de SQL programado que sigue un enfoque interpretado y que permite
a la aplicación el acceso a las BD gestionadas por el SGBD mediante llamadas a diferentes
subrutinas que están disponibles en bibliotecas.

SQL hospedado m Técnica de SQL programado que sigue un enfoque compilado y que
permite incorporar directamente las sentencias del lenguaje SQL (denominadas sentencias de
SQL hospedado) dentro de la aplicación, mezcladas con las sentencias propias del lenguaje de
programación (llamado lenguaje anfitrión).

variable puente con SQL f Mecanismo que ofrece el SQL hospedado para poder referen-
ciar las variables de la aplicación en las sentencias de SQL. Sirve tanto para pasar datos a una
sentencia de SQL hospedado como para recoger los datos que son resultado de una sentencia
de SQL hospedado.
© FUOC • PID_00275642 57 Programación mediante SQL

Bibliografía
Date, C. J.; Darwen, H. (1996). A guide to the SQL standard (4.ª ed.). Massachusetts: Addi-
son-Wesley.

ECPG - Embedded SQL in C (2010). [Documentación en línea: http://


[Link]/pgdocs/postgres/[Link]]

JDBC Driver de PostgreSQL (2010). [Documentación en línea: [Link]

PostgreSQL (2009). [Documentación en línea: [Link]

Sun Microsystems, Inc. (2010). JDBC. [Documentación en línea: http://


[Link]/javase/6/docs/technotes/guides/jdbc/[Link]]

También podría gustarte