Está en la página 1de 82

Especialización Java

Standard Edition
Nivel IV

Instituto de Nuevas Tecnologías


UNEWEB
Índice
1. Concurrencia.................................................................................1

1.1 Qué es un Thread.....................................................................2

1.2 La clase Thread........................................................................4

1.3. La interface Runnable.............................................................6

1.4. El ciclo de vida de un Thread................................................10

1.5 Threads y prioridades.............................................................12

1.6 Sincronización de Threads.....................................................14

1.6.1 Ejercicio sencillo realizado de forma secuencial y paralela


...................................................................................................21

. Sockets..........................................................................................39

2.1 Fundamentos..........................................................................39

2.2 Funcionamiento genérico.......................................................40

2.3 Introducción............................................................................42

2.4 Modelo de comunicaciones con Java.....................................43

2.5 Apertura de Sockets...............................................................44

2.6 Creación de Streams..............................................................46

2.6.1 Creación de Streams de Entrada......................................46

2.6.2 Creación de Streams de Salida........................................47

2.7 Cierre de Sockets...................................................................49

2.8 Clases útiles en comunicaciones............................................51

 Socket..................................................................................51

 ServerSocket........................................................................51

 DatagramSocket..................................................................51
i
 DatagramPacket..................................................................52

 MulticastSocket....................................................................52

 NetworkServer.....................................................................52

 NetworkClient.......................................................................52

 SocketImpl...........................................................................52

2.9 Ejemplo de uso.......................................................................53

2.9.1 Programa Cliente..............................................................54

2.9.2 Programa Servidor............................................................56

2.9.3 Ejecución..........................................................................59

3. Java DataBase Connectivity........................................................61

3.1 Paquete java.sql.....................................................................63

3.2 Correspondencia de los tipos SQL con clases o interfaces en


Java..............................................................................................68

3.2.1 Secuencia normal de flujo................................................69

3.2.2 Secuencia normal para la ejecución de varias instrucciones


dentro de una transacción.........................................................70

ii
1. Concurrencia
En esencia la multitarea nos permite ejecutar varios
procesos a la vez; es decir, de forma concurrente y por
tanto eso nos permite hacer programas que se ejecuten en
menor tiempo y sean más eficientes. Evidentemente no
podemos ejecutar infinitos procesos de forma concurrente
ya que el hardware tiene sus limitaciones, pero raro es a
día de hoy los ordenadores que no tengan más de un
núcleo por tanto en un procesador con dos núcleos se
podrían ejecutar dos procesos a la vez y así nuestro
programa utilizaría al máximo los recursos hardware. Para
ejemplificar la diferencia entre procesos secuenciales y
paralelos mostraremos un par de imágenes, supongamos
que tenemos un programa secuencial en el que se han de
ejecutar 4 procesos; uno detrás de otro, y estos tardan
unos segundos:

1
Si en vez de hacerlo de forma secuencial, lo hiciésemos
con 4 hilos, el programa tardaría en ejecutarse solo 20
segundos, es decir el tiempo que tardaría en ejecutarse el
proceso más largo. Esto evidentemente sería lo ideal, pero
la realidad es que no todo se puede paralelizar y hay que
saber el número de procesos en paralelo que podemos
lanzar de forma eficiente. En principio en esta entrada no
vamos a hablar sobre ello ya que el objetivo de la misma es
ver como se utilizan los hilos en java con un ejemplo
relativamente sencillo y didáctico.

2
1.1 Qué es un Thread
La Máquina Virtual Java (JVM) es un sistema multihilo.
Es decir, es capaz de ejecutar varios hilos de ejecución
simultáneamente. La JVM gestiona todos los detalles,
asignación de tiempos de ejecución, prioridades, etc., de
forma similar a como gestiona un Sistema Operativo
múltiples procesos. La diferencia básica entre un proceso
de Sistema Operativo y un Thread Java es que los hilos
corren dentro de la JVM, que es un proceso del Sistema
Operativo y por tanto comparten todos los recursos,
incluida la memoria y las variables y objetos allí definidos. A
este tipo de procesos donde se comparte los recursos se
les llama a veces procesos ligeros (lightweight process).
Java da soporte al concepto de Thread desde el propio
lenguaje, con algunas clases e interfaces definidas en el
paquete java.lang y con métodos específicos para la
manipulación de Threads en la clase Object. Desde el
punto de vista de las aplicaciones los hilos son útiles
porque permiten que el flujo del programa sea divido en
dos o más partes, cada una ocupándose de alguna tarea
de forma independiente. Por ejemplo un hilo puede
encargarse de la comunicación con el usuario, mientras
que otros actúan en segundo plano, realizando la
transmisión de un fichero, accediendo a recursos del
sistema (cargar sonidos, leer ficheros ...), etc. De hecho,
3
todos los programas con interface gráfico (AWT o Swing)
son multihilo porque los eventos y las rutinas de dibujado
de las ventanas corren en un hilo distinto al principal.

1.2 La clase Thread


La forma más directa para hacer un programa multihilo
es extender la clase Thread, y redefinir el método run().
Este método es invocado cuando se inicia el hilo (mediante
una llamada al método start() de la clase Thread). El hilo se
4
inicia con la llamada al método run() y termina cuando
termina éste. El ejemplo ilustra estas ideas:

