domingo, diciembre 27, 2009

Tarifas 3G y celulares autorizados

Para los que desean conocer los precios y los celulares homologados 3G (autorizados y supuestamente probados por el ICE), aquí dos links:

Tarifas + 12.500 colones mensuales, si desea Internet según entiendo
http://portal.grupoice.com/wps/wcm/connect/web+content/Esp/CatTelecom/Personal/solucVoz/3G/

EDIT:
Nuevo link: http://www.grupoice.com/swf/kolbi/planes_servicios.html

Posible lista de celulares homologados
http://www.teleticatv.com/new/tabla_celulares_hsdpa_850mhz.php

Un saludo,
J

jueves, diciembre 03, 2009

La banda sonora de mi vida (mis influencias musicales en el tiempo)

... Y bueno. Ya hace algún tiempo que no escribo por acá, ya hacia en falta la verdad, y se me ha ocurrido crear uno (o algunos) post para comentar mis gustos musicales, la influencias que como músico amateur y fanático de la música, repercuten y definen tanto en  parte de la aptitud, gustos y forma de ser propiamente!

Debo reconocer que mis gustos actuales, son distintos a los gusto que tenia a las 12 o 15 anos, inicialmente empece con la onda mas punk rock, aunque mi primer cassette fue el OLe Torito de la gran banda nacional Hormigas en la pared, ya no lo conservo, lastimosamente se extravío y pues ni modo.

Este primer cassette fue simplemente estridente, con solo aquella introducción que decía:

"dicen ser dos amigos que por desgracia se fueron en el hueco, solamente fueron dos hijueputas, que no me dijeron donde estaba la maleta"...

Jeje, creo que la puedo cantar de memoria, tan vigente como hace 15 anos.



Aquí un link para bajar el disco.

Grande las hormigas, me acuerdo de los conciertos en la UNA, en San y en la Alianza China.... tiempos aquellos.

Dale Aborigen


Bueno que disco, que grupo y que fuerza; estuve en los dos conciertos en Costa Rica, el primero un de los mejores conciertos a los que asistí, junto con otro de los grandes y chineados para mi Maldita Vecindad y los Hijos del Quinto Patio (tuve un cassette del Gran Circo), conciertaso y en el palacio de los deportes ni que decir!

Este y el camino real, son mis discos preferidos.

Volvió la Alegría Vieja



Este, el segundo disco de la banda argentina dos minutos; me viene a la memoria, cuando compre este disco, lo encargue al cantante del Guato que trabajaba en Vendimusic.





Podría decir que esta es mi banda favorita de música Ska, estuve en sus dos presentaciones en CR; una con sus coterráneos de Control Machete y otra con los argentinos de Todos tus Muertos, eternamente arrepentido por perderme el ultimo concierto en suelo tico, en picachos; por cierto uno de los peores lugares para hacer un concierto, lejos, feo y lejos!

Los discos originales que conservo: baile de mascaras y mostros!

Los Violadores - Ultraviolento

Bueno, este fue uno de los primeros grupos de punk rock argentino, hasta donde tengo entendido, este disco contiene unas canciones en estudio y un concierto con un sonido francamente pésimo o demasiado under!







En mi humilde opinión una de las mejores voces (Saúl Hernández) y uno de los mejores grupos no solo de Mexico, si no de toda la comunidad hispano hablante; el equilibrio lo escuche casi entero una noche estudiando para la prueba de español de bachillerato y fue amor ha primera vista (o escuchada, jeje), tan pronto ahorre el dinero fue mío; también tengo su ultima producción a la fecha, 45; un discaso, aunque sigo prefiriendo el equilibro.

Ministry Psalm 69



El salmo 69, de Ministry es simplemente un discaso, lo pedí por encomienda y me parece que es uno de los mejores de la banda, algo de mis influencias mas oscuras, aquí el link para que le den por la madre.

Offspring - Ignition

