Acceso A Datos - Tema 2

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 47

Acceso a datos.

Tema 2: Acceso a bases de datos


Por Nacho Cabanes

2.1. Tecnologas de conexin a bases de datos


Existen multitud de gestores de bases de datos. Posiblemente, los ms empleados a nivel de
empresa son Oracle y Microsoft SQL Server. En el sector de los servidores de Internet,
tambin tiene mucho peso MySQL. SQLite se emplea mucho para bases de datos de pequeo
tamao incrustadas en aplicaciones. Otro gestor muy conocido y que nos interesar en este
texto porque tiene ventajas adicionales, como ser de cdigo abierto, estar disponible para
distintos sistemas operativos y tener cierto soporte para Bases de Datos Orientadas a Objetos,
es PostgreSQL.

Existen distintas formas de conectar a gestores de bases de datos, como por ejemplo:

Bases de datos incrustadas (embedded), en las que la conexin entre el programa y


la base de datos es directa. Puede permitir la mayor velocidad en la comunicacin,
pero supone hacer cambios (quiz profundos) en el programa en caso de necesitar
conectar a una base de datos distinta, lo que no es deseable.

Como alternativa, es preferible usar un API (interfaz de programacin de aplicaciones)


genrico, que permita conectar de la misma forma a distintos gestores de bases de
datos, as como enviar peticiones y recibir las tuplas con las respuestas de una manera
uniforme. Uno de los estndares ms extendidos es ODBC (Open DataBase
Connectivity), usado habitualmente en C, C++, C# y Visual Basic.

ODBC es poco adecuado para ser usando desde Java, porque se basa en llamadas en
lenguaje C (lo que implica uso de punteros, que no existen en Java), y la conversin
puede suponer riesgos de seguridad y de falta robustez. Por eso, en Java se suele
emplear una alternativa totalmente basada en objetos, llamada JDBC (Java Database
Connectivity).

En la mayora de casos, se deber instalar un driver que permita acceder desde el lenguaje o
herramienta que se est utilizando al gestor de bases de datos deseados. En el mundo Java es
habitual llamar tambin conector a estos drivers.

Para alguna base de datos minoritaria en la que no existiera un driver JDBC pero s uno ODBC,
sera posible emplear un puente ODBC-JDBC (JDBC-to-ODBC bridge).

2.2. Instalacin y manejo bsico de PostgreSQL


Como nos resultar cmodo utilizar ms adelante PostgreSQL para ver las nociones bsicas de
bases de datos orientadas a objetos, ser ese mismo gestor el que empleemos para ver cmo
acceder a bases de datos relacionales desde Java.

Acceso a datos, Tema 2 (versin 1.01) - Pgina 1


2.2.1. Instalacin en Linux
Como ejemplo de instalacin en Linux, usaremos un Ubuntu 16.04.1, que es una versin de
soporte extenso (LTS), aunque su entorno grfico es relativamente incmodo (posiblemente
sera ms cmodo emplear una versin derivada de Ubuntu, como Linux Mint, que incluya
Synaptic o algn otro gestor de paquetes ms cmodo, as como un interfaz ms amigable, ya
sea KDE o algn derivado de Gnome como Cinnamon o Mate).

(Nota: Si empleas VirtualBox o algn otro sistema de virtualizacin para tus pruebas con
PostgreSQL, es recomendable que no te limites al disco duro virtual de 8 GB que posiblemente
te propondr VirtualBox, o se quedar al lmite de su capacidad, lo que puede suponer
problemas inesperados de ejecucin; es preferible reservar al menos 9 o 10 GB).

La forma de conseguir la versin ms reciente de PostgreSQL es no recurrir a los repositorios


de nuestro sistema Linux, sino a la pgina oficial de PostgreSQL:

http://www.postgresql.org/download

El apartado de instaladores para Ubuntu Linux y distribuciones derivadas comienza


recomendando usar los repositorios oficiales:

Pero desde esa misma pgina se puede descargar tambin el instalador interactivo:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 2


En el que slo ser necesario escoger la plataforma (por ejemplo, Linux de 64 bit):

En el momento de escribir este texto, la ltima versin estable es la 9.5.3.2, siendo todava
beta la 9.6, por lo que sera razonable escoger la 9.5.

Acceso a datos, Tema 2 (versin 1.01) - Pgina 3


Eso descargar un fichero con un nombre parecido a postgresql-9.5.3-2-linux-x64-run. El
primer paso ser hacerlo ejecutable, con:

chmod +x postgresql-9.5.3-2-linux-x64-run

Y luego lanzaramos la instalacin como superusuario, con

sudo su
./postgresql-9.5.3-2-linux-x64-run

Y entonces aparecer el asistente grfico:

Que en primer lugar nos preguntar el directorio de instalacin (y que podemos dejar con su
valor por defecto);

Y luego el directorio en el que se guardarn los datos

Acceso a datos, Tema 2 (versin 1.01) - Pgina 4


El administrador de la base de datos tiene como nombre postgres. El siguiente paso ser
escoger la contrasea que empleara ese administrador:

Y, dado que se trata de una arquitectura cliente-servidor, deberemos elegir un puerto a travs
del que se establecer esa comunicacin (se nos propone el 5432, y es razonable dejarlo as);

Acceso a datos, Tema 2 (versin 1.01) - Pgina 5


Finalmente, podramos cambiar la configuracin regional.

Y comenzar la instalacin en s.

El proceso debera ser bastante rpido

Y al final se nos preguntar si queremos lanzar Stack Builder para instalar herramientas
adicionales. Ya que vamos a conectar a PostgreSQL desde Java, ser razonable decir que s.

Acceso a datos, Tema 2 (versin 1.01) - Pgina 6