public class ThreadEjemplo extends Thread {

public ThreadEjemplo(String str) {

super(str);

public void run() {

for (int i = 0; i < 10 ; i++)

System.out.println(i + " " + getName());

System.out.println("Termina thread " + getName());

public static void main (String [] args) {

new ThreadEjemplo("Fulano").start();

new ThreadEjemplo("Mengano").start();

System.out.println("Termina thread main");

5
Ejecutando varias veces el programa, se podrá observar
que no siempre se ejecuta igual.

Notas sobre el programa:

• La clase Thread está en el paquete java.lang. Por tanto,


no es necesario el import.

• El constructor public Thread(String str) recibe un


parámetro que es la identificación del Thread.

• El método run() contiene el bloque de ejecución del


Thread. Dentro de él, el método getName() devuelve el
nombre del Thread (el que se ha pasado como argumento
al constructor).

• El método main crea dos objetos de clase ThreadEjemplo


y los inicia con la llamada al método start()(el cual inicia el
nuevo hilo y llama al método run()).

• Obsérvese en la salida el primer mensaje de finalización


del thread main. La ejecución de los hilos es asíncrona.
Realizada la llamada al método start(), éste le devuelve
control y continua su ejecución, independiente de los otros
hilos.

• En la salida los mensajes de un hilo y otro se van


mezclando. La máquina virtual asigna tiempos a cada hilo.

6
1.3. La interface Runnable
La interface Runnable proporciona un método
alternativo a la utilización de la clase Thread, para los
casos en los que no es posible hacer que la clase definida
extienda la clase Thread. Esto ocurre cuando dicha clase,
que se desea ejecutar en un hilo independiente deba
extender alguna otra clase. Dado que no existe herencia
múltiple, la citada clase no puede extender a la vez la clase
Thread y otra más. En este caso, la clase debe implantar la
interface Runnable, variando ligeramente la forma en que
se crean e inician los nuevos hilos.

El siguiente ejemplo es equivalente al del apartado anterior,


pero utilizando la interface Runnable:

public class ThreadEjemplo implements Runnable {

public void run() {

for (int i = 0; i < 5 ; i++)

System.out.println(i + " " +

Thread.currentThread().getName());

System.out.println("Termina thread " +

Thread.currentThread().getName());

7
public static void main (String [] args) {

new Thread (new ThreadEjemplo(),


"Fulano").start();

new Thread (new ThreadEjemplo(),

"Mengano").start();

System.out.println("Termina thread main");

Obsérvese en este caso:

• Se implementa la interface Runnable en lugar de


extender la clase Thread.

• El constructor que había antes no es necesario.

• En el main obsérvese la forma en que se crea el thread.


Esa expresión es equivalente a:

ThreadEjemplo ejemplo = new ThreadEjemplo();

Thread thread = new Thread (ejemplo, "Pepe");

8
thread.start();

* Primero se crea la instancia de nuestra clase o


después se crea una instancia de la clase Thread,
pasando como parámetros la referencia de nuestro
objeto y el nombre del nuevo thread.

* Por último se llama al método start de la clase


thread. Este método iniciará el nuevo thread y llamará
al método run() de nuestra clase.

• Por último, obsérvese la llamada al método getName()


desde run(). getName es un método de la clase Thread, por
lo que nuestra clase debe obtener una referencia al thread
propio. Es lo que hace el método estático currentThread()
de la clase Thread.

En el siguiente diagrama de clase mostramos la


Interface Runnable y la clase Thread con sus principales
métodos:

9
1.4. El ciclo de vida de un Thread
El gráfico resume el ciclo de vida de un thread:
10
Cuando se instancia la clase Thread (o una subclase) se
crea un nuevo Thread que está en su estado inicial ('New
Thread' en el gráfico). En este estado es simplemente un
objeto más. No existe todavía el thread en ejecución. El
único método que puede invocarse sobre él es el método
start().

Cuando se invoca el método start() sobre el hilo el


sistema crea los recursos necesarios, lo planifica (le asigna
prioridad) y llama al método run(). En este momento el hilo
está corriendo, se encuentra en el estado ‘runable’.

Si el método run() invoca internamente el método


sleep() o wait() o el hilo tiene que esperar por una
operación de entrada/salida, entonces el hilo pasa al
estado 'no runnable' (no ejecutable) hasta que la condición

11
de espera finalice. Durante este tiempo el sistema puede
ceder control a otros hilos activos.

Por último, cuando el método run finaliza el hilo termina


y pasa a la situación 'Dead' (Muerto).

1.5 Threads y prioridades


Aunque un programa utilice varios hilos y
aparentemente estos se ejecuten simultáneamente, el
sistema ejecuta una única instrucción cada vez (esto es
particularmente cierto en sistemas con una sola CPU),
aunque las instrucciones se ejecutan concurrentemente

12
(entremezclándose sus éstas). El mecanismo por el cual un
sistema controla la ejecución concurrente de procesos se
llama planificación (scheduling). Java soporta un
mecanismo simple denominado planificación por prioridad
fija (fixed priority scheduling). Esto significa que la
planificación de los hilos se realiza en base a la prioridad
relativa de un hilo frente a las prioridades de otros.

La prioridad de un hilo es un valor entero (cuanto mayor


es el número, mayor es la prioridad), que puede asignarse
con el método setPriority. Por defecto la prioridad de un hilo
es igual a la del hilo que lo creó. Cuando hay varios hilos
en condiciones de ser ejecutados (estado runnable), la
máquina virtual elige el hilo que tiene una prioridad más
alta, que se ejecutará hasta que:

 Un hilo con una prioridad más alta esté en condiciones


de ser ejecutado (runnable), o
 El hilo termina (termina su método run), o
 Se detiene voluntariamente, o
 Alguna condición hace que el hilo no sea ejecutable
(runnable), como una operación de entrada/salida o, si
el sistema operativo tiene planificación por división de
tiempos (time slicing), cuando expira el tiempo
asignado.

13
Si dos o más hilos están listos para ejecutarse y tienen
la misma prioridad, la máquina virtual va cediendo control
de forma cíclica (round-robin). El hecho de que un hilo con
una prioridad más alta interrumpa a otro se denomina se
denomina 'planificación apropiativa' (preemptive
scheduling).

Cuando un hilo entra en ejecución y no cede


voluntariamente el control para que puedan ejecutarse
otros hilos, se dice que es un hilo egoísta (selfish thread).
Algunos Sistemas Operativos, como Windows, combaten
estas actitudes con una estrategia de planificación por
división de tiempos (time-slicing), que opera con hilos de
igual prioridad que compiten por la CPU. En estas
condiciones el Sistema Operativo asigna tiempos a cada
hilo y va cediendo el control consecutivamente a todos los
que compiten por el control de la CPU, impidiendo que uno
de ellos se apropie del sistema durante un intervalo de
tiempo prolongado. Este mecanismo lo proporciona el
sistema operativo, no Java.

1.6 Sincronización de Threads


Los ejemplos anteriores muestran como un programa
ejecuta varios hilos de forma asíncrona. Es decir, una vez
que es iniciado, cada hilo vive de forma independiente de
los otros, no existe ninguna relación entre ellos, ni tampoco

14
ningún conflicto, dado que no comparten nada. Sin
embargo, hay ocasiones que distintos hilos en un programa
sí necesitan establecer alguna relación entre sí, o compartir
objetos. Se necesita entonces algún mecanismo que
permita sincronizar hilos, así como, establecer unas 'reglas
del juego' para acceder a recursos (objetos) compartidos.

Un ejemplo típico en que dos procesos necesitan


sincronizarse es el caso en que un hilo produzca algún tipo
de información que es procesada por otro hilo. Al primer
hilo le denominaremos productor y al segundo, consumidor.
El productor podría tener el siguiente aspecto:

public class Productor extends Thread {

private Contenedor contenedor;

public Productor (Contenedor c) {

contenedor = c;

public void run() {

for (int i = 0; i < 10; i++) {

contenedor.put(i);

System.out.println("Productor. put: " + i);


15
try {

sleep((int)(Math.random() * 100));

} catch (InterruptedException e) { }

Productor tiene una variables miembro: contenedor es


una referencia a un objeto Contenedor, que sirve para
almacenar los datos que va produciendo. El método run
genera aleatoriamente el dato y lo coloca en el contenedor
con el método put. Después espera una cantidad de tiempo
aleatoria (hasta 100 milisegundos) con el método sleep. El
productor no se preocupa de si el dato ya ha sido
consumido o no. Simplemente lo coloca en el contenedor.

El consumidor, por su parte podría tener el siguiente


aspecto:

public class Consumidor extends Thread {

16
private Contenedor contenedor;

public Consumidor (Contenedor c) {

contenedor= c;

public void run() {

int value = 0;

for (int i = 0; i < 10; i++) {

value = contenedor.get();

System.out.println("Consumidor. get: " +


value);

El constructor es equivalente al del Productor. El método


run, simplemente recupera el dato del contenedor con el
método get y lo muestra en la consola. Tampoco el
consumidor se preocupa de si el dato está ya disponible en
el contenedor o no.

17
Productor y Consumidor se usarían desde un método
main de la siguiente forma:

public class ProducTorConsumidorTest {

public static void main(String[] args) {

Contenedor c = new Contenedor ();

Productor produce = new Productor (c);

Consumidor consume = new Consumidor (c);

produce.start();

consume.start();

Simplemente se crean los objetos, Contendor, Productor


y Consumidor y se inician los hilos de estos dos últimos.

La sincronización que permite a productor y consumidor


operar correctamente, es decir, la que hace que
consumidor espere hasta que haya un dato disponible, y
que productor no genere uno nuevo hasta que haya sido
consumido esta, en la clase Contenedor, se consigue de la
siguiente forma:

18
public class ContainerProduct {

private int dato;

private boolean hayDato = false;

public synchronized int get() {

while (hayDato == false) {

try {

// espera a que el productor coloque un


valor

wait();

} catch (InterruptedException e) {

hayDato = false;

// notificar que el valor ha sido consumido

notifyAll();

return dato;

19
public synchronized void put(int valor) {

while (hayDato == true) {

try {

// espera a que se consuma el dato

wait();

} catch (InterruptedException e) { }

dato = valor;

hayDato = true;

// notificar que ya hay dato.

notifyAll();

La variable miembro dato es la que contiene el valor que


se almacena con put y se devuelve con get. La variable
miembro hayDato es un flag interno que indica si el objeto
contiene dato o no.

20
En el método put, antes de almacenar el valor en dato
hay que asegurarse de que el valor anterior ha sido
consumido. Si todavía hay valor (hayDato es true) se
suspende la ejecución del hilo mediante el método wait.
Invocando wait (que es un método de la clase Object) se
suspende el hilo indefinidamente hasta que alguien le envíe
una 'señal' con el método notify o notifyAll. Cuando esto se
produce (en este caso, la señalización mediante notify lo
produce el método get) el método continúa, asume que el
dato ya se ha consumido, almacena el valor en dato y
envía a su vez un notifyAll para notificar a su vez que hay
un dato disponible.

Por su parte, el método get chequea si hay dato disponible


(no lo hay si hayDato es false) y si no lo hay espera hasta
que le avisen (método wait). Una vez ha sido notificado
(desde el método put) cambia el flag y devuelve el dato,
pero antes notifica a put de que el dato ya ha sido
consumido, y por tanto se puede almacenar otro.

La sincronización se lleva a cabo pues usando los


métodos wait y notifiAll.

Existe además otro componente básico en el ejemplo.


Los objetos productor y consumidor utilizan un recurso
compartido que es el objeto contenedor. Si mientras el
productor llama al método put y este se encuentra

21
cambiando las variables miembro dato y hayDato, el
consumidor llamara al método get y este a su vez
empezara a cambiar estos valores podrían producirse
resultados inesperados (este ejemplo es sencillo, pero
fácilmente pueden imaginarse otras situaciones más
complejas).

Interesa, por tanto, que mientras se esté ejecutando el


método put nadie más acceda a las variables miembro del
objeto. Esto se consigue con la palabra synchronized en la
declaración del método. Cuando la máquina virtual inicia la
ejecución de un método con este modificador adquiere un
bloqueo en el objeto sobre el que se ejecuta el método que
impide que nadie más inicie la ejecución en ese objeto de
otro método que también esté declarado como
syncrhonized. En nuestro ejemplo cuando comienza el
método put se bloquea el objeto de tal forma que si alguien
intenta invocar el método get o put (ambos son
synchronized) quedará en espera hasta que el bloqueo se
libere (cuando termine la ejecución del método). Este
mecanismo garantiza que los objetos compartidos
mantienen la consistencia.

Este método de gestionar los bloqueos implica que:

• Es responsabilidad del programador pensar y gestionar


los bloqueos.

22
• Los métodos synchronized son más costosos en el
sentido de que adquirir y liberar los bloqueos consume
tiempo (este es el motivo por el que no están sincronizados
por defecto todos los métodos).

1.6.1 Ejercicio sencillo realizado de forma


secuencial y paralela
En este ejemplo vamos a simular el proceso de cobro de
un supermercado; es decir, unos clientes van con un carro
lleno de productos y una cajera les cobra los productos,
pasándolos uno a uno por el escaner de la caja
registradora. En este caso la cajera debe de procesar la
compra cliente a cliente, es decir que primero le cobra al
cliente 1, luego al cliente 2 y así sucesivamente. Para ello
vamos a definir una clase “Cajera” y una clase “Cliente” el
cual tendrá un “array de enteros” que representaran los
productos que ha comprado y el tiempo que la cajera
tardará en pasar el producto por el escaner; es decir, que si

23
tenemos un array con [1,3,5] significará que el cliente ha
comprado 3 productos y que la cajera tardara en procesar
el producto 1 ‘1 segundo’, el producto 2 ‘3 segundos’ y el
producto 3 en ‘5 segundos’, con lo cual tardara en cobrar al
cliente toda su compra ‘9 segundos’.

 Solución secuencial:
 Clase “Cajera.java“:

public class Cajera {

private String nombre;

// Constructor, getter y setter

public void procesarCompra(Cliente cliente, long


timeStamp) {

System.out.println("La cajera " + this.nombre + "


COMIENZA A PROCESAR LA COMPRA DEL CLIENTE " +

24
cliente.getNombre() + " EN EL TIEMPO: " +
(System.currentTimeMillis() - timeStamp) / 1000 +"seg");

for (int i = 0; i < cliente.getCarroCompra().length; i+


+) {

this.esperarXsegundos(cliente.getCarroCompra(
)[i]);

System.out.println("Procesado el producto " + (i


+ 1) + " ->Tiempo: " + (System.currentTimeMillis() -
timeStamp) / 1000 + "seg");

System.out.println("La cajera " + this.nombre + " HA


TERMINADO DE PROCESAR " + cliente.getNombre() + "
EN EL TIEMPO: " + (System.currentTimeMillis() -
timeStamp) / 1000 + "seg");

private void esperarXsegundos(int segundos) {

try {

25
Thread.sleep(segundos * 1000);

} catch (InterruptedException ex) {

Thread.currentThread().interrupt();

 Clase “Cliente.java“:

public class Cliente {

private String nombre;

private int[] carroCompra;

// Constructor, getter y setter

Si ejecutásemos este programa propuesto con dos


Clientes y con un solo proceso (que es lo que se suele
hacer normalmente), se procesaría primero la compra del
Cliente 1 y después la del Cliente 2, con lo cual se tardará

26
el tiempo del Cliente 1 + Cliente 2. A continuación vamos a
ver como programamos el método Main para lanzar el
programa. CUIDADO: Aunque hayamos puesto dos
objetos de la clase Cajera (cajera1 y cajera2) no significa
que tengamos dos cajeras independientes, lo que estamos
diciendo es que dentro del mismo hilo se ejecute primero
los métodos de la cajera1 y después los métodos de la
cajera2, por tanto, a nivel de procesamiento es como si
tuviésemos una sola cajera

 Clase “Main.java“:

public class Main {

public static void main(String[] args) {

Cliente cliente1 = new Cliente("Cliente 1", new int[]


221523
{ , , , , , });

Cliente cliente2 = new Cliente("Cliente 2", new int[]


13511
{ , , , , });

Cajera cajera1 = new Cajera("Cajera 1");

27
Cajera cajera2 = new Cajera("Cajera 2");

// Tiempo inicial de referencia

long initialTime = System.currentTimeMillis();

cajera1.procesarCompra(cliente1, initialTime);

cajera2.procesarCompra(cliente2, initialTime);

Si ejecutamos este código tendremos lo siguiente:

La cajera Cajera 1 COMIENZA A PROCESAR LA


COMPRA DEL CLIENTE Cliente 1 EN EL TIEMPO: 0seg

Procesado el producto 1 ->Tiempo: 2seg

Procesado el producto 2 ->Tiempo: 4seg

Procesado el producto 3 ->Tiempo: 5seg

Procesado el producto 4 ->Tiempo: 10seg

Procesado el producto 5 ->Tiempo: 12seg

Procesado el producto 6 ->Tiempo: 15seg

28
La cajera Cajera 1 HA TERMINADO DE PROCESAR
Cliente 1 EN EL TIEMPO: 15seg

La cajera Cajera 2 COMIENZA A PROCESAR LA


COMPRA DEL CLIENTE Cliente 2 EN EL TIEMPO: 15seg

Procesado el producto 1 ->Tiempo: 16seg

Procesado el producto 2 ->Tiempo: 19seg

Procesado el producto 3 ->Tiempo: 24seg

Procesado el producto 4 ->Tiempo: 25seg

Procesado el producto 5 ->Tiempo: 26seg

La cajera Cajera 2 HA TERMINADO DE PROCESAR


Cliente 2 EN EL TIEMPO: 26seg

Como vemos se procesa primero la compra del cliente 1


y después la compra del cliente 2 tardando en procesar
ambas compras un tiempo de 26 segundos.

29
¿Y si en vez de procesar primero un cliente y después
otro, procesásemos los dos a la vez?, ¿Cuánto tardaría el
programa en ejecutarse? Pues bien, si en vez de haber
solo una Cajera (es decir un solo hilo), hubiese dos Cajeras
(es decir dos hilos o threads) podríamos procesar los dos
clientes a la vez y tardar menos tiempo en ejecutarse el
programa. Para ello debemos de modificar la clase
“Cajera.java” y hacer que esta clase herede de la
clase Thread para heredar y sobre-escribir algunos de sus
métodos. Primero vamos a ver como codificamos esta
nueva clase “CajeraThread.java” y después explicamos
sus características.

 Solución Usando la clase Thread:

public class CajeraThread extends Thread {

private String nombre;

private Cliente cliente;

private long initialTime;

// Constructor, getter & setter

30
@Override

public void run() {

System.out.println("La cajera " + this.nombre + "


COMIENZA A PROCESAR LA COMPRA DEL CLIENTE " +

this.cliente.getNombre() + " EN EL TIEMPO: " +

(System.currentTimeMillis() - this.initialTime) / 1000 +

"seg");

for (int i = 0; i <

this.cliente.getCarroCompra().length; i++) {

this.esperarXsegundos(cliente.getCarroCompra
i
()[ ]);

System.out.println("Procesado el producto " + (i


+ 1) + " del cliente " + this.cliente.getNombre() + "->Tiempo: "
+ ( System.currentTimeMillis() - this.initialTime) / 1000 +

"seg");

System.out.println("La cajera " + this.nombre + " HA


TERMINADO DE PROCESAR " + this.cliente.getNombre()
+ " EN EL TIEMPO: " + (System.currentTimeMillis() -

this.initialTime) / 1000 + "seg");

31
}

private void esperarXsegundos(int segundos) {

try {

Thread.sleep(segundos * 1000);

} catch (InterruptedException ex) {

Thread.currentThread().interrupt();

Lo primero que vemos y que ya hemos comentado es


que la clase “CajeraThread” debe de heredar de la clase
Thread: “extendsThread“.

Otra cosa importante que vemos es que hemos sobre-


escrito el método “run()” (de ahí la etiqueta @Override) .
Este método es imprescindible sobre-escribirlo (ya que es
un método que está en la clase Runnable y la clase Thread
Implementa esa Interface) porque en él se va a codificar la
funcionalidad que se ha de ejecutar en un hilo; es decir,
que lo que se programe en el método “run()” se va a
ejecutar de forma secuencial en un hilo. En esta clase
“CajeraThread” se pueden sobre-escribir más métodos
para que hagan acciones sobre el hilo o thread como, por
32
ejemplo, parar el thread, ponerlo en reposos, etc. A
continuación, vamos a ver como programamos el método
Main para que procese a los clientes de forma paralela y
ver como se tarda menos en procesar todo. El método Main
está en la clase “MainThread.java” que tiene el siguiente
contenido

public class MainThread {

public static void main(String[] args) {

Cliente cliente1 = new Cliente("Cliente 1", new


int[] { 2, 2, 1, 5, 2, 3 });

Cliente cliente2 = new Cliente("Cliente 2", new


int[] { 1, 3, 5, 1, 1 });

// Tiempo inicial de referencia

long initialTime = System.currentTimeMillis();

CajeraThread cajera1 = new


CajeraThread("Cajera 1", cliente1, initialTime);

33
CajeraThread cajera2 = new
CajeraThread("Cajera 2", cliente2, initialTime);

cajera1.start();

cajera2.start();

Ahora vamos a ver cuál sería el resultado de esta


ejecución y vamos a comprobar como efectivamente el
programa se ejecuta de forma paralela y tarda solo 15
segundos en terminar su ejecución:

La cajera Cajera 1 COMIENZA A PROCESAR LA


COMPRA DEL CLIENTE Cliente 1 EN EL TIEMPO: 0seg

La cajera Cajera 2 COMIENZA A PROCESAR LA


COMPRA DEL CLIENTE Cliente 2 EN EL TIEMPO: 0seg

Procesado el producto 1 del cliente Cliente 2->Tiempo:


1seg

Procesado el producto 1 del cliente Cliente 1->Tiempo:


2seg

34
Procesado el producto 2 del cliente Cliente 2->Tiempo:
4seg

Procesado el producto 2 del cliente Cliente 1->Tiempo:


4seg

Procesado el producto 3 del cliente Cliente 1->Tiempo:


5seg

Procesado el producto 3 del cliente Cliente 2->Tiempo:


9seg

Procesado el producto 4 del cliente Cliente 2->Tiempo:


10seg

Procesado el producto 4 del cliente Cliente 1->Tiempo:


10seg

Procesado el producto 5 del cliente Cliente 2->Tiempo:


11seg

La cajera Cajera 2 HA TERMINADO DE PROCESAR


Cliente 2 EN EL TIEMPO: 11seg

Procesado el producto 5 del cliente Cliente 1->Tiempo:


12seg

Procesado el producto 6 del cliente Cliente 1->Tiempo:


15seg

35
La cajera Cajera 1 HA TERMINADO DE PROCESAR
Cliente 1 EN EL TIEMPO: 15seg

En este ejemplo vemos como el efecto es como si dos


cajeras procesasen la compra de los clientes de forma
paralela sin que el resultado de la aplicación sufra ninguna
variación en su resultado final, que es el de procesar todas
las compras de los clientes de forma independiente. De
forma gráfica vemos que el programa ha realizado lo
siguiente en dos hilos distintos:

Otra forma de hacer lo mismo pero sin heredar de la


clase “Thread” es implementar la Interface “Runnable”. En
este caso no dispondremos ni podremos sobre-escribir los
métodos de la clase Thread ya que no la vamos a utilizar y

36
solo vamos a tener que sobre-escribir el método “run()“. En
este caso solo será necesario implementar el método
“run()” para que los procesos implementados en ese
método se ejecuten en un hilo diferente. Vamos a ver un
ejemplo de cómo utilizando objetos de las clases
“Cliente.java” y “Cajera.java” podemos implementar la
multitarea en la misma clase donde se llama al método
Main de la aplicación. A continuación vemos la codificación
en la clase “MainRunnable.java“:

 Solución usando la interface RUNNABLE

public class MainRunnable implements Runnable{

private Cliente cliente;

private Cajera cajera;

private long initialTime;

public MainRunnable (Cliente cliente, Cajera


cajera, long initialTime){

this.cajera = cajera;

this.cliente = cliente;

37
this.initialTime = initialTime;

public static void main(String[] args) {

Cliente cliente1 = new Cliente("Cliente 1", new


int[] { 2, 2, 1, 5, 2, 3 });

Cliente cliente2 = new Cliente("Cliente 2", new


int[] { 1, 3, 5, 1, 1 });

Cajera cajera1 = new Cajera("Cajera 1");

Cajera cajera2 = new Cajera("Cajera 2");

// Tiempo inicial de referencia

long initialTime = System.currentTimeMillis();

Runnable proceso1 = new


MainRunnable(cliente1, cajera1, initialTime);

Runnable proceso2 = new


MainRunnable(cliente2, cajera2, initialTime);

new Thread(proceso1).start();

new Thread(proceso2).start();

38
@Override

public void run() {

this.cajera.procesarCompra(this.cliente,
this.initialTime);

En este caso implementamos el método “run()” dentro


de la misma clase donde se encuentra el método Main, y
en el llamamos al método de “procesarCompra()” de la
clase Cajera. Dentro del método Main, nos creamos dos
objetos de la misma clase en la que estamos (“new
MainRunnable”) y nos creamos dos objetos de la clase
Thread para lanzar los proceso y que se ejecuten estos en
paralelo. El resultado de esta ejecución es el mismo que en
el caso anterior:

La cajera Cajera 2 COMIENZA A PROCESAR LA


COMPRA DEL CLIENTE Cliente 2 EN EL TIEMPO: 0seg

La cajera Cajera 1 COMIENZA A PROCESAR LA


COMPRA DEL CLIENTE Cliente 1 EN EL TIEMPO: 0seg

39
Procesado el producto 1 del cliente Cliente 2->Tiempo:
1seg

Procesado el producto 1 del cliente Cliente 1->Tiempo:


2seg

Procesado el producto 2 del cliente Cliente 2->Tiempo:


4seg

Procesado el producto 2 del cliente Cliente 1->Tiempo:


4seg

Procesado el producto 3 del cliente Cliente 1->Tiempo:


5seg

Procesado el producto 3 del cliente Cliente 2->Tiempo:


9seg

Procesado el producto 4 del cliente Cliente 2->Tiempo:


10seg

Procesado el producto 4 del cliente Cliente 1->Tiempo:


10seg

Procesado el producto 5 del cliente Cliente 2->Tiempo:


11seg

La cajera Cajera 2 HA TERMINADO DE PROCESAR


Cliente 2 EN EL TIEMPO: 11seg

40
Procesado el producto 5 del cliente Cliente 1->Tiempo:
12seg

Procesado el producto 6 del cliente Cliente 1->Tiempo:


15seg

La cajera Cajera 1 HA TERMINADO DE PROCESAR


Cliente 1 EN EL TIEMPO: 15seg

41
. Sockets
2.1 Fundamentos
Los sockets son un sistema de comunicación entre
procesos de diferentes máquinas de una red. Más
exactamente, un socket es un punto de comunicación por
el cual un proceso puede emitir o recibir información.

Los sockets han de ser capaces de utilizar el protocolo de


streams TCP (Transfer Contro Protocol) y el de datagramas
UDP (User Datagram Protocol).

Utilizan una serie de primitivas para:

 Establecer el punto de comunicación


 Conectarse a una máquina remota en un determinado
puerto que esté disponible
 Escuchar en un puerto
 Leer o escribir y publicar información
 Desconectarse.

Se puede crear un sistema de diálogo muy completo con


todas las primitivas.

42
2.2 Funcionamiento genérico
Normalmente, un servidor se ejecuta sobre una
computadora específica y tiene un socket que responde en
un puerto específico. El servidor únicamente espera,
escuchando a través del socket a que un cliente haga una
petición.

En el lado del cliente:

Para realizar una petición de conexión, el cliente intenta


encontrar al servidor en la máquina servidora en un puerto
especificado.

El cliente debe conocer para lograr una conexión


efectiva, el nombre de host de la máquina en la cual el
servidor se encuentra ejecutando y el número de puerto en
el cual el servidor está conectado, como lo podemos ver en
la siguiente imagen.

Si no hay ningún tipo de problema, el servidor acepta la


conexión. Además de aceptar, el servidor obtiene un nuevo
socket sobre un puerto diferente. Esto se debe a que
43
necesita un nuevo socket (y, en consecuencia, un numero
de puerto diferente) para seguir atendiendo al socket
original para peticiones de conexión mientras atiende las
necesidades del cliente que se conectó.

Por la parte del cliente, si la conexión es aceptada, un


socket se crea de forma satisfactoria y puede usarlo para
comunicarse con el servidor. Es importante darse cuenta
que el socket en el cliente no está utilizando el número de
puerto usado para realizar la petición al servidor. En lugar
de éste, el cliente asigna un número de puerto local a la
máquina en la cual está siendo ejecutado. Ahora el cliente
y el servidor pueden comunicarse escribiendo o leyendo en
o desde sus respectivos sockets.

44
2.3 Introducción
El paquete java.net de la plataforma Java proporciona
una clase Socket, la cual implementa una de las partes de
la comunicación bidireccional entre un programa Java y
otro programa en la red.

La clase Socket se sitúa en la parte más alta de una


implementación dependiente de la plataforma, ocultando
los detalles de cualquier sistema particular al programa
Java. Usando la clase java.net.Socket en lugar de utilizar
código nativo de la plataforma, los programas Java pueden
comunicarse a través de la red de una forma totalmente
independiente de la plataforma.

De forma adicional, java.net incluye la clase


ServerSocket, la cual implementa un socket el cual los
servidores pueden utilizar para escuchar y aceptar
peticiones de conexión de clientes.

En este curso conoceremos cómo utilizar las clases Socket


y ServerSocket que son las más básica he importantes de
este tema.

2.4 Modelo de comunicaciones con Java


El modelo de sockets más simple es el siguiente:

45
 El servidor establece un puerto y espera durante un
cierto tiempo (timeout segundos), a que el cliente
establezca la conexión.
 Cuando el cliente solicite una conexión, el servidor
abrirá la conexión socket con el método accept().
 El cliente establece una conexión con la máquina host
a través del puerto que se designe en puerto # ·

El cliente y el servidor se comunican con manejadores


InputStream y OutputStream.

2.5 Apertura de Sockets


Si estamos programando un CLIENTE, el socket se abre
de la forma:

Socket miCliente;

46
miCliente = new Socket( "maquina", numeroPuerto );

Donde maquina es el nombre de la máquina en donde


estamos intentando abrir la conexión y numeroPuerto es
el puerto (numérico) del servidor que está corriendo sobre
el cual nos queremos conectar. Cuando se selecciona un
número de puerto, se debe tener en cuenta que los puertos
en el rango 0-1023 están reservados para usuarios con
muchos privilegios (superusuarios o root). Estos puertos
son los que utilizan los servicios estándar del sistema como
email, ftp o http. Para las aplicaciones que se desarrollen,
debemos asegurarnos a la hora de seleccionar un
puerto, este sea por encima del puerto 1023.

En el ejemplo anterior no se usan excepciones; sin


embargo, es una gran idea la captura de excepciones
cuando se está trabajando con sockets. El mismo ejemplo
quedaría como:

Socket miCliente;

try {

miCliente = new
Socket( "maquina",numeroPuerto );

} catch( IOException e ) {

47
System.out.println( e );

A la hora de la implementación de un servidor también


necesitamos crear un objeto socket desde el ServerSocket
para que esté atento a las conexiones que le puedan
realizar clientes potenciales y poder aceptar esas
conexiones:

Socket socketServicio = null;

try {

socketServicio = miServicio.accept();

} catch( IOException e ) {

System.out.println(e);

48
2.6 Creación de Streams
2.6.1 Creación de Streams de Entrada
En la parte CLIENTE de la aplicación, se puede utilizar
la clase DataInputStream para crear un stream de entrada
que esté listo a recibir todas las respuestas que el servidor
le envíe.

DataInputStream entrada;

try {

entrada = new
DataInputStream( miCliente.getInputStream() );

} catch( IOException e ) {

System.out.println( e );

La clase DataInputStream permite la lectura de líneas


de texto y tipos de datos primitivos de Java de un modo
altamente portable; dispone de métodos para leer todos
esos tipos como: read(), readChar(), readInt(), readDouble()
y readLine(). Deberemos utilizar la función que creamos
necesaria dependiendo del tipo de dato que esperemos
recibir del servidor.

49
En el lado del SERVIDOR, también usaremos
DataInputStream, pero en este caso para recibir las
entradas que se produzcan de los clientes que se hayan
conectado:

DataInputStream entrada;

try {

entrada = new
DataInputStream( socketServicio.getInputStream() );

} catch( IOException e ) {

System.out.println( e );

2.6.2 Creación de Streams de Salida


En el lado del CLIENTE, podemos crear un stream de
salida para enviar información al socket del servidor
utilizando las clases PrintStream o DataOutputStream:

PrintStream salida;

try {

salida = new
PrintStream( miCliente.getOutputStream() );

50
} catch( IOException e ) {

System.out.println( e );

La clase PrintStream tiene métodos para la


representación textual de todos los datos primitivos de
Java. Sus métodos write y println() tienen una especial
importancia en este aspecto. No obstante, para el envío de
información al servidor también podemos utilizar
DataOutputStream:

DataOutputStream salida;

try {

salida = new
DataOutputStream( miCliente.getOutputStream() );

} catch( IOException e ) {

System.out.println( e );

La clase DataOutputStream permite escribir cualquiera


de los tipos primitivos de Java, muchos de sus métodos
escriben un tipo de dato primitivo en el stream de salida. De
todos esos métodos, el más útil quizás sea writeBytes().
51
En el lado del SERVIDOR, podemos utilizar la clase
PrintStream para enviar información al cliente:

PrintStream salida;

try {

salida = new
PrintStream( socketServicio.getOutputStream() );

} catch( IOException e ) {

System.out.println( e );

Pero también podemos utilizar la clase


DataOutputStream como en el caso de envío de
información desde el cliente.

2.7 Cierre de Sockets


Siempre deberemos cerrar los canales de entrada y
salida que se hayan abierto durante la ejecución de la
aplicación. En la parte del cliente:

try {

salida.close();

entrada.close();

miCliente.close();

52
} catch( IOException e ) {

System.out.println( e );

Y en la parte del servidor:

try {

salida.close();

entrada.close();

socketServicio.close();

miServicio.close();

} catch( IOException e ) {

System.out.println( e );

Es importante destacar que el orden de cierre es


relevante. Es decir, se deben cerrar primero los streams
relacionados con un socket antes que el propio socket, ya
que de esta forma evitamos posibles errores de escrituras o
lecturas sobre descriptores ya cerrados.

53
2.8 Clases útiles en comunicaciones

 Socket

Es el objeto básico en toda comunicación a través de


Internet, bajo el protocolo TCP. Esta clase proporciona
métodos para la entrada/salida a través de streams que
hacen la lectura y escritura a través de sockets muy
sencilla.

 ServerSocket

Es un objeto utilizado en las aplicaciones servidor para


escuchar las peticiones que realicen los clientes
conectados a ese servidor. Este objeto no realiza el
servicio, sino que crea un objeto Socket en función del
cliente para realizar toda la comunicación a través de él.

54
 DatagramSocket

La clase de sockets datagrama puede ser utilizada para


implementar datagramas no fiables (sockets UDP), no
ordenados. Aunque la comunicación por estos sockets es
muy rápida porque no hay que perder tiempo estableciendo
la conexión entre cliente y servidor.

 DatagramPacket

Clase que representa un paquete datagrama


conteniendo información de paquete, longitud de paquete,
direcciones Internet y números de puerto.

 MulticastSocket

Clase utilizada para crear una versión multicast de la


clase socket datagrama. Múltiples clientes/servidores
pueden transmitir a un grupo multicast (un grupo de
direcciones IP compartiendo el mismo número de puerto).

 NetworkServer

Una clase creada para implementar métodos y variables


utilizadas en la creación de un servidor TCP/IP.

55
 NetworkClient

Una clase creada para implementar métodos y variables


utilizadas en la creación de un cliente TCP/IP.

 SocketImpl

Es un Interface que nos permite crearnos nuestro propio


modelo de comunicación. Tendremos que implementar sus
métodos cuando la usemos. Si vamos a desarrollar una
aplicación con requerimientos especiales de
comunicaciones, como pueden ser la implementación de un
cortafuego (TCP es un protocolo no seguro), o acceder a
equipos especiales (como un lector de código de barras o
un GPS diferencial), necesitaremos nuestra propia clase
Socket.

56
2.9 Ejemplo de uso
Para comprender el funcionamiento de los sockets no
hay nada mejor que estudiar un ejemplo. El que a
continuación se presenta establece un pequeño diálogo
entre un programa servidor y sus clientes, que
intercambiarán cadenas de información.

2.9.1 Programa Cliente


El programa cliente se conecta a un servidor indicando
el nombre de la máquina y el número puerto (tipo de
servicio que solicita) en el que el servidor está instalado.

Una vez conectado, lee una cadena del servidor y la


escribe en la pantalla:

import java.io.DataInputStream;

import java.io.InputStream;

57
import java.net.Socket;

public class Cliente {

static final String HOST = "localhost";

static final int PUERTO=5000;

public Cliente( ) {

try{

Socket skCliente = new Socket( HOST ,

PUERTO );

InputStream aux =
skCliente.getInputStream();

DataInputStream flujo = new


DataInputStream(

aux );

System.out.println( flujo.readUTF() );

skCliente.close();

} catch( Exception e ) {

System.out.println( e.getMessage() );

58
}
public static void main( String[] arg ) {

new Cliente();
}

En primer lugar, se crea el socket denominado


skCliente, al que se le especifican el nombre de host
(HOST) y el número de puerto (PORT) en este ejemplo
constante.

Luego se asocia el flujo de datos de dicho socket


(obtenido mediante getInputStream)), que es asociado a un
flujo (flujo) DataInputStream de lectura secuencial. De
dicho flujo capturamos una cadena ( readUTF() ), y la
imprimimos por pantalla (System.out).

El socket se cierra, una vez finalizadas las operaciones,


mediante el método close().

Debe observarse que se realiza una gestión de


excepción para capturar los posibles fallos tanto de los
flujos de datos como del socket.

59
2.9.2 Programa Servidor
El programa servidor se instala en un puerto
determinado, a la espera de conexiones, a las que tratará
mediante un segundo socket.

Cada vez que se presenta un cliente, le saluda con una


frase "Hola cliente N".

Este servidor sólo atenderá hasta tres clientes, y


después finalizará su ejecución, pero es habitual utilizar
bucles infinitos ( while(true) ) en los servidores, para que
atiendan llamadas continuamente.

60
Tras atender cuatro clientes, el servidor deja de ofrecer
su servicio:

import java.io.DataOutputStream;

import java.io.OutputStream;

import java.net.ServerSocket;

import java.net.Socket;

public class Servidor {

static final int PUERTO=5000;

public Servidor( ) {
try{

ServerSocket skServidor = new

ServerSocket(PUERTO);

System.out.println("Escucho el puerto " +

PUERTO );

for ( int numCli = 0; numCli < 3; numCli++ ) {

// Crea objeto

Socket skCliente = skServidor.accept();

61
System.out.println("Sirvo al cliente " +

numCli);

OutputStream aux =

skCliente.getOutputStream();

DataOutputStream flujo= new

DataOutputStream( aux );

flujo.writeUTF( "Hola cliente " +


numCli );

skCliente.close();

System.out.println("Demasiados clientes
por

hoy");

} catch( Exception e ) {

System.out.println( e.getMessage() );

}
public static void main( String[] arg ) {

62
new Servidor();

}
}

Utiliza un objeto de la clase ServerSocket (skServidor),


que sirve para esperar las conexiones en un puerto
determinado (PUERTO), y un objeto de la clase Socket
(skCliente) que sirve para gestionar una conexión con cada
cliente.

Mediante un bucle for y la variable numCli se restringe


el número de clientes a tres, con lo que cada vez que en el
puerto de este servidor aparezca un cliente, se atiende y se
incrementa el contador.

Para atender a los clientes se utiliza la primitiva


accept() de la clase ServerSocket, que es una rutina que
crea un nuevo Socket (skCliente) para atender a un cliente
que se ha conectado a ese servidor.

Se asocia al socket creado (skCliente) un flujo (flujo) de


salida DataOutputStream de escritura secuencial, en el que
se escribe el mensaje a enviar al cliente.

El tratamiento de las excepciones es muy reducido en


nuestro ejemplo, tan solo se captura e imprime el mensaje
que incluye la excepción mediante getMessage().

63
2.9.3 Ejecución
Aunque la ejecución de los sockets está diseñada para
trabajar con ordenadores en red, en sistemas operativos
multitarea (por ejemplo, Windows y UNIX) se puede probar
el correcto funcionamiento de un programa de sockets en
una misma máquina.

Para ellos se ha de colocar el servidor en una ventana,


obteniendo lo siguiente: >java Servidor Escucho el puerto
5000

En otra ventana se lanza varias veces el programa


cliente, obteniendo:

java cliente:

Hola cliente 1

java cliente:

Hola cliente 2

java cliente:

Hola cliente 3

Mientras tanto en la ventana del servidor se ha impreso:

Sirvo al cliente 1

Sirvo al cliente 2

Sirvo al cliente 3

64
Demasiados clientes por hoy

Cuando se lanza el cuarto de cliente, el servidor ya ha


cortado la conexión, con lo que se lanza una excepción.

Obsérvese que tanto el cliente como el servidor pueden


leer o escribir del socket. Los mecanismos de
comunicación pueden ser refinados cambiando la
implementación de los sockets, mediante la utilización de
las clases abstractas que el paquete java.net provee.

3. Java DataBase Connectivity


Java permite conectarse a bases de datos usando SQL-
92 (Structured Query Language). La gran ventaja de esta
65
utilidad es que una Base de Datos. Puede migrarse y aún
ser vista con el mismo programa Java. Usualmente, una
aplicación con acceso a BD. se programa en un lenguaje
propietario y sobre un sistema manejador de BD
propietario.

La API Java para el manejo de Bases de Datos es


JDBC (Java DataBase Connectivity). Este modelo no es
una derivación de ODBC (Open DataBase Connectivity)
JDBC está escrito en Java ODBC está escrito en C En
esencia, la idea es la misma: un programa dialoga con el
JDBC driver manager quien a su vez se comunica con un
controlador particular.

JDBC es una interface hacia SQL, el lenguaje común de


todas las Bases de Datos relacionales modernas. Las
Bases de Datos de escritorio cuentan con Interfaces
graficas de usuario para manipular directamente los datos,
pero las Bases de Datos servidor se controlan a través de
SQL. Para conectarse a una Base de Datos se debe
especificar el nombre de misma y algunos parámetros
adicionales.

La arquitectura modular de JDBC permite utilizar la


misma interfaz para conectar con distintos tipos de bases
de datos gracias a la implementación de un gestor de

66
drivers de bases de datos como lo podemos observar en la
siguiente figura:

67
3.1 Paquete java.sql
Uso de controladores de las Base de Datos.

 Clase DriverManager : permite establecer y


gestionar conexiones a las BD
 Clase SQLPermission: proporciona los permisos
para poder usar el DriverManager a código en
ejecución dentro de un Security Manager (por
ejemplo, applets)
 Interfaz Driver: métodos para registrar y conectar
controladores basados en tecnología JDBC
 Clase DriverPropertyInfo: propiedades de un
controlador
 Excepciones: SQLException y SQLWarning.

Interfaz de aplicación:

 Envío de instrucciones a la Base de Datos


o Connection: métodos para crear instrucciones y
para gestionar sus propiedades.
o Statement: permite enviar instrucciones a la
Base de Datos.
o PreparedStatement: permite usar instrucciones
preparadas (parametrizadas) o sql básicas.
o CallableStatement: llamada a procedimientos
almacenados en la Base de Datos.

68
o Savepoint: puntos de recuperación en una
transacción.
 Recuperación de los resultados
o ResultSet: conjunto de resultados que retorna la
ejecución de una consulta (query).
o ResultSetMetaData: información sobre
columnas del objeto ResultSet.

A continuación, se detallan un poco mejor interfaces y


clases ubicadas en la capa de aplicación:

 Connection
o Representa la sesión con la conexión a la base
de datos.
o La función principal de Connection es crear
objetos del tipo Statement (Statement,
PreparedStatement, CallableStatement).
o Cómo de crea:
 Connection conn =
DriverManager.getConnection(url);
 Connection conn =
DriverManager.getConnection(url, user,
password);
o Características transaccionales:
 conn.SetAutocommit(false/true);

69
 con.commit();
 conn.rollback();

 Statement
o Es el canal a través del cual se le envían
instrucciones SQL a la base de datos y se
reciben los resultados.
o Las instrucciones SQL puede ser instrucciones
DML (Insert, Update, Delete), DDL (Create, Drop)
o instrucciones SELECT.
o Como se crea:
 Statement stmt= conn.createStatement();
o Sobre una conexión se pueden tener n objetos
Statement.
o Métodos de ejecución:
 Ejecución de instrucciones SELECT
 ResultSet resultadoSelect =
stmt.executeQuery(sql);
 Ejecución de instrucciones DML/DDL
 int resultadoDML =
stmt.executeUpdate(sql);

 PreparedStatement (hereda de Statement)


o Se usa cuando se llama “n” veces la misma
instrucción.

70
o Permite el manejo de parámetros dinámicos
o Ventajas técnicas sobre Statement:
 Si se tiene Statement la instrucción SQL es
compilada cada vez que se usa.
 Con PreparedStatement solo compila una
vez.
o Como se crea:
 PreparedStatement stmt=
conn.prepareStatement(sql);
o Ejemplo:
 sql = “SELECT * FROM productos WHERE
id=? AND fecha=?”
 Parámetro 1:id
 Parámetro 2: fecha
 PreparedStatement stmt=
conn.prepareStatement(sql);
 Pasando parámetros:
 stmt.setInt(1,“10”); //Si quisiéramos el
producto 10
 stmt.setDate(2,“03/12/2016”) //Los
productos creados en esa
fecha.

 ResultSet

71
o Tiene el mismo comportamiento de un cursor
o Define los métodos, que permiten acceder al
cursor generado como resultado de la ejecución
de un SELECT.
o El puntero está ubicado antes de la primera fila.
o Para moverse entre filas se emplea
ResultSet.next()
o Para obtener una columna especifica de la fila,
se puede hacer invocando el método
ResultSet.getXXX (xxx indica el tipo de datos)
o Ejemplo:
String sql=“select * from productos”
Statement stmt_ = conn.createStatemet();
ResultSet resultado_ = stmt_.executeQuery(sql);
while(resultado_.next()){
System.out.println(“ id producto-->
”+resultado_.getInt(“id”));
System.out.println(“ producto-->
”+resultado_.getString(“nombre”));

72
3.2 Correspondencia de los tipos SQL con clases o
interfaces en Java

Correspondencia con tipos SQL  Java

73
3.2.1 Secuencia normal de flujo
1. Establecer la conexión con la Base de Datos
a. Cargar controladores
b. Establecer la conexión
2. Crear un objeto Statement para hacer petición a la
Base de Datos
a. Asociar una sentencia SQL al objeto Statement
b. Proporcionar valores de los parámetros
c. Ejecutar el objeto Statement
3. Procesar los resultados
4. Liberar recursos (cerrar la conexión)

74
3.2.2 Secuencia normal para la ejecución de varias
instrucciones dentro de una transacción
1. Abrir transacción
a. Crear y ejecutar instrucciones.
b. Procesar resultados.
2. Cerrar transacción

Establecimiento de conexión con una Base de Datos

 Registrar un controlador
o Los DriverManager se encargan de gestionar
la conexión y todas las comunicaciones con la
Base de Datos.
o Necesitan conocer los controladores
específicos para las BD que se vayan a
utilizar.
 Ejemplo: Registro de un controlador
o Utilizar el controlador dependiendo los
Sistemas administradores de bases de datos a
utilizar en el desarrollo de la aplicación.
o Estos controladores se pueden instalar
directamente.
o También se pueden registrar con el Class
Loader de Java.
 Ejemplo de registro de un driver de Mysql:
try {

75
Class.forName("com.mysql.jdbc.D
river").newInstance();
} catch (Exception ex) {
// Tratar el error
}
 Los controladores se identifican con un
URL JDBC de la forma
JDBC:subprotocolo:localizadorBD
 El subprotocolo indica el tipo de BD
especifico.
 El localizador permite referenciar de
forma única la BD
o Host y puerto(opcional)
o Nombre de la BD
 La conexión de la BD se hace con el
método getConnection
 public static Connection
getConnection(String url)
 public static Connection
getConnection(String url, String user,
String password)
 Ejemplo:
try {
conexion =

76
DriverManager.getConnection( "jdbc:
mysql://localhost/tienda","admin",
"1234");
} catch (SQLException ex) {
// Tratar el error
}

Creación y ejecución de operaciones en la Base de Datos

 Statement
o Encapsula las instrucciones SQL a la BD
o Se crea a partir de la conexión
instruccion = conexion.createStatement();
o Métodos
 executeQuery(String sql)
 Ejecución de consultas: SELECT
 Devuelve un objeto ResultSet
 executeUpdate(String sql)
 Modificaciones en la BD: INSERT,
UPDATE, DELETE
 Devuelve el número de columnas
afectadas
 execute(String sql)
 Ejecución de instrucciones que pueden
devolver varios conjuntos de resultados

77
 Requiere usar luego getResultSet() o
getUpdateCount() para recuperar los
resultados, y getMoreResults() para ver
los siguientes resultados

Ejemplo:

try {

Statement instruccion = conexion.createStatement();

String query = "SELECT * FROM clientes WHERE


nombre LIKE \"Empresa%\"";

ResultSet resultados =
instruccion.executeQuery(query);

System.out.println("Listado de clientes: ");

while (resultados.next()) {

System.out.println("Cliente”

+resultados.getString("nif") +", Nombre:”

+resultados.getString("nombre") +", Teléfono: "

+resultados.getString("telefono") );

}
} catch (Exception ex) {

78
e.printStackTrace();

 Liberación de Recursos
o Las interfaces ResultSet, PreparedStatement y
Connection disponen del método close.
o Cuando se cierra una conexión, se cierran todos
sus PreparedStatement asociados
o Cuando se cierra un PreparedStatement, se cierran
todos sus ResultSet asociados
o La implementación de la interfaz Connection debe
redefinir finalize para que invoque a close en caso
de que el desarrollador no lo haya hecho
 NOTA: finalize es un método definido en
Object; el recolector de basura lo invoca antes
de eliminar un objeto de memoria
o En resumen, se crea la ilusión de que nunca es
necesario cerrar las conexiones explícitamente.

79

También podría gustarte