Hasta donde se, el primer disco de la banda; un disco bastante punk, aunque no tan rapido como el pennywise pennywise que tuve en cassette, un discaso que solo marca la sombra de lo que fue un gran grupo de punk rock. Aqui un link con varios discos de ellos, incluido el Ignition



Korn _ life is peachy

Desde que escuche A.D.I.D.A.S, sube que este disco tenia que ser mio, habia escuchado el primer y antecesor disco de Korn y sinceramente era de lo mejor, desde el inicio con blind, rajado.

Sin duda tengo que comprar ese disco y quiza el tercero :)











La verdad es que he cometido un pecado capital de la música y seguramente seré castigado comprando un disco de Ricardo Arjona por las justas deidades de la música, al no haber comprado mas discos de Bjork, pero al menos este disco recapitula gran parte de su excelente musica, aunque no es suficiente! Donde este el colacho musical cuando lo necesitas :)

La revancha del tango - Gotan Project



Gracias al programa, Mundo Loco, que hace aNos se trasmitía por radio U; conocí este grupaso francés, este tango electrónico e irreverente es para disfrutar, también tengo el DVD que lleva el mismo titulo, un perfomace en vivo bastante bueno; si lees su nombre al reves es algo como asi: Gotan <=> Tango ;)


karsh Kale - Liberation





Para los que les gusta la onda de la musica mundo, este es un disco que debe tener; lo conoce por Luz en meridiano, junto con otro flechaso, Loreena McKennitt.


De Loreena tengo, 4 discos; The Book of Secrets (rajado), Live in Paris & Toronto (aun mas moustro), The Visit y su ultimo disco A Midwinter Night's Dream (es bueno pero no tanto como los anteriores). Tambien cuento con un Dvd con CD double; Nights from the Alhambra (muy matizzon)



Los héroes del Silencio - El mar no cesa



Según entiendo su primer larga duración, pues anterior a este disco tuvieron el Mar adentro. Héroes del Silencio, es uno de lo mejores grupos que existen y lo mejor rock en España, Honor a quien honor merece, los vi en vivo en un par de ocasiones asi como he visto a Bunbury al menos 3 veces. 

Increíble, pero es el único disco original tengo de esta querida banda; propósito para el 2010 comprar cuantos discos de los héroes pueda, sin duda mi primera opción será el Espíritu del vino, lo tuve en cassette y en mi humilde criterio, el mejor de todos los discos!

Alice in chains

40 minutos después de la ultima campanada termino por ahora no sin antes mencionar a  este legendario grupo de rock, de ellos tengo el gusto de tener el acústico, así como el ultimo disco con el nuevo cantante; puta vaya forma de regresar, increíble disco, de lo mejor que he escuchado, junto con el nuevo de Pearl Jam y el fuerza natural de Cerati son de mis chineados.











lunes, noviembre 16, 2009

Marchamo 2010

Con el fin de año llegan la navidad, la vacaciones, los regalos y el aguinaldo; pero no todo son buenas noticias, también esta la declaración fiscal y el famoso marchamo:

Para los que andan perdidos, bueno, pueden consultarlo aquí:


Y si no se acuerda de la placa, busquela aquí:


Ya si no sabe su numero de identificación.....

Comparando procesadores

Hola,

Hace poco estuve comparando computadoras y dentro de esta tarea también compare procesadores, esta herramienta fue de mucha utilidad:

http://www.cpubenchmark.net/cpu_list.php

Un saludo,
J

ESCALAMIENTO Y REPLICACIÓN DE MySQL PARA NEGOCIOS DE ALTO CRECIMIENTO

https://dct.sun.com/dct/forms/reg_ar_2606_620_0.jsp?cid=e9048

Disfrutalo,
J

viernes, noviembre 06, 2009

Como crear un link simbolico en UNIX

Un link simbólico se trata de un link que hace una referencia (es un apuntador) a un directorio concreto. Es muy útil cuando queremos por ejemplo trabajar en los directorios de nuestro proyecto, pero deseamos que el servidor piense que los directorios se encuentran en su espacio de trabajo.

