Archivo de Junio 2008

Manual de convivencia ciudadana

Miércoles, 25 de Junio de 2008

Mi amiga Beli propone un manual de educación para ir al trabajo todos los días en transporte público:

[…]
7:30 de la mañana, parada del autobús.
Vamos a cambiar las habituales miradas recelosas y los suspiros de cabreo por haber madrugado por un- Buenos días, sonriente a las demás personas que esperan el autobús y cuando este llegue vamos a saludar al conductor con otro educado buenos días, (para que la felicidad sea completa el conductor debería contestarte, pero eso no ocurrirá muy a menudo). Apunte importante: si el autobús está prácticamente vacío no te sientes al lado de otro viajero, es bastante molesto.
Llegamos al metro:
El cartel que reza: “Dejen salir antes de entrar”, está ahí para algo, así que no tapones la salida como si fuera el último metro que vaya a pasar en tu vida, el hecho de que seas el primero en pisar el vagón no quiere decir que vayas a llegar antes a trabajar. MUY IMPORTANTE, si el tren está cerrando sus puertas no intentes meter cualquiera de tus apéndices corporales para bloquearlas, puede desembocar en mutilación y además dentro de 3 minutos pasa otro.
No te sientes en el primer asiento que veas a tu izquierda o tu derecha, por que de ese modo estas taponando a los viajeros que te siguen y por tu puñetera culpa se quedarán sin asiento.
[…]

Hilarante y mordáz. Este es solo un fragmento, lee el artículo entero en Blog de Kikujiro.

Tutorial H2 Database

Jueves, 19 de Junio de 2008

La base de datos H2 es un base de datos relacional programada integramente en Java. Una de las características más importantes a consecuencia de esto es que podemos integrarla completamente en nuestras aplicaciones Java y acceder a a ella lanzando SQL directamente, sin tener que pasar por una conexión a través de sockets, como ocurirría con MySQL, Oracle, Postgress, etc. (aunque también es posible utilizarla a través de conexiones JDBC externas). Esto implica una mayor velocidad y una mejor integración, como ahora veremos.
Cuando trabajamos con la base de datos dentro de nuestra aplicaciones, se llama modo “embedded” (incrustado). Una vez que nuestra aplicación ha abierto la base de datos para trabajar con ella, esta queda fisicamente bloqueada, no permitiendo que otro motor H2 (en otra aplicación Java por ejemplo) pueda acceder a ella. Esto permite mantener la integridad del fichero físico de la base de datos, ya que si dos motores H2 escribieran a la vez podrían corromperla.

Usar y crear bases de datos incrustadas con H2 desde Java es realmente facil. Primero tenemos que bajarnos el último jar de http://www.h2database.com e incluirlo en nuestro CLASSPATH (o en el directorio WEB-INF/lib de nuestra aplicación WAR).

El siguiente código abre una conexión contra una base de datos, definiendo donde se encuentra físicamente en nuestro disco, sin tener que especificar ningún host, ip o puerto, ya que no hay conexión a través de sockets, sino que se accede directamenete al disco.

import java.sql.*;
public class Test {
  public static void main(String[] a) throws Exception {
    Class.forName("org.h2.Driver");
    Connection conn = DriverManager.getConnection("jdbc:h2:/home/vilches/h2/test", "sa", "");
  }
}

Si al abrir una conexión no existe físicamente los archivos de la base de datos, H2 los creará. Por ejemplo:

/home/vilches/h2/test.2.log.db
/home/vilches/h2/test.data.db
/home/vilches/h2/test.index.db
/home/vilches/h2/test.trace.db

La ruta a la base de datos elegida es /home/vilches/h2/test. Esto significa que se utilizará el directorio /home/vilches/h2/ y que en ese directorio se crearán los ficheros de la base de datos, que empezarán por test.* y tendrán distintas extensiones. De esta manera, cambiando solo el nombre del fichero es posible tener varias bases de datos el el mismo directorio, ya que empezarán con nombres distintos (por ejemplo /home/vilches/h2/test y /home/vilches/h2/otro).

Una conexión por sesión es suficiente

Una de las mayores ventajas de usar H2 de forma embebida es que podemos utilizar conexiones sin necesidad de un pool de conexiones. Podemos, por ejemplo, utilizar una única conexión para toda nuestra aplicación y no cerrarla nunca (o cerrarla al salir de la aplicación). En caso de aplicaciones Web, podemos crear una conexión por sesión con un listener que implemente HtppSessionListener:

package test;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.io.*;
public class MySessionListener implements HttpSessionListener {
    public Connection openConnection() throws SQLException, ClassNotFoundException {
        Class.forName("org.h2.Driver");
        return DriverManager.getConnection("jdbc:h2:/home/vilches/h2/test", "", "sa");
    }

    public void sessionCreated(HttpSessionEvent se) {
        try {
            se.getSession().setAttribute("h2.connection", openConnection());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void sessionDestroyed(HttpSessionEvent se) {
        try {
            Connection con = (Connection)se.getSession().getAttribute("h2.connection");
            con.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Para que funcione el listener es necesario darlo de alta en el web.xml:

<listener>
    <listener-class>test.MySessionListener</listener-class>
</listener>

Ahora, desde nuestros servlets o JSPs, podemos acceder a la conexión con:

Connection con = (Connection)se.getSession().getAttribute("h2.connection");

y podremos utilizarla sin preocuparnos por cerrarla.

Abriendo la base de datos al arrancar la aplicación

Una buena practica es abrir por lo menos una conexión al arrancar la aplicación, de esta manera nos aseguramos de que H2 abre fisicamente la base de datos en el disco y la bloquea para el resto de procesos. Para hacer esto, podemos ampliar el listener anterior para que implemente también ServletContextListener, el cual tiene dos métodos que se ejecutan cuando se crea y se destruye el contexto (es decir, se arranca y se para la aplicación web).

Además, vamos a definir la ruta de la base de datos como un parámetro del contexto en el WEB-INF/web.xml llamado “h2.database.path” que leeremos en el arranque (si el parámetro no existe, cogeremos la home del usuario actual con System.getProperty("user.home")) y utilizaremos para crear la url jdbc :

package test;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.io.*;

public class MySessionListener implements ServletContextListener,
    HttpSessionListener {

    // Constante para el nombre de los ficheros
    private final static String DBFILENAME = "test";

    String url;
    Connection globalConnection;

    public void contextInitialized(ServletContextEvent sce) {
        // Leemos el parametro del contexto
        String spath = sce.getServletContext().getInitParameter("h2.database.path");
        if (spath == null) {
            // Si no existe, es la home del usuario
            spath = System.getProperty("user.home");
        }
        try {
            Class.forName("org.h2.Driver");
            File dbfile = new File(spath, DBFILENAME);
            url = "jdbc:h2:file:" + dbfile.getAbsolutePath().replaceAll("\\", "/");
            globalConnection = openConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Connection openConnection() throws SQLException {
        return DriverManager.getConnection(url, "", "sa");
    }

    public void contextDestroyed(ServletContextEvent sce) {
        try {
            globalConnection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void sessionCreated(HttpSessionEvent se) {
        try {
            se.getSession().setAttribute("h2.connection", openConnection());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void sessionDestroyed(HttpSessionEvent se) {
        try {
            Connection con = (Connection)se.getSession().getAttribute("h2.connection");
            con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Para que funcione el listener es necesario darlo de alta en el web.xml y definir el parámetro de contexto con la ruta :

<context-param>
    <param-name>h2.database.path</param-name>
    <param-value>/home/vilches/h2</param-value>
</context-param>
<listener>
    <listener-class>test.MySessionListener</listener-class>
</listener>

Creando tablas al arrancar por primera vez

Ahora supongamos que queremos crear una serie de tablas cuando hemos arrancado la base de datos, pero solo la primera vez, cuando la base de datos está vacía. Para hacer esto vamos a verificar, antes de abrir la primera conexión, si la base de datos existe. Y si no existe, crearemos las tablas (después de abrir la primera conexión). Modificaremos el método contextInitialized para que quede así:

public void contextInitialized(ServletContextEvent sce) {
    // Leemos el parametro del contexto
    String spath = sce.getServletContext().getInitParameter("h2.database.path");
    if (spath == null) {
        // Si no existe, es la home del usuario
        spath = System.getProperty("user.home");
    }

    // Comprobamos si no existe la base de datos
    boolean exists = new File(spath, DBFILENAME+".data.db").exists();

    try {
        Class.forName("org.h2.Driver");
        File dbfile = new File(spath, DBFILENAME);
        url = "jdbc:h2:file:" + dbfile.getAbsolutePath().replaceAll("\\", "/");
        globalConnection = openConnection();

        // Si no existe, llamamos al metodo initDb()
        if (!exists) {
            initDb();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Ahora solo queda implementar initDb() a nuestro gusto.

Ejecutando un script SQL desde Java

Una buena practica es crear un fichero de script con las sentencias SQL y ejecutarlo directamente, sin tener que implementar, una a una, las sentencias de creación de tablas en nuestro código. El siguiente código lee un fichero de script llamado squema-h2.sql.txt situado dentro de la carpeta de de clases (es decir en /WEB-INF/classes/squema-h2.sql.txt) y lo ejecuta. Dentro de este script podemos incluir sentencias para crear tablas y añadir registros. El script necesita que cada sentencia acabe en punto y coma “;”, permite partir las sentencias en más de una línea e ignora los comentarios (líneas que empiezan por “–”, “#” y “//”) y líneas en blanco.

private void initDb() throws SQLException, IOException {
    InputStream is = getClass().getClassLoader().getResourceAsStream("squema-h2.sql.txt");
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    Connection connection = null;
    try {
        connection = openConnection();
        String line = br.readLine();
        StringBuilder statement = new StringBuilder();
        while (line != null) {
            line = line.trim();
            if (!line.startsWith("--") && !line.startsWith("#") && !line.startsWith("//")) {
                statement.append(line);
                if (line.endsWith(";")) {
                    executeLine(connection, statement.toString());
                    statement = new StringBuilder();
                }
            }
            line = br.readLine();
        }
        if (statement.length() > 0) {
            executeLine(connection, statement.toString());
        }
    } finally {
        try {
            br.close();
        } catch (Exception e) {;}
        try {
            if (connection != null) connection.close();
        } catch (Exception e) {;}
    }
}

private void executeLine(Connection connection, String statement) throws SQLException {
    PreparedStatement pstmt = connection.prepareStatement(statement);
    pstmt.execute();
    pstmt.close();
    System.out.println("Ejecutando "+statement);
}

El script /WEB-INF/classes/squema-h2.sql.txt podría ser:

CREATE TABLE USUARIO (
	ID INT AUTO_INCREMENT,
	LOGIN VARCHAR(25) NOT NULL UNIQUE,
	PASSWORD VARCHAR(25) NOT NULL,
	NOMBRE VARCHAR(100) NOT NULL,
	EMAIL VARCHAR(50),
	WEB	VARCHAR(100),

	PRIMARY KEY(ID)
);

-- Esto es un comentario
// Esto también
# Y esto también lo es

insert into usuario values (null, 'admin','admin123','Alberto Vilches',
	'email@sitio.com', 'http://albertovilches.com');

(Para más información sobre la sintaxis SQL de creación de tablas de H2, consultar H2 SQL Grammar reference)

Finalmente, creamos un JSP de ejemplo que liste los registros de la tabla para ver si todo funciona correctamente:

<%@ page import="java.sql.*" %>
<html>
  <body>
  <%
      Connection con = (Connection) request.getSession().getAttribute("h2.connection");

      PreparedStatement stmt = con.prepareStatement("SELECT * FROM USUARIO");
      ResultSet rst = stmt.executeQuery();
      while (rst.next()) {
      %>

  <%=rst.getString("LOGIN")%>, mailto:<%=rst.getString("EMAIL")%><br/>

      <%
      }

  %>
  </body>
</html>

Resumen

Con esto ya tendríamos un acceso a base de datos muy ligero y rápido disponible en nuestras aplicaciones Web. La ruta de la base de datos irá definida en el parámetro “h2.database.path” en el web.xml. El listener se encargará de crear la base de datos con las todas las tablas y mantenerla siempre abierta mientras esté activa la aplicación Web. También tendremos en la sesión siempre una conexión disponible lista para usar.

Bájate el código de ejemplo completo: War de ejemplo con H2 incrustado

Crear una conexión JDBC

Jueves, 19 de Junio de 2008

Para acompañar al viejo post sobre PL-SQL Oracle desde Java (en algunos comentarios y mensajes) voy a explicar a continuación como establecer una conexión contra la base de datos sin usar un pool de conexiones.

1 Lo primero que necesitaremos son los drivers JDBC en formato jar, incluir este archivo jar en el CLASSPATH de nuestro proyecto y cargarlos en memoria:

Oracle: JDBC Download page Oracle

Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();

MySql: MySQL Connector J

Class.forName("com.mysql.jdbc.Driver").newInstance();

2 Para crear la conexión, necesitamos una url JDBC, la cual contiene toda la información necesaria para conectarse:

Oracle: necesitaremos saber el host (el nombre de la máquina o su IP), el puerto de escucha del listener de Oracle (1521 por ejemplo), el servicio o sid, el usuario y la clave. Diferentes formatos de la url JDBC pueden ser:

jdbc:oracle:thin:@host:puerto:sid
jdbc:oracle:thin:usuario/clave@host:puerto:sid
jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=host)(PORT=puerto)))(CONNECT_DATA=(SERVICE_NAME=servicio)(SERVER=SHARED)))
jdbc:oracle:thin:usuario/clave@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=host)(PORT=puerto)))(CONNECT_DATA=(SERVICE_NAME=servicio)(SERVER=SHARED)))

Más información en FAQ JDBC Oracle: Connections

MySql: host (nombre de la máquina o su IP), puerto de escucha (3306 por defecto), nombre de la base de datos, usuario y clave. El formato de la url es:

jdbc:mysql://host:puerto/database
jdbc:mysql://host:puerto/database?user=usuario&password=clave

(Más información en MySql JDBC Reference)

Una vez tengamos la url JDBC, podemos crear la conexión contra la base de datos:

Connection connection = DriverManager.getConnection(ulr, usuario, clave);

Si la url ya contiene el usuario y la clave (hay formatos que ya la llevan):

Connection connection = DriverManager.getConnection(ulr);

Con el objeto connection ya podemos trabajar, sin olvidarnos de que una vez hemos acabado, hay que cerrar la conexión. Esto es muy importante hacerlo, ya que si se queda abierta, estamos desaprovechando recursos, creando lo que se llama connection leaks (conexiones perdidas) con la base de datos.
La mejor forma de hacerlo es englobar todo el código en una estructura try/catch/finally y efectuando el cierre de la conexión en el finally. De esta manera nos aseguramos de que falle o no falle el código, se cerrará la conexión.

Connection connection;
try {
    // ...
    connection = DriverManager.getConnection(ulr);
    // ...
} finally {
    if (connection != null) {
        try {
            connection.close();
        } catch (SQLException e) {
			      e.printStackTrace();
        }
    }
}

Veamos ahora un par de ejemplos completo. Para Oracle definimos una url que incluye el usuario y la clave, por lo que no necesitaremos pasarle esta información en el método getConnection():

import java.sql.*;

public class OracleConnection {
    public static void main(String args[]) {

        String usuario = "vil";
        String password = "secreta";

        String host = "localhost"; // tambien puede ser una ip como "192.168.1.14"
        String puerto = "1521";
        String sid = "prueba";

        String driver = "oracle.jdbc.driver.OracleDriver";

        String ulrjdbc = "jdbc:oracle:thin:" + usuario + "/" + password + "@" + host + ":" + puerto + ":" + sid;

        Connection connection = null;
        try {
            Class.forName(driver).newInstance();
            connection = DriverManager.getConnection(ulrjdbc);

            // Ya tenemos el objeto connection creado

            ResultSet result = connection.createStatement().executeQuery("SELECT 'hola mundo' FROM DUAL");
            result.next();
            System.out.println(result.getString(1));

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                }
            }
        }
    }
}

Y para MySQL (usaremos una url sin usuario y clave, por lo que pasaremos esta información al método getConnection):

import java.sql.*;

public class MySQLConnection {
    public static void main(String args[]) {

        String usuario = "vil";
        String password = "secreta";

        String host = "localhost"; // tambien puede ser una ip como "192.168.1.14"
        String puerto = "3306";
        String database = "prueba";

        String driver = "com.mysql.jdbc.Driver";

        String ulrjdbc = "jdbc:mysql://" + host + ":" + puerto + "/" + database;

        Connection connection = null;
        try {
            Class.forName(driver).newInstance();
            connection = DriverManager.getConnection(ulrjdbc, usuario, password);

            // Ya tenemos el objeto connection creado

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                }
            }
        }
    }
}

Resumen para crear una conexión con cualquier base de datos:

  • Insertar en el CLASSPATH el jar con los drivers de nuestra base de datos (drivers Oracle, drivers MySQL o buscar en Google “drivers jdbc basededatos”)
  • Cargar los drivers con Class.forName(sdriver).newInstance();, siendo sdriver un String con la clase del driver.
  • Crear la conexión con DriverManager.getConnection(ulrjdbc, usuario, password);, siend urljdbc una url con el formato que define la propia base de datos (Oracle: jdbc:oracle:thin:@host:puerto:sid, MySQL: jdbc:mysql://host:puerto/database o buscar en Google “jdbc url basededatos”)
  • Cerrar siempre la conexión dentro del finally