Primero deberemos escoger la instalacin que queremos personalizar (no es difcil, slo
tenemos una):

Y luego indicaremos los extras que deseamos, y que en principio sern slo un driver de
base de datos, el que usa el estndar JDBC:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 7


Se nos pedir confirmacin:

Se descargarn los ficheros necesarios:

Y, salvo que indiquemos que queremos saltar la instalacin, se instalarn:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 8


La instalacin, igual que la del propio PostgreSQL, comenzar con una ventana de bienvenida

Luego podremos escoger la carpeta de instalacin

Y comenzar la instalacin en s:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 9


Si todo es correcto, pronto tendremos la confirmacin de que ha terminado:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 10


El sistema est listo para usar. PostgreSQL incluye un cliente sencillo desde el que podramos
dar las primeras rdenes. Se llama psql y en Ubuntu quiz no est a la vista, pero podramos
acceder a l mediante la opcin buscar:

Se nos preguntar a qu servidor conectar (se nos propone localhost, que es el correcto, al
estar trabajando desde el propio equipo que acta de servidor), base de datos (postgres es
la nica que existe por ahora), puerto (5432 es el habitual, si no lo hemos cambiado), usuario
(por ahora slo existe el administrador, postgres) y contrasea (la que hayamos escogido
para el administrador).

Vamos a crear una nueva base de datos muy sencilla, que llamaremos datos1, para hacer las
primeras pruebas:

CREATE DATABASE datos1;

Acceso a datos, Tema 2 (versin 1.01) - Pgina 11


Aun as, este cliente (psql) no es cmodo de usar, no slo porque sea un entorno de texto, sino
que porque es habitual que ni siquiera permita copiar y pegar rdenes en SQL que hayamos
preparado con un editor externo. Por eso, en la mayora de las pruebas emplearemos
pgAdmin, que tambin se instala junto con PostgreSQL, y que, en el caso de Ubuntu,
podemos buscar:

Al entrar, nos aparecer un servidor de PostgreSQL al que podremos conectar usando el botn
derecho del ratn:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 12


Bastar con indicar la contrasea de administrador que habamos elegido:

Y se nos mostrar que en ese servidor hay dos bases de datos: la que crea el propio sistema
(postgres) y la que hemos creado nosotros desde psql (datos1).

Tras hacer clic en nuestra base de datos, podremos pulsar el botn SQL para dar rdenes en
este lenguaje:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 13


Nuestra primera orden podra ser la de creacin de una primera tabla, todava muy simple,
slo con campos de texto y sin clave primaria:

CREATE TABLE personas (


nombre VARCHAR(50),
email VARCHAR(40)
);

Acceso a datos, Tema 2 (versin 1.01) - Pgina 14


Al pulsar el botn verde (Execute query) se lanzar la orden que hemos tecleado. Si no hay
ningn error, podremos insertar tambin algn dato:

INSERT INTO personas VALUES ('Persona Uno','pers1@correo.com');


INSERT INTO personas VALUES ('Persona Dos','pers2@correo.com');

Y podramos usar tambin la orden SELECT para comprobar los datos existentes:

SELECT * FROM personas;

Acceso a datos, Tema 2 (versin 1.01) - Pgina 15


Tambin se puede acceder de forma visual a los datos almacenados. Para ello, en el panel
izquierdo, dentro de la base de datos datos1, podemos acceder a sus esquemas (schemas),
y dentro de los que son pblicos (public) debera aparecer la lista de tablas (tables), que
por ahora slo contendr personas.

En la barra de herramientas, junto al botn SQL, tendremos otro que permitir ver los datos en
el objeto escogido:

Eso s, como es una primera tabla tan sencilla que no tiene ni siquiera una clave primaria se
nos mostrar un aviso de que podremos ver los datos pero no editarlos:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 16


Y, efectivamente, ah deberan estar los datos:

Ejercicios propuestos

2.2.1.1: Elige otra distribucin de Linux ms amigable (por ejemplo, Linux Mint) e instala
PostgreSQL en ella.

2.2.1.2: Crea una base de datos llamada pruebas1. Crea en ella una tabla programas para
guardar una coleccin de software. En una primera aproximacin guardaremos slo tres
campos: un cdigo, un nombre y el nombre del dispositivo de copia de seguridad en el que
est el respaldo de ese programa. Introduce dos datos de ejemplo y comprueba que se han
almacenado correctamente.

2.2.2. Instalacin en Windows


Los pasos para instalar PostgreSQL en Windows son bsicamente los mismos que para Linux:
descargar desde su web oficial, ejecutar el asistente de instalacin y, ya que vamos a conectar
desde Java, usar Stack Builder para instalar el correspondiente conector.

La nica diferencia es que PostgreSQL, psql y pgAdmin estarn todos accesibles fcilmente
desde el men de Inicio.

Acceso a datos, Tema 2 (versin 1.01) - Pgina 17


Y el manejo de psql y pgAdmin ser idntico al de la versin para Linux, incluso con las mismas
limitaciones (como el no poder pegar sobre psql, por lo que ser preferible usar pgAdmin
para la mayora del trabajo real).

Acceso a datos, Tema 2 (versin 1.01) - Pgina 18


Ejercicios propuestos

2.2.2.1: Instala PostgreSQL en Windows, ya sea en mquina real o en mquina virtual (hacerlo
en mquina virtual har que el manejo de PostgreSQL sea ms lento pero que no lo sea el
equipo anfitrin cuando no ests empleando PostgreSQL, por lo que puede ser una opcin ms
recomendable en un ordenador moderno).

