Skip to content


El acceso a la base de datos con Java

Para un programador de ambientes COBOL, C o C embebido, la necesidad de frameworks y patrones diversos para el acceso a la base de datos en Java puede resultar extraña. Aquí revisamos algunas de las ideas más populares.

JDBC

Previamente al Java, el acceso a la base de datos se solía efectuar mediante un API relativamente arbitrario proporcionado por el vendedor. La popularidad del lenguaje SQL proporcionó un avance significativo en la portabilidad; sin embargo, salvo el ODBC, la manera de “disparar” las consultas SQL así como obtener los resultados, resultó nuevamente en librerías propietarias de cada vendedor. Notablemente ODBC en el momento de su aparición (en el mundo Microsoft), resultó siendo una capa que generaba retardos inaceptables, era propensa a errores, así como relativamente difícil de utilizar.

Los diseñadores de Java correctamente reconocieron la necesidad de contar con un equivalente estándar al ODBC, por lo que se especificó el JDBC desde el inicio. Esto fue un avance importante puesto que afortunadamente los vendedores comprendieron la necesidad y conveniencia de adherirse a dicho estándar para facilitar el uso de sus productos a los desarrolladores.

Un problema de JDBC radica en su relativo bajo nivel de abstracción (así como en el uso de excepciones de captura obligatoria) lo que obliga a escribir abundante código para tareas relativamente sencillas y comunes. Otro inconveniente resulta del pobre soporte para la clarificación de problemas, obligando a que la aplicación procese códigos de error propietarios, complicando el desarrollo y rompiendo la portabilidad. En ese sentido, muchos desarrolladores crearon independientemente pequeñas librerías auxiliares para simplificar estas tareas.

La conclusión natural fueron los frameworks que abstraen estos inconvenientes del JDBC.

Pero subsistían otro problemas: 1) la incompatibilidad del SQL, y 2) la existencia del SQL!

Empecemos por el segundo. No queremos decir que el SQL deba desaparecer del mundo; sin embargo, es evidente que cualquier aplicación en la que se mezclan dos o más lenguajes resulta relativamente difícil de mantener (en nuestro caso, Java y SQL.) Esto es muy evidente en la gran cantidad de programas “legacy” desarrollados con C embebido, en los que el mantenimiento se hace tremendamente tedioso.

Sorprendentemente, otros lenguajes anteriores como COBOL están exentos de este problema, pero a costa de ser virtualmente imposible su interacción con bases de datos distintas a la que proporciona el vendedor de la plataforma.

Volviendo al Java, se suele plantear dos estrategias de solución: 1) Aislar el SQL con la posibilidad de parametrizarlo desde Java, y 2) Eliminar el SQL mediante una capa de O/R mapping.

En el primer caso (generalmente implementado con iBATIS), el SQL queda restringido a archivos de configuración; este SQL se transforma internamente en un objeto al que se puede parametrizar en tiempo de ejecución (por ejemplo, para especificar los datos de una consulta.)

O/R Mapping

El segundo caso es una solución más compleja, la cual consiste en crear clases y objetos que “mapean” las tablas y sus filas, respectivamente. Con esta infraestructura, por ejemplo, una actualización en un campo de un registro se convierte de:

update TABLA set campo=”Valor” where id=”id”

en:

objeto.setCampo(“Valor”);

Este enfoque tiene una virtud adicional: el SQL potencialmente desaparece de la aplicación. En realidad, el SQL es generado internamente por el framework o librería, y es disparado de forma transparente vía JDBC. En otras palabras, esta capa tiene la posibilidad de hacer desaparecer el problema “1” siempre que tenga la capacidad de personalizar la generación de SQL a cada vendedor de base de datos (por ejemplo, esto se conoce como el “dialecto” en Hibernate.)

Sin embargo, este enfoque no está libre de aspectos negativos. En primer lugar, es evidente que la complejidad de la capa O/R mapping no es despreciable, y su aprendizaje no es trivial. En segundo lugar, diversas consultas a la base de datos (por ejemplo, joins, subselects, etc.) se pueden optimizar mejor con SQL. Por ejemplo, en Hibernate, la necesidad de escribir expresiones complejas de SQL obliga a la creación de un lenguaje propio muy similar al SQL llamado HQL, el cual al menos en teoría es totalmente portable… sin embargo, se reintroduce el problema “2”!!! Otro inconveniente es la relativamente alta “cantidad de configuración” que requiere el mapeo… y la consecuente fragilidad frente a los cambios en la estructura de la base de datos.

DAO

Una de las bondades que debería proporcionar la programación orientada a objetos (POO) es la facilidad en el mantenimiento de las aplicaciones. En realidad, cualquier código en cualquier lenguaje siempre es muy fácil de cambiar… el problema es que otras partes del programa dejan de funcionar bien por culpa de aquel cambio.

En ese sentido, la POO recomienda algunas estrategias tales como el tan conocido “encapsulamiento”, y algunos patrones (menos conocidos) para “desacoplar” dichos “encapsulados” (también llamados genéricamente componentes.)

La estrategia fundamental de desacoplamiento consiste en el uso de interfaces claramente separadas de las implementaciones. Con esta idea en mente, muchos diseños plantean el uso del patrón “DAO” (data access object), que no es nada más que el mismo principio de programar contra interfaces, en lugar de objetos concretos; siendo estos objetos los encargados de las operaciones de acceso a la base de datos. Por ejemplo, la interfaz:

public interface DAO_Cliente {

public Cliente getCliente(String nombre);

}

Se puede implementar mediante diversas estrategias (clases concretas):

public class DAO_Cliente_JDBC {

public Cliente getCliente(String nombre) { /* implementación JDBC */ }

}

public class DAO_Cliente_iBATIS {

public Cliente getCliente(String nombre) { /* implementación iBATIS */ }

}

La idea es que el código que accede a los datos sólo tiene acceso a la interfaz, y nunca se entera de la implementación:

DAO_Cliente dao = (DAO_Cliente) framework.getDAO(“cliente”);

Cliente cliente = dao.getCliente(“xxx”);

Evidentemente, debe también existir algún mecanismo para seleccionar el objeto DAO concreto, lo cual generalmente es parte de la configuración algún framework.

Eventualmente podríamos agregar otra estrategia de acceso a los datos:

public class DAO_Cliente_HBM {

public Cliente getCliente(String nombre) { /* implementación Hibernate */ }

}

Y la aplicación podría utilizarla simplemente cambiando la configuración del framework…. muy potente a la vista!

Sin embargo, en la mayoría de aplicaciones el patrón DAO no tiene utilidad. Por qué? Debido a que es altamente probable que la estrategia de acceso a los datos inicialmente elegida, se mantenga por el resto de la vida de la aplicación.

Pero existen al menos dos motivaciones para recomendar el patrón DAO:

  1. Al estar la lógica de negocio desacoplada de la estrategia de base de datos, dicha lógica de negocio se puede reutilizar sin modificación en otra aplicación que utilice otra estrategia de base de datos.

  2. Cualquer aplicación (por ejemplo en fase de prototipo) puede utilizar una implementación ficticia de DAO para efectos de Test, y ser automáticamente funcional.

public class DAO_Cliente_Test {

public Cliente getCliente(String nombre) { return “Panchito Shelton”; }

}

Categoria: Database, Java. Tagged with , , , , , .

0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

Some HTML is OK

(required)

(required, but never shared)

(required)

or, reply to this post via trackback.