Para crearlo debemos ejecutar el siguiente comando:


ln -s  [directorio|archivo]real [directorio|archivo]simbólico

Como puede darse cuenta, el comando básicamente espera como primer argumento el directorio o archivo real y como segundo, el archivo o directorio simbólico.

Mas info: ln --help

Por ultimo, para ver tu link, ejecuta

ls -l

De esta manera te mostrara los links simbólicos y algo mas de información, como los permisos entre otros.

jueves, noviembre 05, 2009

Grupo de Trovadores - Pajaros en la cabeza



Hola a todos,

Hace no mas de dos semanas, abrimos este grupo en google, para la gente que gusta del genero de la trova y la canción de autor:

http://groups.google.com/group/pajarosenlacabeza

Ahí lo tenes, por si queres compartir con nosotros, tu música, tus opiniones y vivencias en torno a este hermosos genero,

Un saludo,
J

martes, octubre 06, 2009

DVD del concierto de bunbury en el Palacio de los Deportes

Los compas de http://hdsemule.blogspot.com/, al parecer estan editando un DVD grabado del gran concierto del español bunbury.

A continuación les dejo con una pequeña presentación matizada con la canción, no fue bueno pero fue lo mejor:

domingo, octubre 04, 2009

Cuando un ángel abandona la tierra



Cuando un ángel abandona la tierra.

Este ángel, lucho desde sur a norte, durante 74 primaveras; ha luchado contra la violencia, las dictaduras y ha llevado su mensaje de amor y buena esperanza a corazones llenos de maldad. Pero ni siquiera los ángeles duran para siempre; tienen un tiempo finito en la tierra, hasta que su creador, les reclama a su lado.

Va volando, blanca y llena de un absoluto resplandor, mientras entona sus últimos versos, ya próxima a atravesar, las puertas de su maravilloso paradero de eternidad, mientras nuestro ángel canta!!!

Gracias a la vida, que me ha dado tanto.

---

Honores y alegrías, a quien alegro y amo al mundo, un beso a la distancia "Negra"

martes, septiembre 29, 2009

Aprenda hacer pagina Web, desde cero! (1)



La idea detrás de este articulo, es ayudar al lector a introducirse en el mundo Web y mas propiamente en la creación de las paginas.

Ademas de lo anteriormente expuesto, también se desea que el lector, aprenda a crear paginas valiendose de las mejores practicas y las técnicas mas adecuadas.

Manos a la obra.


Antes de iniciar deberíamos bajarnos algún programa para la edición de las paginas. En Internet existen infinidad de editores y muchos de ellos gratis. A mi en particular me gusta Aptana Studio, puedes buscar los detalles de la descarga aquí: http://www.aptana.com/.

Entendiendo el HTML.

HTML, es una lenguaje de marcado mediante el cual le indicamos al navegador de Internet, como deseamos que se renderice la información;

El lenguaje utiliza tags o lo que es lo mismo: etiquetas; y las mismas pueden tratarse como abiertas o cerradas, veamos un par de ejemplos;


Etiqueta abierta:

<tag> contenido </tag>

Etiqueta cerrada:

<tag/>

Como apreciara, la etiqueta abierta permite contenido u otras etiquetas dentro de si, por ejemplo si usted desea hacer un párrafo, podría hacerlo de la siguiente manera:

<p> Un párrafo </p>

Las etiquetas cerradas, a diferencia, son utilizadas para renderizar elementos sin contenido textual, algunos ejemplos:

Cambio de linea:
<br/>

Imagen:
<img src=”unaimagen.png”/>

Creando el esqueleto de nuestra pagina Web



Una pagina Web, en su forma mas general se compone por dos elementos, el encabezado y el cuerpo del documento. Ambos elementos se encapsulan dentro de las etiquetas “html”, que indica que estamos describiendo un documento HTML.




<html>
<head>
</head>
<body>
</body>
</html>




Si copias este código a tu editor de texto y lo salvas como index.html, puedes abrirlo en tu navegador favorito con lo cual obtendrás una pagina en blanco, lista para empezar a ser trabajada.