2.2.2.2: Crea una base de datos llamada pruebas1. Crea en ella una tabla programas para
guardar una coleccin de software. En una primera aproximacin guardaremos slo tres
campos: un cdigo, un nombre y el nombre del dispositivo de copia de seguridad en el que
est el respaldo de ese programa. Introduce dos datos de ejemplo y comprueba que se han
almacenado correctamente.

2.2.3. Ejercicios de repaso de SQL


Igual que necesitas tener soltura programando, tambin debers tener al un cierto nivel de
SQL. En el Aula Virtual tienes material de repaso adicional, que te puede ayudar a coger /
recuperar base. Como forma de que puedas evaluar tus conocimientos y ver si necesitas
repasar, aqu tienes varios ejercicios progresivos.

2.2.3.1: Crea una base de datos llamada BDTareas, que usaremos para anotar tareas. En ella
en principio habr una nica tabla llamada tareas, con dos campos: el cdigo de la tarea
(numrico, que ser clave primaria) y la descripcin de la tarea. Aade dos tareas: la 1 ser
Repasar acceso a datos y la 2 ser Instalar NetBeans.

2.2.3.2: Modifica la tabla para aadir dos campos (al final de los ya existentes): uno ser la
fecha prevista para la tarea y otro ser si est terminada o no.

2.2.3.3: Modifica la primera tarea para que tenga fecha de maana y no est terminada, y la
segunda para que tenga fecha de pasado maana y tampoco est terminada. Aade dos tareas
ms.

2.2.3.4: Muestra las tareas cuyo cdigo sea inferior a diez.

2.2.3.5: Muestra las tareas que contengan la palabra datos.

2.2.3.6: Muestra el cdigo de la ltima tarea (el mayor cdigo).

2.2.3.7: Muestra la cantidad de tareas que hay para cada da.

2.2.3.8: Muestra el cdigo de la tarea ms antigua (menor cdigo) que contenga la palabra
NetBeans.

2.2.3.9: Muestra la descripcin y fecha de las tareas cuyo cdigo es inferior al cdigo de la
tarea ms antigua (menor cdigo) que contenga la palabra NetBeans.

2.2.3.10: Crea una nueva tabla categorias. Cada categora tendr dos campos: un cdigo
(texto, clave primaria) y la descripcin. Aade la categora E, Estudios y la categora O,

Acceso a datos, Tema 2 (versin 1.01) - Pgina 19


Ocio. Modifica la tabla tareas para aadir el cdigo de categora (codcateg). Asigna la
categora E a las tareas 1 y 2.

2.2.3.11: Muestra la descripcin de todas las tareas, junto con la descripcin de la categora a
la que pertenecen, para todas las categoras de las que se haya indicado descripcin.

2.2.3.12: Muestra la descripcin de todas las tareas, junto con la descripcin de la categora a
la que pertenecen o NULL en caso de que no se haya indicado esa categora.

2.2.3.13: Muestra la descripcin de todas las tareas, junto con la descripcin de la categora a
la que pertenecen o (Ninguna) en caso de que no se haya indicado esa categora.

2.2.3.13: Muestra la descripcin de todas las tareas, junto con la descripcin de la categora a
la que pertenecen, para todas las tareas (puede que alguna categora aparezca como NULL) y
para todas las categoras (y puede que alguna tarea aparezca como NULL, si esa categora no
tiene tareas asignadas).

Acceso a datos, Tema 2 (versin 1.01) - Pgina 20


2.3. Conexin JDBC a PostgreSQL
A nivel genrico, los pasos necesarios sern:

Abrir una conexin.


Preparar un objeto de tipo Statement, que representar a una (o varias) sentencia(s)
SQL.
A travs de ese Statement, lanzar consultas y obtener resultados.
Usar excepciones para gestionar los posibles errores.

Ya como aplicacin real, los pasos necesarios para conectar y obtener un bloque de
informacin son los siguientes:

Usar Class.forName para cargar el driver que se haya escogido. En nuestro caso ser:

Class.forName("org.postgresql.Driver");

Establecer una conexin, indicando la URL del servidor, el usuario que conecta y la
contrasea. Ser algo como:

Connection con = DriverManager.getConnection(url, usuario, password);

Preparar una sentencia que se ejecutar sobre esa conexin:

Statement statement = con.createStatement();

La orden SQL concreta a ejecutar se indicar en un mtodo executeQuery. Si se trata


de una consulta que devuelva datos, stos estarn accesibles a travs de un conjunto
de resultados (ResultSet):

ResultSet rs = statement.executeQuery("SELECT * FROM personas");

Ese conjunto de resultados se recorrer de forma secuencial: rs.next() ser


verdadero si hay ms datos en ese conjunto de resultados:

while (rs.next()) { ... }

Si cada fila del resultado est formada por varios campos, podemos obtener el valor de
cada uno de ellos con rs.getString( x ), donde x puede ser un nmero (la posicin de
esa columna en la consulta, comenzando con 1) o un nombre de campo (indiferente
de maysculas o minsculas):

String nombre = rs.getString(1);


