viernes, agosto 28, 2009

Tristes hombres

Tristes guerras
si no es amor la empresa.
Tristes, tristes.

Tristes armas
si no son las palabras.
Tristes, tristes.

Tristes hombres
si no mueren de amores.
Tristes, tristes.

De "Cancionero y romancero de ausencias"
Miguel Hernández




jueves, agosto 27, 2009

Espacar cadenas y caracteres especiales en postgres

Hola, si quieres escapar una cadena, que tiene una comilla o algún caracteres especial puede utilizar el token, E, delante de la cadena, ejemplo:

insert into Table (field) values (E'Texto \' que debe ser escapado\n');

Un saludo,
J

JDBC – Transacciones – Parte 3

El presente articulo, resulta ser el seguimiento de dos anteriores artículos, que usted puede encontrar en este mismo sitio Web; es importante que lea los anteriores artículos, con el fin que usted pueda comprender el código resultante, pues el código del actual articulo no es mas que una simple evolución del código analizado en artículos anteriores.

Que es una transacción?



Los que cursaron bases de datos en la Universidad deben tener bien claro, lo que el manejo de transacciones implica; diremos de forma muy general, que las transacciones nos sirven para agrupar un conjunto de sentencias SQL, regularmente de tipo “update” (insertar, eliminar o actualizar) bajo una misma operación, pudiendo rechazarla o enviarla toda ella a nuestro gusto. De esta forma podemos garantizar que todas las operaciones fueron ejecutadas como una sola operación atómica. Esto es muy importante, para mantener la consistencia; imaginemos la compra de un usuario, donde se guardan datos en diferentes tablas, tales como productos que compro, la información personal del usuario, la información del pago, la dirección de shipping; pero que pasaría si cobramos y guardamos esta información, de igual manera realizamos el cambio en nuestro inventario manifestando la venta, pero por alguna razón la dirección a donde hacer shipping no pudo ser guardada. Estaremos ante un caso de negocio, que nos deja mal parados ante el cliente que pago, pero no recibira sus productos, de igual manera hay muchos otros escenarios prácticos donde aplicar las transacciones.

Vamos al código; de forma similar al anterior articulo, donde vimos la implementación de un DAO con JDBC, en este capitulo introduciremos los patrones Factory y AbstractFactory, así como el Delegate; para implementar la fabrica de transacciones, la fabrica para crear las fabricas de transacciones y la clase que encapsula nuestras transacciones.

Lo primero que debemos definir, son las operaciones necesarias para realizar nuestro delegado, que será el que maneje nuestras transacciones:


/**
* Clase encargada de encapsular el manejo de transacciones, es decir se le delega
* la transaccionabilidad de las consultas.
* @author jsanca
*
*/
public interface TransactionDelegate {

/**
* Invocar a este metodo cuando inicia la transaccion.
* Deberia colocar todas las precondiciones necesarias para ejecutar la transaccion.
*/
public void start ();

/**
* Realiza el commit de la transaccion.
*/
public void commit ();

/**
* Realiza el rollback de la transaccion
*/
public void rollback ();

/**
* Invocar a este metodo para finalizar la transaccion, suceda o no un error.
* El mismo sera el encargado de volver al estado anterior a la transaccion
* y cerrar los recursos solicitados al inicio de la transaccion.
*/
public void end ();
} // E:O:F:TransactionDelegate.


Aunque los nombres de los método, no sean los mejores, me parece son bastante fáciles de deducir su funcionalidad, en todo caso imaginemos el siguiente escenario; contamos con 3 daos, los cuales realizan una sola transacción lógica, el flujo correcto para utilizar el delegado seria:


TransactionDelegate delegate = .. // código de inicialización

try {

delegate.start();

dao1.insert(...);
dao2.update(....);
dao2.delete(...)
dao3.update(..);
dao1.insert(...);

delegate.commit();
} catch (…) {

delegate.rollback();
} finally {

delegate.end();
}


Podemos concluir, que las transacciones, primero se inicializan, en caso que todas las operaciones sean exitosas, se realiza el commit, caso contrario rollback y siempre terminamos la transacción. Comentario aparte, merece mencionar el uso de programación orientada aspectos, para evitar todo este código dentro de un servicio de negocios, como se puede notar el código es bastante recetario y ensucia bastante la lectura del código.

Ahora que tenemos definido, que deseamos sea responsabilidad del Delegate, realicemos nuestra implementación para JDBC, no sin antes mencionar, que esta especificación nos permitiría manejar transacciones de forma transparente no solo para JDBC, si no para otros tipos de persistencia, pues el diseño por contrato que hemos hecho, evita el acopla con cualquier cosa relacionada con JDBC y solo especifica el concepto de transacción.


import java.sql.Connection;
import java.sql.SQLException;

/**
* Implementacion para JDBC
*
* @author jsanca
*
*/
public class JDBCTransactionDelegateImpl implements TransactionDelegate {

private Connection connection = null;

private boolean oldStateOfAutoCommit = false;
private int oldStateOfTransactionIsolation = Connection.TRANSACTION_READ_COMMITTED;

public JDBCTransactionDelegateImpl(Connection connection) {

this(connection, Connection.TRANSACTION_READ_COMMITTED);
} // JDBCTransactionDelegateImpl.

public JDBCTransactionDelegateImpl(Connection connection, int isolation) {

try {

this.connection = connection;
this.oldStateOfTransactionIsolation = this.connection
.getTransactionIsolation();
this.connection.setTransactionIsolation(isolation);
} catch (SQLException e) {

throw new DAOException(e);
}
} // JDBCTransactionDelegateImpl.

public void commit() {

try {

this.connection.commit();
} catch (SQLException e) {

throw new DAOException(e);
}
} // commit.

public void end() {

try {

this.connection.setAutoCommit(this.oldStateOfAutoCommit);
this.connection
.setTransactionIsolation(this.oldStateOfTransactionIsolation);
this.connection.close();
} catch (SQLException e) {

throw new DAOException(e);
}
} // end.

public void rollback() {

try {

this.connection.rollback();
} catch (SQLException e) {

throw new DAOException(e);
}
} // rollback.

public void start() {

try {

if (this.connection.getAutoCommit()) {
// En caso que la conexion se commite de forma
// automatica, guardamos el estado anterior e
// indicamos que no deseamos el commit automatico.
this.connection.setAutoCommit(false);
this.oldStateOfAutoCommit = true;
}
} catch (SQLException e) {

throw new DAOException(e);
}
} // start.

/**
* Este metodo solo es miscelaneo para los que quieran trabajar directamente
* con JDBC.
*
* @return Connection
*/
public Connection getConnection() {

return this.connection;
} // getConnection.

} // E:O:F:JDBCTransactionDelegateImpl.


