Sistemas

Sistemas

miércoles, 4 de julio de 2012

JAVA 35. Estructuras dinámicas: Listas tipo Pila - Problema de aplicación

Hasta ahora hemos visto como desarrollar los algoritmos para administrar una lista tipo Pila, hemos visto que hay bastante complejidad en el manejo de punteros pero todo esto acarrea ventajas en la solución de problemas que requieren una estructura de tipo Pila.

Planteo del problema:

Este práctico tiene por objetivo mostrar la importancia de las pilas en las Ciencias de la Computación y más precisamente en la programación de software de bajo nivel.

Todo compilador o intérprete de un lenguaje tiene un módulo dedicado a analizar si una expresión está correctamente codificada, es decir que los paréntesis estén abiertos y cerrados en un orden lógico y bien balanceados.

Se debe desarrollar una clase que tenga las siguientes responsabilidades (clase Formula):



- Ingresar una fórmula que contenga paréntesis, corchetes y llaves.
- Validar que los ( ) [] y {} estén correctamente balanceados. 


Para la solución de este problema la clase formula tendrá un atributo de la clase Pila.

Veamos como nos puede ayudar el empleo de una pila para solucionar este problema.

Primero cargaremos la fórmula en un JTextField.

Ejemplo de fórmula: (2+[3-12]*{8/3})

El algoritmo de validación es el siguiente:

Analizamos caracter a caracter la presencia de los paréntesis, corchetes y llaves.

Si vienen símbolos de apertura los almacenamos en la pila.

Si vienen símbolos de cerrado extraemos de la pila y verificamos si está el mismo símbolo pero de apertura: en caso negativo podemos inferir que la fórmula no está correctamente balanceada.

Si al finalizar el análisis del último caracter de la fórmula la pila está vacía podemos concluir que está correctamente balanceada.