String nombre = rs.getString("NOMBRE");
(si un campo es numrico, no hace falta leerlo como cadena y posteriormente
convertirlo a nmero, sino que se puede emplear otros getters como .getInt(1).

Acceso a datos, Tema 2 (versin 1.01) - Pgina 21


Finalmente, se deber cerrar el ResultSet y la conexin:

rs.close();
con.close();

En nuestros primeros ejemplos no haremos ningn control de errores, nos limitaremos a


permitir que main lance la correspondiente excepcin (lo que provocara el final de la
ejecucin).

As, un ejemplo completo podra ser:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class ConsultaBD1 {

public static void main(String[] args) throws ClassNotFoundException,


SQLException {
Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "pwd";
Connection con = DriverManager.getConnection(url, usuario, password);

Statement statement = con.createStatement();

String sentenciaSQL = "SELECT * FROM personas ORDER BY nombre";


ResultSet rs = statement.executeQuery(sentenciaSQL);

System.out.println("Nombre" + "\t" + "E-mail");


System.out.println("-----------------------------------------");

while (rs.next()) {
System.out.println(rs.getString(1) + "\t " + rs.getString(2));
}
rs.close();
con.close();
}
}

(En un caso real, es habitual que en vez de localhost se indique la direccin IP del servidor,
que no tiene por qu coincidir con el cliente, por ejemplo 192.168.1.2).

Es posible que al lanzar este programa obtengamos el mensaje de error:

Exception in thread "main" java.lang.ClassNotFoundException:


org.postgresql.Driver
porque ese fichero (el driver JDBC de PostgreSQL) no se encuentre en el class path (la ruta de
bsqueda para clases auxiliares del programa).

Acceso a datos, Tema 2 (versin 1.01) - Pgina 22


La solucin ms razonable es emplear un entorno integrado, como NetBeans o Eclipse, en el
que se pueda aadir una librera al proyecto con facilidad. Como alternativa si se compila
desde el terminar, es posible indicar la ruta del driver JDBC en el momento de compilar el
programa, usando la opcin -cp (o -classpath):

javac -cp .:/opt/PostgreSQL/pgJDBC/postgresql-9.4.1208.jar ConsultaBD1.java

Y tambin a la hora de lanzar el programa:

java -cp .:/opt/PostgreSQL/pgJDBC/postgresql-9.4.1208.jar ConsultaBD1

Lo que debera dar como resultado:

Nombre E-mail
-----------------------------------------
Persona Dos pers2@correo.com
Persona Uno pers1@correo.com

Desde NetBeans tambin se podr crear con facilidad proyectos que accedan a bases de datos.

Podramos descargar NetBeans desde su pgina oficial, o bien usar el Centro de Software
Ubuntu:

Incluso en una distribucin relativamente poco amigable para usuarios avanzados como es el
Ubuntu bsico, es fcil que podamos instalar NetBeans en un par de clics:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 23


Como es habitual, se nos pedir nuestra contrasea, para ver si tenemos permisos suficientes
para instalar aplicaciones:

Y entonces comenzar la descarga en segundo plano, que podremos notar por la barra de
progreso que mostrar el icono de NetBeans en el panel izquierdo de la pantalla:

En un instante, NetBeans quedar listo para comenzar:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 24


Y deberemos crear un nuevo proyecto (New Project)

El proyecto ser de tipo Aplicacin Java:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 25


Y el nombre podra ser algo como BaseDeDatos1:

Un fuente como el anterior debera no mostrar errores en el momento de teclear (salvo quiz
la necesidad de incluirlo dentro de un package):

Acceso a datos, Tema 2 (versin 1.01) - Pgina 26


Pero si el programa emplea alguna biblioteca (library) externa, es muy probable que en el
momento de compilar s obtengamos mensajes de error:

La forma de aadir una biblioteca es pulsar el botn derecho sobre el proyecto para acceder a
sus Propiedades (Properties):

Acceso a datos, Tema 2 (versin 1.01) - Pgina 27


Y ah tendremos una opcin Libraries, con un botn Add library:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 28


Es muy posible que entre las bibliotecas disponibles se encuentre una llamada PostgreSQL
JDBC driver:

Si no fuera el caso, o como alternativa, se puede pulsar el botn Add JAR/folder en vez de
Add library, para no aadir desde una biblioteca del sistema, sino desde un fichero
descargado:

En el caso de Windows, basta con descargar NetBeans (en su versin para JavaSE) desde su
pgina oficial, instalarlo. La forma de crear un proyecto y de aadir bibliotecas es la misma que
en Linux.

Ejercicios propuestos

2.3.1: Comprueba desde Java que los dos datos que has introducido en el apartado 2.2.2 son
accesibles.

Acceso a datos, Tema 2 (versin 1.01) - Pgina 29


2.4. Sentencias de definicin de datos
Hemos visto que se pueden lanzar consultas que devuelvan datos con .executeQuery. Si se
trata de consultas de definicin de datos (parte del DDL, Data Definition Language), como
CREATE, ALTER o DROP, se deber emplear .executeUpdate, as:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class ConsultaBD2 {

public static void main(String[] args) throws ClassNotFoundException,


SQLException {
Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "pwd";
Connection con = DriverManager.getConnection(url, usuario, password);

Statement statement = con.createStatement();

String sentenciaSQL = "CREATE TABLE personas2 ( "+


"codigo VARCHAR(4) PRIMARY KEY, "+
"nombre VARCHAR(50), "+
"email VARCHAR(40) "+
");";
statement.executeUpdate(sentenciaSQL);

con.close();
}
}

Ejercicios propuestos

2.4.1: Crea desde Java una nueva tabla dentro de la base de datos prueba1, que permita
guardar los datos sobre cada dispositivo de copia de seguridad: cdigo, nombre y situacin.

2.4.2: Desde Java, aade a la tabla de programas un nuevo campo, que indicar su tamao,
medido en KB.

2.5. Sentencias de manipulacin de datos


Al igual que para las consultas de modificacin de datos, las de manipulacin de datos (parte
del DML, como INSERT, UPDATE o DELETE) no devolvern un conjunto de datos, y se debern
llamar usando .executeUpdate. Eso s, en este caso puede merecer la pena comprobar el
valor devuelto, que es un nmero entero, que representa la cantidad de filas afectadas (y que
en el caso de las sentencias de definicin de datos se poda ignorar, porque siempre vale cero):

import java.sql.Connection;
import java.sql.DriverManager;

Acceso a datos, Tema 2 (versin 1.01) - Pgina 30


import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class ConsultaBD3 {

public static void main(String[] args) throws ClassNotFoundException,


SQLException {
Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "pwd";
Connection con = DriverManager.getConnection(url, usuario, password);

Statement statement = con.createStatement();

String sentenciaSQL = "INSERT INTO personas VALUES "+


"('Persona Tres','pers3@correo.com'), "+
"('Persona Cuatro','pers4@correo.com');";
int cantidad = statement.executeUpdate(sentenciaSQL);

System.out.println("Datos insertados: " + cantidad);


con.close();
}
}

La salida de este programa debera ser:

Datos insertados: 2

Y si ahora volvemos a lanzar el programa, que mostraba todos los datos, deberan mostrase 4:

Nombre E-mail
-----------------------------------------
Persona Cuatro pers4@correo.com
Persona Dos pers2@correo.com
Persona Tres pers3@correo.com
Persona Uno pers1@correo.com

(La otra orden habitual que forma parte del DML es SELECT, que, como ya sabemos, s
devuelve en general un conjunto de datos, por lo que se lanza con .executeQuery).

Ejercicios propuestos

2.5.1: Crea desde Java un programa que aada tres filas en la tabla de dispositivos de copia de
seguridad. Comprueba el valor devuelto, para asegurarte de que todas se han insertado
correctamente.

Acceso a datos, Tema 2 (versin 1.01) - Pgina 31


2.6. Ejecucin de procedimientos
PostgreSQL, al igual que Oracle y que (con ms limitaciones) MySQL, permite definir funciones
usando su lenguaje de programacin propio, PL/pgSQL. El formato bsico de una funcin ser:

CREATE [OR REPLACE] FUNCTION function_name (arguments)


RETURNS return_datatype AS $variable_name$
DECLARE
declaration;
[...]
BEGIN
< function_body >
[...]
RETURN { variable_name | value }
END; LANGUAGE plpgsql;

A este tipo de funciones se les suele llamar tambin procedimientos almacenados (Stored
Procedures) y se pueden llamar luego desde otros lenguajes en el equipo cliente. De hecho, es
una de las formas de trabajar que se suelen recomendar para evitar ataques de inyeccin de
SQL.

Como ejemplo de funcin que reciba un parmetro y que devuelva un valor, podemos crear
una que diga la cantidad de datos que hay en la tabla personas cuyo nombre sigue un cierto
patrn:

CREATE OR REPLACE FUNCTION CantidadPersonas (comodin VARCHAR) RETURNS INTEGER


AS
$$
DECLARE
cantidad INTEGER;
BEGIN
SELECT COUNT(*) INTO cantidad FROM personas WHERE nombre LIKE comodin;
RETURN cantidad;
END;
$$
LANGUAGE plpgsql;

Si, despus de crear esa orden, tecleamos en pgAdmin

select CantidadPersonas('Per%');

deberamos obtener como respuesta

cantidadpersonas
4

mientras que para

select CantidadPersonas('N%');

la respuesta sera

cantidadpersonas
0

Acceso a datos, Tema 2 (versin 1.01) - Pgina 32


(Dos curiosidades, antes de seguir adelante: esta funcin realiza una bsqueda sensible a
maysculas y minsculas; adems, el texto de bsqueda se debe indicar entre comillas
simples, no dobles).

Ahora es posible crear un programa en Java que llame a esa funcin, que le pase parmetros y
que obtenga sus resultados. Para ello, emplearemos un CallableStatement:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.CallableStatement;
import java.sql.Types;

public class EjemploDeStoredProcedure {

public static void main(String[] args)


throws ClassNotFoundException, SQLException{

Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "n";
Connection con = DriverManager.getConnection(url, usuario, password);

CallableStatement cStmt = con.prepareCall(


"{call CantidadPersonas(?)}");
cStmt.registerOutParameter(1, Types.INTEGER);
cStmt.setString(1, "Per%");

cStmt.execute();
int resultado = cStmt.getInt(1);
System.out.println("Resultado: " + resultado);
con.close();
}
}

Si la funcin es ms sencilla, como la siguiente, que no tiene parmetros, quiz no se pueda


llamar con un callable statement, porque obtengamos un error diciendo que intentamos
acceder a la columna 1 de 0 columnas disponibles en registerOutParameter. Esto no es
grave, porque no se tratara de algo susceptible de ataques como los de inyeccin de SQL, por
lo que se podra acceder simplemente con un SELECT:

CREATE OR REPLACE FUNCTION CantidadTotalPersonas() RETURNS INTEGER AS


$$
DECLARE cantidad INTEGER;
BEGIN
SELECT COUNT(*) INTO cantidad FROM personas;
RETURN cantidad;
END;
$$
LANGUAGE plpgsql;

de modo que un programa de prueba podra ser:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 33


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.CallableStatement;
import java.sql.Statement;
import java.sql.Types;

public class LlamadaAFuncionSencilla {

public static void main(String[] args) throws ClassNotFoundException,


SQLException{

Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "n";
Connection c = DriverManager.getConnection(url, usuario, password);

Statement s = c.createStatement();
String consulta = "select CantidadTotalPersonas()";
ResultSet rs = s.executeQuery(consulta);
rs.next();
System.out.println("Resultado: " + rs.getInt(1));
c.close();
}
}

De hecho, esta funcin es tan sencilla que no necesita declarar variables ni usar ninguna
caracterstica de PL/pgSQL, por lo que se podra usar SQL simple (aunque en eso caso, se nos
avisara de que el resultado no debe ser un INTEGER sino un BIGINT) y el delimitador no
necesitara ser algo que comience y acabe con $, sino que bastara con una comilla simple:

CREATE OR REPLACE FUNCTION CantidadTotalPersonas() RETURNS BIGINT AS '


SELECT COUNT(*) FROM personas;
' LANGUAGE sql;

Si una funcin tiene que devolver dos o ms valores, una primera solucin (antinatural) puede
ser hacerlo en forma de vector (array), un tipo de datos que veremos con ms detalle en el
siguiente tema, pero esto hara que acceder a esos datos no fuera tan sencillo:

CREATE OR REPLACE FUNCTION HolaAdios() RETURNS TEXT[] AS


$$
BEGIN
RETURN ARRAY['hola','adios'];
END;
$$
LANGUAGE plpgsql;

Por ejemplo, el segundo dato de ese array se obtendra con:

select HolaAdios[2] from HolaAdios();

Acceso a datos, Tema 2 (versin 1.01) - Pgina 34


Cuando se trata de devolver muchos datos, lo habitual es que se trate de varios registros, y en
ese caso el tipo de datos sera un conjunto de (setof) datos del tipo base, como en este
ejemplo:

CREATE OR REPLACE FUNCTION ListaPersonas (comodin VARCHAR)


RETURNS SETOF personas AS '
SELECT * FROM personas WHERE nombre LIKE comodin;
' LANGUAGE sql;

Que se podra lanzar desde SQL con

SELECT * FROM ListaPersonas('%t%');

Si hubiera que realizar operaciones sobre cada datos, podra ser ms interesante usar
PL/pgSQL en vez de SQL, as:

CREATE OR REPLACE FUNCTION ListaPersonas2 (comodin VARCHAR)


RETURNS SETOF personas AS
$$
DECLARE
registro personas;
BEGIN
FOR registro IN SELECT * FROM personas WHERE nombre LIKE comodin LOOP
registro.nombre = UPPER(registro.nombre);
RETURN NEXT registro;
END LOOP;
END;
$$
LANGUAGE plpgsql;

(La manipulacin de este ejemplo, convertir a maysculas, se podra haber hecho


perfectamente desde SQL, pero pretende mostrar la forma de recorrer, modificar y devolver
los registros desde PL/pgSQL).

Se podra acceder desde SQL de igual forma que en la funcin anterior, mientras que un
programa Java que recorriese el conjunto resultado y lo mostrase no podra usar
CallableStatement, sino PreparedStatment, que es una clase de ms bajo nivel, ms
dependiente del gestor de base de datos concreto, pero a cambio s permite devolver un
conjunto de datos (con el inconveniente adicional de que los datos se reciben como un nico
string):

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.sql.Types;

public class EjemploDeStoredProcedure {

public static void main(String[] args)


throws ClassNotFoundException, SQLException{

Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "n";

Acceso a datos, Tema 2 (versin 1.01) - Pgina 35


Connection con = DriverManager.getConnection(url, usuario, password);

PreparedStatement pStmt = con.prepareStatement(


"SELECT ListaPersonas2(?)");

pStmt.setString(1, "%t%");
ResultSet rs = pStmt.executeQuery();

while (rs.next()) {
System.out.println("Detalles: " + rs.getString(1));
}
rs.close();
con.close();
}
}

Cuyo resultado sera:

Detalles: ("PERSONA CUATRO",pers4@correo.com)

Ejercicios propuestos:

2.6.1: Crea una funcin en PL/pgSQL, que dado el cdigo de un dispositivo de copia de
seguridad, devuelva su nombre.

2.6.2: Crea un programa en Java que pida al usuario el cdigo de un dispositivo de copia de
seguridad y muestre su nombre, usando la funcin anterior.

2.6.3: Crea una funcin en PL/pgSQL, que dada una ubicacin, devuelva la cantidad de
dispositivos de copia de seguridad que hay almacenados en ella.

2.6.4: Crea un programa en Java que lea los nombres de todas las ubicaciones, los almacene en
una lista y luego compruebe la cantidad de dispositivos que hay almacenados en cada una,
usando la funcin anterior, para poder mostrar aquellas ubicaciones que no estn vacas.

2.7. Gestin de errores


Hasta ahora, no estamos tratando los errores de ninguna forma, simplemente estamos
dejando que main se interrumpa lanzando la correspondiente excepcin;

public static void main(String[] args)


throws ClassNotFoundException, SQLException

Una alternativa un poco ms amigable es usar un bloque try-catch para atrapar el error,
informar al usuario y permitir que el programa prosiga a pesar del problema.

Una primera aproximacin ser simplemente mostrar el error existente


usando"printStackTrace(). Por ejemplo, podemos crear una versin alternativa del programa
ConsultaBD2, que creaba una tabla, pero aadiendo esta vez la posibilidad de que avise en

Acceso a datos, Tema 2 (versin 1.01) - Pgina 36


caso de que no se haya podido crear la tabla (cosa que ocurrir en esta segunda ocasin, en
que la tabla ya existe):

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class ConsultaBD2b {

public static void main(String[] args) {

try {

Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "pwd";
Connection con = DriverManager.getConnection(
url, usuario, password);

Statement statement = con.createStatement();

String sentenciaSQL = "CREATE TABLE personas2 ( "+


"codigo VARCHAR(4) PRIMARY KEY, "+
"nombre VARCHAR(50), "+
"email VARCHAR(40) "+
");";
statement.executeUpdate(sentenciaSQL);
con.close();
}
catch (ClassNotFoundException ce) { ce.printStackTrace(); }
catch (SQLException se) { se.printStackTrace(); }
System.out.println("Terminado!");
}
}

El resultado de este programa sera algo como:

org.postgresql.util.PSQLException: ERROR: relation "personas2" already exists


at
org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImp
l.java:2284)
at
org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java
:2003)
at
org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:200)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:424)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:321)
at org.postgresql.jdbc.PgStatement.executeUpdate(PgStatement.java:297)
at ConsultaBD2b.main(ConsultaBD2b.java:27)
Terminado!