Esta implementación es bastante simple, recibimos una conexión JDBC y en los métodos gestionamos su transaccionabilidad, solo quiero aclarar algunos puntos; en el método start, como puede apreciar guardamos el estado actual o por defecto de la transacción y configuramos la conexión para que evite el auto commit, esta propiedad no permite encadenar varias sentencias SQL bajo una misma transacción, pues envía al servidor directamente cada una de las operaciones, por este motivo debe ser seteado a false. También debemos poner nuestra atención en el transacción isolation; se preguntara que demonios es esto de la isolation, pues sucede que las transacciones pueden ser configuradas para comportarse de varias formas y todas ellas se realizan con base en el nivel de isolation, asignado, veamos:

Connection.TRANSACTION_NONE: con esta indicamos que no soportaremos transacciones.

Connection.TRANSACTION_READ_COMMITTED: este es el mejor valor para las transacciones, o al menos así lo percibo yo, pues no permite que otros usuarios puedan leer valores hasta que estos no se encuentren realmente enviados (commit) a la base de datos, así se evita la lectura de datos sucios.

Connection.TRANSACTION_READ_UNCOMMITED: todo lo contrario del anterior, podríamos leer un dato que aun no ha sido enviado de forma exitosa o inclusive un dato que este pronto a hacersele rollback.

Connection.TRANSACTION_REPEATABLE_READ: Este nivel de isolation, nos protege un poco trabajar con datos sucios, evita las lecturas repetidas, pero la lectura de datos fantasmas puede ocurrir.

Connection.TRANSACTION_SERIALIZABLE: Este es el nivel mas fuerte o seguro, pues evita todos los errores que permiten los niveles anteriores, el único problema es que todas estas validaciones para evitar errores, perjudicarían el perfomance de la aplicación, a continuación coloco de mas rápido a mas lentos los niveles antes citados:


TRANSACTION_NONE – FASTEST
TRANSACTION_READ_UNCOMMITED – FASTEST
TRANSACTION_READ_COMMITED – FAST
TRANSACTION_REPEATABLE_READ – MEDIUM
TRANSACTION_SERIALIZABLE – SLOW

Depende de las necesidades especificas del negocio, los ingenieros tendrán que decidir entre uno u otra.

Muy bien, continuemos con el ejemplo. Para encapsular en nuestros servicios de negocios, el tipo especifico de persistencia, debemos utilizar el delegado (la interface) y resultaría muy conveniente ocultar la creación de este mediante una fabrica, así tenemos la siguiente interface y su implementación:


/**
* Define el funcionamiento basico de una fabrica de transacciones.
* @author jonathansanchez
*
*/
public interface TransactionFactory {

/**
* Obtiene una transaccion.
* @return TransactionDelegate.
*/
public abstract TransactionDelegate createNewTransaction();

} // E:O:F:JDBCTransactionFactory.


import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

/**
* Fabrica para crear transacciones
*
* @author jsanca
*
*/
public class JDBCTransactionFactoryImpl implements TransactionFactory {

private DataSource dataSource = null;

/**
* Setea el DataSource
*
* @param dataSource
*/
public void setDataSource(DataSource dataSource) {

this.dataSource = dataSource;
} // setDataSource.

/**
* Obtiene una conexion.
*
* @return Connection.
*/
public Connection getConnection() {

Connection connection = null;

try {

connection = this.dataSource.getConnection();
} catch (SQLException e) {

throw new DAOException(e);
}

return connection;
} // getConnection.

/* (non-Javadoc)
* @see com.jsanca.drivermanager.TransactionFactory#createNewTransaction()
*/
public TransactionDelegate createNewTransaction() {

Connection connection = null;
TransactionDelegate delegate = null;

connection = this.getConnection();
delegate = new JDBCTransactionDelegateImpl(connection);

return delegate;
} // createNewTransaction
} // E:O:F:JDBCTransactionFactory.


No hay mucho que decir de estas dos clases, simplemente encapsulan la creación del delegado y adicionalmente diremos que la implementación almacena un datasource (que es nuestro viejo pool de conexiones) para servir conexiones a la base de datos.

Llegados a este punto, necesitaremos hacer algunas modificaciones mínimas a nuestro BaseDAO y sus clases derivadas, intentaremos hacer los cambios mínimos con el fin de afectar, en el menor grado, las clases que ya pudieran existir:


import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;