Un punto importante, que debemos entender, es que el body contendrá el contenido que se vera en la pagina y el encabezado contendrá la meta información para la pagina, mas adelante mostraremos los elementos que se pueden incluir en esta sección.



Por ejemplo, si usted coloca algún texto dentro de los tags body, y refresca la pagina en su navegador, podrá ver el contenido que escribió. Como notara, no necesita herramientas muy sofisticadas, inclusivo con el editor vi en Unix o notepad en Windows, puede ser suficientes, sin embargo es recomendado un IDE, que ayuda en el autocompletado de los tags, entre otras opciones.



CSS


Los CSS o estilos en cascada, son utilizados para darle formato a nuestra pagina, si hacemos nuestro trabajo correctamente, nunca deberíamos tener etiquetas para dar estilo a nuestras paginas, me refiero aspectos tales como: color, tipografías, bordes, alineamientos, etc.



Muchas de las etiquetas con las cuales podemos trabajar, contienen un estilo por defecto, por ejemplo:




<h1> Un titulo de tipo 1 </h1>




Si colocamos este texto, dentro de la etiqueta “body”, podemos apreciar que nuestro texto tiene un tamaño bastante grande, se encuentra sombreado e incluye márgenes izquierdos y superiores. En algunas ocasiones, podríamos desear personalizar desde cero nuestra pagina, de tal manera que las etiquetas no tengan ningún tipo de estilo, para ello podemos utilizar una técnica llamada “CSS reset” y para llevarla a cabo, podemos utilizar algunas librerías existentes, a mi en particular me gusta la de YUI, para ello solo incluya dentro de las etiquetas “head” la siguiente linea:




<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.8.0r4/build/reset/reset-min.css"/>




Una vez incluida esta linea, actualice su pagina, en el navegador de Internet y notara que su etiqueta “h1”, cambiara el estilo, mostrandose como un simple texto. De esta manera, usted puede empezar a dar el estilo que usted mejor prefiera.




Mas adelante estudiaremos la forma de utilizar los CSS, para dar formato, por ahora solo quería mencionar que existen y la forma de resetearlos.




Colocando el titulo a nuestra pagina.




Lo ultimo que vamos ha hacer, en esta primera entrega del tutorial, sera colocar el titulo de nuestra pagina, antes de aprender como, quiero dejar claro que al referirnos a titulo, nos referimos al titulo en la ventana del navegador y no en la pagina propiamente.




Para colocar el titulo, simplemente el siguiente tag, dentro del tag “head”:





<head>
<title>Mi titulo</title>
</head>





De esta manera, el titulo sera mostrado.




Esto es todo por ahora, pronto la segunda entrega.

viernes, septiembre 25, 2009

Reflexión acerca de Java y otros lenguajes (cual es el mejor)

Reflexión acerca de Java y otros lenguajes


Hace poco, me encontré bajo fuego cruzado, en medio de una guerra gueeks, donde se discutía el tema, si era Java el mejor lenguaje o no, que si este, que si otro, etc.

Yo antes de seguir adelante, quiero poner en claro que he sido programador, de Php, C++, VB, C#, Java y Perl; aunque debo reconocer que la mayor parte de mi experiencia, se centra en Java; Ya son mas de años desde que usaba Java con Forte o el IDE de Borland.

Yo he trabajado en varios lenguajes, y todos tienen sus pros y contras; a mi gusto personal, como lenguaje; C# al ser mas reciente, (aunque ya tiene sus años) es el mejor; aprendió muchas lecciones de Java, así como Java lo hizo de C++, lo que lo hacen unos de mis preferidos, junto con Groovy.

C++, me gusta por que las aplicaciones se sienten rápidas y te da mucha potencia y la posibilidad de llegar a niveles muy bajos, lo que te permite mas optimizacion y eficiencia, sin embargo, el lenguaje no esta hecho para ser compartido, y a veces no resulta sencillo tener varias versiones para diferentes OS, punto que Java supera en la practica y .Net intenta solo en la teoría.