Acceso a datos, Tema 2 (versin 1.01) - Pgina 37


Como se puede ver, aparece el mensaje Terminado! al final, lo que indica que la ejecucin no
se ha interrumpido. Aun as, en general, ser preferible analizar los posibles errores por
separado, de modo que los mensajes de error (y la posible recuperacin de stos) sean ms
especficos:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class ConsultaBD2c {

public static void main(String[] args) {

try {

Class.forName("org.postgresql.Driver");

String url = "jdbc:postgresql://localhost:5432/datos1";


String usuario = "postgres";
String password = "pwd";
Connection con = DriverManager.getConnection(
url, usuario, password);

Statement statement = con.createStatement();

String sentenciaSQL = "CREATE TABLE personas2 ( "+


"codigo VARCHAR(4) PRIMARY KEY, "+
"nombre VARCHAR(50), "+
"email VARCHAR(40) "+
");";
statement.executeUpdate(sentenciaSQL);
con.close();
}
catch (ClassNotFoundException ce) {
System.out.println("PostgreSQL no accesible");
}
catch (SQLException se) {
System.out.println("No se ha podido crear la tabla");
}
System.out.println("Terminado!");
}
}

que mostrara un mensaje mucho ms razonable:

No se ha podido crear la tabla


Terminado!

De hecho, incluso una SQLException es algo demasiado genrico, de modo que en un caso
real se podran analizar los valores de getMessage() y de getErrorCode() para saber el tipo
de error concreto que ha existido (en general, esto puede ser preferible a usar varios bloques
try-catch en un fragmento de cdigo tan pequeo, para separar la lgica de programa de la
lgica de la gestin de errores, que es lo que pretende el uso de try-catch.

Acceso a datos, Tema 2 (versin 1.01) - Pgina 38


Ejercicios propuestos

2.7.1: Crea un programa que pida al usuario datos de programas y los aada a la base de datos.
En caso de que se introduzca un cdigo repetido, el programa debe comportarse de forma
amigable: no interrumpir la ejecucin, sino pedir un nuevo cdigo y guardar los datos
(supondremos que el usuario sabe qu datos va a introducir pero se est inventando los
cdigos, de modo que el problema no se tratar de que ha tratado de volver a introducir una
ficha que ya estaba, sino que es una ficha nueva, en la que por error ha reutilizado un cdigo
que ya haba empleado, pero el resto de datos s son correctos).

Acceso a datos, Tema 2 (versin 1.01) - Pgina 39


2.8. Complemento para el mundo real: JUnit
Las pruebas ayudan a validar el comportamiento de un fragmento de software. Hay pruebas
de muy diversos tipos. Por ejemplo, son muy habituales las pruebas de correccin, que
consisten en tratar de garantizar que el programa obtiene resultados correctos en la mayor
cantidad posible de circunstancias. Otras pruebas frecuentes en sistemas reales que pueden
estar sometidos a mucha carga, son las pruebas de rendimiento, en las que se fuerza la
cantidad de peticiones por segundo a un sistema, el tamao de los datos a manipular o
cualquier otra circunstancia que podra provocar que es sistema no respondiera correctamente
en su entorno real de funcionamiento.

A nivel de tamao del software a probar, tambin se suele distinguir entre pruebas de
unidad (por ejemplo, para una clase concreta), de integracin (cuando se unen varias clases
para dar lugar a un programa) y de sistema (para cuando todo el conjunto del software ya est
en funcionamiento). Tambin se suele hablar de pruebas de aceptacin del usuario, cuando
comprueba que el software cumple con todos los requisitos que se haban especificado.

Nosotros daremos un repaso rpido a una forma de realizar pruebas de correccin


automatizadas en Java, empleando una biblioteca llamada JUnit.

Partiremos de una clase cuyo planteamiento no es correcto. Ser una clase Comparador, con
un nico mtodo MayorDe3, que devuelva el mayor de tres nmeros enteros que se le
pasen como parmetros:

// Ejemplo de clase a la que aplicar pruebas

class Comparador {
public static int mayorDe3(int a, int b, int c) {
int resultado = a;
if ((b > a) && (b > c)) resultado = b;
if ((c > a) && (c > b)) resultado = c;
return resultado;
}
}

Un programa que utilizase esa clase podra ser as:

// Ejemplo de clase que usa Comparador

public class UsaComparador {

public static void main(String[] args) {


int n1 = 3, n2 = 0, n3 = -5;

int mayor = Comparador.mayorDe3(n1, n2, n3);

System.out.println("El mayor es " + mayor);


}
}

Acceso a datos, Tema 2 (versin 1.01) - Pgina 40


Este programa escribe en pantalla el resultado 3, por lo que parece ser correcto. Pero
podemos crear una batera de pruebas ms detallada, con distintos valores y en distinto orden,
para tener ms certeza.

Con JUnit, cada prueba ser tpicamente una funcin void, precedida por @Test y con una
llamada a assertEquals que permita verificar si el resultado de una cierta llamada a una
funcin es igual a un cierto valor:

public class PruebaDeComparador {


@Test
public void MayorAMenor() {
// Comprobar si el mayor de (3,2,1) vale 3
assertEquals( Comparador.mayorDe3(3, 2, 1), 3);
}
}

En el caso de que utilicemos NetBeans, el primer paso ser crear la clase que queremos
probar. Despus se deber aadir al proyecto la biblioteca JUnit:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 41


A continuacin, podremos pulsar el botn derecho sobre la clase para la que queremos hacer
pruebas, escoger la opcin Tools y, dentro de ella, Create / Update tests.

Se nos propondr crear una clase con el mismo nombre que la nuestra, pero terminado en
Test. Se nos propondr generar una serie de cdigo para nosotros. Al tratarse de una nica
clase, la propia documentacin de NetBeans dice que se puede desmarcar las opciones de
generar un Test initializer y un Test finalizer:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 42


El resultado ser una clase como esta:

package comparador;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;

public class ComparadorTest {

public ComparadorTest() {
}

@BeforeClass
public static void setUpClass() {
}

@AfterClass
public static void tearDownClass() {
}

/**
* Test of main method, of class Comparador.
*/
@Test
public void testMain() {
System.out.println("main");
String[] args = null;
Comparador.main(args);

Acceso a datos, Tema 2 (versin 1.01) - Pgina 43


// TODO review the generated test code and remove the default call to
fail.
fail("The test case is a prototype.");
}

/**
* Test of mayorDe3 method, of class Comparador.
*/
@Test
public void testMayorDe3() {
System.out.println("mayorDe3");
int a = 0;
int b = 0;
int c = 0;
int expResult = 0;
int result = Comparador.mayorDe3(a, b, c);
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call to
fail.
fail("The test case is a prototype.");
}

En nuestro caso, que no existe un main que probar, podemos eliminar el mtodo
testMain. Adems, en testMayorDe3 podemos dar un poco ms de sentido al cuerpo del
mtodo, dando valores a a, b, y c, as como el resultado esperado a expResult, y
eliminar la orden fail de ejemplo que apareca al final para recordarnos que por ahora slo se
trataba de un prototipo:

/**
* Test of mayorDe3 method, of class Comparador.
*/
@Test
public void testMayorDe3() {
System.out.println("mayorDe3");
int a = 3;
int b = 2;
int c = 1;
int expResult = 3;
int result = Comparador.mayorDe3(a, b, c);
assertEquals(expResult, result);
}

Para lanzar las pruebas, bastar ir al men Run y escoger la opcin Test Project:

Acceso a datos, Tema 2 (versin 1.01) - Pgina 44


(Nota: Si obtenemos un error diciendo que no encuentra org/hamcrest/SelfDescribing,
deberemos aadir esa biblioteca al proyecto, igual que hicimos con JUnit).

Si todo va bien, la ventana inferior derecha de la pantalla mostrar que los tests se han pasado
correctamente:

En un caso general, no bastar con un nico test, sino que deberemos preparar bastantes. Por
ejemplo, para esta funcin mayorDe3 se podra hacer una batera como:

@Test
public void testMayorDe3a() {
System.out.println("Decreciente");
assertEquals(Comparador.mayorDe3(3, 2, 1), 3);
}

@Test
public void testMayorDe3b() {
System.out.println("Creciente");
assertEquals(Comparador.mayorDe3(1, 2, 3), 3);
}

@Test
public void testMayorDe3c() {
System.out.println("MayorCentro");
assertEquals(Comparador.mayorDe3(2, 3, 1), 3);

Acceso a datos, Tema 2 (versin 1.01) - Pgina 45


}

@Test
public void testMayorDe3d() {
System.out.println("Negativos");
assertEquals(Comparador.mayorDe3(-1, -2, -3), -1);
}

@Test
public void testMayorDe3e() {
System.out.println("Dos mayores, uno menor");
assertEquals(Comparador.mayorDe3(3, 3, 0), 3);
}

@Test
public void testMayorDe3f() {
System.out.println("Uno menor, dos mayores");
assertEquals(Comparador.mayorDe3(0, 3, 3), 3);
}

@Test
public void testMayorDe3g() {
System.out.println("Tres iguales");
assertEquals(Comparador.mayorDe3(3, 3, 3), 3);
}

Esta batera, ms detallada, s mostrara que hay casos en los que nuestro algoritmo no se
comporta bien:

En un caso general, antes de liberar cada nueva versin de un programa, se debera volver a
lanzar las pruebas automatizadas, para descubrir nuevos bugs que los cambios hubieran
podido introducir. Esto es lo que se conoce como realizar pruebas de regresin.

Ejercicios propuestos:

2.8.1: Crea dos funciones: una que devolver la primera solucin de una ecuacin de segundo
grado (- b + Raz(b*b - 4*a*c) / 2a ) y otra que devolver la segunda solucin de una ecuacin
de segundo grado (- b - Raz(b*b - 4*a*c) / 2a ) Elige una forma de tratar los casos en los que
hay solucin nica o en los que no existe solucin. Crea una batera de pruebas que te permita
tener unas mnimas garantas de que las funciones se comportan correctamente. Por ejemplo,

Acceso a datos, Tema 2 (versin 1.01) - Pgina 46


una primer caso de prueba podra ser que para x2 - 4 = 0, los coeficientes son a=1, b=0, c=-4 y
las soluciones deberan ser x1=-2, x2=2.

2.9. Para profundizar


Para saber ms de PostgreSQL, puedes consultar su documentacin en lnea:
https://www.postgresql.org/docs/
Si quieres saber ms sobre JDBC, puedes consultar la referencia oficial:
http://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/GettingStartedTOC.fm.html
Para saber ms sobre el uso de JDBC con PostgreSQL:
https://jdbc.postgresql.org/documentation/81/index.html
Para ver las peculiaridades de Statement, PreparedStatement y CallableStatement:
http://crunchify.com/what-are-the-difference-between-jdbcs-statement-preparedstatement-and-
callablestatement/

La pgina oficial de JUnit es: http://junit.org/junit4/

Y el getting started est accesible en: https://github.com/junit-team/junit4/wiki/Getting-started

Acceso a datos, Tema 2 (versin 1.01) - Pgina 47

También podría gustarte