public class BaseDAO {

private static JDBCTransactionFactoryImpl transactionFactory = null;
private JDBCTransactionDelegateImpl delegate = null;
private boolean isTransactional = false;

/**
* Constructor por defecto.
*/
public BaseDAO() {

super();
} // BaseDAO.

/**
* Constructor por defecto.
*/
public BaseDAO(TransactionDelegate delegate) {

super();
this.delegate = JDBCTransactionDelegateImpl.class.cast(delegate);
this.isTransactional = true;
} // BaseDAO.

/**
* Este es un metodo utilitario, para crear un DataSource con base en los
* datos, contenidos en el properties.
* [Quiza no deberia estar aqui, pero se deja para
* evitar complicaciones]
*
* @param properties
*/
public static DataSource createDataSource(Properties properties) {

BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(properties.getProperty("url"));
dataSource.setDriverClassName(properties.getProperty("driver"));
dataSource.setUsername(properties.getProperty("user"));
dataSource.setPassword(properties.getProperty("pass"));

return dataSource;
} // init.

protected Connection getConnection() {

return (null != this.delegate) ? this.delegate.getConnection()
: BaseDAO.transactionFactory.getConnection();
} // getConnection.

protected void closeQuiet(Connection conn) {

if (null != conn) {

try {
if (!this.isTransactional) {

conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
} // closeQuiet.

public static void setTransactionFactory(JDBCTransactionFactoryImpl transactionFactory) {

BaseDAO.transactionFactory = transactionFactory;
} // setTransactionFactory.

protected void closeQuiet(Statement stm) {

if (null != stm) {

try {
stm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} // closeQuiet.

protected void closeQuiet(ResultSet rs) {

if (null != rs) {

try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} // closeQuiet.

protected void closeQuiet(Connection conn, Statement stm, ResultSet rs) {

this.closeQuiet(rs);
this.closeQuiet(stm);
this.closeQuiet(conn);
} // closeQuiet.

} // E:O:F:BaseDAO.


Como podrá darse cuenta, en realidad cambio poco; se agregaron dos constructores y el método getConnection, ahora obtiene la conexión de un TransaccionDelegate o directamente del TransaccionFactory, que guarda el pool de conexiones. En el caso del método closeQuit(Connection), primero preguntamos, si nos encontramos o no en una transacción, de esta forma evitamos cerrar la conexión actual que sera reutilizada en otros métodos o por otros daos.

Modifique sus DAO's, agregando los constructores de la super clase, en el caso del EmpleadoDAO seria:


public class EmpleadoDAOJDBCImpl extends BaseDAO implements EmpleadoDAO {


public EmpleadoDAOJDBCImpl() {
super();
}

public EmpleadoDAOJDBCImpl(TransactionDelegate delegate) {
super(delegate);
}
…...


Prontos a terminar este capitulo, implementaremos la fabrica abstracta, la cual es una fabrica de fabricas, que permite obtener implementaciones para diferentes tipos de persistencia, imaginemos que tuviéramos persistencias a través de Servicios Web, JDBC, JMS o alguna otra tecnología transaccional, la idea es que los servicios de negocios no se vean afectado por un cambio en el tipo de persistencia, para este propósito utilizar la fabrica abstracta nos cae de lujo:


import java.util.Properties;

import javax.sql.DataSource;

/**
* Fabrica para crear implementaciones de TransactionFactory.
* @author jonathansanchez
*
*/
public class TransactionFactoryAbstractFactory {

public final static String JDBC = "jdbc";

public final static String IMPLEMENTATION_TYPE_PROPERTY_KEY = "implementation";

public TransactionFactory createFactory (Properties factoryProperties) {

TransactionFactory factory = null;

if (JDBC.equalsIgnoreCase(factoryProperties.getProperty(IMPLEMENTATION_TYPE_PROPERTY_KEY))) {

factory = this.createFactory(factoryProperties);
}

return factory;
} // createFactory.

public JDBCTransactionFactoryImpl createJDBCFactory (Properties properties) {

DataSource dataSource = null;
JDBCTransactionFactoryImpl factory = null;

dataSource = BaseDAO.createDataSource(properties);
factory = new JDBCTransactionFactoryImpl ();
factory.setDataSource(dataSource);

return factory;
} // createJDBCFactory.
} // E:O:F:TransactionAbstractFactory.


Por ultimo modifiquemos nuestra prueba de unidad, para que este conforme a los nuevos cambios:


import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import junit.framework.TestCase;

public class EmpleadoDAOTest extends TestCase {

private EmpleadoDAO empleadoDAO = null;

private JDBCTransactionFactoryImpl factory = null;

private TransactionDelegate transactionDelegate = null;

public void setFactory(JDBCTransactionFactoryImpl factory) {
this.factory = factory;
}

private static SimpleDateFormat dateFormat = new SimpleDateFormat(
"dd/MM/yyyy");

public void testEmpleadoDAO() throws ParseException {

Integer id = null;
Map< string, object=""> empleado = new HashMap();
Map< string, object=""> empleado2;
Collection< map>> empleados;

empleado.put("name", "Bob Marley");
empleado.put("birth_date", dateFormat.parseObject("06/02/1945"));

try {

this.transactionDelegate.start();

id = this.empleadoDAO.insert(empleado);

assertNotNull(id);

empleado.put("empleado_id", id);

empleado2 = this.empleadoDAO.get(id);

assertNotNull(empleado2);

assertTrue(empleado, empleado2);

empleado.put("name", "Albert Einstein");
empleado.put("birth_date", dateFormat.parseObject("14/03/1879"));

assertTrue(this.empleadoDAO.update(empleado));

empleado2 = this.empleadoDAO.get(id);

assertNotNull(empleado2);

assertTrue(empleado, empleado2);

assertTrue(this.empleadoDAO.delete(id));

empleado2 = this.empleadoDAO.get(id);

if (null != empleado2) {

fail("El empleado debio ser nulo");
}

id = this.empleadoDAO.insert(empleado);

empleados = this.empleadoDAO.findAll();

assertNotNull(empleados);
assertTrue(empleados.size() > 0);

this.transactionDelegate.commit();
} catch (DAOException e) {

this.transactionDelegate.rollback();
throw e;
} finally {

this.transactionDelegate.end();
}
} // testCrud.

private void assertTrue(Map empleado,
Map empleado2) {

assertTrue(empleado.get("empleado_id").equals(
empleado2.get("empleado_id")));
assertTrue(empleado.get("name").equals(empleado2.get("name")));
assertTrue(empleado.get("birth_date").equals(
empleado2.get("birth_date")));

} // assertTrue.

@Override
protected void setUp() throws Exception {

super.setUp();
if (null == this.factory) {

Properties properties = new Properties();
TransactionFactoryAbstractFactory abstractFactory = new TransactionFactoryAbstractFactory();
properties.setProperty("url", "jdbc:postgresql://localhost:5432/mydatabase");
properties.setProperty("driver", "org.postgresql.Driver");
properties.setProperty("user", "myuser");
properties.setProperty("pass", "mypass");
properties
.setProperty(
TransactionFactoryAbstractFactory.IMPLEMENTATION_TYPE_PROPERTY_KEY,
TransactionFactoryAbstractFactory.JDBC);

this.factory = abstractFactory.createJDBCFactory(properties);
}

this.transactionDelegate = this.factory.createNewTransaction();

this.empleadoDAO = new EmpleadoDAOJDBCImpl(this.transactionDelegate);

} // setUp.
} // E:O:F:EmpleadoDAOTest.


La corremos y todo debería ir color verde :).

Podemos concluir que el uso de transacciones en Java resulta muy sencillo y hemos aprendido a encapsularlo de una manera bastante apropiada. Adicionalmente, seria perfecto que este código sea sacado de nuestro servicios de negocios y sea envuelto por objetos Proxy implementados a través de AOP, como lo mencionamos anteriormente, de esta manera ensuciaremos menos nuestros servicios lo que aumenta la cohesión y proporciona un código mas bonito, elegante y elegible.

Para capítulos posteriores, tocaremos temas como; lectura de cursores para paginación de resultados, ejecución de múltiples consultas en una sola operación (batch), cutpoints, el uso del RowSet, Wrapper para el resultSet, invocación a procedimientos almacenados, reflexión para popular nuestro Java Beans, entre otras cosas.

Un abrazo y espero les haya gustado.
J

lunes, agosto 17, 2009

JDBC segunda parte (implementando un DAO)

Como parte de esta segunda entrega, vamos a introducirnos mas en el mundo de JDBC y entre otras cosas tales como Test Driven, Patrón de diseño DAO, Diseño por contrato, etc. Así también vamos a extender nuestro ejemplo anterior.

DAO y Diseño por contrato

Un objeto de acceso a datos (DAO por sus siglas en ingles) nos permite encapsular las operaciones de acceso a datos (a muy groso modo), si a este diseño le agregamos una programación por contrato, esto nos proporciona mucha agilidad en el proceso de programación; porque? Como veremos pronto, el diseño por contrato permite trabajar de forma independiente, pues no se ocupa contar con la implementación concreta para realizar algunas otras labores con anterioridad o en paralelo.

Nota: La explicación a fondo de los conceptos de DAO, diseño por contrato y Test driven sobrepasan los objetivos del presente articulo, por lo que no se profundizara en ellos, simplemente serán mostrados en el ejemplo.

En el articulo anterior, estuvimos trabajando con la tabla Empleado; una manera apropiada de acceder a los datos de esta entidad, es mediante la implementación de un patrón DAO, la misma será especificada mediante una interface la cual representa nuestro contrato a utilizar, independiente totalmente de la implementación final; vamos pues al ejemplo:


import java.util.Collection;
import java.util.Map;

public interface EmpleadoDAO {

/**
* Retorna el empleado con el identificador id.
* @param id Integer
* @return Map
*/
public Map get (Integer id);

/**
* Actualiza el empleado
* @param empleado Map
* @return boolean true si es exito
*/
public boolean update (Map empleado);

/**
* Elimina el empleado
* @param id Integer
* @return boolean true si es exito.
*/
public boolean delete (Integer id);

/**
* Inserta el empleado
* @param empleado Mao
* @return Integer retorna el id del empleado nuevo.
*/
public Integer insert (Map empleado);

/**
* Obtiene todos los empleados
* @return Collection con los empleados disponibles.
*/
public Collection> findAll ();
} // E:O:F:EmpleadoDAO.



Si miramos con atención esta interface, simplemente estamos indicando que hacer, mas no el como. Las operaciones presentes en la interface, están relacionadas solamente con la gestión de la entidad y no con lógica de algún negocio en particular; en buena teoría podríamos tomar la entidad empleado y reutilizarla en algún otro proyecto.

Ya con esta interface, podríamos asignarle a un programador que implemente nuestra prueba de unidad para este DAO y a otro que vaya trabajando en la implementación del DAO.

A continuación mostraremos una versión muy simplificada de una posible prueba de unidad, para nuestro DAO:


import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import junit.framework.TestCase;

public class EmpleadoDAOTest extends TestCase {

private EmpleadoDAO empleadoDAO = null;

private static SimpleDateFormat dateFormat = new SimpleDateFormat(
"dd/MM/yyyy");

public void testEmpleadoDAO() throws ParseException {

Integer id = null;
Map empleado = new HashMap();
Map empleado2;
Collection> empleados;

empleado.put("name", "Bob Marley");
empleado.put("birth_date", dateFormat.parseObject("06/02/1945"));

id = this.empleadoDAO.insert(empleado);

assertNotNull(id);

empleado.put("empleado_id", id);

empleado2 = this.empleadoDAO.get(id);

assertNotNull(empleado2);

assertTrue(empleado, empleado2);

empleado.put("name", "Albert Einstein");
empleado.put("birth_date", dateFormat.parseObject("14/03/1879"));

assertTrue(this.empleadoDAO.update(empleado));

empleado2 = this.empleadoDAO.get(id);

assertNotNull(empleado2);

assertTrue(empleado, empleado2);

assertTrue(this.empleadoDAO.delete(id));

empleado2 = this.empleadoDAO.get(id);

if (null != empleado2) {

fail("El empleado debio ser nulo");
}

id = this.empleadoDAO.insert(empleado);

empleados = this.empleadoDAO.findAll();

assertNotNull(empleados);
assertTrue(empleados.size() > 0);
} // testCrud.

private void assertTrue(Map empleado,
Map empleado2) {

assertTrue(empleado.get("empleado_id").equals(empleado2.get("empleado_id")));
assertTrue(empleado.get("name").equals(empleado2.get("name")));
assertTrue(empleado.get("birth_date").equals(
empleado2.get("birth_date")));

} // assertTrue.


} // E:O:F:EmpleadoDAOTest.


Para los que están familiarizados con Junit, esta clase les será fácil de entender, no deseo entrar en detalles acerca del funcionamiento de la prueba de unidad, pero en términos generales invocamos a todas las operaciones que proporciona el DAO y determinamos si los resultados obtenidos, son los esperados, así bien preguntamos si el campo no es nulo, si el Mapa recuperado tiene los mismo valores que el obtenido de la base de datos, etc, si algunas de esta condiciones no se cumple, la prueba falla y muestra el respectivo error.

Ahora vamos a ir creando nuestra implementación, paso a paso:

Lo primero que vamos a crear, antes de nuestra implementación, será una excepción para reportar nuestro errores de acceso a datos, al trabajar con JDBC tendremos que gestionar excepciones derivadas del SQLException, sin embargo si cambiáramos de mecanismo de persistencia, estas excepciones ya no serian validas, por ejemplo que guardáramos todo en un archivo plano, probablemente tendríamos IOException en vez de SQLException, así que es mejor envolver (wrapper) las excepciones en una propia:


public class DAOException extends RuntimeException {


public DAOException() {

}


public DAOException(String arg0) {
super(arg0);

}

public DAOException(Throwable arg0) {
super(arg0);

}


public DAOException(String arg0, Throwable arg1) {
super(arg0, arg1);

}

}



BaseDAO:


Una buena practica de programación, es encapsular ciertas operaciones comunes a varios objetos, en un objeto padre, para ello implementaremos un BaseDAO, el cual contenga algunas objetos o funcionalidad genérica:


import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;

public class BaseDAO {

private static DataSource dataSource = null;

/**
* Inicializa los recursos comunes para todos los daos.
*
* @param properties
*/
public static void init(Properties properties) {

BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(properties.getProperty("url"));
dataSource.setDriverClassName(properties.getProperty("driver"));
dataSource.setUsername(properties.getProperty("user"));
dataSource.setPassword(properties.getProperty("pass"));
BaseDAO.dataSource = dataSource;
} // init.

protected Connection getConnection() {

try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new DAOException(e);
}
} // getConnection.

protected void closeQuiet(Connection conn) {

if (null != conn) {

try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} // closeQuiet.

protected void closeQuiet(Statement stm) {

if (null != stm) {

try {
stm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} // closeQuiet.

protected void closeQuiet(ResultSet rs) {

if (null != rs) {

try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
} // closeQuiet.

protected void closeQuiet(Connection conn, Statement stm, ResultSet rs) {

this.closeQuiet(rs);
this.closeQuiet(stm);
this.closeQuiet(conn);
} // closeQuiet.



} // E:O:F:BaseDAO.


Nuestro BaseDAO, solo gestiona nuestro pool de conexiones y proporciona algunos métodos para cerrar objetos JDBC sin enviar errores. Esto ultimo se realiza, pues en la mayoría de ocasiones si una conexión falla al cerrarse, es poco lo que podemos hacer, quizá podríamos indicarle al usuario del error, pero dependiendo del tipo de usuario, seria poco útil.


Método Get:


El método get obtiene un empleado, basado en el identificador pasado como parámetro a la función:


/*
* (non-Javadoc)
*
* @see com.jsanca.drivermanager.EmpleadoDAO#get(java.lang.Integer)
*/
public Map get(Integer id) {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
ResultSetMetaData metaData = null;
Map empleado = null;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("SELECT * FROM empleado WHERE empleado_id = ?;");
statement.setObject(1, id);

resultSet = statement.executeQuery();

metaData = resultSet.getMetaData();

if (resultSet.next()) {

empleado = this.fillEmpleado(resultSet, metaData);
}
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(connection, statement, resultSet);
}
return empleado;
} // get.

private Map fillEmpleado(ResultSet resultSet,
ResultSetMetaData metaData) throws SQLException {
Map empleado;
int columnCount;
String columnName;
empleado = new LinkedHashMap();

columnCount = metaData.getColumnCount();

for (int i = 1; i <= columnCount; i += 1) { columnName = metaData.getColumnName(i); empleado.put(columnName, resultSet.getObject(columnName)); } return empleado; } // fillEmpleado.



En este ejemplo, utilizamos el objeto PreparedStatement para realizar las consultas, este objeto como notaran, cuenta con varias diferencias al Statement, las cuales en su mayoría son ventajas; en el ejemplo anterior debíamos insertar los valores, concatenando las cadenas al query, con el PreparedStatement utilizamos los comodines ?. Estos comodines indican que espera un parámetro y el orden de aparición en el String, es el orden esperado a la hora de insertar los datos.

Así pues, para insertar el primer comodín una cadena tendríamos que codificar;

statement.setObject(1, “Una cadena”);

Tome en cuenta que el indice inicia en 1 y no en cero como en los vectores.

En nuestro ejemplo en particular, asignamos un entero que representa el identificador del empleado que deseamos recuperar.

Lo siguiente que debemos analizar es el método fillEmpleado; esta operación utiliza el ResultSet que ya habíamos visto, junto con un nuevo objeto, el ResultSetMetaData. El ResultSetMetaData contiene la meta información relacionada con la consulta, es decir el nombre de las columnas, cantidad de columnas recuperadas entre otras cosas. En nuestro ejemplo en particular, obtenemos el nombre de la columna y el valor asociado, para introducirlo en nuestro mapa con los datos correspondientes al empleado.

Lo ultimo que deseo ver, es la utilización de try, catch, finally; el try, permite comprobar excepciones, las excepciones comprobadas pueden ser gestionadas por la instrucción catch. Por ultimo, existan o no errores, las sentencias dentro de finally, serán siempre ejecutas, es por eso que debemos colocar aquí el código para cerrar nuestros recursos o cualquier otra operación que siempre deba ser ejecutada.


Método findAll:



public Collection> findAll() {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
ResultSetMetaData metaData = null;
Map empleado = null;
Collection
> empleados = null;

try {

connection = this.getConnection();
statement = connection.prepareStatement("SELECT * FROM empleado;");

resultSet = statement.executeQuery();

metaData = resultSet.getMetaData();

while (resultSet.next()) {

empleado = this.fillEmpleado(resultSet, metaData);

empleados = (null == empleados) ? new ArrayList
>()
: empleados;
empleados.add(empleado);
}
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(connection, statement, resultSet);
}

return empleados;
} // findAll.


Este procedimiento es muy parecido al método get, la única diferencia es que obtenemos una lista de mapas con los datos de los empleados, en vez de solo un mapa que representa a un solo empleado.


Método Delete:



/*
* (non-Javadoc)
*
* @see com.jsanca.drivermanager.EmpleadoDAO#delete(java.lang.Integer)
*/
public boolean delete(Integer id) {

Connection connection = null;
PreparedStatement statement = null;
boolean success = false;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("DELETE FROM empleado WHERE empleado_id = ?;");
statement.setObject(1, id);
success = statement.executeUpdate() > 0;
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return success;
} // delete.



Este método resulta muy parecido a los anteriores, los únicos puntos a tomar en cuenta es la invocación al método executeUpdate, en vez de executeQuery; el primero permite invocar operaciones de actualización, a.k.a: insert, update, delete. El mismo retorna un entero con la cantidad de filas afectadas por la operación.


Método Update:



/*
* (non-Javadoc)
*
* @see com.jsanca.drivermanager.EmpleadoDAO#update(java.util.Map)
*/
public boolean update(Map empleado) {

Connection connection = null;
PreparedStatement statement = null;
boolean success = false;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("UPDATE empleado SET name = ?, birth_date = ? WHERE empleado_id = ?;");
this.setEmpleadoParameters(empleado, statement);
statement.setObject(3, empleado.get("empleado_id"));
success = statement.executeUpdate() > 0;
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return success;
} // update.

private void setEmpleadoParameters(Map empleado,
PreparedStatement statement) throws SQLException {
statement.setObject(1, empleado.get("name"));
statement.setDate(2, new java.sql.Date(Date.class.cast(
empleado.get("birth_date")).getTime()));
} // setEmpleadoParameters.


Al igual que los métodos anteriores, este método simplemente asigna algunos datos mas (note que se utiliza java.sql.Date en vez de java.util.Date, pues es el dato que espera la BD en este caso particular), el resto del código debería resultarte ya entendible.


Método insert:


Antes de ver este código, quiero comentar una situación particular, que me ocurrió al programar el ejemplo; regularmente utilizamos la operación, executeUpdate para realizar la inserción, como en los casos anteriores, la única deferencia es que necesitamos obtener el identificador generado por la base de datos, algunos driver de JDBC no soportan este tipo de operaciones, por lo que se incluye un workaround (un hack o truco), para la inserción en el singular driver de Postgres; quiero dejar claro que no es la manera mas correcta, pero la incluyo pues puede ser útil para alguno otro mortal por ahí.


/*
* (non-Javadoc)
*
* @see com.jsanca.drivermanager.EmpleadoDAO#insert(java.util.Map)
*/
public Integer insert(Map empleado) {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
Integer id = null;

try {

connection = this.getConnection();
statement = connection.prepareStatement(
"INSERT INTO empleado (name, birth_date) values (?, ?)",
Statement.RETURN_GENERATED_KEYS);
this.setEmpleadoParameters(empleado, statement);
if (statement.executeUpdate() > 0) {

resultSet = statement.getGeneratedKeys();
if (resultSet.next()) {

id = resultSet.getInt(1);
}
}
} catch (SQLException e) {

if (this.isAutoGeneratedKeysIsNotSupported(e)) {

id = altertiveInsert(empleado);
} else {
throw new DAOException(e);
}
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return id;
} // insert.

private Integer altertiveInsert(Map empleado) {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
Integer id = null;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("INSERT INTO empleado (name, birth_date) values (?, ?) RETURNING empleado.empleado_id");
this.setEmpleadoParameters(empleado, statement);

resultSet = statement.executeQuery();
if (resultSet.next()) {

id = Integer.class.cast(resultSet.getObject(1));
}
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return id;
} // altertiveInsert.

private boolean isAutoGeneratedKeysIsNotSupported(SQLException e) {

boolean result = false;
result |= -1 != e.getMessage().indexOf("autogenerated");
result |= -1 != e.getMessage().indexOf("keys");
return result;
} // isAutoGeneratedKeysIsNotSupported.


Bien, lo primero que deseo que noten, es la creación del PreparedStatement, después del query como segundo parámetro pasamos: Statement.RETURN_GENERATED_KEYS, esto le indica a nuestro driver que esperamos el identificador generado, similar a la operación update, asignamos los parámetros a insertar, ejecutamos la actualización y en caso que afecte mas de una fila, solicitamos un ResultSet del cual obtendríamos nuestro identificador generado. En caso que el codigo descrito anteriormente se desplome y arroje una excepción, esta capturada por la instrucción catch y analizada por el método isAutoGeneratedKeysIsNotSupported, en caso que el error describa algo relacionado con la generación de llaves, se intentara un método de insercion alternativo; altertiveInsert, este metodo ejecuta la siguiente operación, valida en Postgres: INSERT INTO empleado (name, birth_date) values (?, ?) RETURNING empleado.empleado_id, como notara el returning devolverá el identificador del empleado insertado, por ultimo tome en cuenta que la operación invocada es executeQuery en vez de executeUpdate, caso contrario obtendrá una excepciones pues la operación no espera nada de retorno.

Ahora que tenemos toda la implementación, solo nos basta volver a nuestra prueba de unidad e incluir el código para arrancar nuestro DAO.


@Override
protected void setUp() throws Exception {

super.setUp();
Properties properties = new Properties ();
properties.setProperty("url", "jdbc:postgresql://localhost:5432/mydatabase");
properties.setProperty("driver", "org.postgresql.Driver");
properties.setProperty("user", "myuser");
properties.setProperty("pass", "mypass");
BaseDAO.init(properties);
this.empleadoDAO = new EmpleadoDAOJDBCImpl ();


} // setUp.



Esta seria la versión definitiva y completa de nuestro test y nuestra implementación:


import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import junit.framework.TestCase;

public class EmpleadoDAOTest extends TestCase {

private EmpleadoDAO empleadoDAO = null;

private static SimpleDateFormat dateFormat = new SimpleDateFormat(
"dd/MM/yyyy");

public void testEmpleadoDAO() throws ParseException {

Integer id = null;
Map empleado = new HashMap();
Map empleado2;
Collection> empleados;

empleado.put("name", "Bob Marley");
empleado.put("birth_date", dateFormat.parseObject("06/02/1945"));

id = this.empleadoDAO.insert(empleado);

assertNotNull(id);

empleado.put("empleado_id", id);

empleado2 = this.empleadoDAO.get(id);

assertNotNull(empleado2);

assertTrue(empleado, empleado2);

empleado.put("name", "Albert Einstein");
empleado.put("birth_date", dateFormat.parseObject("14/03/1879"));

assertTrue(this.empleadoDAO.update(empleado));

empleado2 = this.empleadoDAO.get(id);

assertNotNull(empleado2);

assertTrue(empleado, empleado2);

assertTrue(this.empleadoDAO.delete(id));

empleado2 = this.empleadoDAO.get(id);

if (null != empleado2) {

fail("El empleado debio ser nulo");
}

id = this.empleadoDAO.insert(empleado);

empleados = this.empleadoDAO.findAll();

assertNotNull(empleados);
assertTrue(empleados.size() > 0);
} // testCrud.

private void assertTrue(Map empleado,
Map empleado2) {

assertTrue(empleado.get("empleado_id").equals(empleado2.get("empleado_id")));
assertTrue(empleado.get("name").equals(empleado2.get("name")));
assertTrue(empleado.get("birth_date").equals(
empleado2.get("birth_date")));

} // assertTrue.

@Override
protected void setUp() throws Exception {

super.setUp();
Properties properties = new Properties ();
properties.setProperty("url", "jdbc:postgresql://localhost:5432/mydatabase");
properties.setProperty("driver", "org.postgresql.Driver");
properties.setProperty("user", "myuser");
properties.setProperty("pass", "mypass");
BaseDAO.init(properties);
this.empleadoDAO = new EmpleadoDAOJDBCImpl ();


} // setUp.
} // E:O:F:EmpleadoDAOTest.







import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

public class EmpleadoDAOJDBCImpl extends BaseDAO implements EmpleadoDAO {

/*
* (non-Javadoc)
*
* @see com.jsanca.drivermanager.EmpleadoDAO#delete(java.lang.Integer)
*/
public boolean delete(Integer id) {

Connection connection = null;
PreparedStatement statement = null;
boolean success = false;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("DELETE FROM empleado WHERE empleado_id = ?;");
statement.setObject(1, id);
success = statement.executeUpdate() > 0;
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return success;
} // delete.

/*
* (non-Javadoc)
*
* @see com.jsanca.drivermanager.EmpleadoDAO#get(java.lang.Integer)
*/
public Map get(Integer id) {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
ResultSetMetaData metaData = null;
Map empleado = null;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("SELECT * FROM empleado WHERE empleado_id = ?;");
statement.setObject(1, id);

resultSet = statement.executeQuery();

metaData = resultSet.getMetaData();

if (resultSet.next()) {

empleado = this.fillEmpleado(resultSet, metaData);
}
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(connection, statement, resultSet);
}
return empleado;
} // get.

private Map fillEmpleado(ResultSet resultSet,
ResultSetMetaData metaData) throws SQLException {
Map empleado;
int columnCount;
String columnName;
empleado = new LinkedHashMap();

columnCount = metaData.getColumnCount();

for (int i = 1; i <= columnCount; i += 1) { columnName = metaData.getColumnName(i); empleado.put(columnName, resultSet.getObject(columnName)); } return empleado; } // fillEmpleado. /* * (non-Javadoc) * * @see com.jsanca.drivermanager.EmpleadoDAO#insert(java.util.Map) */ public Integer insert(Map empleado) {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
Integer id = null;

try {

connection = this.getConnection();
statement = connection.prepareStatement(
"INSERT INTO empleado (name, birth_date) values (?, ?)",
Statement.RETURN_GENERATED_KEYS);
this.setEmpleadoParameters(empleado, statement);
if (statement.executeUpdate() > 0) {

resultSet = statement.getGeneratedKeys();
if (resultSet.next()) {

id = resultSet.getInt(1);
}
}
} catch (SQLException e) {

if (this.isAutoGeneratedKeysIsNotSupported(e)) {

id = altertiveInsert(empleado);
} else {
throw new DAOException(e);
}
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return id;
} // insert.

private void setEmpleadoParameters(Map empleado,
PreparedStatement statement) throws SQLException {
statement.setObject(1, empleado.get("name"));
statement.setDate(2, new java.sql.Date(Date.class.cast(
empleado.get("birth_date")).getTime()));
} // setEmpleadoParameters.

private Integer altertiveInsert(Map empleado) {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
Integer id = null;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("INSERT INTO empleado (name, birth_date) values (?, ?) RETURNING empleado.empleado_id");
this.setEmpleadoParameters(empleado, statement);

resultSet = statement.executeQuery();
if (resultSet.next()) {

id = Integer.class.cast(resultSet.getObject(1));
}
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return id;
} // altertiveInsert.

private boolean isAutoGeneratedKeysIsNotSupported(SQLException e) {

boolean result = false;
result |= -1 != e.getMessage().indexOf("autogenerated");
result |= -1 != e.getMessage().indexOf("keys");
return result;
} // isAutoGeneratedKeysIsNotSupported.

/*
* (non-Javadoc)
*
* @see com.jsanca.drivermanager.EmpleadoDAO#update(java.util.Map)
*/
public boolean update(Map empleado) {

Connection connection = null;
PreparedStatement statement = null;
boolean success = false;

try {

connection = this.getConnection();
statement = connection
.prepareStatement("UPDATE empleado SET name = ?, birth_date = ? WHERE empleado_id = ?;");
this.setEmpleadoParameters(empleado, statement);
statement.setObject(3, empleado.get("empleado_id"));
success = statement.executeUpdate() > 0;
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(statement);
this.closeQuiet(connection);
}

return success;
} // update.

public Collection
> findAll() {

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
ResultSetMetaData metaData = null;
Map empleado = null;
Collection
> empleados = null;

try {

connection = this.getConnection();
statement = connection.prepareStatement("SELECT * FROM empleado;");

resultSet = statement.executeQuery();

metaData = resultSet.getMetaData();

while (resultSet.next()) {

empleado = this.fillEmpleado(resultSet, metaData);

empleados = (null == empleados) ? new ArrayList
>()
: empleados;
empleados.add(empleado);
}
} catch (SQLException e) {

throw new DAOException(e);
} finally {

this.closeQuiet(connection, statement, resultSet);
}

return empleados;
} // findAll.

} // E:O:F:EmpleadoDAOJDBCImpl.


Bueno, es todo; espero haya sido de su agrado. Para futuros artículos comentaremos las operaciones en Batch, los cursores, transacciones, entre otros apasionantes temas.

Hasta la proxima!

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.

martes, agosto 04, 2009

El dia de una semana

En esta época del ano, en el ártico el sol no se oculta del todo durante una semana completa, solo asciende y bajo aparentando un ocaso, pero nunca es consumida por el horizonte.
El vídeo dura un 1 min y 20 sec y resulta muy interesante ver el viaje del sol, a decir verdad parece que jugara con el paisaje, interesante fenómeno, espero que lo disfruten.


Asi ni al 2012 llegamos

Si me preguntan yo no creo en profecías ni anuncios en esa linea, pero así de verdad no vamos a llegar a la famosa fecha del calendario Maya en el 2012.

Ya los científicos nos han hecho saber los problemas que acarrea el derretimiento de los polos y los cambios climáticos causado por el calentamiento planetario; antes uno podía volver la mirada al lado pues en cierta manera los cambios no eran tal palpables, pero vean esto:

Imagen de Alaska derretida


Imagen de Beaufort derretida


O cambiamos o colgamos las tennis, en nosotros esta!

lunes, agosto 03, 2009

Cidade Negra - Girassol

Desde el acústico MTV de Ciudad Negra, les dejo esta hermosa canción, Girasol (tiene subtítulos):

Hoy Lunes 3 de Agosto - Carlos Varela - Club Citi - 8 PM



Hoy a las 8 P:M, en Club City tendremos el placer de escuchar por segunda ocasión en el país, al cantautor cubano Carlos Varela. Bautizado por algunos como el trovador de la revolución por algunas de sus controversiales letras y de la mano y apadrino de un gigante como es Pablo Milanes, Varela cuenta ya con mas de 5 discos y en esta ocasión nos presenta su disco nuevo.

La cita es hoy a las 8 y las entradas son muy accesibles, 13 mil general y 25 vip, pero en ese lugar el VIP no es gran cosa.

Un abrazo los espero ahí,
J

domingo, agosto 02, 2009

Lista youtube de maldita Vecindad y los Hijos del Quinto Patio

Ya llego su pachucote:

http://www.youtube.com/watch?v=xTerBJbCs2g&feature=PlayList&p=F69D346074E5985F&index=0&playnext=1

Esta buenisima, tiene 20 canciones entre ellas Pachuco, Kumbala en vivo, vídeo oficial de morenaza (jeje que bueno el mae en patines y que recuerdos aquellos), un clásico del Baile de Mascaras; Ojos Negros, el cómico Super Mercado de su primer disco, el personaje inspirado en el Kaliman de las revista; Solin en su acústico MTV, desde el disco gran circo; poco de sangre,
Video oficial del Gran Circo, desde la gira pata de perro raza, vídeo oficial de Solin.
El cuenta cuentos, don Palabras, Uno de los personajes mas queridos del baile de mascaras.

.. Pura diversión (nunca jamas había escuchado esta rola)

El apañon en vivo,


En fin dem bueno cabrones...

También les dejo el Web, de Maldita Vecindad

Un saludo

El extraño diario de madrugada



Hola,

El Domingo ya asomo su cara hacer mas de 3 horas; la noche le abriga una muy caliente frasada y algunos pendientes insisten en que aun no es hora de irme a la cama.

A mi lado una garrafa de agua frió, al otro el TV proyecta una Película de Jason Born ( Identidad desconocida creo), en frente mi computadora me escucha teclear estas palabras.

Es extraño, entre semana suelo cerrar los ojos poco después de la una de la mañana, pero antes de las 2, solo unas pocas ocasiones he superado mi ritual, pero hoy Sábado mi cansado cuerpo recobro un segundo aire y no quiere dormir, mi imaginacion, mi mente y las ganas de hacer, vuelan o al menos gatean.

Mientras escribo, programo un pequeño servidor para hacer pruebas de llamadas restFul desde algún cliente y leo algunos pendientes, sudo como porcino cuyas venas van a conocer el frió acero del puñal para los tamales de Diciembre y pienso en la gran cantidad de cosas que tengo que hacer,... pienso acerca del refran: No dejes para mañana lo que puedes hacer hoy tiene un limite, ojala mi cuerpo comprendiera ese limite, pero en fin, aquí estoy y sigo tecleando.

A todas esas soledades de madrugadas, fieles a la Internet o esos amantes que siguen aprovechando el ultimo cachito de luna, para todos aquellos que desafían a Morfeo e ignoran el canto del gallo, este pequeño diario les saluda.

Alguien mas ahí? Alguien? Alguno, alguna que no pueda dormir? :o zzZzzZZ
J