Ejemplos de fórmulas no balanceadas:

}(2+[3-12]*{8/3})
Incorrecta: llega una } de cerrado y la pila está vacía.
{[2+4}]
Incorrecta: llega una llave } y en el tope de la pila hay un corchete [.
{[2+4]
Incorrecta: al finalizar el análisis del último caracter en la pila queda 

pendiente una llave {.


Pila

Programa:

public class Pila {
 
 class Nodo {
     char simbolo;
     Nodo sig;
 }
 
    private Nodo raiz;
    
    Pila () {
        raiz=null;
    }
    
    public void insertar(char x) {
     Nodo nuevo;
        nuevo = new Nodo();
        nuevo.simbolo = x;
        if (raiz==null)
        {
            nuevo.sig = null;
            raiz = nuevo;
        }
        else
        {
            nuevo.sig = raiz;
            raiz = nuevo;
        }
    }
    
    public char extraer ()
    {
        if (raiz!=null)
        {
            char informacion = raiz.simbolo;
            raiz = raiz.sig;
            return informacion;
        }
        else
        {
            return Character.MAX_VALUE;
        }
    }  
    
    public boolean vacia() {
        if (raiz==null) {
            return true;
        } else {
            return false;
        }
    }
}





import javax.swing.*;
import java.awt.event.*;
public class Formula extends JFrame implements ActionListener {
    private JTextField tf1;
    private JButton boton1;
    public Formula() {
        setLayout(null);
        tf1=new JTextField("{2*(4-5)-{3*4}-[4-5]}");
        tf1.setBounds(10,10,230,30);
        add(tf1);
        boton1=new JButton("Verificar fórmula.");
        boton1.setBounds(10,70,180,30);
        add(boton1);
        boton1.addActionListener(this);
    }
    
    public void actionPerformed(ActionEvent e) {
        if (e.getSource()==boton1) {
           if (balanceada()) {
               setTitle("Está correctamente balanceada.");
           } else {
               setTitle("No está correctamente balanceada.");
           }
        }
    }

    public boolean balanceada() {
        Pila pila1;  
     pila1 = new Pila ();    
     String cadena=tf1.getText();
     for (int f = 0 ; f < cadena.length() ; f++)
     {
         if (cadena.charAt(f) == '(' || cadena.charAt(f) == '[' || cadena.charAt(f) == '{') {
          pila1.insertar(cadena.charAt(f));
         } else {
          if (cadena.charAt(f)==')') {
              if (pila1.extraer()!='(') {
                  return false;
              }
           } else {
              if (cadena.charAt(f)==']') {
                  if (pila1.extraer()!='[') {
                      return false;
                  }
              } else {
                  if (cadena.charAt(f)=='}') {
                      if (pila1.extraer()!='{') {
                          return false;
                      }
                  }
              }
             }
        }   
        }
     if (pila1.vacia()) {
         return true;
     } else {
        return false;
     }
    } 
    
    
    public static void main(String[] ar) {
        Formula formula1=new Formula();
        formula1.setBounds(0,0,350,140);
        formula1.setVisible(true);
    }
    
}


Primero declaramos y definimos la clase Pila. Almacenamos en cada nodo un caracter y llamamos al campo de información símbolo.

No es necesario implementar los métodos imprimir, cantidad, etc. Porque no se requieren para este problema.

La clase Formula tiene como atributos:

    private JTextField tf1;
    private JButton boton1;

En el constructor creamos los dos objetos y los ubicamos:

        setLayout(null);
        tf1=new JTextField("{2*(4-5)-{3*4}-[4-5]}");
        tf1.setBounds(10,10,230,30);
        add(tf1);
        boton1=new JButton("Verificar fórmula.");
        boton1.setBounds(10,70,180,30);
        add(boton1);
        boton1.addActionListener(this);
En el método actionPerformed llamamos al método balanceada que debe retornar si la fórmula están correctos los parentesis, corchetes y llaves:
        if (e.getSource()==boton1) {
           if (balanceada()) {
               setTitle("Está correctamente balanceada.");
           } else {
               setTitle("No está correctamente balanceada.");
           }
        }

El método más importante es el balanceada.

En este analizamos la fórmula para verificar si está correctamente balanceada.
En este método es donde está gran parte del algoritmo de este problema. Retorna true en caso de ser correcta y false en caso contrario.

Definimos una pila y extraemos el contenido del JTextField:

        Pila pila1;  
     pila1 = new Pila ();    
     String cadena=tf1.getText();

El for se repite tantas veces como caracteres tenga el JTextField.
Se deben procesar sólo los símbolos ( [ { y ) ] }.

Si el símbolo es un ( [ { de apertura procedemos a cargarlo en la pila:

         if (cadena.charAt(f) == '(' || cadena.charAt(f) == '[' || cadena.charAt(f) == '{') {
          pila1.insertar(cadena.charAt(f));

En caso de ser un ) cerrado debemos extraer un carácter de la pila y verificar si no coincide con el paréntesis de apertura ( la fórmula está incorrecta:

          if (cadena.charAt(f)==')') {
              if (pila1.extraer()!='(') {
                  return false;
              }

El mismo proceso es para los símbolos ] }.

Al finalizar el análisis de toda la cadena si la pila está vacía podemos afirmar que la fórmula está correctamente balanceada, en caso contrario quiere decir que faltan símbolos de cerrado y es incorrecta:

     if (pila1.vacia()) {
         return true;
     } else {
        return false;
     }


Es importante entender que la clase Formula utiliza un objeto de la clase Pila para resolver el algoritmo de verificar el balanceo de la fórmula, pero no accede directamente a los nodos de la lista.

JAVA 34. Estructuras dinámicas: Listas tipo Pila

Una lista se comporta como una pila si las inserciones y extracciones las hacemos por un mismo lado de la lista. También se las llama listas LIFO (Last In First Out - último en entrar primero en salir)

Importante: Una pila al ser una lista puede almacenar en el campo de información cualquier tipo de valor (int, char, float, vector de caracteres, un objeto, etc)

Para estudiar el mecanismo de utilización de una pila supondremos que en el campo de información almacena un entero (para una fácil interpretación y codificación)

Inicialmente la PILA está vacía y decimos que el puntero raiz apunta a null (Si apunta a null decimos que no tiene una dirección de memoria):

pila vacía

Insertamos un valor entero en la pila: insertar(10)

pila

Luego de realizar la inserción la lista tipo pila queda de esta manera: un nodo con el valor 10 y raiz apunta a dicho nodo. El puntero del nodo apunta a null ya que no hay otro nodo después de este.

Insertamos luego el valor 4: insertar(4)

pila

Ahora el primer nodo de la pila es el que almacena el valor cuatro.

raiz apunta a dicho nodo. Recordemos que raiz es el puntero externo a la lista que almacena la dirección del primer nodo.

El nodo que acabamos de insertar en el campo puntero guarda la dirección del nodo que almacena el valor 10.

Ahora qué sucede si extraemos un nodo de la pila. ¿Cuál se extrae? Como sabemos en una pila se extrae el último en entrar.

Al extraer de la pila tenemos: extraer()

pila

La pila ha quedado con un nodo.

Hay que tener cuidado que si se extrae un nuevo nodo la pila quedará vacía y no se podrá extraer otros valores (avisar que la pila está vacía)

Problema 1:

Confeccionar una clase que administre una lista tipo pila (se debe poder insertar, extraer e imprimir los datos de la pila)

Programa:

public class Pila {
 
    class Nodo {
        int info;
        Nodo sig;
    }
 
    private Nodo raiz;
    
    public Pila () {
        raiz=null;
    }
    
    public void insertar(int x) {
     Nodo nuevo;
        nuevo = new Nodo();
        nuevo.info = x;
        if (raiz==null)
        {
            nuevo.sig = null;
            raiz = nuevo;
        }
        else
        {
            nuevo.sig = raiz;
            raiz = nuevo;
        }
    }
    
    public int extraer ()
    {
        if (raiz!=null)
        {
            int informacion = raiz.info;
            raiz = raiz.sig;
            return informacion;
        }
        else
        {
            return Integer.MAX_VALUE;
        }
    }
    
    public void imprimir() {
        Nodo reco=raiz;
        System.out.println("Listado de todos los elementos de la pila.");
        while (reco!=null) {
            System.out.print(reco.info+"-");
            reco=reco.sig;
        }
        System.out.println();
    }
    
    public static void main(String[] ar) {
        Pila pila1=new Pila();
        pila1.insertar(10);
        pila1.insertar(40);
        pila1.insertar(3);
        pila1.imprimir();
        System.out.println("Extraemos de la pila:"+pila1.extraer());
        pila1.imprimir();        
    }
}
Analicemos las distintas partes de este programa:

    class Nodo {
        int info;
        Nodo sig;
    }
 
    private Nodo raiz;


Para declarar un nodo debemos utilizar una clase. En este caso la información del nodo (info) es un entero y siempre el nodo tendrá una referencia de tipo Nodo, que le llamamos sig.

El puntero sig apunta al siguiente nodo o a null en caso que no exista otro nodo. Este puntero es interno a la lista.

También definimos un puntero de tipo Nodo llamado raiz. Este puntero tiene la dirección del primer nodo de la lista. En caso de estar vacía la lista, raiz apunta a null (es decir no tiene dirección)

El puntero raiz es fundamental porque al tener la dirección del primer nodo de la lista nos permite acceder a los demás nodos.

    public Pila () {
        raiz=null;
    }
En el constructor de la clase hacemos que raiz guarde el valor null. Tengamos en cuenta que si raiz tiene almacenado null la lista está vacía, en caso contrario tiene la dirección del primer nodo de la lista.

    public void insertar(int x) {
     Nodo nuevo;
        nuevo = new Nodo();
        nuevo.info = x;
        if (raiz==null)
        {
            nuevo.sig = null;
            raiz = nuevo;
        }
        else
        {
            nuevo.sig = raiz;
            raiz = nuevo;
        }
    }
Uno de los métodos más importantes que debemos entender en una pila es el de insertar un elemento en la pila.
Al método llega la información a insertar, en este caso en particular es un valor entero.
La creación de un nodo requiere dos pasos:

- Definición de un puntero o referencia a un tipo de dato Nodo:

     Nodo nuevo;
- Creación del nodo (creación de un objeto):
        nuevo = new Nodo();
Cuando se ejecuta el operador new se reserva espacio para el nodo. Realmente se crea el nodo cuando se ejecuta el new.

pila

Paso seguido debemos guardar la información del nodo:

        nuevo.info = x;

En el campo info almacenamos lo que llega en el parámetro x. Por ejemplo si llega un 5 el nodo queda:

pila

Por último queda enlazar el nodo que acabamos de crear al principio de la lista.

Si la lista está vacía debemos guardar en el campo sig del nodo el valor null para indicar que no hay otro nodo después de este, y hacer que raiz apunte al nodo creado (sabemos si una lista esta vacía si raiz almacena un null)

        if (raiz==null)
        {
            nuevo.sig = null;
            raiz = nuevo;
        }

pila

Gráficamente podemos observar que cuando indicamos raiz=nuevo, el puntero raiz guarda la dirección del nodo apuntado por nuevo.

Tener en cuenta que cuando finaliza la ejecución del método el puntero nuevo desaparece, pero no el nodo creado con el operador new.

En caso que la lista no esté vacía, el puntero sig del nodo que acabamos de crear debe apuntar al que es hasta este momento el primer nodo, es decir al nodo que apunta raiz actualmente.

        else
        {
            nuevo.sig = raiz;
            raiz = nuevo;
        }

Como primera actividad cargamos en el puntero sig del nodo apuntado por nuevo la dirección de raiz, y posteriormente raiz apunta al nodo que acabamos de crear, que será ahora el primero de la lista.

Antes de los enlaces tenemos:

pila

Luego de ejecutar la línea:

            nuevo.sig = raiz;

Ahora tenemos:

pila

Por último asignamos a raiz la dirección que almacena el puntero nuevo.

            raiz = nuevo;

La lista queda:

pila

El método extraer:

    public int extraer ()
    {
        if (raiz!=null)
        {
            int informacion = raiz.info;
            raiz = raiz.sig;
            return informacion;
        }
        else
        {
            return Integer.MAX_VALUE;
        }
    }

El objetivo del método extraer es retornar la información del primer nodo y además borrarlo de la lista.

Si la lista no está vacía guardamos en una variable local la información del primer nodo:

            int informacion = raiz.info;

Avanzamos raiz al segundo nodo de la lista, ya que borraremos el primero:

            raiz = raiz.sig;

el nodo que previamente estaba apuntado por raiz es eliminado automáticamente por la máquina virtual de Java, al no tener ninguna referencia.

Retornamos la información:

            return informacion;

En caso de estar vacía la pila retornamos el número entero máximo y lo tomamos como código de error (es decir nunca debemos guardar el entero mayor en la pila)

            return Integer.MAX_VALUE;

Es muy importante entender gráficamente el manejo de las listas. La interpretación gráfica nos permitirá plantear inicialmente las soluciones para el manejo de listas.

pila

Por último expliquemos el método para recorrer una lista en forma completa e imprimir la información de cada nodo:

    public void imprimir() {
        Nodo reco=raiz;
        System.out.println("Listado de todos los elementos de la pila.");
        while (reco!=null) {
            System.out.print(reco.info+"-");
            reco=reco.sig;
        }
        System.out.println();
    }

Definimos un puntero auxiliar reco y hacemos que apunte al primer nodo de la lista:

        Nodo reco=raiz;

Disponemos una estructura repetitiva que se repetirá mientras reco sea distinto a null. Dentro de la estructura repetitiva hacemos que reco avance al siguiente nodo:

        while (reco!=null) {
            System.out.print(reco.info+"-");
            reco=reco.sig;
        }

Es muy importante entender la línea:

            reco=reco.sig;

Estamos diciendo que reco almacena la dirección que tiene el puntero sig del nodo apuntado actualmente por reco.

Gráficamente:

pila

Al analizarse la condición:

        while (reco!=null) {

se valúa en verdadero ya que reco apunta a un nodo y se vuelve a ejecutar la línea:

            reco=reco.sig;

Ahora reco apunta al siguiente nodo:

pila

La condición del while nuevamente se valúa en verdadera y avanza el puntero reco al siguiente nodo:

            reco=reco.sig;

pila

Ahora sí reco apunta a null y ha llegado el final de la lista (Recordar que el último nodo de la lista tiene almacenado en el puntero sig el valor null, con el objetivo de saber que es el último nodo)

Para poder probar esta clase recordemos que debemos definir un objeto de la misma y llamar a sus métodos:

    public static void main(String[] ar) {
        Pila pila1=new Pila();
        pila1.insertar(10);
        pila1.insertar(40);
        pila1.insertar(3);
        pila1.imprimir();
        System.out.println("Extraemos de la pila:"+pila1.extraer());
        pila1.imprimir();        
    }

Insertamos 3 enteros, luego imprimimos la pila, extraemos uno de la pila y finalmente imprimimos nuevamente la pila.

Problema 2:

Agregar a la clase Pila un método que retorne la cantidad de nodos y otro que indique si esta vacía.

Programa:

public class Pila {
 
 class Nodo {
     int info;
     Nodo sig;
 }
 
    private Nodo raiz;
    
    Pila () {
        raiz=null;
    }
    
    public void insertar(int x) {
     Nodo nuevo;
        nuevo = new Nodo();
        nuevo.info = x;
        if (raiz==null)
        {
            nuevo.sig = null;
            raiz = nuevo;
        }
        else
        {
            nuevo.sig = raiz;
            raiz = nuevo;
        }
    }
    
    public int extraer ()
    {
        if (raiz!=null)
        {
            int informacion = raiz.info;
            raiz = raiz.sig;
            return informacion;
        }
        else
        {
            return Integer.MAX_VALUE;
        }
    }
    
    public void imprimir() {
        Nodo reco=raiz;
        System.out.println("Listado de todos los elementos de la pila.");
        while (reco!=null) {
            System.out.print(reco.info+"-");
            reco=reco.sig;
        }
        System.out.println();
    }
    
    public boolean vacia() {
        if (raiz==null) {
            return true;
        } else {
         return false; 
        }
    }
    
    public int cantidad() {
        int cant=0;
        Nodo reco=raiz;
        while (reco!=null) {
            cant++;
            reco=reco.sig;
        }
        return cant;
    }
    
    public static void main(String[] ar) {
        Pila pila1=new Pila();
        pila1.insertar(10);
        pila1.insertar(40);
        pila1.insertar(3);
        pila1.imprimir();
        System.out.println("La cantidad de nodos de la lista es:"+pila1.cantidad());
        while (pila1.vacia()==false) {
            System.out.println(pila1.extraer());
        }
    }
}


Para verificar si la pila esta vacía verificamos el contenido de la variable raiz, si tiene null luego la lista esta vacía y por lo tanto retornamos un true:

    public boolean vacia() {
        if (raiz==null) {
            return true;
        } else {
         return false; 
        }
    }


El algoritmo para saber la cantidad de nodos es similar al imprimir, pero en lugar de mostrar la información del nodo procedemos a incrementar un contador:

    public int cantidad() {
        int cant=0;
        Nodo reco=raiz;
        while (reco!=null) {
            cant++;
            reco=reco.sig;
        }
        return cant;
    }

Para probar esta clase en la main creamos un objeto de la clase Pila insertamos tres enteros:

        Pila pila1=new Pila();
        pila1.insertar(10);
        pila1.insertar(40);
        pila1.insertar(3);

Imprimimos la pila (nos muestra los tres datos):

        pila1.imprimir();

Llamamos al método cantidad (nos retorna un 3):

        System.out.println("La cantidad de nodos de la lista es:"+pila1.cantidad());

Luego mientras el método vacía nos retorne un false (lista no vacía) procedemos a llamar al método extraer:

        while (pila1.vacia()==false) {
            System.out.println(pila1.extraer());
        }

Problemas propuestos

  1. Agregar un método a la clase Pila que retorne la información del primer nodo de la Pila sin borrarlo.

JAVA 33. Estructuras dinámicas: Listas

Una lista es un conjunto de nodos, cada uno de los cuales tiene dos campos: uno de información y un apuntador al siguiente nodo de la lista. Además un apuntador externo señala el primer nodo de la lista.

Representación gráfica de un nodo:



La información puede ser cualquier tipo de dato simple, estructura de datos o inclusive uno o más objetos.

La dirección al siguiente nodo es un puntero.

Representación gráfica de una lista:


Como decíamos, una lista es una secuencia de nodos (en este caso cuatro nodos). La información de los nodos en este caso es un entero y siempre contiene un puntero que guarda la dirección del siguiente nodo.

raiz es otro puntero externo a la lista que contiene la dirección del primer nodo.

El estado de una lista varía durante la ejecución del programa:



De esta forma representamos gráficamente una lista vacía.

Si insertamos un nodo en la lista quedaría luego:



Si insertamos otro nodo al principio con el valor 9 tenemos:



Lo mismo podemos borrar nodos de cualquier parte de la lista.

Esto nos trae a la mente el primer problema planteado: el desarrollo del procesador de texto. Podríamos utilizar una lista que inicialmente estuviera vacía e introdujéramos un nuevo nodo con cada línea que tipea el operador. Con esta estructura haremos un uso muy eficiente de la memoria. Tipos de listas.

Según el mecanismo de inserción y extracción de nodos en la lista tenemos los siguientes tipos:

* Listas tipo pila. * Listas tipo cola. * Listas genéricas. Una lista se comporta como una pila si las inserciones y extracciones las hacemos por un mismo lado de la lista. También se las llama listas LIFO (Last In First Out - último en entrar primero en salir)

Una lista se comporta como una cola si las inserciones las hacemos al final y las extracciones las hacemos por el frente de la lista. También se las llama listas FIFO (First In First Out - primero en entrar primero en salir)

Una lista se comporta como genérica cuando las inserciones y extracciones se realizan en cualquier parte de la lista.

Podemos en algún momento insertar un nodo en medio de la lista, en otro momento al final, borrar uno del frente, borrar uno del fondo o uno interior, etc.

JAVA 32. Estructuras dinámicas

Conocemos algunas estructuras de datos como son los vectores y matrices. No son las únicas. Hay muchas situaciones donde utilizar alguna de estas estructuras nos proporcionará una solución muy ineficiente (cantidad de espacio que ocupa en memoria, velocidad de acceso a la información, etc.)

Ejemplo 1. Imaginemos que debemos realizar un procesador de texto, debemos elegir la estructura de datos para almacenar en memoria las distintas líneas que el operador irá tipeando. Una solución factible es utilizar una matriz de caracteres. Pero como sabemos debemos especificar la cantidad de filas y columnas que ocupará de antemano.

Podría ser por ejemplo 2000 filas y 200 columnas. Con esta definición estamos reservando de antemano 800000 bytes de la memoria, no importa si el operador después carga una línea con 20 caracteres, igualmente ya se ha reservado una cantidad de espacio que permanecerá ociosa.

Tiene que existir alguna estructura de datos que pueda hacer más eficiente la solución del problema anterior.

Ejemplo 2. ¿Cómo estarán codificadas las planillas de cálculo? ¿Reservarán espacio para cada casilla de la planilla al principio? Si no la lleno, ¿lo mismo se habrá reservado espacio?

Utilizar una matriz para almacenar todas las casillas de una planilla de cálculo seguro será ineficiente.

Bien, todos estos problemas y muchos más podrán ser resueltos en forma eficiente cuando conozcamos estas nuevas estructuras de datos (Listas, árboles)

martes, 24 de mayo de 2011

JAVA 31. EJEMPLO BÁSICO DE MYSQL CON JAVA


Una vez instalado MySQL, descargado el driver para java de MySQL y con una base de datos y una tabla creada en MySQL, vamos a hacer un pequeño programa en java que nos permita conectarnos a la base de datos MySQL y consultar la tabla que hemos creado.

Además, necesitamos la clase org.gjt.mm.mysql.Driver que viene con el driver de MySQL. Por ello, en nuestro CLASSPATH o incluido en nuestro proyecto con nuestro IDE favorito, debemos incluir el jar que contiene el driver MySQL (mysql-connector-java-3.1.7-bin.jar) o la versión más moderna y compatible con la versión de nuestro servidor de MySQL.

Lo primero que tenemos que hacer es asegurarnos que el Driver se inicializa y se registra, para ello


try
{
   Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e)
{
   e.printStackTrace();
}


Establecer la conexión con la base de datos 

 

Debemos tener el servidor de MySQL arrancado. Si hemos instalado y dejado esa opción como estaba, cada vez que encendamos el ordenador, se arrancará el servidor de MySQL, por lo que no tenemos que preocuparnos por ello.

El servidor de MySQL abre por defecto el puerto 3306 para aceptar conexiones de posibles clientes, de programas que quieran conectarse y acceder a la base de datos. Nuestro programa java, si quiere consultar la tabla de base de datos que hemos creado, deberá conectarse a este servidor.
Para establecer la conexion, la clase DriverManager tiene métodos getConnection(). Usaremos uno de ellos

// Establecemos la conexión con la base de datos.
Connection conexion = DriverManager.getConnection ("jdbc:mysql://localhost/prueba","root", "la_clave");
El primer parámetro del método getConnection() es un String que contiene la url de la base de datos:

  • jdb:mysql porque estamos utilizando un driver jdbc para MySQL, que es el que nos hemos bajado.
  • localhost porque el servidor de base de datos, en mi caso, está en el mismo ordenador en el que voy a correr el prorama java. Aquí puede ponerse una IP o un nombre de máquina que esté en la red.
  • prueba es el nombre de la base de datos que he creado dentro de mysql. Se debe poner la base de datos dentro del servidor de MySQL a la que se quiere uno conectar. Es el nombre que pusimos cuando desde SQL hicimos create database prueba;

Los otros dos parámetros son dos String. Corresponden al nombre de usuario y password para acceder a la base de datos. Al instalar MySQL se crea el usuario root y se pide la password para él. Como no hemos creado otros usuarios, usaremos este mismo.

Si todo va bien, en conexion tendremos nuestra conexión a la base de datos.

Esta conexión es en realidad un socket entre java y la base de datos, aunque para nosotros es transparente. Lo que sí es importante, es saber que si varios hilos comparten esta conexión, deben usarla sincronizadamente. Si no se hace así, los mensajes que van por el socket se pueden entremezclar y los hilos pueden leer cachos de mensaje destinados al otro hilo. Otra opción es que cada hilo cree su propia conexión. Finalmente, la mejor opción de todas si nuestra aplicación va a tener varios hilos intentando acceder a la base de datos, es usar un Pool de conexiones.

Realizar una consulta


Para realizar cualquier acción sobre la base de datos (consulta, insertar nuevos registros, modificar los existentes o borrar), necesitamos una clase Statement. Para obtenerla, se le pide dicha clase a la conexión. La forma de hacerlo, para una consulta, es la siguiente:

// Preparamos la consulta
Statement s = conexion.createStatement();
ResultSet rs = s.executeQuery ("select * from persona");


La parte de createStatement() no tiene ningú secreto, salvo que puede lanzar una excepción que hay que capturar.

El Statement obtenido tiene un método executeQuery(). Este método sirve para realizar una consulta a base de datos.
  • El parámetro que se pasa en un String en el que está la consulta en lenguaje SQL. No hace falta terminarlo con punto y coma. En nuestro caso "select * from persona". siendo persona el nombre que hemos puesto a la tabla en la base de datos.
  • El resultado nos lo devuelve el método como un ResultSet. Este ResultSet no es más que una clase java similar a una lista en la que está el resultado de la consulta. Cada elemento de la lista es uno de los registros de la base de datos. En realidad, ResulSet no contiene todos los datos, sino que los va consiguiendo de la base de datos según se van pidiendo. Por ello, el método executeQuery() puede tardar poco, pero el recorrer los elementos del ResultSet no es tan rápido. De esta forma se evita que una consulta que dé muchos resultados tarde mucho tiempo y llene la memoria del programa java.

 

Leer los resultados 

 

El ResultSet contiene dentro los registros leidos de la base de datos. Inicialmente, tal cual nos lo devuelve el Statement.executeQuery(), tiene internamente un "puntero" apuntando justo delante del primer registro. El método next() del ResultSet hace que dicho puntero avance al siguiente registro, en este caso, al primero. Si lo consigue, el método next() devuelve true. Si no lo consigue (no hay siguiente registro que leer), devuelve false.

Por tanto, una forma de ir leyendo los registros en meternos en un while.  
// Recorremos el resultado, mientras haya registros para leer, y escribimos el resultado en pantalla.
while (rs.next())
{
    System.out.println (rs.getInt (1) + " " + rs.getString (2)+ " " + rs.getDate(3));
}

Una vez que el "puntero" está apuntando a un registro, los métodos getInt(), getString(), getDate(), etc nos van devolviendo los valores de los campos de dicho registro. Podemos pasar a estos métodos un índice (que comienza en 1) para indicar qué columna de la tabla de base de datos deseamos. También podemos usar un String con el nombre de la columna (tal cual está en la tabla de base de datos).

Es responsabilidad nuestra saber qué tipo de dato hay en cada columna, aunque si nos equivocamos y RecordSet es capaz de hacer la conversión, la hará por nosotros. Por ejemplo, en cualquiera de los campos anteriores podemos pedir un getString() y nos devolveran los números como String y la fecha como String.
También podemos usar getObject(), y el RecordSet nos devolverá el Object más adecuado para el tipo de campo que pedimos.

Cerrar la conexión

 

Una vez que terminamos de usar la conexión, se debería cerrar, o bien terminar el programa, con lo que se cierra automáticamente.

// Cerramos la conexion a la base de datos.
conexion.close();

 

El ejemplo:

 /*

* PruebaMySQL.java
*
* Programa de prueba para conexión a una base de datos de MySQL.
* Presupone que el servidor de base de datos está arrancado, disponible,
* en el puerto por defecto.
* El usuario y password de conexión con la base de datos debe cambiarse.
* En la base de datos se supone que hay una base de datos llamada prueba y que
* tiene una tabla persona con tres campos, de esta manera:
* mysql> create database prueba;
* mysql> use prueba;
* mysql> create table persona (id smallint auto_increment, nombre varchar(60), 
 *      nacimiento date, primary key(id)); 
 */
 
//package chuidiang.ejemplos.base_datos;
 
import java.sql.*;
 
/**
* Clase de prueba de conexión con una base de datos MySQL
*/
public class PruebaMySQL {
    
    /** 
     * Crea una instancia de la clase MySQL y realiza todo el código 
     * de conexión, consulta y muestra de resultados.
     */
    public PruebaMySQL() 
    {
        // Se mete todo en un try por los posibles errores de MySQL
        try
        {
            // Se registra el Driver de MySQL
            DriverManager.registerDriver(new org.gjt.mm.mysql.Driver());
            
            // Se obtiene una conexión con la base de datos. Hay que
            // cambiar el usuario "root" y la clave "la_clave" por las
            // adecuadas a la base de datos que estemos usando.
            Connection conexion = DriverManager.getConnection (
                "jdbc:mysql://localhost/prueba","root", "la_clave");
            
            // Se crea un Statement, para realizar la consulta
            Statement s = conexion.createStatement();
            
            // Se realiza la consulta. Los resultados se guardan en el 
            // ResultSet rs
            ResultSet rs = s.executeQuery ("select * from persona");
            
            // Se recorre el ResultSet, mostrando por pantalla los resultados.
            while (rs.next())
            {
                System.out.println (rs.getInt ("Id") + " " + rs.getString (2)+ 
                    " " + rs.getDate(3));
            }
            
            // Se cierra la conexión con la base de datos.
            conexion.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
    
    /**
     * Método principal, instancia una clase PruebaMySQL
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        new PruebaMySQL();
    }
    
}

JAVA 30. LO BÁSICO DE MYSQL CON JAVA

Una vez instalada la base de datos, MySQL en nuestro ejemplo, vamos a ver de forma rápida como podemos conectarnos con ella y ejecutar los comandos SQL básicos: SELECT, INSERT, UPDATE y DELETE.
También, por si acaso, crearemos desde Java la tabla de base de datos que vamos a usar y la borraremos.

EL DRIVER CON LA BASE DE DATOS


Lo primero que necesitamos para conectarnos con una base de datos es un Driver (o Connector) con ella. Ese Driver es la clase que, de alguna forma, sabe cómo hablar con la base de datos. Desgraciadamente (y hasta cierto punto es lógico), java no viene con todos los Drivers de todas las posibles bases de datos del mercado. Debemos ir a internet y obtener el Driver, normalmente en la página de nuestra base de datos.

Tendremos que poner ese jar accesible en nuestro proyecto. Dependiendo de qué utilicemos para programar en java hay muchas opciones.
  • Guardarlo en la carpeta <DIRECTORIO_DE_JAVA>/jre/lib/ext. Si has hecho una instalación por defecto en Windows, estará en C:\Archivos de Programa\Java\jdk1.5.0_05\jre\lib\ext. Esta es la carpeta en la que java nos deja poner jar adicionales para que se encuentren por defecto. Es buena idea ponerlo aquí si vamos a hacer muchos programas con base de datos. Si solo vamos a hacer uno, es mejor no ponerlo aquí. Si decidimos usar esta carpeta, también debemos tener la precaución de ir retirando jars de versiones antiguas cuando pongamos las nuevas, o tendremos problemas.
  • Añadirlo en nuestro IDE. En el ejemplo de eclipse, damos con el botón derecho del ratón sobre el proyecto, para sacar el menú y elegimos "properties"->"java build path"->"Libraries"->"Add external JARs..." y seleccionamos nuestro jar.

 

CONECTARNOS CON LA BASE DE DATOS


Una vez que java tiene el jar accesible y sabe dónde encontrarlo, ya podemos empezar con el código. Lo primero es conectarse con la base de datos

El código puede ser como este:

import java.sql.Connection;
import java.sql.DriverManager;
...
try
{
   Class.forName("com.mysql.jdbc.Driver");
   Connection conexion = DriverManager.getConnection("jdbc:mysql://localhost/agenda", "root", "LA_PASSWORD");
   ...

En primer lugar hemos hecho los import adecuados para poder obtener la conexión. 

Luego hay que asegurarse de que se inicializa el Driver y se registra. Para ello hay dos opciones. Hacer un new de él o bien instanciarlo con Class.forName("nombre de clase"), que es como hacer el new, pero de una forma rara.

La ventaja de hacerlo con Class.forName() es que sólo necesitaremos el Driver de una base de datos si lo usamos. Imagina que haces un programa que permite permite conectarse con varias bases de datos distintas: MySQL, PostGres, Oracle, etc. En algún sitio damos a elegir al usuario qué tipo de base de datos tiene.

Si hacemos new de los Driver, tendremos que hacer unos if para en función de la elegida, hacer new de uno o de otro. El problema es que en nuestro código estarán los import y los new de todos los Driver de todas las bases de datos necesaras, los vayamos a usar o no. El programa no compilará y no correrá si no tiene todos los Driver accesibles.


/* Todos estos import son necesarios para que compilen los news correspondientes */
import driver_de_mysql;
import driver_de_postgres;
import driver_de_oracle;
...
if (ha_elegido_mysql)
   new driver_de_mysql();
if (ha_elegido_postgres)
   new driver_de_postgres();
if (ha_elegido_oracle)
   new driver_de_oracle();

Sin embargo, con Class.forName() no necesitamos el import. Haremos los mismos if de antes, pero usaremos simplemente String distintos para llamar a Class.forName(). No necesitamos los import y no necesitamos los jar con los Driver, salvo el jar con el Driver que realmente elija el usuario. Es cosa suya tener instalado SU driver de SU base de datos.

/* No hacen falta imports */
...
if (ha_elegido_mysql)
   Class.forName("driver_de_mysql");
if (ha_elegido_postgres)
   Class.forName("driver_de_postgres");
if (ha_elegido_oracle)
   Class.forName("driver_de_oracle");


Una vez que nos hemos asegurado que java tiene el Driver cargado, simplemente pediremos conexión con la base de datos a la clase DriverManager.

Connection conexion = DriverManager.getConnection("jdbc:mysql://localhost/agenda", "root", "LA_PASSWORD");

DriverManager tiene muchos métodos getConnection() con parámetros variados. Todos son variantes de lo mismo y la información que suministramos es la misma. Aquí hemos utilizado uno con tres parámetros String, que vamos a explicar.
  • url: Es una cadena que nos permite localizar la base de datos. Para mysql, el formato es "jdbc:mysql://ordenador_donde_corre_la_base_de_datos/nombre_base_datos". Donde se pone el nombre o IP del ordenador en el que se encuentra nuestro servidor de base de datos y el nombre de la base de datos. En nuestro ejemplo, tenemos el servidor de base de datos corriendo en el mismo ordenador que el programa java, por lo que ponemos localhost. La base de datos la he llamado agenda. El comando SQL para crear la base de datos agenda sería
mysql> CREATE DATABASE agenda;
  • user: Un usuario válido para la base de datos.
  • password: La clave del usuario.
Si todo va bien, tendremos nuestra conexión a la base de datos. Si va mal, saltará una excepción. Por eso es importante (y obligatorio para que compile) meter todo esto en un try-catch.

Esta forma de obtener una conexión está bien para apliaciones sencillas, en el que únicamente se establece una conexión con la base de datos, no hay muchos hilos trabajando, etc. Si nuestra aplicación es algo más compleja/seria, en el que varios hilos pueden trabjar simultáneamente, en vez de obtner las conexiones directamente con DriverManager.getConnection(), es mejor obtenerlas a través de un Pool de Conexiones.

 

Creamos la tabla en Base de Datos ... y la borramos.

 

Para enviar comandos SQL a la base de datos, se usa la clase Statement de java. Esta clase se obtiene a partir de la conexión, de esta forma:

Statement st = conexion.createStatement();

por supuesto, dentro de un try-catch.

Statement tiene muchos métodos, pero hay dos interesentantes: executeUpdate() y executeQuery(). El primero se usa para sentencias SQL que impliquen modificaciones en la base de datos (INSERT, UPDATE, DELETE, etc). El segundo sólo para consultas (SELECT y similares).

Nuestra creación de tabla afecta a la base de datos, así que con executeUpdate().

st.executeUpdate("CREATE TABLE contacto (id INT AUTO_INCREMENT, PRIMARY KEY(id), nombre VARCHAR(20), apellidos VARCHAR(20), telefono VARCHAR(20))");

Esto crea una tabla contacto con cuatro campos: id, nombre, apellidos y telefono.

Para borrar la tablita esta, lo mismo, pero con DROP en vez de CREATE.

st.executeUpdate("DROP TABLE contacto");

Insertar datos en la base de datos

 

Vamos a hacer un esfuerzo de imaginación y supongamos que no hemos borrado la tabla contacto, que sigue existiendo en base de datos. Vamos a meterle datos.

String nombres[]={"Juan","Pedro","Antonio"};
String apellidos[]={"Gomez","Lopez","Alvarez"};
String telefonos[]={"123","456","789"};
...

for (int i=0;i<nombres.length;i++)
   st.executeUpdate("INSERT INTO contacto (nombre, apellidos, telefono) VALUES ('"+nombres[i]+"','"+apellidos[i]+"','"+telefonos[i]+"' )");

también en un try-catch.

He puesto los datos a insertar en unos arrays. Tú puedes recogerlos de un sitio con más sentido. Por ejemplo, si es una agenda de contactos, lo mejor es pedírselos al usuario. Yo, por simplicidad y porque para el ejemplo esta mejor, los pongo en un array y con un bucle los voy insertando.

Fíjate que la sentencia INSERT se va componiendo a base de sumar cadenas. Sería más eficiente con un StringBuffer, pero así se simplifica algo el ejemplo.

Fíjate que en el INSERT metemos comillas simples para los valores de cadenas, es decir, en nombre, apellidos y telefono. El INSERT sería, de verdad, así

INSERT INTO contacto (nombre, apellidos, telefono) VALUES ('Juan', 'Gomez', '123');

Ahí se ven más claras las comillas simples para los datos que definimos como VARCHAR en la creación de la tabla, es decir, Juan, Gomez y 123. Por eso en el código las hemos puesto

...VALUES ('"+nombres[i]+"','"...

Si fuesen datos numéricos no harían falta, son cosas de SQL, pero tenemos que tener cuidado de componerlas bien.

Consultar datos de la base de datos

 

Vamos a hacer ahora una consulta de los datos que acabamos de insertar en la base de datos. Las consultas se hacen con executeQuery() y nos devolverán un

ResultSet.

El ResultSet de alguna forma representa una conexión hacia los datos. En el ResultSet NO están todavía los datos. Según se los vayamos pidiendo, los irá trayendo de base de datos. Esto quiere decir que si una consulta devuelve muchos resultados, no se nos va a llenar la memoria por el hecho de hacer la consulta.
Para traer el primer resultado, debemos llamar el método next() del ResulSet. Para el siguiente otro next() y así sucesivamente hasta que next() devuelva false, indicando que ya no quedaban datos. El código puede ser así

ResultSet rs = st.executeQuery("SELECT * FROM contacto");

while (rs.next())
{
   System.out.println("nombre="+rs.getObject("nombre")+
      ", apellidos="+rs.getObject("apellidos")+
      ", telefono="+rs.getObject("telefono"));
}
rs.close();

Hemos hecho un SELECT para obtener los datos. Luego un bucle while(rs.next()), es decir, mientras next() vaya trayendo resultados.

Después de next(), el resultado recién traido está disponible en el ResulSet. La forma de recoger los campos es pedirlos con algún método get(). Si sabemos de qué tipo es el dato, podemos pedirlo con getInt(), getString(), etc. Si no lo sabemos o nos da igual (como en este caso), bastará con un getObject(), capaz de traer cualquier tipo de dato.

En estos métodos get() podemos pasar como parámetro un entero, empezando en 1, que es el número del campo en el SELECT. Es decir, si hacemos SELECT campo1, campo2, campo3 ..., si pedimos getObject(1) obtenemos el valor de campo1, para getObject(2) el de campo2, etc.
Otra opción que a mi me gusta más, es pasar el nombre del campo, como se muestra en el código. Así, en el ejemplo de antes, getObject("campo1") nos devuelve el valor del campo1.

Update en la base de datos

 

Para UPDATE usamos executeUpdate(). Vamos a cambiar el número de teléfono de Juan. Ya que en la tabla hemos puesto un campo id, vamos primero a obtener el id de Juan para luego usarlo en el UPDATE. Esto no tiene por qué ser así, es sólo el ejemplo.

Para obtener el id:

rs = st.executeQuery("SELECT id FROM contacto WHERE nombre='Juan'");
rs.next();
int id = rs.getInt("id");

Es un SELECT para obtener el campo id de Juan. Con rs.next() hacemos que venga el primer resultado de la consulta (y único en nuestro ejemplo). Como sabemos que es un INT, lo recogemos con getInt() y lo guardamos.

Para hacerlo bien, deberíamos ver si rs.next() devuelve o no resultado (quizás no haya nadie que se llame Juan en la base de datos) o si devuelve más de uno (más de un Juan en la base de datos), pero no vamos a complicarnos la vida en este ejemplo sencillo.

Ahora, sabiendo el id, podemos hacer el UPDATE.

st.executeUpdate("UPDATE contacto SET telefono='111' WHERE id="+id);
Esta vez, como el campo id es numérico, no hemos puesto las comillas simples.
 

Borrar datos de la base de datos

 

El borrado se hace con DELETE y executeUpdate(). Vamos a borrar a Pedro, que nos hemos peleado con él. Obtenemos primero su id, igual que antes.

rs = st.executeQuery("SELECT id FROM contacto WHERE nombre='Pedro'");
rs.next();
id = rs.getInt("id");
y ahora lo borramos
st.executeUpdate("DELETE FROM contacto WHERE id="+id);

Cerramos conexiones

 

Los Connection, Statement y ResultSet con conexiones abiertas con base de datos. Debemos cerrarlas.
ResultSet se cierra solo cuando hacemos otra llamada execute() al Statement del que obtuvimos este ResultSet o bien cuando el recolector de basura "recolecta" al ResultSet. No nos preocupa en principio que se quede abierto, porque se acabará cerrando solo.

Eso sí, no podemos hacer nada con el Statement hasta que hayamos terminado con el ResultSet o se nos cerrará. Si necesitamos realizar otra cosa con base de datos, debemos crear otro Statement distinto.

Cuando terminemos con el Statement, lo cerramos con st.close().
Cuando terminemos con la Connection, la cerramos con conexion.close().
 

JAVA 29. INSTALACIÓN DE MYSQL EN WINDOWS

Lo primero, descargarse MySQL de algún sitio.

Instalación:

Ejecuto el Setup.exe. Me sale una ventana que me permite elegir instalación por defecto o personalizada. Elijo instalación a medida, más que nada por ir viendo que opciones. 

Aparece un arbol con las cosas a instalar. Dejo lo que viene por defecto, aunque veo que se pueden instalar ficheros de cabecera .h y librerías para poder acceder a MySQL desde C. Es un detalle a tener en cuenta. También elijo el directorio de instalación. 

Pregunta si quiero configurar MySQL durante la instalación. Le contesto que sí. Selecciono "configuración detallada", que es la que viene por defecto. 

Me da a elegir tres posibles tipos de máquina en la que instalar MySQL. Elijo la opción de máquina para desarrollo, que es la opción por defecto. Supuestamente el servidor MySQL en una máquina de desarrollo se configura de forma que consuma pocos recursos. 

Me da a elegir el tipo de base de datos. Elijo base de datos multifuncional. 

Me da a elegir la ubicación de las tablas de la base de datos. Dejo el directorio por defecto. 

Me da a elegir el número de conexiones que va a admitir la base de datos. Dejo DSS/OLAP, para pocas conexiones, que es la opción por defecto. 

Habitlio acceso a través de tpc/ip que está marcado por defecto y con el puerto por defecto 3306
Juego de caracteres por defecto. 

Elijo instalar con servicio de windows y que lo lance automáticamente, todo ello está por defecto. 

Le marco, que no lo estaba, para que incluya en el path tanto el cliente como el servidor de MySQL, y asi poder llamarlo desde línea de comandos. Esto es necesario para que otros programas (por ejemplo, un servidor apache con php) encuentren las librerías de MySQL cuando las necesiten. 

Elijo pasword de root y dejo el resto por defecto (no acceso en remoto al root y no creo cuenta anónima). 

He tenido que conectar a internet para que no fallara en la última parte de la instalación. No sé muy bien si es porque MySQL intenta acceder a Internet para algo o símplemente, porque como se instala como servicio, tiene que abrir un puerto de escucha de internet.

Creación de una base de datos, una tabla y un registro de prueba 

 

Aunque MySQL crea una base de datos de prueba que llama "test", decido crearme mi propia base de datos, con mi propia tabla y un registro en ella. 

Una vez instalado, me voy al menú de " inicio", "programas", "mysql", "MySQL Server 4.1",  "MySQL Command Line Client". Me sale una ventanita negra que me pide el password y trata de acceder a internet. como la conexión es a través de red, necesita el acceso a internet. Meto la password de root/administrador que puse al instalar y me sale un "prompt" de MySQL

Voy a crear una base de datos, con una tabla y una entrada de datos para poder acceder desde java.

mysql> create database prueba;
mysql> use prueba;
mysql> create table persona (id smallint auto_increment, nombre varchar(60), nacimiento date, primary key(id));
mysql> insert persona values (NULL, 'Pedro', '1995-09-12');