martes, agosto 11, 2009

Iniciando con JDBC

La razón principal, por la cual he decidido escribir este articulo, es precisamente pensando en esos noveles desarrolladores, aquellos que cursan bases de datos y desean hacer su proyecto final con Java.

Podríamos preguntarnos; y por que no utilizar un ORM de una vez, o una base de datos OO, o una CloudDB, etc? Bueno la simplicidad es una de las razones, ademas que también considero importante conocer las cosas por debajo y JDBC sin duda es una de las tecnologías que en algunas ocasiones hay que utilizar para tirar de la bases de datos, cuando el eficiencia es necesaria o simplemente limitaciones del framework no proporciona la suficiente flexibilidad que deseamos.

Justificado el asunto, manos a la obra....

JDBC, como muchas otras cosas en Java, es un estándar y una especificación; con estándar decimos que se trata de algo que se utiliza de la misma manera por la gran mayoría de las personas y existen unos estatutos acordados, que así lo constatan (al menos así lo veo yo), especificación en términos prácticos, se trata de un conjunto de clases, regularmente interfaces, que definen que debe hacer la biblioteca sin embargo no sugiere como, el como depende del “vendor” especifico, es decir Oracle y su driver, MySQL y su driver etc. Adicionalmente una especificación podría contener clases abstractas que definan plantillas o funcionalidad común y quiza algunas clases concretas con servicios utilitarios o genéricos.

Así pues, JDBC es una especificación para el acceso a bases de datos. Por tanto, al utilizar JDBC con algo de cuidado, podría garantizar que nuestras clases; nuestro código debería funcionar en cualquier contenedor y con cualquier base de datos que tenga un driver JDBC para acceder al mismo.

Como notara, ya en las primeras lineas hemos estado hablando con cierta terminología, en especial hemos mencionado la existencia de un “driver”; bueno, y que demonios es un driver?

Como JDBC solo define un conjunto de clases para acceder a la base de datos y NO como acceder, el como debe ser implementado por el “vendor” o por algún tercero, así pues Oracle tiene una librería la cual se incluye en el classpath, para poder acceder a su base de datos, de igual manera MySQL, MsSQL (la cual inclusive tiene librerías open source de terceros), etc.

Adicionalmente, Sun ofrece un driver genérico para acceder a cualquier base de datos mediante TCP/IP, pero utilizando SQL estándar, a decir verdad conozco de su existencia, pero nunca he necesitado utilizarla.

También existen un mecanismo para conectarse mediante ODBC, pero el desempeño es tan malo, que la verdad no deberíamos ni mencionarlo aquí :)

Driver, donde todo empieza:

Antes de iniciar nuestro trabajo Java y nuestra base de datos, primero debemos tener ciertos detalles listos; el primero debemos tener nuestro driver instalado en nuestro classpath, esto dependerá de nuestro servidor o en caso que sea una aplicación de escritorio con Swing o alguna otra cosa necesitaremos configurar nuestro classpath para referenciar el driver de nuestra base de datos, en la mayoría de los casos, incluirla en la carpeta lib (en la mayoría de los IDES), funcionara.

Lo siguiente es conocer el URL, el nombre de la clase que implementa el driver, usuario, y password; puede que en alguna otra ocasión necesites mas parámetros para configurar otros detalles de tu conexión, pero eso no es realmente importante ahora.
Por mencionar un ejemplo, estos podrían ser los datos para conectarnos a una base de datos Postgres:



Url=jdbc:postgresql://mydatabaseserver.com:5432/mydatabase
User=myuser
Password=mypassword
Driver=org.postgresql.Driver


Una vez que tengamos estos datos, tenemos varias forma de obtener nuestra primera coneccion, la forma mas simple seria instanciar nuestro driver y obtener una conexión desde la clase http://java.sun.com/j2se/1.4.2/docs/api/java/sql/DriverManager.html; a mi en particular no me agrada demasiado este camino, también contamos con pooles de conexiones los cuales encapsula toda la creacion de la conexión y crea un grupo de conexiones reusables para evitar ciertos cuellos de botella (muchas veces, realizar la consulta no tarda tanto como si pasa con la autenticación y obtención de una nueva conexión, razón por la cual conviene utilizar un pool). Otro mecanismo que conozco es utilizar JNDI y obtener la conexión desde el contenedor, esta opción solo es accesible desde un servidor.

