Como extender QuickDB a una nueva BD
Driver y Conexión
El primer paso al intentar extender QuickDB para que soporte la interacción con algún nuevo DBMS (sistema de gestión de bases de datos, por sus siglas en ingles), es buscar el
ODBC que nos permita vincular el lenguaje en el que estemos trabajando con la Base de Datos con la que ser quiere trabajar.
De esta forma QuickDB implementa una interfaz genérica que consume los métodos expuestos por la respectiva implementación del ODBC, y entonces le es posible interactuar con distintas Bases de Datos a través de la implementación del ODBC de cada una teniendo que realizar muy pocos cambios, o ninguno dependiendo el caso.
No obstante aunque el ODBC es un estándar de acceso a Bases de Datos, cada DBMS suele presentar sus propias variaciones que terminan impactando luego en su respectiva implementación del driver.
Las diferencias principales que suelen presentarse al tratar de agregar soporte a un nuevo DBMS son las siguientes:
- Operaciones de grabar datos utilizando la apertura de un bloque en el ResultSet.
- Operaciones de modificación de datos.
- Operaciones para obtener el último ID generado luego de una inserción (por medio del driver).
- Adaptar los mapeos de los tipos de datos propios del lenguaje, a los establecidos por el DBMS.
- Operación de Creación de Tabla: esta operación ya sea por el tipo de dato, especificación de constraints, etc. Suele variar para cada DBMS.
- Funciones Fecha: difieren para cada DBMS.
Migrar QuickDB a un Nuevo Lenguaje
Utilizando ODBC
La primera actividad al comenzar la tarea de migración de QuickDB a un nuevo lenguaje es conocer las Bases de Datos que poseen implementaciones del estandar ODBC para conectar ese determinado lenguaje con la Base de Datos correcta.
Al implementar los métodos que interactuaran con el driver ODBC es importante tratar de mantener estas implementaciones lo mas genéricas posibles, ya que hay que tener en cuenta que luego esos métodos deberían ser de utilidad para un nuevo DBMS solo cambiando el driver y modificando las características de la conexión. Obviamente como se explicó previamente en "Como extender QuickDB a una nueva BD", es posible que siempre existan modificaciones, pero estas deberían ser mínimas y fácilmente solucionables a través de la herencia y re-escritura solo de los fragmentos de código apropiados, manteniendo en lineas generales una implementación general para los distintos DBMS.
Reflexión
La reflexión es uno de los pilares fundamentales de QuickDB, y es en lo que se basa para brindar la facilidad de uso inherente que posee.
QuickDB no busca solo permitir realizar el mapeo objeto-relacional, sino que además esta actividad sea de una gran sencillez para el programador, reduciendo lo más posible el tiempo que se suele invertir para definir como se interactuara con la BD, y esta caracteristica es indudablemente posible gracias a la reflexión.
La facilidad de uso de QuickDB nace de su capacidad para obtener toda la información relevante de las estructuras de datos que se pretenden persistir, sin que para ello el programador deba invertir tiempo en tediosas configuraciones.
Al trabajar con Reflexión en QuickDB, las siguientes operaciones son las que toman mayor importancia:
- Obtener el nombre de la Clase: de esta forma se puede conocer el nombre de la Tabla a la que se mapeara el objeto.
- Obtener instancia de la Clase Padre: obtener la instancia de la clase padre del objeto que se intenta persistir es de suma importancia cuando el modelo de datos esta planteado de esa forma, para representar la estructura de datos de la forma más precisa posible. Ademas la reflexión suele permitir solo acceder a los datos del objeto con el que se esta trabajando, y algunas veces se pierden los datos heredados si no se accede a una instancia del padre. A su vez, es de suma importancia saber identificar cuando acceder a los datos de la Clase Padre y cuando no, ya que la Clase del modelo de datos puede estar heredando de alguna otra Clase, pero no por ello significar que esa Clase Padre debe ser persistida también (el ejemplo más simple es darse cuenta que si no se establecen ciertas condiciones, se podria llegar a persistir hasta la Clase Object, en el caso de Java)
- Obtener Anotaciones/Atributos/Decoradores (el nombre varia según el lenguaje): basicamente son metadatos que agregamos a la Clase y que creando las "Anotaciones" de la forma correcta, esta información persiste aun en tiempo de ejecución y puede ser accedida por QuickDB para los casos en que el desarrollador desea exponer alguna información adicional acerca de algún Objeto.
- Obtener el tipo de dato de los atributos: esta operación es de importancia cuando se intenta crear una Tabla en la BD que represente precisamente al objeto, o conocer más en profundidad la estructura del Objeto con el que se esta trabajando, ya que como se puede consultar en la Documentación de Usuario los tipos datos nativos del lenguaje se suelen mapear directamente a los tipos de datos de la Base de Datos, pero analizando el tipo de dato de los atributos del Objeto podriamos descubrir que cierto determinado atributo no es un tipo de dato nativo, sino que es una colección o una referencia a otro Objeto, por lo que requeriria un tratamiento especial.
- Obtener los Valores de los Atributos: Obviamente al ser la funcionalidad principal persistir los datos de los objetos, debe haber alguna forma de poder obtener el valor de las variables que componen el Objeto para volcarlos en la Base de Datos.
- Colocar Valores a los Atributos: Así como en el punto anterior se habla de la importancia de poder obtener el valor de los atributos para volcarlos en la Base de Datos, es de igual importancia que los datos obtenidos desde la Base de Datos puedan ser insertados en un Objeto para restablecer su estado.
Crear Representación de Objetos
Ni el driver ODBC, ni la Base de Datos entiende las operaciones a nivel de objetos, es por ello que QuickDB nace a cubrir esa necesidad.
Es por ello que QuickDB al trabajar con los objetos utiliza la potencia de la Reflexión para hacer introspección del Objeto y poder crear una representación del mismo que sea utilizable por los distintos algoritmos del Administrador de QuickDB para enviar esos datos a la Base de Datos.
Recuperar Objeto desde la BD
Para que QuickDB sea de utilidad, asi como permite salvar datos en la Base de Datos, también debe permitir la recuperación de estos datos, y que estos sean presentados al desarrollador en la forma en que los grabo en primera instancia, es decir, Objetos.
Una vez mas, QuickDB recurre a las posibilidades de la reflexión para tomar el ResultSet obtenido de ejecutar determinada consulta en la BD y a partir del conocimiento de la relación entre el Objeto y la Tabla en la Base de Datos, va obteniendo los valores de la Tabla Resultante para ir completando los valores del Objeto.
Siguiendo Convenciones
Las convenciones se utilizan para eliminar las tareas de configuración dentro de QuickDB, hay muchas propiedades que pueden ser deducidas automáticamente sin necesidad de configuración, y a continuación se muestran cuales son los detalles a tener en cuenta en el Modelo de Datos para que sea soportado por QuickDB sin necesidad de realizar mas que la escritura de las Entidades.
Estas convenciones son necesarias al no trabajar con "Anotaciones", de lo contrario, pueden escribirse las Entidades de cualquier otra forma siempre que se especifiquen dichas características mediante las "anotaciones".
Para mayor información y ejemplos se puede consultar la Guia de Usuario.
Anotaciones
QuickDB utiliza las "Anotaciones" (metadatos insertados en la Clase, su nombre varian segun el lenguaje) para extender la información del Objeto o proporcionar algún tipo de información especifica sobre el mismo a parte de lo que QuickDB pudiera inferir.
Las anotaciones con las que cuenta QuickDB para realizar todas las tareas necesarias son las siguientes:
- Parent
- Table
- Column
- ColumnDefinition
- Validation
Para mayor información y ejemplos se puede consultar la Guia de Usuario.
AdminBase
AdminBase es la Clase con la que directa o indirectamente interactuara el desarrollador, es la Clase que contiene todas las operaciones realizables por QuickDB y es la que se encarga de orquestar las operaciones de la manera adecuada para llevar a cabo las distintas tareas.
Diseño de Clases
La siguiente definición de las clases, representa la interfaz que debería brindar cada una para cumplir con los propósitos de QuickDB, la implementación de todos los métodos no es de forma estricta, sino que pueden haber algunos métodos que varíen según las necesidades del lenguaje.
La clase AdminBase es la única que exige que se mantengan los métodos con los nombres y funcionalidades establecidas.
La siguiente no es una lista completa de todas las Clases, sino que se trata de las Clases mas relevantes con las que se encuentra el Desarrollador al comenzar (el resto de las clases Irán agregándose luego).
ConnectionDB
* : Consultar Algoritmo en la respectiva Clase de QuickDB/Java
| Métodos | Vista Externa | Vista Interna |
| ConnectionDB( String[] ) (Constructor) | Recibe una conjunto variable de Strings que determinan las propiedades de la conexión con la BD. | * |
| connectMySQL() | Realiza la conexión con MySQL. | Carga el driver de MySQL, crea la url de conexión e invoca al método privado connect() para que se realice la conexión apropiada. |
| connectPostgres() | Realiza la conexión con Postgres. | Carga el driver de PostgreSQL, crea la url de conexión e invoca al método privado connect() para que se realice la conexión apropiada. |
| connectSQLite() | Realiza la conexión con SQLite. | Carga el driver de SQLite, crea la url de conexión e invoca al método privado connect() para que se realice la conexión apropiada. |
| connectFirebird() | Realiza la conexión con Firebird. | Carga el driver de Firebird, crea la url de conexión e invoca al método privado connect() para que se realice la conexión apropiada. |
| closeConnection() | Cierra la conexión establecida. | * |
| openBlock(String nombreTabla) | Crea un bloque de inserción de Datos en la Tabla especificada. | * |
| insertField( String columnName, Object columnValue ) | Dentro del bloque creado, inserta en la columna especificada el valor del objeto recibido por parámetro. | * |
| closeBlock() | Cierra el bloque creado para la inserción de datos. | * |
| existTable(String tableName) | Comprueba que la tabla especificada existe, es decir, ya se encuentra creada en la BD. | * |
| updateField(String table, String where) | Devuelve un ResultSet modificable con los valores de la tabla especificada que cumplen la condición colocada en el "where". | * |
| select(String select) | Recibe una sentencia SQL completa y devuelve el ResultSet resultante. | * |
| selectWhere(String table, String where) | Recibe el nombre de la Tabla, y la sección de la condición de una consulta SQL y devuelve el ResultSet obtenido. | * |
| getConnection() | Devuelve la conexión creada por esta Clase para ser manipulada fuera de la misma. | * |
| initTransaction() | Inicia un bloque de ejecución de operaciones con la BD de forma atómica. | Establece "autoCommit" a False. |
| cancelTransaction() | Cancela la transacción creada. | Hace un Rollback de las operaciones ejecutadas dentro de dicho bloque. |
| confirmTransaction() | Confirma la operaciones ejecutadas dentro del bloque de transacción atómica. | Realiza un "commit" en la BD. |
| executeQuery(String sql) | Ejecuta la sentencia SQL contra la BD, sin devolver nada. | * |
| disconnect() | Termina la conexión iniciada. | * |
| deleteRows(String tableName, String where) | Elimina las filas de la Tabla especificada que cumplan con la condición suministrada a través del "where". | * |
AdminBase
* : Consultar Algoritmo en la respectiva Clase de QuickDB/Java
| Métodos | Vista Externa | Vista Interna |
| initialize(DATABASE db, String[] args) | Recibe un enumerador DATABASE especificando la BD con la que se va a trabajar, y una serie de parámetros que son los que se utilizaran para crear la conexión. | Devuelve una instancia de la misma Clase o alguna de las Clases Hijas dependiendo la BD con la que se desea trabajar. |
| initializeViews(DATABASE db, String[] args) | Inicializa la conexión de todas las Vistas que contenga el programa. | * |
| save(Object object) | Guarda el Objeto recibido por parámetro en la BD. | Obtiene una representación matricial del Objeto y luego ejecuta la operación especificada en la BD. |
| saveGetIndex(Object object) | Guarda el Objeto recibido por parámetro en la BD y retorna el valor del indice generado para el mismo. | Obtiene una representación matricial del Objeto y luego ejecuta la operación especificada en la BD. |
| modify(Object object) | Modifica los valores en la BD para que se asemejen a los cambios realizados en el Objeto proporcionado. | Obtiene una representación matricial del Objeto y luego ejecuta la operación especificada en la BD. |
| delete(Object object) | Elimina el Objeto proporcionado de la BD. | Obtiene una representación matricial del Objeto y luego ejecuta la operación especificada en la BD. |
| executeQuery(String statement) | Ejecuta en la BD la sentencia SQL suministrada. Devolviendo True si la ejecución fue exitosa, o False si no se pudo ejecutar. | * |
| obtainAll(Object object, String sql) | Recibe la Clase del tipo de objetos del cual se desea obtener una colección, y una sentencia SQL completo o solo la sección del WHERE de la misma según se desee. Devuelve una colección de objetos con los datos que cumplen las condiciones especificadas en la BD. | * |
| lazyLoad(Object[] object) | Recibe en la primer llamada el Objeto con el que se va a trabajar, y la sentencia SQL completa especificando la condición del objeto. Este método cargara los valores del Objeto a un primer nivel, es decir, sin cargar los valores de los objetos a los que este haga referencia, esos distintos niveles se Irán cargando sucesivamente en las siguientes llamadas al método donde solo se pasara como parámetro el Objeto previamente cargado. | * |
| obtainWhere(Object object, String sql) | Recibe como parámetro el Objeto que se desea cargar con datos y la sección del WHERE de la consulta SQL. | * |
| obtainJoin(String sql, int cols) | Recibe una sentencia SQL completa, y el número de columnas con el que trabaja la sentencia. Devuelve un Matriz de Objetos con los resultados. | * |
| obtain(Object obj) | Recibe el Objeto con el que se va a operar, y devuelve una instancia de la Clase Query con la que se armara la consulta a través de los métodos de la misma. | * |
| obtain(Object obj, String sql) | Recibe el Objeto con el que se va a operar, y la condición de la búsqueda expresada en el formato "StringQuery" (expresión de condición en objetos) | * |
| obtainSelect(Object object, String sql) | Recibe el Objeto con el que se va a operar, y la sentencia SQL completa. | * |
| saveAll(Collection array) | Recibe una colección de Objetos que se desean guardar en la BD. Devuelve un array con la cantidad de elementos insertados o un array con los indices de los elementos insertados, si la colección que se intenta guardar es una referencia a una colección en otro objeto. | * |
| modifyAll(Collection array) | Recibe una colección de Objetos que se desean modificar en la BD. Devuelve un array con la cantidad de elementos que no pudieron ser actualizados o un array con los indices de los elementos insertados, si la colección que se intenta guardar es una referencia a una colección en otro objeto. | * |
| openAtomicBlock() | Inicia un bloque de ejecución de operaciones atómicas. | * |
| closeAtomicBlock() | Cierra el bloque de ejecución de operaciones atómicas, confirmando las operaciones en la BD. | * |
| cancelAtomicBlock() | Cancela el bloque de ejecución de operaciones atómicas, volviendo atrás todos los cambios realizados al comenzar el bloque. | * |
| setAutoCommit(boolean value) | Establece la condición de AutoCommit a verdadero, haciendo que cada objeto se guarde automáticamente, independientemente de la relación que tenga con otros. | * |
| checkTableExist(String table) | Verifica si la tabla proporcionada existe. | * |
| close() | Cierra la conexión con la BD. | * |
View
Clase abstracta.
| Métodos | Vista Externa | Vista Interna |
| initializeAdminBase(AdminBase admin) | Recibe una instancia de AdminBase y la asigna a la instancia propia de la Clase para poder realizar sus operaciones.
| Este método no inicializa todas las Vistas que se puedan tener, sino que solo es valido para la cual se esta ejecutando este método. |
| renameColumns() | Este método al sobreescribirlo se especifica el nombre que se le asignara a cada columna de la Tabla resultante de la busqueda. | El hecho de renombrar las columnas permite que sean mapeados a los atributos que contenga la Clase que este heredando de View en el caso de que sus atributos no lleven el mismo nombre de los que se obtengan. Los nombres se especifican en un String colocando cada nombre separado por una "," |
| columns() | Al sobreescribir este método se especifica cuales seran las columnas a tener en cuenta de la busqueda resultante. | * |
| classes() | Al sobreescribir este método se establece de que Clases es cada una de las Columnas especificadas en el método mencionado anteriormente. Devuelve un Array con las Clases. | * |
| query() | Este método es obligatorio que sea implementado por la Clase hija, ya que es el que establece la condición que cumplirán los objetos pertenecientes a la Vista. | La Condición puede ser expresada como una sentencia SQL completa o utilizando el Sistema de Query de QuickDB. |
| obtain() | Completa los datos del objeto Vista según la condición establecida internamente. | * |
| obtainAll() | Devuelve un Array de Objetos del mismo tipo de Vista que cumplen con la condición establecida internamente. | * |
Anotaciones
Table
| Table puede estar acompañada de un parámetro o no, el parámetro de Table indica a que tabla de la Base de Datos se mapeara esta entidad. Al no incluir el parámetro simplemente se asume que la Tabla lleva el mismo nombre que la Clase. | @Table
@Table("NombreTabla") |
Parent
Parent indica que esta Clase hereda de otra y que por lo tanto los atributos de la Clase Padre se guardaran en otra tabla (esta anotación no lleva ninguna atributo).
Column
En cuanto a las anotaciones Column, estas se utilizan para definir de forma completa un atributo de la entidad con su correspondiente mapeo dentro de una Tabla en la Base de Datos.
Los distintos parámetros de la Anotación Column, poseen todos valores por defecto, por lo que solo es necesario completar aquellos que sean necesarios para determinado caso particular.
| Parámetro | Significado |
| name | Nombre del Campo de dicha Tabla en la Base de Datos, si no se especifica toma el mismo nombre del atributo. |
| type | Indica el tipo de dato del atributo, el cual puede ser PRIMARYKEY, FOREIGNKEY, COLLECTION, PRIMITIVE (si no se especifica se asume que el tipo es PRIMITIVE, el cual es una de las primitivas del lenguaje contemplando además String y java.sql.Date). |
| getter | Este parámetro presenta la posibilidad de poder expresar explícitamente cual sera el método del cual obtendremos el valor de dicho atributo (si no se especifica se asume que si el atributo lleva el nombre "atributo" el método getter sera "getAtributo"). |
| setter | El mismo caso anterior pero para los métodos de seteo. |
| collectionClass | Cuando el atributo marcado con esta anotación es una colección se puede decir explícitamente que tipo de objetos contendrá esta colección. |
| ignore | Colocándole como valor True le decimos a QuickDB que no tenga en cuenta este atributo al momento de persistir el objeto. |
ColumnDefinition
| Parámetro | Significado |
| type | El tipo de dato con el que se creara dicha columna en la Base de Datos. Por defecto el tipo es VARCHAR. |
| length | Largo del tipo de Dato. Por defecto no especifica largo a los tipos, salvo a VARCHAR que le asigna un largo predeterminado de 150. |
| notNull | Especifica si la columna puede aceptar valores nulos o no. Por defecto su valor es true. |
| defaultValue | El valor que tiene cada campo de la columna por defecto. Por defecto su valor es una cadena vacía. |
| autoIncrement | Valor por defecto False. |
| unique | Estable si el valor del campo es de tipo único. Valor por defecto false. |
| primary | Estable si la columna es de tipo clave primaria. Valor por defecto false. |
Validation
| Parámetro | Significado |
| conditionMatch | Recibe un array de Strings con las distintas expresiones regulares a evaluar. |
| maxLength | Especifica el largo máximo que puede tener cierto campo (incluido el valor especificado). |
| numeric | Realiza comprobaciones de tipos numericas sobre el campo. Recibe un array con un conjunto de valores numericos, estando organizados de esta forma: [condición, valor]. |
| date | Realiza comprobaciones sobre un objeto de tipo fecha. Recibe un array con un conjunto de valores numericos, estando organizados de esta forma: [parte_de_fecha, condición, valor]. |