C#; me inclino a decir que este lenguaje, "como lenguaje" es mejor que Java. Tiene sobrecarga de operadores (asunto que extraño demasiado de C++ en Java), se pueden manejar punteros si se ocupa y aunque Java ha empatado mucho su sintaxis y .Net ha complicando el lenguaje introducciendo pegas, como LinkQ en el lenguaje (no malentiendan, es cool, pero me parece que va lento, igual aclaro, solo implemente una prueba de concepto básica y probablemente con la conflagración mas pobre). A todo eso, me encanto programa en C#, en su momento me parecia mejor que Java y tome en cuenta que cuando aprendí C#, no existía Visual Studio, ni nada y todo se hacia a mano, es por eso que vi la belleza del lenguaje.

Ruby, Php, Python, Perl, etc; guau cuantos lenguajes con P (excepto Ruby claro), estos lenguajes ágiles la verdad los aborrezco bastante. Me resultan super tiesos e inseguros al no tener tipado fuerte, en el caso de Php, le pasa algo parecido que a C/C++; no es un lenguaje pensado para ser OOP desde el inicio y tiene demasiados parches, ni que decir de Perl, es tan OO como VB 6, osea!.
Los frameworks al estilo Rails, te ofrecen resultados de forma rápida, pero tienes que hacer las cosas como ellos dicen, me refiero al famoso Golden Way, lo cual me parece tiezo y por ende difícil de customizar. Si se toma en cuenta, que el 15% del esfuerzo en un sistema, se utiliza al construirlo y el resto en mantenerlo, vale la pena hacer algo, mantenible, fácil de entender y de lo que sintamos un dominio total sobre lo que hace por debajo (o al menos el mayor posible).

Java; Mira, nuestro querido Java, no supera en mi humilde opinión, a C++ o C# en poder, o lo que es lo mismo; como lenguaje. No es tan ágil como Perl, Php, Ruby, etc; aunque ahora puede correr lenguajes script (bastantes por cierto), que ayudan a tener mas dinamismo al programar y una sintaxis menos estricta. Ninguno iguala la portabilidad, he hecho aplicaciones en Swing y corren en Windows XP, Vista, Mac OS y diferentes distros de Linux, sin problema alguno.

En fin, si el lenguaje no es tan versátil o poderoso, que lo hace popular y que nos resiste a prescindir de el, pues aun usando Groovy, siento el sabor a Java, no creo que sea Swing y su portabilidad nada mas.

En mi humilde criterio, el poder de Java no reside en el lenguaje, ni siquiera en el SDK, que a diferencia de Ms, es propuesto y aprobado por una junta, conformada desde varios jugadores grandes, como IBM, Oracle, Sony Ericcson, etc, hasta profesores Universitarios. Pero nisiquiera esto y tampoco, los años que lleva el lenguaje desarrollando Apps, o Web, tampoco (casi 20).

El poder de Java reside en la comunidad, una comunidad volcada al código abierto, donde existen alternativas para las tecnologías estándar, que se vuelven de facto, además de extensiones, customizaciones, utilerias, etc, etc, etc, etc, etc.

El único punto, que reconozco a Php, en el campo del Open Source, son los Foros y CMS. En mi opinión, el código es deplorable, pero las funcionalidades van muy por delante de los esfuerzos de la comunidad Java.

Asi pues, mi tesis va por la comunidad y considero que es el alfa y omega de Java, son sus fuerte simientos, su sosten, todo su poder y tu que opinas?

martes, septiembre 22, 2009

Nueva canción de Sabina (un sabinaso desde su disco nuevo Vinagre y Rosas)

Afortunadamente, Joaquin es incansable y después del alivio de luto, nos da otro regalo, Vinagre y Rosa titula su nuevo disco y aquí una de sus canciones, dedicada al poeta fallecido, Angel Gonzales.



Un saludo,
J

domingo, septiembre 20, 2009

A primera vista