A continuación utilizaremos dos de los mecanismos para obtener una conexión, el primero mediante el DriverManager, el segundo mediante Apache Commons DBCP (http://commons.apache.org/dbcp/) un pool de conexiones bastante sencillo de utilizar.

DriverManager:

El siguiente código muestra como obtener nuestra conexión, tome en cuenta que los datos de prueba incluidos en el ejemplo deben ser cambiados por tus datos, a excepción del driver, a menos claro que la base de datos sea diferente a postgres.
package com.jsanca.drivermanager;


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


public class DriverManagerExample {

public Connection getConnection (String url, String driver, String user, String pass) throws Exception {

Connection connection = null;
// Lo primero es instanciar nuestro driver.
Class.forName(driver);

connection = DriverManager.getConnection(url, user, pass);

return connection;
}

public static void main(String[] args) throws Exception {

Connection connection = new DriverManagerExample ().getConnection("jdbc:postgresql://mydatabaseserver.com:5432/mydatabase",
"org.postgresql.Driver", "myuser", "mypass");

}
}



La linea:

Class.forName(driver);

Carga la clase que implementa el driver a nuestro classpath (ojo que no la instancia), este es el mecanismo estándar para agregar un nuevo driver a nuestra aplicación.

El driver manager es una clase tipo singleton, mediante la cual podemos obtener conexiones de los driver registrados en nuestro sistema, la siguiente linea solo obtiene una simple conexión.

A este punto ya tenemos una conexión lista para ser utiliza.

Ahora, supongamos que tenemos la tabla Empleados, cuya primera columna tiene el id y la segunda el nombre. Así pues, se nos apetece obtener estos datos utilizando Java y nuestra clase DriverManagerExample. Manos a la obra:

package com.jsanca.drivermanager;

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

public class DriverManagerExample {

public Connection getConnection (String url, String driver, String user, String pass) throws Exception {

Connection connection = null;
// Lo primero es instanciar nuestro driver.
Class.forName(driver);

connection = DriverManager.getConnection(url, user, pass);

return connection;
}

public static void main(String[] args) throws Exception {

Connection connection = new DriverManagerExample ().getConnection("jdbc:postgresql://mydatabaseserver.com:5432/mydatabase",
"org.postgresql.Driver", "myuser", "mypass");


Statement statement = connection.createStatement();

ResultSet resultSet = statement.executeQuery("select id, nombre from Empleados ");

while (resultSet.next()) {

System.out.println(resultSet.getObject(1) + “ = ” + resultSet.getObject(2));
}
}
}



Si nos fijamos bien, esta es nuestra misma clase DriverManagerExample, sin embargo cuenta con algunas linea nuevas, la primera que analizaremos es:

Statement statement = connection.createStatement();



Esta linea solicita una sentencia sql a la conexión, con la cual podemos realizar consultas tanto para cambiar la base de datos como para obtener datos de ella.

La siguiente linea es:

ResultSet resultSet = statement.executeQuery("select id, nombre from Empleados ");




Ya se imaginara de que se trata, si usted esta familiarizado con SQL; el objeto Statement cuanta con una gran variedad de métodos para realizar diferentes operaciones, en este caso executeQuery nos permite realizar una operación de consulta y obtener un ResultSet, como resultado de la operación. Vale mencionar que JDBC proporciona la posibilidad de obtener mas de una consulta en una sola peticion, pero esto sera tratado mas adelante.

Por ultimo, tenemos:

while (resultSet.next()) {

System.out.println(resultSet.getObject(1) + “ = ” + resultSet.getObject(2));
}



Estas instrucciones recorren nuestro ResultSet, hasta el ultimo registro, imprimiendo en la consula el id y el nombre del empleado.

Los resultSet se gobiernan mediante cursores y para poder utilizar u obtener los datos, debemos colocar el mismo en la primera posicion, por ello el llamado al metodo next, y asi continuamente hasta que se terminen las filas.

El resultSet cuenta con muchísimos metodos para obtener los datos ya tipados, sin embargo yo prefiero obtener el objeto y después castearlo, pues dependiendo del driver en una base de datos, un entero puede ser traducido como un objeto Integer o un BigInteger, etc.

Mi ultima observación; tome en cuenta que el indice de la consulta inicia en 1 y no en 0, como suelen iniciar los vectores. También se pueden obtener los resultado indicando el nombre de la columna y para mayor facilidad se dispone de un ResultSetMetaData, sin embargo esto sera cubierto en artículos posteriores.

A continuación seguiremos estudiando nuestro primer ejemplo y evolucionando. En JDBC una buena practica para ahorrar en recursos y no tener problemas de desempeño; es cerrar los recursos JDBC que solicitamos, siguiendo este principio, deberíamos cerrar los recursos en el siguiente orden; ResultSet, Statement, Connection. Ahora bien en el caso que invoquemos al método “close” del objeto e intentemos obtener otro statement, será disparada una fea excepción de SQL.

Para evitar este inconveniente, podríamos utilizar un pool de conexiones, un camino sencillo para hacernos con un pool, es crearlo en nuestro servidor y obtenerlo a traves de JNDI, sin embargo si usted no desea utilizar esta tecnología o no cuenta con un servidor, una solución bastante buena es utilizar DBCP (http://commons.apache.org/dbcp/); esta framework nos permite crear un pool de conexiones del cual obtener y reutilizar conexiones ahorrandonos recursos y tiempo, al evitar la reconexión de la conexión cada vez que ocupes realizar una operación, a continuación nuestro código con DBCP.


package com.jsanca.drivermanager;

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

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;

public class DBCPDataSourceExample {

private static DataSource dataSource = null;

public Connection getConnection(String url, String driver, String user,
String pass) throws Exception {

Connection connection = null;

if (null == dataSource) {

dataSource = this.getDataSource(url, driver, user, pass);
}

connection = dataSource.getConnection();

return connection;
}

public DataSource getDataSource(String url, String driver, String user,
String pass) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(user);
dataSource.setPassword(pass);
return dataSource;
}

public static void main(String[] args) throws Exception {

Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;

try {
Connection connection = new DBCPDataSourceExample ().getConnection("jdbc:postgresql://mydatabaseserver.com:5432/mydatabase",
"org.postgresql.Driver", "myuser", "mypass");


Statement statement = connection.createStatement();

ResultSet resultSet = statement.executeQuery("select id, nombre from Empleados ");

while (resultSet.next()) {

System.out.println(resultSet.getObject(1));
}
} finally {

if (null != resultSet) {
resultSet.close();
}

if (null != statement) {
statement.close();
}

if (null != connection) {
connection.close();
}
}
}
}



Como puede apreciar el código de este nuevo ejemplo no ha cambiado demasiado; ahora en vez de utilizar el DriverManager para obtener la conexión a la base de datos, utilizamos la interface DataSource, la misma es implementada como un pool de conexiones por DBCP. El metodo get DataSource, simplemente configura y crea el nuevo pool (tome en cuenta que ya no ocupa cargar por reflexion el driver, BDCP se encarga de todo y ademas nos crea un grupo de conexiones para reutilizar). Por ultimo, la sentencia es agrupada en un try/finally donde se cierran los recursos, en caso que estos hayan sido creados. Para terminar solo queda mencionar que al cerrar la conexión, BDCP no cierra la conexión, solo la devuelve al pool, ademas esto depende de la implementacion, puede hechar un ojo al framework para ver otras variantes.

2 comentarios:

Victor dijo...

Hola JSanca muy bueno el articulo sobre jdbc y muy buenos ejemplos, queria hacerte un comentario sobre el ejemplo final estas implementando apache BDCP pero estas llamando a la conexion del ejemplo anterior en la parte:

Connection connection = new DriverManagerExample ().getConnection("jdbc:postgresql://mydatabaseserver.com:5432/mydatabase",
"org.postgresql.Driver", "myuser", "mypass");

Deberia ser:
Connection connection = new DBCPDataSourceExample().getConnection("jdbc:postgresql://mydatabaseserver.com:5432/mydatabase",
"org.postgresql.Driver", "myuser", "mypass");
ademas de agreagar la libreria apache commons pool.

Bueno espero que tomes esta sugerencia saludos y saludos desde Peru

jsanca dijo...

Muchas gracias Victor, un saludo desde Costa Rica!