La primera canción, es la interpretación en español de Pedrito Aznar, la segunda el grande Chico cesar autor de la canción.

A primera vista - Pedro Aznar!


Cuando no tenía nada deseé
Cuando todo era ausencia esperé
Cuando tuve frío temblé
Cuando tuve coraje llamé

Cuando llegó carta la abrí
Cuando escuché a Prince (Salif Keita) bailé
Cuando el ojo brilló entendí
Cuando me crecieron alas volé

Cuando me llamó allá fui
Cuando me di cuenta estaba ahí
Cuando te encontré me perdí
En cuanto te vi me enamoré


CHICO CÉSAR - A PRIMEIRA VISTA




Quando não tinha nada, eu quis
Quando tudo era ausência, esperei
Quando tive frio, tremi
Quando tive coragem, liguei...

Quando chegou carta, abri
Quando ouvi prince, dancei
Quando o olho brilhou, entendi
Quando criei asas, voei...

Quando me chamou, eu vim
Quando dei por mim, tava aqui
Quando lhe achei, me perdi
Quando vi você, me apaixonei...

Amarazáia zoê, záia, záia
A hin hingá do hanhan.....
Ohhh!
Amarazáia zoê, záia, záia
A hin hingá do hanhan.....

Quando não tinha nada, eu quis
Quando tudo era ausência, esperei
Quando tive frio, tremi
Quando tive coragem, liguei...

Quando chegou carta, abri
Quando ouvi Salif Keita, dancei
Quando o olho brilhou, entendi
Quando criei asas, voei...

Quando me chamou, eu vim
Quando dei por mim, tava aqui
Quando lhe achei, me perdi
Quando vi você, me apaixonei...

Amarazáia zoê, záia, záia
A hin hingá do hanhan...
Ohhhhh!
Amarazáia zoê, záia, záia
A hin hingá do hanhan.....

Quando me chamou, eu vim
Quando dei por mim, tava aqui
Quando lhe achei, me perdi
Quando vi você, me apaixonei...

Amarazáia zoê, záia, záia
A hin hingá do hanhan....

Ohhhhh!
Amarazáia zoê, záia, záia
A hin hingá do hanhan...(2x)

Ohhhhh!
Amarazáia zoê, záia, záia...

-- El amor actua de misteriosas formas, resulta mejor no entenderlo, si no mas bién, recibelo alegre con los brazos abiertos en pares; como si fuera la ultima vez que pudieras tenerlo entre tus brazos --

Un saludo,
J

miércoles, septiembre 09, 2009

Cero futbol - estadio - tv nada proteste xq la sele apesta!



Ya en otras ocasiones había escrito acerca de la pésima actitud de la sele, desde que juega la copa de oro; se nota que no tiene un pensamiento ganador, ni ganas de competir y ser mejor.

Con el resultado de hoy, que suma tres derrotas, 8 goles recibidos y 3 partidos sin hacer un solo gol, ni uno solo desde aquel agónico gol de Froylan, contra México en la Semifinal de la copa de Oro.

Ya poco se puede hacer; esos maes no creo que hagan la hombrada de ganar en USA, tomando por hecho que le ganen a Trinidad, la piñata de la hexagonal (pues a como andan, todo puede pasar; empate o incluso perder, todo es posible con la mediocre sele). Sin embargo, con el nivel de la sele, hasta puede perder el repechaje, en vez de ganarle a Honduras el tercer puesto.

Pero en fin, esos son los hechos; el fútbol de CR, es mediocre y el campeonato poco competitivo; yo le propongo a usted aficionado del fútbol, que se abstenga todo lo que su fiebre le permita de ver fútbol, ir a los estadios, etc. Es importante mandar un mensaje a la dirigencia y a los jugadores, que los que pagamos sus salarios, estamos super inconformes y queremos un cambio y un, creo yo, merecido cuadro patrio, que nos de orgullo y pasión al ver el blanco, azul y rojo, dejando la sangre y las ganas en la cancha.

Un saludo,
J

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.