Arranz jose persistencia orientada a objetos en java

Page 1

ESTRATEGIAS DE PERSISTENCIA ORIENTADA A OBJETOS EN JAVA CON JDBC, UNA COMPARATIVA DESDE LA PRテ,TICA

Josテゥ Marテュa Arranz Santamarテュa e-mail: jmarranz@dii.etsii.upm.es Madrid, 6 de Octubre 2003


Tecnologías de Persistencia en Java, una comparativa desde la práctica

INDICE 1. INTRODUCCIÓN....................................................................................................................................... 3 2. PLANTEAMIENTO DEL PROBLEMA .................................................................................................. 5 2.1 DECISIONES DE IMPLEMENTACIÓN........................................................................................................ 5 2.1.1 CORRESPONDENCIA ENTRE TABLA Y CLASE INCLUSO EN HERENCIA .................................................... 5 2.1.2 CORRESPONDENCIA ENTRE FILA Y OBJETO ........................................................................................... 6 2.1.3 CORRESPONDENCIA ENTRE COLUMNA DE TABLA Y ATRIBUTO DE CLASE ............................................ 6 2.1.4 IDENTIDAD GESTIONADA POR LA APLICACIÓN (APPLICATION IDENTITY) ............................................. 6 2.1.5 RELACIONES ENTRE TABLAS EXPRESADAS A TRAVÉS DE PUNTEROS Y COLECCIONES ......................... 6 2.1.6 SE TENDERÁ A ENCAPSULAR TODO TIPO DE ACCESO A LA BASE DE DATOS EN CLASES ORIENTADAS A LA PERSISTENCIA ............................................................................................................................................... 6 2.1.7 SE TENDERÁ HACIA EL RESPETO DE LAS FORMAS CLÁSICAS DE MODELAR ESQUEMAS RELACIONALES 6 2.1.8 SE USARÁ ANSI SQL 92 ....................................................................................................................... 6 2.1.9 NO SE USARAN CARACTERÍSTICAS ORIENTADAS A OBJETOS DE SQL3 (ANSI SQL1999).................... 7 2.1.10 USO DE CLAVES SEMÁNTICAS EN EL MODELO RELACIONAL ............................................................... 7 2.2 MODELO DE ENTIDADES O CLASES .................................................................................................... 7 2.2.1 MODELO DE UNA ENTIDAD .................................................................................................................... 7 2.2.2 MODELO DE UNA ENTIDAD RELACIONADA CON MUCHAS INSTANCIAS DE OTRA ENTIDAD................... 7 2.2.3 MODELO MUCHOS-MUCHOS ENTRE ENTIDADES .................................................................................... 8 2.2.4 MODELO DE ENTIDAD CON HERENCIA DE OTRA ENTIDAD .................................................................... 8 2.3 MODELO RELACIONAL ........................................................................................................................... 9 2.3.1 MODELO UNA TABLA............................................................................................................................. 9 2.3.2 MODELO UNO-MUCHOS (DOS TABLAS) ................................................................................................. 9 2.3.3 MODELO MUCHOS-MUCHOS (TRES TABLAS) ......................................................................................... 9 2.3.4 MODELO DE HERENCIA ........................................................................................................................ 10 3. HERRAMIENTAS .................................................................................................................................... 11 4. SOLUCIÓN JDBC .................................................................................................................................... 12 4.1 TIPO BMP.............................................................................................................................................. 12 4.1.1 UNA TABLA ......................................................................................................................................... 13 4.2 TIPO BMP AVANZADO O TIPO BMP 2................................................................................................ 21 4.2.1 CLASES COMUNES (FRAMEWORK) ....................................................................................................... 22 4.2.2 UNA TABLA ......................................................................................................................................... 25 4.2.3 RELACIÓN UNO – MUCHOS ................................................................................................................. 29 4.2.4 RELACIÓN MUCHOS – MUCHOS .......................................................................................................... 35 4.2.5 HERENCIA ............................................................................................................................................ 42 4.3 TIPO CMP ............................................................................................................................................. 52 4.3.1 CLASES COMUNES (FRAMEWORK) ....................................................................................................... 53 4.3.2 UNA TABLA ......................................................................................................................................... 56 4.3.3 RELACIÓN UNO-MUCHOS ................................................................................................................... 59 4.3.4 RELACIÓN MUCHOS-MUCHOS ............................................................................................................ 62 4.3.5 HERENCIA ............................................................................................................................................ 65 5. CONLUSIONES ........................................................................................................................................ 72 5.1 NECESIDAD DE UN FRAMEWORK.......................................................................................................... 72 5.2 NECESIDAD DE FRAMEWORKS MÁS SOFISTICADOS ............................................................................ 72 5.3 ORIENTACIÓN A OBJETOS Y PATRONES COMO HERRAMIENTAS QUE PERMITEN LA TRANSPARENCIA Y LA TOLERANCIA A CAMBIOS ........................................................................................ 72 5.4 BMP MENOS TRANSPARENTE QUE CMP............................................................................................. 73 5.5 CMP TIENE SERIOS PROBLEMAS CON LA HERENCIA ......................................................................... 73 5.6 JDBC ADECUADO PARA MODELOS SENCILLOS O BASES DE DATOS POCO ESTÁNDAR ...................... 73 persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.2


Tecnologías de Persistencia en Java, una comparativa desde la práctica

1. INTRODUCCIÓN La persistencia o el almacenamiento permanente, es una de las necesidades básicas de cualquier sistema de información de cualquier tipo. Desde las tarjetas perforadas hasta los modernos sistemas actuales de bases de datos, las tecnologías persistentes han realizado un largo viaje en la historia de la informática. El problema de cómo “programar la persistencia”, es decir automatizar las acciones de almacenamiento y recuperación de datos desde el sistema de almacenamiento, es uno de los más relevantes en cualquier sistema software que manipula información no volátil, hasta el punto de que condiciona enormemente la programación del mismo. El problema de la programación de la persistencia ha estado fuertemente influido por el sistema de base de datos, de hecho el propio sistema de base de datos, sea del tipo que fuera (relacional, jerárquico, orientado a objetos etc), obviamente ofrece su propio sistema de acceder a la información de forma programática. Así por ejemplo en una base de datos relacional podemos hacer programas de gestión de datos en SQL, pues uniendo las operaciones DDL, DML y los procedimientos almacenados, tenemos un completo (aunque no muy estructurado y no muy estándar) lenguaje de programación. De hecho el uso intensivo de SQL unido a herramientas de programación visual (tal y como OracleForms, Access, PowerBuilder) tuvieron su auge como paradigma de desarrollo en una época (esto no quita que sigan siendo productos populares). Otro ejemplo es el caso de una base de datos jerárquica como ADABAS de Software AG que accederá a sus datos a través de su lenguaje Natural. Sin embargo SQL no fue diseñado obviamente como un lenguaje de propósito general (para hacer cualquier tipo de programa), además el problema de las herramientas visuales apoyadas en SQL es que han estado orientadas desde sus comienzos al diseño rápido de aplicaciones relativamente pequeñas, con lógicas de negocio no muy complejas, por su carácter interpretado no son muy eficientes, son muy monolíticas, obvian los modernos paradigmas de la programación y además necesitan de una infraestructura para ejecutarse y por tanto de la licencia de la herramienta visual en cada cliente (aunque ahora es habitual que la infraestructura necesaria para la ejecución de un programa desarrollado con alguna herramienta de programación sea de libre distribución). Esto conlleva a que siempre ha sido una necesidad que los habituales lenguajes de programación de propósito general, tuvieran alguna forma de acceso a los diferentes tipos de bases de datos. Los lenguajes tradicionales tal y como C y C++, han podido acceder a las bases de datos, pero casi siempre de forma propietaria, aunque en el mundo relacional estándares como el ANSI SQL/CLI para SQL dinámico (desarrollado por Microsoft de forma más propietaria como ODBC en Windows, evolucionando a niveles más altos de abstracción como OleDB y ADO), y el SQL embebido, también un estándar ANSI para SQL estático, han ayudado a que este terreno no fuera un absoluto reino de taifas. Otras tecnologías propietarias como el antiguo RogueWave DBTools++ para C++ tuvieron también su momento y popularidad. Sin embargo la popularización de Java1 y su fuerte apuesta por la estandarización promovida por su creador, Sun Microsystems, ha dado lugar a una serie de estándares para acceder a bases de datos desde Java: JDBC, J2EE Entity Beans, JDO, SQLJ y JCA. Todas ellas pretenden universalizar el acceso a diferentes fuentes de datos tal que el código fuente no dependa de la base de datos concreta a la que se conecta, y unas más que otras pretenden reducir al mínimo la incompatibilidad entre la tecnología de la base de datos y el modelo orientado a objetos de Java (la llamada “impedance mistmatch”). La más antigua de todas y la más flexible es JDBC. JDBC2 es una API orientada a la consulta de bases de datos relacionales utilizando llamadas en donde se suministran sentencias SQL (SQL dinámico) a través de una conexión a la base de datos, y la respuesta se convierte a objetos simples de Java correspondientes a los tipos simples SQL (aunque el uso de SQL3 permite actualmente corresponder clases Java con tipos de datos SQL definidos por el programador). Otras tecnologías de persistencia subyacen en JDBC tal y como habitualmente J2EE BMP, implementaciones de JDO y SQLJ.

1

http://java.sun.com

2

“JDBC Data Access API”, http://java.sun.com/products/jdbc/

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.3


Tecnologías de Persistencia en Java, una comparativa desde la práctica El JDBC como tecnología básica de acceso a base de datos es lo suficientemente flexible como para abordar la persistencia desde diferentes estrategias. Es habitual encontrarse libros sobre como usar JDBC, pero no sobre “las formas” de usar JDBC por ejemplo para conseguir “el santo grial” de la persistencia: la persistencia transparente orientada a objetos. El presente documento pretende hacer una comparativa de estrategias de persistencia basadas en JDBC. Consideramos el problema de las bases de datos relacionales, pues estas son las que abrumadoramente están presentes en los sistemas de información. Enfocaremos el problema de la persistencia claramente desde la orientación a objetos, el gran paradigma de la programación, nuestro problema será cómo almacenar nuestro modelo de información expresado en objetos Java en una base de datos relacional, buscando a la vez el máximo nivel de transparencia de la gestión de la persistencia, es decir, que las clases que representan nuestro modelo de información, estén lo más posible libres de código de acceso a la base de datos. De esta manera conoceremos lo que aporta cada estrategia en la consecución de este objetivo de persistencia transparente orientada a objetos. El objetivo es que el propio lector llegue a sus propias conclusiones desde el seguimiento del código que da lugar el uso de las diversas técnicas para resolver idénticos o muy similares problemas (pues veremos que la elección influye un poco en el problema). Esto no quiere decir que huyamos totalmente de hacer valoraciones, o que nuestro análisis pretenda ser universal pues se basará en problemas concretos, aunque bastante habituales y representativos. Podremos ver numerosos ejemplos de código planteados de forma sistemática. No se mostrará todo el código sino el imprescindible para poder entender los requisitos de programación que nos exige cada tecnología.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.4


Tecnologías de Persistencia en Java, una comparativa desde la práctica

2. PLANTEAMIENTO DEL PROBLEMA Las bases de datos relacionales deben su nombre al concepto de “relación” originalmente formulado como equivalente al término “tabla”3, término mucho más popular. Luego básicamente una base de datos relacional es un conjunto de datos organizados de forma tabular, en donde la unidad de información con identidad y unicidad es la fila a su vez compuesta por una serie de atributos o columnas. Cada tabla en el esquema de una base de datos suele expresar por tanto un concepto o entidad en el mundo real, entidad que a su vez tiene características las cuales se expresan a través de columnas de la tabla. La tablas suele estar relacionadas entre sí, es decir existen relaciones conceptuales entre los diferentes tipos de información. Estas relaciones se manifiestan estructuralmente a través de las claves foráneas (foreign keys), una clave foránea es un campo de una tabla cuyos valores presentes en filas concretas han de coincidir con valores en una correspondiente columna que ha de ser clave primaria (primary key) de una tabla relacionada. De esta manera se expresa estructuralmente el concepto de relación y de pertenencia entre entidades presente en la realidad, pues las filas de la tabla relacionada (con la clave foránea) pertenecen a la tabla principal, puesto que no puede existir información en tabla “foránea” si a su vez no existe la información correspondiente en la tabla principal. Luego una base de datos relacional es un conjunto de tablas con un conjunto de relaciones entre sí. Las relaciones están sujetas, al igual que en el mundo real, a una cardinalidad, la cardinalidad expresa cuantos elementos pueden estar relacionados en una tabla relacionada para un elemento dado. En las bases de datos relacionales a través de las relaciones clave primaria-foránea, se consiguen las cardinalidades son “1-1”, “1-muchos”, “muchos-muchos”, aunque por programación siempre se pueden conseguir cardinalidades con valores concretos en el caso de “muchos”. La manipulación del modelo relacional y su correspondencia con un modelo orientado a objetos, es uno de los problemas clásicos que afronta la programación orientada a objetos y las tecnologías de persistencia, como ya hablamos en la introducción, y la solución no es única. En nuestra aproximación al problema a través de ejemplos, haremos elecciones o decisiones de implementación entre las diversas posibles.

2.1 DECISIONES DE IMPLEMENTACIÓN 2.1.1

Correspondencia entre tabla y clase incluso en herencia

Es la aproximación más obvia, pero nada impide que una clase pueda manipular varias tablas relacionadas o que una tabla se corresponda con varias clases como a veces se suele hacer en el caso de la herencia4. Esta regla se aplicará incluso en caso de herencia: cada clase del árbol de herencia se corresponderá con una tabla o también llamada “vertical”. Esto exigirá que las tablas tengan relaciones 1-1, partiendo de la tabla que se corresponda con la clase más alta del árbol de derivación como tabla primaria. En el caso de la herencia este método tiene sus ventajas y sus inconvenientes (peor rendimiento que otras opciones), pero lo que si es claro es que es la expresión más cercana al concepto de herencia en bases de datos relacionales sin características de orientación a objetos y sin duda es la mejor desde el punto de vista de la claridad y la “mantenibilidad” cuando se hace un uso intensivo de la herencia en el modelo persistente.

3

“A Relational Model of Data for Large Shared Data Banks”. Codd, E.F. CACM. 1970.

4

“The Fundamentals of Mapping Objects to Relational Databases”, Scott W. Ambler, 2003, http://www.agiledata.org/essays/mappingObjects.html

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.5


Tecnologías de Persistencia en Java, una comparativa desde la práctica

2.1.2

Correspondencia entre fila y objeto

Al igual que en el caso anterior no es la única opción. En el caso de herencia cada objeto se corresponderá con las filas de las correspondientes tablas relacionadas a través del árbol de herencia impuesto por las clases de este árbol, de acuerdo al principio de correspondencia entre tabla y clase. 2.1.3

Correspondencia entre columna de tabla y atributo de clase

Incluso en el caso de las claves primarias y foráneas. 2.1.4

Identidad gestionada por la aplicación (application identity)

Es decir los valores de las claves que dan identidad a las filas de las tablas serán impuestos por nuestro programa y se corresponderán con atributos de las clases y no transparentes al programador en el caso de claves autogeneradas ya sea por la base de datos o por la infraestructura Java de persistencia, y ocultas al programador (datastore identity). 2.1.5

Relaciones entre tablas expresadas a través de punteros y colecciones

Es decir se tratará de hacer corresponder el modelo de tablas y relaciones con un modelo de clases y relaciones. No siempre existirán atributos de clase con el correspondiente puntero o colección, pero existirán métodos en la clase (“gets”) que nos devolverán el puntero o la colección de los objetos relacionados. Esto supone que las clases que representan información persistente tenderán a encapsular los accesos a la base de datos para modelar por sí mismas las relaciones, esta es una buena práctica de programación. La finalidad aparte de la conveniente encapsulación es conseguir la “ilusión” de manejar un modelo de objetos como si fueran objetos normales en memoria o POJOs (Plain Old Java Objects). 2.1.6

Se tenderá a encapsular todo tipo de acceso a la base de datos en clases orientadas a la persistencia

Se crearán clases de utilidad o clases Home de acuerdo con la filosofía del patrón DAO5 (que es un caso particular para persistencia del clásico patrón Façade6), correspondientes a cada clase persistente vinculada a una tabla, con la finalidad de encapsular la gestión de consultas a conjuntos de elementos, crear o destruir un elemento etc. La finalidad es conseguir que el modelo de clases persistente y de clases de utilidad persistentes tengan una imagen homogénea que no dependa de la tecnología de persistencia ni del tipo de base de datos en la misma línea de la ilusión de manejar POJOs. 2.1.7

Se tenderá hacia el respeto de las formas clásicas de modelar esquemas relacionales

Es decir se tenderá a considerar a priori que se parte de un modelo de base de datos concebido de forma ajena al tipo de estrategia de persistencia Java que se elija. De esta manera se trata de poner a prueba la tecnología respecto a su capacidad de modelar en clases Java, modelos de bases de datos antiguos (legacy) previos a la existencia incluso de la propia tecnología. Se intentará que el modelo relacional elegido no varíe por culpa de la estrategia seguida de la persistencia Java. 2.1.8

Se usará ANSI SQL 92

Debido a que este estándar está ampliamente soportado por la mayoría de las bases de datos comerciales y gratuitas del mercado. De esta manera nuestros modelos serán muy independientes de las bases de datos elegidas, la base de datos elegida será por tanto irrelevante desde el punto de vista de la programación.

5

“Data Access Objects”, http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

6

“Façade Pattern”, http://home.earthlink.net/~huston2/dp/facade.html

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.6


Tecnologías de Persistencia en Java, una comparativa desde la práctica

2.1.9

No se usaran características orientadas a objetos de SQL3 (ANSI SQL1999)

El uso de estas características podría simplificar notablemente la programación, sin embargo es un hecho que este estándar dista mucho de estar adoptado por la mayoría de las bases de datos modernas. 2.1.10

Uso de claves semánticas en el modelo relacional

El uso de claves semánticas, que la columna (o columnas) clave primaria de la tabla represente en concepto y valores la identidad de la entidad en el modelo real, no es una buena práctica de diseño de base de datos, pues el modelo resultantes es extremadamente rígido ante un cambio en el concepto de identidad en el modelo real, lo cual no es en absoluto raro. Sin embargo ha sido desde siempre una práctica muy extendida y aún sigue siéndolo, aunque su uso tenderá a decaer en la medida que las herramientas de gestión de la persistencia tienden a sugerir un modelo de identidad no semántico. De esta manera pondremos a prueba las programación de la persistecia respecto a su capacidad de gestionar la identidad impuesta por un modelo de base de datos previo a la elección de la tecnología de persistencia.

2.2 MODELO DE ENTIDADES O CLASES La realidad que queremos modelar es un simple ejemplo de las entidades básicas y las relaciones de los clientes de un banco y sus cuentas corrientes. 2.2.1

Modelo de una entidad

Consideraremos inicialmente el modelado de una simple entidad: el cliente del banco, con atributos tales como el nif, el nombre, la edad y la fecha de alta en el banco. Obviamente la identidad más clara de la entidad es el nif. Este es su esquema UML de la clase correspondiente ya expresados los atributos con tipos de dato Java.

Cliente #m_nif:String #m_nombre:String #m_edad:int #m_alta:java.util.Date

Implementaremos la clase Java y las operaciones necesarias para almacenar una instancia, actualizar los datos no clave, y su eliminación en la base de datos (el ciclo de vida), así como las consultas típicas a partir del nif, nombre, edad y alta. 2.2.2

Modelo de una entidad relacionada con muchas instancias de otra entidad

Relacionaremos al cliente con las cuentas corrientes de su propiedad a través de la entidad CuentaCliente. Dicha entidad no representa la cuenta corriente sino más bien la vinculación del cliente con dicha cuenta. Un cliente podrá tener varias cuentas corrientes, pero por ahora consideramos que una cuenta no puede tener varios titulares. El atributo que identifica la cuenta corriente será el número de cuenta (ncc), otro atributo significativo será el instante de la última operación que ha realizado el cliente sobre su cuenta.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.7


Tecnologías de Persistencia en Java, una comparativa desde la práctica

Cliente

CuentaCliente

#m_nif:String 1 #m_nombre:String #m_edad:int #m_alta:java.util.Date

0..* #m_ncc:String #m_nif:String #m_ultimaOperacion:java.sql.Timestamp

Implementaremos el ciclo de vida de ambas entidades, consultas por cada de uno de sus atributos y la relación expresada en Java a través de un puntero y una colección. 2.2.3

Modelo muchos-muchos entre entidades

A continuación ampliaremos el modelo introduciendo la entidad CuentaCorriente, y permitiremos la posibilidad de que una cuenta corriente tenga varios titulares. Tendremos por tanto que un cliente puede tener varias cuentas en propiedad y una cuenta puede tener varios titulares, es por tanto una relación muchos-muchos entre entidades. Como sabrá el lector, en el modelo relacional la relación directa muchos-muchos entre dos tablas no existe, para expresarla se usa una tabla intermedia. En nuestro caso esta tabla intermedia será la entidad CuentaCliente, pero ahora al permitirse que una misma cuenta tenga varios clientes, la identidad de CuentaCliente será la unión de ncc y nif como identidad en conjunto. El siguiente diagrama UML, expresa la relación directa muchos-muchos entre entidades y como se consigue a través la una entidad intermedia necesaria para el modelo relacional (en un modelo puramente Java no es necesaria).

Cliente CuentaCorriente

#m_nif:String #m_nombre:String 0..* #m_edad:int #m_alta:Date

0..* #m_ncc:String #m_saldo:long

1

1 0..*

0..* CuentaCliente

#m_ncc:String #m_nif:String #m_ultimaOperacion:Timestamp

2.2.4

Modelo de entidad con herencia de otra entidad

Si observamos la entidad Cliente considerada anteriormente, los datos nif, nombre y edad no son datos inherentes al concepto de cliente de un banco, es decir son datos que pueden estar presentes en las entidades de otros dominios de información, incluso en el modelo de información del banco en cuanto se modele la información de los empleados del banco por ejemplo. Es fácil entender que estos datos son propios de cualquier Persona, si una persona dada es a su vez Cliente del banco se entiende que además debe tener un atributo de su fecha de alta como cliente, esta fecha de alta no es un atributo de una persona (ni de un empleado del banco que puede no ser cliente del mismo) sino que se entiende que es específico de su papel como cliente del banco. Esta relación conceptual entre Persona y Cliente en persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.8


Tecnologías de Persistencia en Java, una comparativa desde la práctica

programación orientada a objetos se ha modelado clásicamente como una herencia entre una clase o entidad Persona y Cliente, más exactamente Cliente hereda las propiedades de Persona y añade las suyas específicas.

Persona #m_nif:String #m_nombre:String #m_edad:int

Cliente #m_alta:Date

2.3 MODELO RELACIONAL A continuación expresamos las correspondientes tablas para cada modelo de entidades (clases) a través de la sentencia SQL CREATE correspondiente. 2.3.1

Modelo una tabla

CREATE TABLE cliente ( nif CHAR(9) PRIMARY KEY, nombre VARCHAR(200), edad SMALLINT, alta DATE );

2.3.2

Modelo uno-muchos (dos tablas)

La tabla cliente es idéntica al caso de una tabla. CREATE TABLE cuenta_cliente ( ncc CHAR(20), nif CHAR(9), ultima_op TIMESTAMP, PRIMARY KEY (ncc), FOREIGN KEY (nif) REFERENCES cliente (nif), UNIQUE (ncc,ultima_op) );

Notar como la clave primaria ncc y la clave foránea nif determinan la relación uno muchos. La restricción UNIQUE sirve para asegurar que no pueda haber dos operaciones en el mismo instante sobre la cuenta 2.3.3

Modelo muchos-muchos (tres tablas)

La tabla cliente es idéntica al caso de una tabla. CREATE TABLE cuenta

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.9


Tecnologías de Persistencia en Java, una comparativa desde la práctica ( ncc CHAR(20) PRIMARY KEY, saldo BIGINT ); CREATE TABLE cuenta_cliente ( nif CHAR(9), ncc CHAR(20), ultima_op TIMESTAMP, PRIMARY KEY (nif,ncc), FOREIGN KEY (nif) REFERENCES cliente (nif), FOREIGN KEY (ncc) REFERENCES cuenta (ncc), UNIQUE (ncc,ultima_op) );

En este caso la combinación nif y ncc forman la clave primaria por eso la relación es muchos-muchos, a su vez son claves foráneas lo que muestra que esta tabla muestra el vínculo entre el par de filas cliente – cuenta que deben existir previamente. La restricción UNIQUE sirve para asegurar que no pueda haber dos operaciones en el mismo instante sobre la cuenta 2.3.4

Modelo de herencia

De acuerdo a las decisiones de implementación que adoptamos anteriormente, la herencia de dos clases se manifestará como dos tablas relacionas con una relación 1-1, siendo la tabla principal la que coincide con la clase más alta en el árbol de derivación. En el caso de la herencia se produce una duplicación de columnas clave necesario en el modelo relacional, dicha duplicidad no la repetiremos en el modelo de clases, esta sería una excepción a la regla de correspondencia atributocolumna enunciada anteriormente. CREATE TABLE persona ( nif CHAR(9) PRIMARY KEY, nombre VARCHAR(200), edad SMALLINT ); CREATE TABLE cliente ( nif CHAR(9) PRIMARY KEY, alta DATE, FOREIGN KEY (nif) REFERENCES persona (nif) );

De acuerdo con este esquema puede existir un registro en la tabla persona que no esté en la tabla cliente, pero al contrario, si existe un registro en cliente necesariamente debe existir en la tabla persona.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.10


Tecnologías de Persistencia en Java, una comparativa desde la práctica

3. HERRAMIENTAS Todo desarrollo software exige unas herramientas de ayuda al desarrollo y de ejecución. •

Compilador, librerías y máquina virtual Java: usaremos el paquete más moderno de Sun hasta el momento, el J2SE 1.4.2 SDK7

Entorno de desarrollo: NetBeans IDE8 v3.5

Herramientas de compilación y de gestión de configuración: Ant9 v1.5.1, la versión integrada en NetBeans

Base de datos: para nuestros fines hemos optado por una sencilla bases de datos 100% Java muy compatible con ANSI SQL 92 : PointBase10 v4.5

Entorno de gestión visual de Bases de Datos: Squirrel SQL11 v1.2 Beta 3

El hecho de usar una base de datos muy estándar ANSI SQL 92 junto con el uso de JDBC que pretende independizar el programa del sistema de base de datos concreto, unido a la fuerte presencia del estándar ANSI SQL 92 en las bases de datos comerciales y de código abierto, ilustra que la elección de la base de datos pueda hacerse hoy por criterios de rendimiento, escalabilidad y quizás por sus herramientas de gestión, pero no por razones de acoplamiento tecnológico a una marca concreta. Constatamos que podemos sustituir nuestra base de datos por las grandes bases de datos comerciales tal y como Oracle DB e IBM DB/2 sin necesidad de cambiar ni una sola línea de código (en la medida en que no nos salgamos del SQL estándar), pero ganando evidentemente la capacidad de gestionar enormes cantidades de información, para miles de usuarios concurrentes a gran velocidad.

7

http://java.sun.com/j2se/1.4.2/download.html

8

http://www.netbeans.org/

9

http://ant.apache.org/

10

http://www.pointbase.com

11

http://squirrel-sql.sourceforge.net/

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.11


Tecnologías de Persistencia en Java, una comparativa desde la práctica

4. SOLUCIÓN JDBC La especificación JDBC es la más antigua de todas las tecnologías de persistencia Java, y en ese sentido la más madura. Fue concebida como una API con la única finalidad de conectar con una base de datos relacional, enviar sentencias SQL a la misma y convertir los resultados a los tipos básicos de Java (enteros, cadenas, coma flotante ...). Estrictamente hablando, JDBC no es más que una especificación “en papel” y unas cuantas interfases que no hacen nada por sí mismas, los distintos fabricantes de bases de datos son los que han de implementar, dar cuerpo, a la especificación, pero el resultado que se consigue es que la API de acceso es la misma para cualquier base de datos que tenga un driver JDBC que cumpla la especificación, salvo en lo que concierne a las propias sentencias SQL, que son pasadas como cadenas de argumento, en donde sí puede haber una variación significativa de una base de datos a otra (si se usan las características SQL menos estándar de cada marca). Es relativamente sencilla de entender y utilizar, y muy flexible precisamente para adaptarse a las diversas estrategias de manipulación de datos, puesto que no impone ninguna filosofía a la hora de modelar un sistema de clases correspondientes a un modelo de tablas. La contrapartida es que en el caso de querer representar las entidades (tablas) presentes en una base de datos con un modelo de clases, dicho trabajo ha de hacerse “manualmente”. En resumen podemos decir que es una tecnología muy flexible pero a su vez con una alta “impedancia” respecto a sincronizar clases del usuario con tablas. Como nuestra finalidad es conseguir dicha sincronización entre tablas y clases, filas e instancias, seguiremos las pautas que ya enunciamos en el apartado de “Decisiones de Implementación”, estas decisiones nos acotan bastante nuestras posibilidades. Aun así usaremos varias filosofías de programación, cuyos nombres vendrán dados por su similitud a las filosofías empleadas en las tecnologías J2EE EJB Entity Beans: BMP (Bean Managed Persistence) y CMP (Container Managed Persistence). Clasificaremos nuestros ejemplos en tres filosofías: •

Tipo BMP: se asemejará a la forma de programar un EJB BMP aunque obviamente sin las características que se obtienen por el hecho de utilizar los servicios de autentificación, ciclo de vida, control de la persistencia, transacciones, pooling etc.

Tipo BMP Avanzado o BMP 2: será similar al tipo BMP pero modelaremos un sencillo framework que evitará repetir sistemáticamente algunos algoritmos típicos de acceso a base de datos, y por otra parte llevaremos a las clases de utilidad (Home) la mayor parte del código de gestión de la persistencia.

Tipo CMP: pretenderá ser similar en filosofía a los EJB CMP, aprovechando parte de la infraestructura definida en el tipo BMP 2.

Como podemos comprobar vienen a ser dos filosofías: BMP y CMP.

4.1 TIPO BMP Al igual que en los EJB BMP la clase persistente es una clase de datos normal en donde además existirán métodos que gestionen su persistencia, es decier su vinculación con la tabla asociada, por tanto será encargada de hacer la inserción, actualización, lectura, eliminación y también las consultas. Desarrollaremos una clase Home correspondiente a cada clase persistente, pero su finalidad será poco más que la de transferir sus llamadas a la clase persistente que actúa de forma similar a un bean BMP. Aunque la idea está basada en los EJB BMP no llegaremos al extremo de definir interfaces correspondientes con cada clase, aunque esta no es mala práctica en un programa “real”, mostraremos que JDBC no nos obliga a esto (lo cual si ocurre en los verdaderos EJB BMP). Como mucho definiremos un par de interfases Persistente y PersistenteHome pensadas para obligar al programador a implementar los métodos correspondientes a la gestión de la persistencia y homogeneizar la apariencia del código, más que para independizar interfase de implementación. persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.12


Tecnologías de Persistencia en Java, una comparativa desde la práctica

4.1.1

Una Tabla

4.1.1.1

NoEncontradoException.java

Se usará cuando necesitemos lanzar una excepción que indique que no se puede leer una fila en la base de datos que debería existir. Reutilizremos esta clase en todos los casos que consideremos, de ahí que la situemos en un paquete más alto (paquete jdbc) que los paquetes específicos de cada caso de uso. package jdbc; public class NoEncontradoException extends Exception { public NoEncontradoException() { } }

4.1.1.2

Database.java

Es una clase de utilidad cuya única finalidad es la de encapsular el establecimiento de una conexión. La reutilizaremos de nuevo en los demás ejemplos de uso de JDBC y de esta manera no repetimos un código que es idéntico. package jdbc; import java.sql.*; import java.io.*; import java.util.*; import java.net.*; import util.CargarPropiedades; public class Database { private Properties conProps = new Properties(); public Database() throws ClassNotFoundException, InstantiationException, IllegalAccessException, FileNotFoundException, IOException, URISyntaxException { conProps = CargarPropiedades.cargar("jdbc/jdbc.properties"); String driver = conProps.getProperty("driver"); try { Class.forName(driver).newInstance(); } catch(ClassNotFoundException ex) { System.out.println("Driver no encontrado:" + driver); throw ex; } } public Connection conectar() throws SQLException { String url = conProps.getProperty("urldb"); String user = conProps.getProperty("user"); String password = conProps.getProperty("password"); return DriverManager.getConnection(url,user,password); } }

4.1.1.3

Persistente.java

Es la interfase con los métodos que una clase persistente debe implementar. package jdbc.tipobmp.comun; import java.sql.*; public interface Persistente {

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.13


Tecnologías de Persistencia en Java, una comparativa desde la práctica public void setConnection(Connection con); public Connection getConnection(); public void setHome(PersistenteHome home); public PersistenteHome getHome(); public void eliminar() throws SQLException; public void actualizar() throws SQLException; }

Observamos la presencia de los métodos que asocian y obtienen la conexión con la base de datos, la asociación con el objeto de utilidad, la eliminación y la actualización del registro de la base de datos. La inserción se realizará a través del método crear() específico de cada clase persistente. 4.1.1.4

PersistenteHome.java

Es la interfase con los métodos que una clase de utilidad persistente debe implementar. package jdbc.tipobmp.comun; import java.sql.*; public interface PersistenteHome { public void setConnection(Connection con); public Connection getConnection(); public abstract Persistente crearObjeto(); }

4.1.1.5

Cliente.java

La clase Cliente implementa los métodos necesarios para sincronizar su estado con el de la fila correspondiente de la base de datos. Además implementa los métodos de búsqueda más habituales tal y como buscar el cliente con el nif dado, los clientes con un nombre dado, edad, fecha de alta (devolviendo una colección de objetos Cliente), un método que cuenta los clientes que existen (contar()) y un método especial, buscarAbierto() con la finalidad de poder hacer consultas menos típicas sobre los clientes. package jdbc.tipobmp.unatabla; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp.comun.*; public class Cliente implements Persistente { protected String m_nif; protected String m_nombre; protected int m_edad; protected java.util.Date m_alta; protected Connection m_con; protected PersistenteHome m_home; public Cliente() { m_nombre = ""; m_alta = new java.util.Date(); } public Cliente(String nif,String nombre,int edad,java.util.Date alta) { m_nif = nif; m_nombre = nombre; m_edad = edad; m_alta = alta; }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.14


Tecnologías de Persistencia en Java, una comparativa desde la práctica public String getNif() { return m_nif; } public void setNif(String nif) { m_nif = nif; } public String getNombre() { return m_nombre; } public void setNombre(String nombre) { m_nombre = nombre; } public int getEdad() { return m_edad; } public void setEdad(int edad) { m_edad = edad; } public java.util.Date getAlta() { return m_alta; } public void setAlta(java.util.Date alta) { m_alta = alta; } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public void setHome(PersistenteHome home) { m_home = home; } public PersistenteHome getHome() { return m_home; } public void crear(String nif,String nombre,int edad,java.util.Date alta) throws SQLException { m_nif = nif; m_nombre = nombre; m_edad = edad; m_alta = alta; insertar(); }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.15


Tecnologías de Persistencia en Java, una comparativa desde la práctica

public void actualizar() throws SQLException { PreparedStatement stmt = null; try { String sql = "UPDATE cliente SET nombre=?, edad=?, alta=? WHERE nif=?"; stmt = getConnection().prepareStatement(sql); stmt.setString(1,m_nombre); stmt.setInt(2,m_edad); stmt.setDate(3,new java.sql.Date(m_alta.getTime())); stmt.setString(4,m_nif); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } public void eliminar() throws SQLException { PreparedStatement stmt = null; try { String sql = "DELETE FROM cliente WHERE nif=?"; stmt = getConnection().prepareStatement(sql); stmt.setString(1,m_nif); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } m_con = null; } public void insertar() throws SQLException { PreparedStatement stmt = null; try { String sql = "INSERT INTO cliente (nombre,edad,alta,nif) VALUES(?,?,?,?)"; stmt = getConnection().prepareStatement(sql); stmt.setString(1,m_nombre); stmt.setInt(2,m_edad); stmt.setDate(3,new java.sql.Date(m_alta.getTime())); stmt.setString(4,m_nif); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } public void leer() throws SQLException,NoEncontradoException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente WHERE nif=?"; stmt = getConnection().prepareStatement(sql); stmt.setString(1,m_nif); ResultSet res = stmt.executeQuery(); if (!res.next()) new NoEncontradoException(); leerFila(res); } finally

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.16


Tecnologías de Persistencia en Java, una comparativa desde la práctica { if (stmt != null) stmt.close(); } } public void leerFila(ResultSet res) throws SQLException { m_nif = res.getString("nif"); m_nombre = res.getString("nombre"); m_edad = res.getInt("edad"); m_alta = res.getDate("alta"); } protected Collection leer(ResultSet res) throws SQLException { Collection col = new LinkedList(); while(res.next()) { Cliente obj = (Cliente)getHome().crearObjeto(); obj.leerFila(res); col.add(obj); } return col; } public Cliente buscarPorClavePrimaria(Object clave) throws SQLException,NoEncontradoException { Cliente obj = (Cliente)getHome().crearObjeto(); String nif = (String)clave; obj.setNif(nif); obj.leer(); return obj; } public Collection buscarPorEdad(int edad) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente WHERE edad=?"; stmt = m_con.prepareStatement(sql); stmt.setInt(1,edad); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorNombre(String nombre) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente WHERE nombre=?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,nombre); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarAbierto(String sql)

persistencia_java.doc v.1.0 José María Arranz Santamaría

throws SQLException

Pág.17


Tecnologías de Persistencia en Java, una comparativa desde la práctica { Statement stmt = null; try { stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); return leer(res); } finally { if (stmt != null) stmt.close(); } } public int contar() throws SQLException { Statement stmt = null; try { String sql = "SELECT COUNT(*) AS num FROM cliente"; stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); res.next(); return res.getInt("num"); } finally { if (stmt != null) stmt.close(); } } public String toString() { return "nif: " + m_nif + " nombre: " + m_nombre + " edad: " + m_edad + " alta: " + m_alta; } }

A simple vista podemos comprobar la gran cantidad de sentencias similares, esto es mala señal, significa que no hemos hecho apenas ningún esfuerzo de abstracción. La repetición sistemática de código conlleva el problema de dar lugar a programas muy rígidos y muy difíciles de evolucionar, pues cualquier pequeño cambio “en la filosofía” de hacer algo puede suponer una modificación del código en infinidad de lugares del código, con el correspondiente riesgo de olvidar algunos sitios. Tras la inspección de los métodos “buscar” podemos advertir varios problemas que afectan al rendimiento y a la capacidad del sistema: 1.

La carga de un objeto es realizada totalmente, es decir se cargan todos los atributos de la fila en la base de datos, sin embargo es posible que la mayoría de los atributos no sean accedidos en el uso del objeto cargado. Esto es un problema de rendimiento pues se hacen lecturas innecesarias de datos.

2.

La consulta de un conjunto de elementos supone la formación de una colección de objetos, correspondientes a los registros resultantes de la consulta. Consideremos una consulta de 1000 de resultados cuando sólo nos interesará acceder a los 10 primeros (lo cual es bastante habitual a la hora de mostrar listados de colecciones de datos muy grandes), los 1000 objetos se cargarán en memoria aunque no se usen. Esto supone un serio problema de rendimiento (carga innecesaria de elementos que no se utilizan) y capacidad (puede poner al límite la capacidad del sistema).

3.

Los métodos de consulta no hacen ningún tipo de comprobación de si un objeto ya ha sido cargado anteriormente y está en memoria, por lo que vuelve a ser cargado con la pérdida de rendimiento que supone esto significa que no aprovechamos las acciones previas para evitar acciones futuras innecesarias.

Los puntos 1 y 2 es posible minizarlos con lo que se llama la carga perezosa o lazy loading, que consiste básicamente en cargar bajo demanda, es decir cuando se intente usar el atributo o se avance en el recorrido de la colección, se cargará de la base de datos la correspondiente columna o fila. Conseguir la lazy loading no es un problema trivial.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.18


Tecnologías de Persistencia en Java, una comparativa desde la práctica El punto 3 evidencia la falta de algún tipo de caché de objetos persistentes que se consultara antes de acudir a la base de datos, proceso que por muy rápida que fuera la base de datos es significativamente más lento que una búsqueda en memoria de un pequeño fragmento de la información de la base de datos, por ello cada vez que hacemos una búsqueda consultamos a la base de datos información que podemos tener ya en memoria. Hacer un caché de objetos persistentes tiene muchas implicaciones, tal y como gestionar la identidad de los objetos en memoria de acuerdo con la identidad en la base de datos (métodos equals, hash), controlar el estado del objeto respecto al almacenamiento persistente (si es nuevo, si ha sido modificado, si ha sido eliminado, si no es persistente etc), interceptar toda búsqueda para que se haga primero en la caché, lo cual es una gran dificultad en el caso de búsquedas abiertas pues supondría analizar la “intencionalidad” de la consulta SQL etc. Este problema de rendimiento no quedará resuelto en nuestros ejemplos de JDBC para ilustrar la complejidad que supone hacer un sistema persistente eficiente y a medida basado en JDBC únicamente. No resolveremos estos problemas en nuestros ejemplos de JDBC, pues así ilustramos la necesidad de una mayor infraestructura de gestión de la persistencia más hayá de las simples llamadas a JDBC que aquí exponemos, poniendo en evidencia la necesidad de tecnologías más avanzadas. 4.1.1.6

ClienteHome.java

Como ya dijimos anteriormente, la finalidad de esta clase es la de encapsular las operaciones de creación de un nuevo objeto persistente (a modo de clase factoría, lo cual buena práctica pues asegura que el objeto persistente queda asociado a una conexión y al objeto Home que lo creó) y transferir las llamadas de búsqueda de objetos a un objeto persistente auxiliar creado al efecto. package jdbc.tipobmp.unatabla; import import import import

java.sql.*; java.util.*; jdbc.NoEncontradoException; jdbc.tipobmp.comun.*;

public class ClienteHome implements PersistenteHome { protected Connection m_con; public ClienteHome(Connection con) { m_con = con; } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public Cliente buscarPorClavePrimaria(Object clave) throws SQLException,NoEncontradoException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarPorClavePrimaria(clave); } public Collection buscarPorEdad(int edad) throws SQLException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarPorEdad(edad); } public Collection buscarPorNombre(String nombre) throws SQLException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarPorNombre(nombre); } public Collection buscarAbierto(String sql) throws SQLException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarAbierto(sql); }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.19


Tecnologías de Persistencia en Java, una comparativa desde la práctica public int contar() throws SQLException { Cliente obj = (Cliente)crearObjeto(); return obj.contar(); } public Cliente crear(String nif,String nombre,int edad,java.util.Date alta) throws SQLException { Cliente obj = (Cliente)crearObjeto(); obj.crear(nif,nombre,edad,alta); return obj; } public Persistente crearObjeto() { Persistente obj = new Cliente(); obj.setConnection(m_con); obj.setHome(this); return obj; } }

4.1.1.7

JDBCInicio.java

Esta clase es un programa ejemplo del tipo de operaciones típicas de la manipulación de datos persistentes: conectar con la base de datos, crear un objeto persistente, cambiar sus datos y actualizar en la base de datos, buscar por clave, buscar por los diferentes tipos de atributos, búsquedas más sofisticadas y por último la destrucción. Todo ello en una transacción dirigida por la base de datos. package jdbc.tipocmp.unatabla; import java.sql.*; import java.util.*; import java.text.*; import jdbc.Database; public class JDBCInicio { public JDBCInicio() { } public static void main(String[] args) throws Exception { Connection con = null; try { con = new Database().conectar(); ClienteHome clienteHome = new ClienteHome(con); con.setAutoCommit(false); Cliente cliente1 = clienteHome.crear("50000000P","Iñaki Jabilondo",45,new GregorianCalendar(2003,8,20).getTime()); Cliente cliente2 = clienteHome.crear("40000000P","Luis del Olmo",47,new GregorianCalendar(2003,8,21).getTime()); cliente1.setNombre("Iñaki Gabilondo"); cliente1.actualizar(); cliente1 = (Cliente)clienteHome.buscarPorClavePrimaria("50000000P"); System.out.println(cliente1); Collection col = clienteHome.buscarPorNombre("Luis del Olmo"); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.20


Tecnologías de Persistencia en Java, una comparativa desde la práctica col = clienteHome.buscarPorEdad(45); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } String sql = "SELECT * FROM cliente WHERE nombre LIKE 'Luis%' OR nombre LIKE '%Gabilondo'"; col = clienteHome.buscarAbierto(sql); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } int num = clienteHome.contar(); System.out.println(num); cliente1.eliminar(); cliente2.eliminar(); con.commit(); } catch(Exception ex) { ex.printStackTrace(); } finally { if (con != null) { con.rollback(); con.close(); } } } }

Podríamos seguir con los casos de relación uno-muchos, muchos-muchos y herencia. Sin embargo hemos visto que hay un montón de código repetitivo, intentaremos generalizar las operaciones con el fin de disminuir la cantidad de código elevando la calidad del mismo, para ello desarrollamos un pequeño framework persistente en el llamado “tipo BMP 2” y desde dicho modelo afrontaremos el problema de las relaciones y la herencia.

4.2 TIPO BMP AVANZADO O TIPO BMP 2 Desarrollar un framework supone que el código genérico, el que no está vinculado a ningún tipo de clase persistente concreto, es mayor, y establece un “contrato” más complicado con la clase persistente concreta, dicha clase tendrá que definir una serie de funciones que serán llamadas por el framework más que por el programador directamente. No pretendemos realizar un framework sofisticado, sino ilustrar la necesidad de un framework para disminuir la impedancia entre el modelo relacional y de objetos, evitando hacer un montón de código repetitivo. De hecho las demás tecnologías de persistencia que veremos, exceptuando el propio JDBC, son sofisticados frameworks de persistencia estandarizados por la industria. Nuestro framework no ofrecerá a la necesidad del lazy loading y de una caché de objetos. Por una parte programaremos un conjunto de clases genéricas que traten a los objetos persistentes de una forma genérica (el núcleo del framework), y por otra parte aquellas tareas de gestión de la persistencia que nos resultan difíciles de programar las llevaremos a las clases Home específicas. Esto último es en opinión del que escribe un buen

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.21


Tecnologías de Persistencia en Java, una comparativa desde la práctica patrón, pues despeja lo más posible la clase persistente acercándonos al ideal de la persistencia transparente del modelo de información (digamos que la persistencia se aglutina aparte en clases especiales destinadas para ello) y es también en contrapartida un error en la filosofía de los EJB BMP, pues en ellos se mezcla el tratamiento de la persistencia del objeto respecto a la fila (el patrón de uso más habitual de los BMP), con la manipulación de conjuntos a través de las llamadas de búsqueda (findByXXX), el hecho de que los BMP mantengan una conexión y se especificara su contrato con el container, invitaría a los diseñadores de la especificación inicial a que definir una lógica similar a posibles clases Home podría suponer una reiteración del modelo (a modo de Entity Bean Home). De todas formas siempre se puede minimizar este “problema” de diseño en los EJB BMP creando una especie de implementación de la interfase Home llamada desde el bean ahora “más despejado” de código persistente. 4.2.1

Clases Comunes (framework)

Aunque expondremos el código de las clases genéricas, lo más importante de cualquier framework es conocer cual es el “contrato” (expresado sobre todo por las interfases) que han de cumplir los componentes que son manipulados por el mismo y los servicios que ofrece, y no tanto como está programado por dentro. 4.2.1.1

Persistente.java

La interfaz Persistente es idéntica al caso anterior, sin embargo veremos que no será necesario implemenmtar sus métodos en cada clase persistente, sino que lo haremos en una clase base genérica. 4.2.1.2

PersistenteHome.java

Esta interfase será más complicada que en el caso anterior, puesto que esta serie de métodos han de ser implementados por una parte por la clase que implemente el interfaz de forma genérica, pero también por la clase Home correspondiente a cada clase persistente para aquellos aspectos que no son fácilmente generalizables. package jdbc.tipobmp2.comun; import java.sql.*; import jdbc.NoEncontradoException; public interface PersistenteHome { public void setConnection(Connection con); public Connection getConnection(); public abstract Persistente crearObjeto(); public abstract Persistente crearObjeto(Object clave); public void setStatementParams(Persistente obj,String table,PreparedStatement stmt) throws SQLException; public void setStatementClave(Persistente obj,PreparedStatement stmt) throws SQLException; public public public public public

void void void void void

eliminar(Persistente obj) throws SQLException; actualizar(Persistente obj) throws SQLException; insertar(Persistente obj) throws SQLException; leer(Persistente obj) throws SQLException,NoEncontradoException; leer(Persistente obj,ResultSet res) throws SQLException;

}

4.2.1.3

PersistenteImpl.java

La clase de la que han de derivar las clases persistentes concretas. package jdbc.tipobmp2.comun; import java.sql.*; public abstract class PersistenteImpl implements Persistente { private Connection m_con; private PersistenteHome m_home; public PersistenteImpl()

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.22


Tecnologías de Persistencia en Java, una comparativa desde la práctica { }

public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public void setHome(PersistenteHome home) { m_home = home; } public PersistenteHome getHome() { return m_home; } public void actualizar() throws SQLException { getHome().actualizar(this); } public void eliminar() throws SQLException { getHome().eliminar(this); } }

Podemos ver lo simple que es esta clase, apenas un contenedor de los atributos conexión y referencia al objeto Home que crea el objeto persistente, y los métodos actualizar() y eliminar() que derivan la llamada al objeto Home. De hecho nada impediría poner este código en la propia clase persistente concreta del modelo de datos y de esta manera no ser intrusivo en la herencia “por arriba”, e incluso prescindir de albergar el atributo de la conexión con la base de datos, pues ésta se puede obtener a partir del objeto Home (hemos mantenido este atributo por su afinidad al verdadero J2EE BMP). 4.2.1.4

PersistenteHomeImpl.java

La clase de la que han de derivar las clases persistentes de utilidad o Home: package jdbc.tipobmp2.comun; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; public abstract class PersistenteHomeImpl implements PersistenteHome { protected Connection m_con; public PersistenteHomeImpl(Connection con) { m_con = con; } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con;

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.23


Tecnologías de Persistencia en Java, una comparativa desde la práctica } public Persistente crear(Persistente obj) { obj.setConnection(m_con); obj.setHome(this); insertar(obj); return obj; }

throws SQLException

public Persistente crear(Object clave) throws SQLException { Persistente obj = crearObjeto(clave); return crear(obj); } public void insertar(Persistente obj,String table,String sql) { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); setStatementParams(obj,table,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } }

throws SQLException

public void actualizar(Persistente obj,String table,String sql) throws SQLException { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); setStatementParams(obj,table,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } public void eliminar(Persistente obj,String table,String sql) { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); setStatementClave(obj,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } }

throws SQLException

public void leer(Persistente obj,String sql) throws SQLException,NoEncontradoException { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql);

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.24


Tecnologías de Persistencia en Java, una comparativa desde la práctica setStatementClave(obj,stmt); ResultSet res = stmt.executeQuery(); if (!res.next()) new NoEncontradoException(); leer(obj,res); } finally { if (stmt != null) stmt.close(); } } protected Collection leer(ResultSet res) throws SQLException { Collection col = new LinkedList(); while(res.next()) { Persistente obj = crearObjeto(); obj.setConnection(m_con); obj.setHome(this); leer(obj,res); col.add(obj); } return col; } public Persistente buscarPorClavePrimaria(Object clave) throws SQLException { Persistente obj = crearObjeto(clave); try { obj.setConnection(m_con); obj.setHome(this); leer(obj); return obj; } catch(NoEncontradoException ex) { return null; } } public Collection buscarAbierto(String sql) throws SQLException { Statement stmt = null; try { stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); return leer(res); } finally { if (stmt != null) stmt.close(); } } }

Básicamente el objetivo de esta clase es realizar las llamadas a la base de datos necesarias para gestionar la persistencia del objeto manipulado, llamando a los métodos necesarios definidos en Persistente de dicho objeto que actuaran a modo de callbacks. 4.2.2

Una Tabla

Una vez definida la infraestructura abordamos el primer caso que ya consideramos de forma directa anteriormente, ahora gracias a la infraestructura tendremos una disminución muy significativa de código sin perder funcionalidad y apenas flexibilidad, lo cual redunda en la calidad del código resultante.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.25


Tecnologías de Persistencia en Java, una comparativa desde la práctica 4.2.2.1

Cliente.java

La clase persistente que representa al cliente. Notar la casi completa inexistencia de código relacionado con la persistencia, gracias a que la inmensa mayor parte residirá en la clase Home creada al efecto. Varios de sus métodos serán llamados por la clase Home, como por ejemplo el método crear() que define todos los datos de la instancia y que se llamará en creación del registro en la base de datos. package jdbc.tipobmp2.unatabla; import java.sql.*; import jdbc.tipobmp2.comun.*; public class Cliente extends PersistenteImpl { protected String m_nif; protected String m_nombre; protected int m_edad; protected java.util.Date m_alta; public Cliente() { m_nombre = ""; m_alta = new java.util.Date(); } public Cliente(String nif) { m_nif = nif; } public void crear(String nif,String nombre,int edad,java.util.Date alta) { m_nif = nif; m_nombre = nombre; m_edad = edad; m_alta = alta; } public String getNif() { return m_nif; } public void setNif(String nif) { m_nif = nif; } public String getNombre() { return m_nombre; } public void setNombre(String nombre) { m_nombre = nombre; } public int getEdad() { return m_edad; } public void setEdad(int edad) { m_edad = edad; } public java.util.Date getAlta() {

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.26


Tecnologías de Persistencia en Java, una comparativa desde la práctica return m_alta; } public void setAlta(java.util.Date alta) { m_alta = alta; } public String toString() { return "nif: " + m_nif + " nombre: " + m_nombre + " edad: " + m_edad + " alta: " + m_alta; } }

4.2.2.2

ClienteHome.java

Es la clase que más nos interesa respecto a la persistencia, pues sobre ella recae la coordinación del objeto persistente, teniendo a su vez un contrato con el framework a través de su clase base. package jdbc.tipobmp2.unatabla; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class ClienteHome extends PersistenteHomeImpl { public ClienteHome(Connection con) throws SQLException { super(con); } public void insertar(Persistente obj) { insertar(obj,"cliente","INSERT VALUES(?,?,?,?)"); }

throws SQLException INTO

cliente

(nombre,edad,alta,nif)

public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"cliente","UPDATE cliente SET nombre=?, edad=?, alta=? WHERE nif=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cliente","DELETE FROM cliente WHERE nif=?"); } public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM cliente WHERE nif=?"); } public void leer(Persistente obj,ResultSet res) { Cliente cliente = (Cliente)obj;

throws SQLException

cliente.setNif(res.getString("nif")); cliente.setNombre(res.getString("nombre")); cliente.setEdad(res.getInt("edad")); cliente.setAlta(res.getDate("alta")); } public void setStatementParams(Persistente stmt) throws SQLException { Cliente cliente = (Cliente)obj;

persistencia_java.doc v.1.0 José María Arranz Santamaría

obj,String

tabla,PreparedStatement

Pág.27


Tecnologías de Persistencia en Java, una comparativa desde la práctica

stmt.setString(1,cliente.getNombre()); stmt.setInt(2,cliente.getEdad()); stmt.setDate(3,new java.sql.Date(cliente.getAlta().getTime())); stmt.setString(4,cliente.getNif()); } public void setStatementClave(Persistente SQLException { Cliente cliente = (Cliente)obj;

obj,PreparedStatement

stmt)

throws

stmt.setString(1,cliente.getNif()); } public Collection buscarPorEdad(int edad) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente WHERE edad=?"; stmt = m_con.prepareStatement(sql); stmt.setInt(1,edad); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorNombre(String nombre) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente WHERE nombre=?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,nombre); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public int contar() throws SQLException { Statement stmt = null; try { String sql = "SELECT COUNT(*) AS num FROM cliente"; stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); res.next(); return res.getInt("num"); } finally { if (stmt != null) stmt.close(); } } public Cliente crear(String nif,String throws SQLException { Cliente obj = (Cliente)crearObjeto();

persistencia_java.doc v.1.0 José María Arranz Santamaría

nombre,int

edad,java.util.Date

Pág.28

alta)


Tecnologías de Persistencia en Java, una comparativa desde la práctica obj.crear(nif,nombre,edad,alta); return (Cliente)crear(obj); } public Persistente crearObjeto() { return new Cliente(); } public Persistente crearObjeto(Object clave) { String nif = (String)clave; return new Cliente(nif); } }

Los métodos insertar, actualizar, leer, setStatementParams, setStatementeClave, crear, crearObjeto, son contractuales, realizan aquellas tareas que es difícil hacer de forma genérica y se hacen aquí de forma sencilla en la clase específica tal y como suministrar las sentencias SQL necesarias, unas son llamadas por el programador directamente (ej. actualizar), otras son llamadas por el framework (los setStatementXXX por ejemplo) a través de la clase base. 4.2.2.3

JDBCInicio.java

Puede ser idéntico a la clase del mismo nombre en el modelo “tipo BMP”, pues no hemos cambiado la interfaz que es ofrecida al programador final. Esta es una de las grandezas de la programación orientada a objetos y la aplicación de patrones de programación, que una parte de un programa puede cambiar profundamente pero mientras no cambie el “contrato” que existía con el resto de la aplicación, lo demás no tiene que cambiar. En la práctica esto no ocurre tan idealmente, pero se consigue que las implicaciones de los cambios con el resto de la aplicación sean mínimos respecto a una programación con reutilización nula. 4.2.3

Relación Uno – Muchos

Reutilizaremos la infraestructura definida para el caso de una tabla, adaptaremos el resto de las clases para introducir la relación y crearemos las nuevas clases correspondientes a la nueva tabla. 4.2.3.1

Cliente.java

La única diferencia respecto al caso de una tabla es la introducción de un método para obtener las cuentas corrientes que son propiedad del cliente, esto lo conseguimos a través de una búsqueda con un objeto CuentaClienteHome. public Collection getClienteCuentas() throws SQLException { CuentaClienteHome cuentaCliHome = new CuentaClienteHome(getConnection()); return cuentaCliHome.buscarPorCliente(m_nif); }

4.2.3.2

ClienteHome.java

Es idéntico funcionalmente al caso de una tabla, salvo la introducción del método buscarPorCuenta(): public Collection buscarPorCuenta(String ncc) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT c.nif,c.nombre,c.edad,c.alta \n"+ "FROM cliente c,cuenta_cliente cc \n"+ "WHERE cc.ncc=? AND c.nif=cc.nif"; stmt = m_con.prepareStatement(sql); stmt.setString(1,ncc); ResultSet res = stmt.executeQuery(); return leer(res);

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.29


Tecnologías de Persistencia en Java, una comparativa desde la práctica } finally { if (stmt != null) stmt.close(); } }

Este método se podría haber realizado de la siguiente forma: public Collection buscarPorCuenta2(String ncc) throws SQLException { CuentaClienteHome cuentaCliHome = new CuentaClienteHome(getConnection()); CuentaCliente cuentaCli = (CuentaCliente)cuentaCliHome.buscarPorClavePrimaria(ncc); Collection col = new LinkedList(); Cliente cliente = cuentaCli.getCliente(); col.add(cliente); return col; }

Sin embargo presenta el problema de ser mucho menos eficiente que la forma anterior, porque realiza dos consultas en vez de una, la primera en la búsqueda de la cuenta y la segunda en la búsqueda del cliente de la cuenta. En la forma anterior hacemos un join de las dos tablas, es de sobra conocido que el join, que viene a ser una mezcla de dos consultas, es significativamente más rápido que dos consultas separadas, pues hay que añadir que además hay un ahorro en el transporte de información del programa a la base de datos. Esta es una de las pocas ventajas del JDBC frente a otros sistemas, que permite una gran flexibilidad a la hora de poder utilizar las características más avanzadas de la base de datos concreta. 4.2.3.3

CuentaCliente.java

Modelamos la vinculación de un cliente con su cuenta corriente con esta clase. package jdbc.tipobmp2.unomuchos; import java.sql.*; import jdbc.tipobmp2.comun.*; public class CuentaCliente extends PersistenteImpl { protected String m_ncc; // Número de la cuenta corriente protected String m_nif; protected Timestamp m_ultimaOperacion; public CuentaCliente() { } public CuentaCliente(String ncc) { m_ncc = ncc; } public void crear(String nif, String ncc, Timestamp ultimaOp) { m_nif = nif; m_ncc = ncc; m_ultimaOperacion = ultimaOp; } public String getNif() { return m_nif; } public void setNif(String nif) { m_nif = nif; } public String getNcc()

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.30


Tecnologías de Persistencia en Java, una comparativa desde la práctica { return m_ncc; } public void setNcc(String ncc) { m_ncc = ncc; } public Timestamp getUltimaOperacion() { return m_ultimaOperacion; } public void setUltimaOperacion(Timestamp ultimaOp) { m_ultimaOperacion = ultimaOp; } public Cliente getCliente() throws SQLException { ClienteHome clienteHome = new ClienteHome(getConnection()); return (Cliente)clienteHome.buscarPorClavePrimaria(m_nif); } public String toString() { return "ncc: " + m_ncc m_ultimaOperacion; } }

+

"

nif:

"

+

m_nif

+

"

última

op.:

"

+

Observamos el método getCliente() que obtiene a través de una consulta usando la clase ClienteHome, el cliente asociado a la cuenta conocido el nif que es atributo de la misma. 4.2.3.4

CuentaClienteHome.java

De forma similar a ClienteHome creamos esta clase para la gestión del ciclo de vida de los objetos CuentaCliente y para realizar búsquedas. package jdbc.tipobmp2.unomuchos; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class CuentaClienteHome extends PersistenteHomeImpl { public CuentaClienteHome(Connection con) throws SQLException { super(con); } public void insertar(Persistente obj) throws SQLException { insertar(obj,"cuenta_cliente","INSERT INTO cuenta_cliente (ultima_op,nif,ncc) VALUES(?,?,?)"); } public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"cuenta_cliente","UPDATE cuenta_cliente SET ultima_op=? WHERE ncc=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cuenta_cliente","DELETE FROM cuenta_cliente WHERE ncc=?"); }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.31


Tecnologías de Persistencia en Java, una comparativa desde la práctica

public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM cuenta_cliente WHERE ncc=?"); } public void leer(Persistente obj,ResultSet res) { CuentaCliente cuenta = (CuentaCliente)obj;

throws SQLException

cuenta.setNif(res.getString("nif")); cuenta.setNcc(res.getString("ncc")); cuenta.setUltimaOperacion(res.getTimestamp("ultima_op")); } public void setStatementParams(Persistente obj,String stmt) throws SQLException { CuentaCliente cuenta = (CuentaCliente)obj;

tabla,PreparedStatement

stmt.setTimestamp(1,cuenta.getUltimaOperacion()); stmt.setString(2,cuenta.getNif()); stmt.setString(3,cuenta.getNcc()); } public void setStatementClave(Persistente obj,PreparedStatement SQLException { CuentaCliente cuenta = (CuentaCliente)obj;

stmt)

throws

stmt.setString(1,cuenta.getNcc()); } public Collection buscarPorDespuesUltimaOp(Timestamp instante) SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cuenta_cliente WHERE ultima_op >= ?"; stmt = m_con.prepareStatement(sql); stmt.setTimestamp(1,instante); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } }

throws

public Collection buscarPorAntesUltimaOp(Timestamp instante) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cuenta_cliente WHERE ultima_op < ?"; stmt = m_con.prepareStatement(sql); stmt.setTimestamp(1,instante); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorCliente(String nif) {

persistencia_java.doc v.1.0 José María Arranz Santamaría

throws SQLException

Pág.32


Tecnologías de Persistencia en Java, una comparativa desde la práctica PreparedStatement stmt = null; try { String sql = "SELECT * FROM cuenta_cliente WHERE nif=?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,nif); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorNcc(String ncc) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cuenta_cliente WHERE ncc=?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,ncc); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public CuentaCliente crear(String nif,String ncc,Timestamp SQLException { CuentaCliente obj = (CuentaCliente)crearObjeto(); obj.crear(nif,ncc,ultimaOp); return (CuentaCliente)crear(obj); }

ultimaOp)

throws

public Persistente crearObjeto() { return new CuentaCliente(); } public Persistente crearObjeto(Object clave) { String ncc = (String)clave; return new CuentaCliente(ncc); } }

4.2.3.5

JDBCInicio.java

Como ejemplo de uso nos interesará como novedad navegar por las relaciones, dicha navegación supondrá las necesarias consultas a la base de datos. Crearemos tres cuentas corrientes asociadas dos de ellas a un cliente y la restante al segundo cliente, obtendremos a través del cliente las cuentas asociadas con Cliente.getClienteCuentas(), a través de la cuenta su cliente asociado con CuentaCliente.getCliente(), buscaremos el cliente de una cuenta dada con ClienteHome.buscarPorCuenta() etc. package jdbc.tipobmp2.unomuchos; import java.sql.*; import java.util.*; import java.text.*; import jdbc.Database; public class JDBCInicio {

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.33


Tecnologías de Persistencia en Java, una comparativa desde la práctica public JDBCInicio() { } public static void main(String[] args) throws Exception { Connection con = null; try { con = new Database().conectar(); ClienteHome clienteHome = new ClienteHome(con); CuentaClienteHome cuentaCliHome = new CuentaClienteHome(con); con.setAutoCommit(false); Cliente cliente1 = clienteHome.crear("50000000P","Iñaki Gabilondo",45,new GregorianCalendar(2003,8,20).getTime()); Cliente cliente2 = clienteHome.crear("40000000P","Luis del Olmo",47,new GregorianCalendar(2003,8,21).getTime()); CuentaCliente cuentaCli1 = cuentaCliHome.crear("50000000P","111", Timestamp(new GregorianCalendar(2003,8,20,10,0,0).getTimeInMillis())); CuentaCliente cuentaCli2 = cuentaCliHome.crear("40000000P","222", Timestamp(new GregorianCalendar(2003,8,21,11,0,0).getTimeInMillis())); CuentaCliente cuentaCli3 = cuentaCliHome.crear("50000000P","333", Timestamp(new GregorianCalendar(2003,8,22,12,0,0).getTimeInMillis()));

new new new

cuentaCli1 = (CuentaCliente)cuentaCliHome.buscarPorClavePrimaria("111"); System.out.println(cuentaCli1); Collection col = cliente1.getClienteCuentas(); for(Iterator it = col.iterator(); it.hasNext(); ) { CuentaCliente cuentaCli = (CuentaCliente)it.next(); System.out.println(cuentaCli); } Cliente cliente = cuentaCli1.getCliente(); System.out.println(cliente); col = cuentaCliHome.buscarPorNcc("111"); for(Iterator it = col.iterator(); it.hasNext(); ) { CuentaCliente cuentaCli = (CuentaCliente)it.next(); System.out.println(cuentaCli); } col = cuentaCliHome.buscarPorDespuesUltimaOp(new GregorianCalendar(2003,8,20,10,0,0).getTimeInMillis())); for(Iterator it = col.iterator(); it.hasNext(); ) { CuentaCliente cuentaCli = (CuentaCliente)it.next(); System.out.println(cuentaCli); }

Timestamp(new

col = clienteHome.buscarPorCuenta("111"); for(Iterator it = col.iterator(); it.hasNext(); ) { cliente = (Cliente)it.next(); System.out.println(cliente); } String sql = "SELECT * FROM cuenta_cliente"; col = cuentaCliHome.buscarAbierto(sql); for(Iterator it = col.iterator(); it.hasNext(); ) { CuentaCliente cuentaCli = (CuentaCliente)it.next(); System.out.println(cuentaCli); }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.34


Tecnologías de Persistencia en Java, una comparativa desde la práctica cuentaCli1.eliminar(); cuentaCli2.eliminar(); cuentaCli3.eliminar(); cliente1.eliminar(); cliente2.eliminar(); con.commit(); } catch(Exception ex) { ex.printStackTrace(); } finally { if (con != null) { con.rollback(); con.close(); } } } }

4.2.4

Relación Muchos – Muchos

Reutilizamos la infraestructura persistente genérica de nuevo, e introduciremos una nueva clase CuentaCorriente que represente a la cuenta corriente, con su respectiva clase CuentaCorrienteHome, y modificaremos el modelo para permitir ahora que una cuenta pueda tener varios propietarios, gracias a que CuentaCliente, como el vínculo entre la cuenta y el cliente, ahora tendrá como identidad conjunta el nif y el ncc, , en sincronía con su correspondiente tabla cuenta_cliente que será ahora la tabla intermedia cuenta_cliente que exprese la relación muchosmuchos en el modelo relacional. Recordar que estrictamente hablando dos clases pueden tener una relación muchos-muchos sin necesidad de recurrir a una clase intermedia, dicha clase intermedia viene impuesta por el modelo relacional, aunque también es cierto (y es lo habitual) que podríamos manejar la tabla cuenta_cliente de forma oculta sin manifestarse en una clase, pero por otra parte nos interesa mostrar el contenido de cuenta_cliente en su respectiva clase, pues dicha tabla intermedia puede almacenar información útil en la relación cliente-cuenta, aparte del vínculo entre identidades, tal y como el instante en el cliente dado hizo su última operación sobre la cuenta dada. De todas formas también ofreceremos métodos en Cliente y CuentaCorriente que devolverán las respectivas colecciones de cuentas y clientes de igual manera que se haría en un modelo Java normal. 4.2.4.1

Cliente.java

La gran novedad respecto al caso anterior uno-muchos es el método que devuelve directamente los objetos CuentaCorriente que pertenecen al cliente: public Collection getCuentas() throws SQLException { CuentaCorrienteHome cuentaHome = new CuentaCorrienteHome(getConnection()); return cuentaHome.buscarPorCliente(this); }

Por variar ligeramente los ejemplos, permitiremos pasar como argumento un puntero al propio objeto cuya identidad sirve como criterio de búsqueda en vez de suministrar directamente el valor clave (en este caso la cadena del nif). 4.2.4.2

ClienteHome.java

La variación relevante respecto al caso uno-muchos es el método de búsqueda (con dos formas) de los clientes asociados a una cuenta dada como argumento: public Collection buscarPorCuenta(String ncc)

persistencia_java.doc v.1.0 José María Arranz Santamaría

throws SQLException

Pág.35


Tecnologías de Persistencia en Java, una comparativa desde la práctica { PreparedStatement stmt = null; try { String sql = "SELECT c.nif,c.nombre,c.edad,c.alta " + "FROM cliente c,cuenta_cliente cc,cuenta WHERE "+ "c.nif = cc.nif AND "+ "cc.ncc = cuenta.ncc AND "+ "cuenta.ncc = ?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,ncc); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorCuenta(CuentaCorriente cuenta) { return buscarPorCuenta(cuenta.getNcc()); }

throws SQLException

Notar que se hace un join de tres tablas, pero esto es mucho más eficiente que hacer sucesivas consultas más simples. 4.2.4.3

CuentaCliente.java

La novedad respecto al caso uno-muchos es un nuevo método getCuenta() que devuelve la cuenta corriente de esta asociación cliente-cuenta, al igual que el método getCliente() lo hacía para el cliente. public CuentaCorriente getCuenta() throws SQLException { CuentaCorrienteHome cuentaHome = new CuentaCorrienteHome(getConnection()); return (CuentaCorriente)cuentaHome.buscarPorClavePrimaria(m_ncc); }

4.2.4.4

CuentaClienteClave.java

Esta es una clase nueva. Su finalidad es representar el valor clave o más exactamente de las claves del vínculo clientecuenta, pues como dijimos ahora la clave es la combinación nif – ncc, nuestro framework está preparado para manejar la identidad de un objeto persistente como un simple objeto Java de ahí la necesidad de crear una clase especial ahora, pues anteriormente no hubo necesidad al no existir claves compuestas, puesto que la clave del cliente era un simple objeto String que representaba el nif y el de la cuenta era también otro objeto cadena con el número de cuenta (ncc). package jdbc.tipobmp2.muchosmuchos; public class CuentaClienteClave { private String m_nif; private String m_ncc; public CuentaClienteClave(String nif,String ncc) { m_nif = nif; m_ncc = ncc; } public String getNif() { return m_nif; } public void setNif(String nif) {

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.36


Tecnologías de Persistencia en Java, una comparativa desde la práctica m_nif = nif; } public String getNcc() { return m_ncc; } public void setNcc(String ncc) { m_ncc = ncc; } }

4.2.4.5

CuentaClienteHome.java

No introducimos ninguna funcionalidad nueva excepto las consecuencias de manejar ahora una clave compuesta, por ejemplo el uso del nuevo objeto clave CuentaClienteClave . public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"cuenta_cliente","UPDATE cuenta_cliente SET ultima_op=? WHERE nif=? AND ncc=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cuenta_cliente","DELETE FROM cuenta_cliente ncc=?"); }

WHERE

nif=?

AND

public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM cuenta_cliente WHERE nif=? AND ncc=?"); } . . . public void setStatementClave(Persistente obj,PreparedStatement SQLException { CuentaCliente cuenta = (CuentaCliente)obj;

stmt)

throws

stmt.setString(1,cuenta.getNif()); stmt.setString(2,cuenta.getNcc()); } . . . public Persistente crearObjeto(Object clave) { CuentaClienteClave ccclave = (CuentaClienteClave)clave; return new CuentaCliente(ccclave.getNif(),ccclave.getNcc()); }

4.2.4.6

CuentaCorriente.java

Representará a la cuenta corriente, el número de cuenta tendrá identidad propia independiente de los clientes, y un dato nuevo será el saldo de la cuenta. package jdbc.tipobmp2.muchosmuchos; import java.sql.*; import java.util.*; import jdbc.tipobmp2.comun.*;

public class CuentaCorriente extends PersistenteImpl { protected String m_ncc; // Número de la cuenta corriente protected long m_saldo;

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.37


Tecnologías de Persistencia en Java, una comparativa desde la práctica /** Creates a new instance of Cliente */ public CuentaCorriente() { } public void crear(String ncc, long saldo) { m_ncc = ncc; m_saldo = saldo; } public CuentaCorriente(String ncc) { m_ncc = ncc; } public String getNcc() { return m_ncc; } public void setNcc(String ncc) { m_ncc = ncc; } public long getSaldo() { return m_saldo; } public void setSaldo(long saldo) { m_saldo = saldo; } public Collection getCuentaClientes() throws SQLException { CuentaClienteHome cuentaCliHome = new CuentaClienteHome(getConnection()); return cuentaCliHome.buscarPorNcc(m_ncc); } public Collection getClientes() throws SQLException { ClienteHome clienteHome = new ClienteHome(getConnection()); return clienteHome.buscarPorCuenta(this); } public String toString() { return "ncc: " + m_ncc + " saldo: " + m_saldo; } }

4.2.4.7

CuentaCorrienteHome.java

Como relevante de esta clase está el método buscarPorCliente() que a través de joins obtiene las cuentas corrientes de una cliente dado, de forma similar a como se obtenían los clientes propietarios de una cuenta dada en la clase ClienteHome. package jdbc.tipobmp2.muchosmuchos; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class CuentaCorrienteHome extends PersistenteHomeImpl

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.38


Tecnologías de Persistencia en Java, una comparativa desde la práctica { public CuentaCorrienteHome(Connection con) throws SQLException { super(con); } public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"cuenta","UPDATE cuenta SET saldo=? WHERE ncc=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cuenta","DELETE FROM cuenta WHERE ncc=?"); } public void insertar(Persistente obj) throws SQLException { insertar(obj,"cuenta","INSERT INTO cuenta (saldo,ncc) VALUES(?,?)"); } public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM cuenta WHERE ncc=?"); } public void leer(Persistente obj,ResultSet res) throws SQLException { CuentaCorriente cuenta = (CuentaCorriente)obj; cuenta.setNcc(res.getString("ncc")); cuenta.setSaldo(res.getLong("saldo")); } public void setStatementParams(Persistente obj,String stmt) throws SQLException { CuentaCorriente cuenta = (CuentaCorriente)obj;

tabla,PreparedStatement

stmt.setLong(1,cuenta.getSaldo()); stmt.setString(2,cuenta.getNcc()); } public void setStatementClave(Persistente obj,PreparedStatement SQLException { CuentaCorriente cuenta = (CuentaCorriente)obj;

stmt)

throws

stmt.setString(1,cuenta.getNcc()); } public Collection buscarPorSaldoMayor(long saldo) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cuenta WHERE saldo >= ?"; stmt = m_con.prepareStatement(sql); stmt.setLong(1,saldo); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorSaldoMenor(long saldo) {

persistencia_java.doc v.1.0 José María Arranz Santamaría

throws SQLException

Pág.39


Tecnologías de Persistencia en Java, una comparativa desde la práctica PreparedStatement stmt = null; try { String sql = "SELECT * FROM cuenta WHERE saldo < ?"; stmt = m_con.prepareStatement(sql); stmt.setLong(1,saldo); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorCliente(String nif) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT ct.ncc,ct.saldo "+ "FROM cuenta ct,cuenta_cliente cc,cliente c WHERE "+ "ct.ncc = cc.ncc AND "+ "cc.nif = c.nif AND "+ "c.nif = ?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,nif); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorCliente(Cliente cliente) { return buscarPorCliente(cliente.getNif()); }

throws SQLException

public CuentaCorriente crear(String ncc,long saldo) throws SQLException { CuentaCorriente obj = (CuentaCorriente)crearObjeto(); obj.crear(ncc,saldo); return (CuentaCorriente)crear(obj); } public Persistente crearObjeto() { return new CuentaCorriente(); } public Persistente crearObjeto(Object clave) { String ncc = (String)clave; return new CuentaCorriente(ncc); } }

4.2.4.8

JDBCInicio.java

Este ejemplo de uso creará dos clientes y tres cuentas que asociará a los dos clientes y se navegará a través de las relaciones entre cuentas y clientes. package jdbc.tipobmp2.muchosmuchos; import java.sql.*; import java.util.*; import java.text.*;

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.40


Tecnologías de Persistencia en Java, una comparativa desde la práctica import jdbc.Database; public class JDBCInicio { public JDBCInicio() { } public static void main(String[] args) throws Exception { Connection con = null; try { con = new Database().conectar(); ClienteHome clienteHome = new ClienteHome(con); CuentaClienteHome cuentaCliHome = new CuentaClienteHome(con); CuentaCorrienteHome cuentaHome = new CuentaCorrienteHome(con); con.setAutoCommit(false); Cliente cliente1 = clienteHome.crear("50000000P","Iñaki Gabilondo",45,new GregorianCalendar(2003,8,20).getTime()); Cliente cliente2 = clienteHome.crear("40000000P","Luis del Olmo",47,new GregorianCalendar(2003,8,21).getTime()); CuentaCorriente cuenta1 = cuentaHome.crear("111",200000); CuentaCorriente cuenta2 = cuentaHome.crear("222",100000); CuentaCliente cuentaCli1 = cuentaCliHome.crear("50000000P","111", Timestamp(new GregorianCalendar(2003,8,20,10,0,0).getTimeInMillis())); CuentaCliente cuentaCli2 = cuentaCliHome.crear("40000000P","111", Timestamp(new GregorianCalendar(2003,8,21,11,0,0).getTimeInMillis())); CuentaCliente cuentaCli3 = cuentaCliHome.crear("50000000P","222", Timestamp(new GregorianCalendar(2003,8,22,12,0,0).getTimeInMillis())); CuentaCorriente cuenta = (CuentaCorriente)cuentaHome.buscarPorClavePrimaria("111"); System.out.println(cuenta); Collection col = cliente1.getCuentas(); for(Iterator it = col.iterator(); it.hasNext(); ) { cuenta = (CuentaCorriente)it.next(); System.out.println(cuenta); } col = cuenta1.getClientes(); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } col = cuentaHome.buscarPorSaldoMayor(1000); for(Iterator it = col.iterator(); it.hasNext(); ) { cuenta = (CuentaCorriente)it.next(); System.out.println(cuenta); } col = cuentaHome.buscarPorCliente(cliente1); for(Iterator it = col.iterator(); it.hasNext(); ) { cuenta = (CuentaCorriente)it.next(); System.out.println(cuenta); } col = clienteHome.buscarPorCuenta(cuenta1); for(Iterator it = col.iterator(); it.hasNext(); )

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.41

new new new


Tecnologías de Persistencia en Java, una comparativa desde la práctica { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } String sql = "SELECT * FROM cuenta"; col = cuentaHome.buscarAbierto(sql); for(Iterator it = col.iterator(); it.hasNext(); ) { cuenta = (CuentaCorriente)it.next(); System.out.println(cuenta); } cuentaCli1.eliminar(); cuentaCli2.eliminar(); cuentaCli3.eliminar(); cuenta1.eliminar(); cuenta2.eliminar(); cliente1.eliminar(); cliente2.eliminar(); con.commit(); } catch(Exception ex) { ex.printStackTrace(); } finally { if (con != null) { con.rollback(); con.close(); } } } }

4.2.5

Herencia

A continuación modelaremos el ejemplo de herencia Cliente-Persona. La herencia es una paradigma básico de la programación orientada a objetos, pero que no concuerda bien con el modelo relacional (sin extensiones de orientación a objetos). Como ya enunciamos en las decisiones de implementación, haremos corresponder cada clase con una tabla aunque esto suponga que una instancia Java persistente represente a varias filas de varias tablas, pues este es el modelo más simétrico al concepto de herencia aunque no sea el que mejor rendimiento tenga. Reutilizaremos el framework usado en los anteriores casos, de hecho está concebido para ayudar en la gestión persistente de la herencia, aunque con el apoyo del programador en las clases concretas persistentes. A la hora de hacer comparaciones, lo haremos respecto al caso de una tabla, pues el ejemplo es similar sólo que ahora el cliente no está definido por sí mismo sino que está basado en herencia. 4.2.5.1

Persona.java

En esta clase recae ahora buena parte del código que residía en la clase Cliente en el caso de una tabla. package jdbc.tipobmp2.herencia; import java.sql.*; import jdbc.tipobmp2.comun.*; public class Persona extends PersistenteImpl { protected String m_nif; protected String m_nombre;

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.42


Tecnologías de Persistencia en Java, una comparativa desde la práctica protected int m_edad; public Persona() { m_nombre = ""; } public Persona(String nif) { m_nif = nif; } public void crear(String nif, String nombre, int edad) { m_nif = nif; m_nombre = nombre; m_edad = edad; } public String getNif() { return m_nif; } public void setNif(String nif) { m_nif = nif; } public String getNombre() { return m_nombre; } public void setNombre(String nombre) { m_nombre = nombre; } public int getEdad() { return m_edad; } public void setEdad(int edad) { m_edad = edad; } public String toString() { return "nif: " + m_nif + " nombre: " + m_nombre + " edad: " + m_edad; } public boolean equals(Object obj) { if (obj instanceof Persona) { Persona obj2 = (Persona)obj; return m_nif.equals(obj2.getNif()); } return false; } public int hashCode() { return m_nif.hashCode(); } }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.43


Tecnologías de Persistencia en Java, una comparativa desde la práctica

Notar la novedad de los métodos equals() y hashCode() que no han estado presentes hasta ahora. Justificaremos más adelante su necesidad, adelantando que sirven para distinguir si dos objetos Persona o derivados de Persona tienen la misma identidad, es decir el mismo nif. 4.2.5.2

PersonaHome.java

Al igual que con Persona, esta clase contendrá buena parte del código de la clase Cliente del caso de una tabla. package jdbc.tipobmp2.herencia; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class PersonaHome extends PersistenteHomeImpl { public PersonaHome(Connection con) throws SQLException { super(con); } public void insertar(Persistente obj) throws SQLException { insertar(obj,"persona","INSERT INTO persona (nombre,edad,nif) VALUES(?,?,?)"); } public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"persona","UPDATE persona SET nombre=?, edad=? WHERE nif=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"persona","DELETE FROM persona WHERE nif=?"); } public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM persona WHERE nif=?"); } public void leer(Persistente obj,ResultSet res) { Persona persona = (Persona)obj;

throws SQLException

persona.setNif(res.getString("nif")); persona.setNombre(res.getString("nombre")); persona.setEdad(res.getInt("edad")); } public void setStatementParams(Persistente stmt) throws SQLException { Persona persona = (Persona)obj;

obj,String

table,PreparedStatement

stmt.setString(1,persona.getNombre()); stmt.setInt(2,persona.getEdad()); stmt.setString(3,persona.getNif()); } public void setStatementClave(Persistente SQLException { Persona persona = (Persona)obj;

obj,PreparedStatement

stmt)

throws

stmt.setString(1,persona.getNif()); }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.44


Tecnologías de Persistencia en Java, una comparativa desde la práctica public Collection buscarPorEdad(int edad) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM persona WHERE edad=?"; stmt = m_con.prepareStatement(sql); stmt.setInt(1,edad); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorNombre(String nombre) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM persona WHERE nombre=?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,nombre); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarTodos() throws SQLException { return buscarAbierto("SELECT * FROM persona"); } public Collection buscarTodosConHerencia() { Set res = new HashSet();

throws SQLException

ClienteHome clienteHome = new ClienteHome(m_con); Collection col; col = clienteHome.buscarTodos(); res.addAll(col); col = buscarTodos(); res.addAll(col); return res; } public int contar() throws SQLException { Statement stmt = null; try { String sql = "SELECT COUNT(*) AS num FROM persona"; stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); res.next(); return res.getInt("num"); } finally { if (stmt != null) stmt.close(); }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.45


Tecnologías de Persistencia en Java, una comparativa desde la práctica } public Persona crear(String nif,String nombre,int edad) { Persona obj = (Persona)crearObjeto(); obj.crear(nif,nombre,edad); return (Persona)crear(obj); }

throws SQLException

public Persistente crearObjeto() { return new Persona(); } public Persistente crearObjeto(Object clave) { String nif = (String)clave; return new Persona(nif); } }

Especialmente interesante es el método buscarTodosConHerencia() cuya método es obtener todos los objetos Persona y aquellos que derivan de Persona. Está basado en buscarTodos() que devuelve todos los objetos Persona basados en registros de la tabla persona. El método ClienteHome.buscarTodos() es idéntico salvo que los objetos retornados son los objetos Cliente correspondientes a los objetos Persona. public Collection buscarTodosConHerencia() { Set res = new HashSet();

throws SQLException

Una colección del tipo java.util.HashSet, es por definición al implementar la interfaz Set, una colección que no admite duplicados, la duplicidad es comprobada a través de los métodos Object.equals() y Object.hashCode(), el primero ha de devolver true si el objeto argumento es sí mismo (tiene la misma identidad), y el segundo ha de devolver un entero que sea único respecto a la identidad del objeto. ClienteHome clienteHome = new ClienteHome(m_con); Collection col; col = clienteHome.buscarTodos(); res.addAll(col);

Introduce en la colección Set la colección de objetos Cliente retornados en la consulta. col = buscarTodos(); res.addAll(col);

Introduce también en la colección HashSet la colección de objetos Persona retornados en la consulta. De acuerdo a nuestro modelo de tabular para cada registro en la tabla cliente existe un registro en la tabla persona, esto significa que una parte de los objetos Persona son los “mismos” (tienen la misma identidad, el mismo nif, aunque la instancia sea diferente) que los objetos Cliente obtenidos anteriormente. Como en Persona definimos la identidad respecto al nif en equals() y hashCode() y no respecto a la posición en memoria (el comportamiento por defecto), y como los objetos Cliente heredan este concepto de identidad, la colección HashSet (res) no insertará estos objetos Persona coincidentes en identidad con los objetos Cliente ya insertados. De esta forma para cada identidad diferente se devuelve el objeto más derivado, pues es el que originariamente introdujo la información en la base de datos. return res; }

Si existieran más clases persistentes derivadas de Persona, tendríamos que añadir el código específico correspondiente para que este método retornara dichos objetos. Hay que constatar que existen varios problemas de rendimiento: 1.

Se obtiene información repetida, pues al hacer la consulta de los objetos Cliente es necesario leer los datos de la tabla persona para cargar el objeto completo, dichos datos ya han sido leidos en la anterior consulta de objetos Persona. Como ya sabemos este problema quedaría minimizado con un caché de objetos.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.46


Tecnologías de Persistencia en Java, una comparativa desde la práctica

2.

Al leer objetos Cliente es posible que solo accedamos a atributos de la clase Cliente o a atributos de la clase Persona pero no a ambos conjuntos a la vez, y sin embargo hemos leído ambos registros. La optimización posible como sabemos es la lazy loading.

Este mismo tratamiento podría aplicarse a los diversos métodos de búsqueda si se quisiera que se devolviera el objeto más derivado. Implementamos sólo un método para ilustrar que la extracción de datos con la herencia dista mucho de ser automática. Existen otras técnicas que simplificarían el código y lo haría más eficiente en el caso de la herencia, tal y como añadir en la tabla persona algún tipo de código que nos informara sobre el tipo de objeto (el nombre de la clase Java por ejemplo) que escribió el registro, sin embargo esto viola el principio de partir de un modelo relacional “clásico” sin dependencias con la tecnología de persistencia. 4.2.5.3

Cliente.java

Implementa lo que es específico de ser cliente de un banco tal y como el alta, basándose cuando sea necesario en la clase base Persona. package jdbc.tipobmp2.herencia; import java.sql.*; import jdbc.tipobmp2.comun.*; public class Cliente extends Persona { protected java.util.Date m_alta; public Cliente() { } public Cliente(String nif) { super(nif); } public void crear(String nif,String nombre,int edad,java.util.Date alta) { super.crear(nif,nombre,edad); m_alta = alta; } public java.util.Date getAlta() { return m_alta; } public void setAlta(java.util.Date alta) { m_alta = alta; } public String toString() { String res = super.toString(); return res + " alta: " + m_alta; } }

4.2.5.4

ClienteHome.java

La clase Home se nos presenta significativamente más complicada que en los ejemplos sin herencia, pues es necesario coordinar la carga de las filas de las diferentes tablas (persona y cliente) que forman un objeto Cliente.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.47


Tecnologías de Persistencia en Java, una comparativa desde la práctica En las operaciones de inserción, actualización y eliminación no tenemos más remedio que usar dos sentencias SQL independientes, una para la fila en persona y otra para la fila en cliente, pues las sentencias INSERT, UPDATE y DELETE no fueron concebidas para hacer joins entre tablas y operar en varias filas de diferentes tablas a la vez. En las operaciones de lectura sí conseguiremos hacer la lectura de a la vez de las filas correspondientes en cliente y persona, a través de joins. Si inspeccionamos el código vemos cómo hay métodos que serán llamados directamente por el programador o bien por el framework que llaman a su vez a los correspondientes métodos de la clase base coordinando las operaciones a lo largo del árbol de derivación.

package jdbc.tipobmp2.herencia; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class ClienteHome extends PersonaHome { /** Creates a new instance of ClienteHome */ public ClienteHome(Connection con) throws SQLException { super(con); } public void insertar(Persistente obj) throws SQLException { super.insertar(obj); insertar(obj,"cliente","INSERT INTO cliente (alta,nif) VALUES(?,?)"); } public void actualizar(Persistente obj) throws SQLException { super.actualizar(obj); actualizar(obj,"cliente","UPDATE cliente SET alta=? WHERE nif=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cliente","DELETE FROM cliente WHERE nif=?"); super.eliminar(obj); } public void leer(Persistente obj) { leer(obj,"SELECT * FROM persona.nif=cliente.nif"); }

throws SQLException,NoEncontradoException cliente,persona

public void leer(Persistente obj,ResultSet res) { super.leer(obj,res);

WHERE

cliente.nif=?

AND

throws SQLException

Cliente cliente = (Cliente)obj; cliente.setAlta(res.getDate("alta")); } public void setStatementParams(Persistente obj,String tabla,PreparedStatement stmt) throws SQLException { if (!tabla.equals("cliente")) super.setStatementParams(obj,tabla,stmt); else { Cliente cliente = (Cliente)obj; stmt.setDate(1,new java.sql.Date(cliente.getAlta().getTime())); stmt.setString(2,cliente.getNif()); } }

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.48


Tecnologías de Persistencia en Java, una comparativa desde la práctica public Collection buscarPorEdad(int edad) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente,persona WHERE edad=? AND persona.nif = cliente.nif"; stmt = m_con.prepareStatement(sql); stmt.setInt(1,edad); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorNombre(String nombre) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente,persona WHERE nombre=? AND persona.nif = cliente.nif"; stmt = m_con.prepareStatement(sql); stmt.setString(1,nombre); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorAlta(java.util.Date alta) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente,persona WHERE cliente.alta=? persona.nif = cliente.nif"; stmt = m_con.prepareStatement(sql); stmt.setDate(1,new java.sql.Date(alta.getTime())); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarTodos() throws SQLException { return buscarAbierto("SELECT * FROM cliente.nif=persona.nif"); }

cliente,persona

public int contar() throws SQLException { Statement stmt = null; try { String sql = "SELECT COUNT(*) AS num FROM cliente"; stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); res.next(); return res.getInt("num");

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.49

AND

WHERE


Tecnologías de Persistencia en Java, una comparativa desde la práctica } finally { if (stmt != null) stmt.close(); } } public Cliente crear(String nif,String throws SQLException { Cliente obj = (Cliente)crearObjeto(); obj.crear(nif,nombre,edad,alta); return (Cliente)crear(obj); }

nombre,int

edad,java.util.Date

alta)

public Persistente crearObjeto() { return new Cliente(); } public Persistente crearObjeto(Object clave) { String nif = (String)clave; return new Cliente(nif); } }

Hemos tenido que repetir la mayor parte de los métodos de búsqueda definidos en Persona, pues si son llamados desde un objeto ClienteHome se entiende que se pretende obtener objetos Cliente, necesitando consultas más complicadas que sean capaces de leer a la vez los registros de las dos tablas. Esto es un problema importante, pues significa que en todas las clases derivadas se debe de repetir esta funcionalidad con joins cada vez más complicados a medida que bajamos en el árbol de derivación. De todas formas la complejidad del join es preferible a realizar consultas sucesivas en cada tabla del árbol de derivación, pues la pérdida de rendimiento sería muy signifativa: consideremos una consulta sobre la tabla cliente con 1000 resultados y que tuviéramos que hacer otras 1000 correspondientes consultas para obtener la parte de la tabla persona que forma el objeto Cliente, como bien sabe cualquier administrador de bases de datos, una consulta de 1000 resultados es enormemente más eficiente que 1000 consultas de un solo resultado (quizás en muy muy altos volúmenes de concurrencia de usuarios pudiera igualar o superar el rendimiento las consultas múltiples, pero esto no ocurre en niveles normales de escala). Un framework más avanzado podría componer estos joins de una forma genérica que evitara la repetición sistemática en cada clase específica, sin embargo, como veremos más adelante, en la industria no es habitual ver resuelto de forma satisfactoria y con un buen rendimiento este problema, en diversos frameworks de hecho se elude (Libelis Lido JDO 1.4.4) con un modelo de tablas más simple (una sola) o bien directamente no se plantea el problema de la herencia (EJB Entity Beans) o bien se aborda sólo para sistemas de bases de datos orientadas a objetos (Poet FastObjects12, Versant13) que no gozan del amplio favor de la industria como es el caso de las relacionales. Esto es sin duda bastante frustante para un desarrollador que usa intensivamente el concepto de herencia y lo quiere ver expresado en el modelo relacional de la forma más simple. 4.2.5.5

JDBCInicio.java

Ponemos en acción la herencia con un ejemplo de uso, creando un objeto Persona y dos objetos Cliente en la base de datos. package jdbc.tipobmp2.herencia; import java.sql.*; import java.util.*; import java.text.*; import jdbc.Database;

12

http://www.fastobjects.com/

13

http://www.versant.com/

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.50


Tecnologías de Persistencia en Java, una comparativa desde la práctica public class JDBCInicio { public JDBCInicio() { } public static void main(String[] args) throws Exception { Connection con = null; try { con = new Database().conectar(); ClienteHome clienteHome = new ClienteHome(con); PersonaHome personaHome = new PersonaHome(con); con.setAutoCommit(false); Persona persona = personaHome.crear("60000000P","José María García",45); persona = (Persona)personaHome.buscarPorClavePrimaria("60000000P"); System.out.println(persona); Cliente cliente1 = clienteHome.crear("50000000P","Iñaki Jabilondo",45,new GregorianCalendar(2003,8,20).getTime()); Cliente cliente2 = clienteHome.crear("40000000P","Luis del Olmo",47,new GregorianCalendar(2003,8,21).getTime()); cliente1.setNombre("Iñaki Gabilondo"); cliente1.actualizar(); cliente1 = (Cliente)clienteHome.buscarPorClavePrimaria("50000000P"); System.out.println(cliente1); Collection col = clienteHome.buscarPorNombre("Luis del Olmo"); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } col = clienteHome.buscarPorEdad(45); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } col = clienteHome.buscarPorAlta(new GregorianCalendar(2003,8,21).getTime()); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } String sql = "SELECT * FROM cliente,persona WHERE (nombre LIKE 'Luis%' OR nombre LIKE '%Gabilondo') AND cliente.nif=persona.nif"; col = clienteHome.buscarAbierto(sql); for(Iterator it = col.iterator(); it.hasNext(); ) { Cliente cliente = (Cliente)it.next(); System.out.println(cliente); } int num = personaHome.contar(); System.out.println(num); num = clienteHome.contar(); System.out.println(num);

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.51


Tecnologías de Persistencia en Java, una comparativa desde la práctica

col = personaHome.buscarTodosConHerencia(); for(Iterator it = col.iterator(); it.hasNext(); ) { Persona per = (Persona)it.next(); System.out.println(per); // Muestra dos clientes y una persona } cliente1.eliminar(); cliente2.eliminar(); persona.eliminar(); con.commit(); } catch(Exception ex) { ex.printStackTrace(); } finally { if (con != null) { con.rollback(); con.close(); } } } }

Es de destacar las siguientes sentencias: int num = personaHome.contar(); System.out.println(num); num = clienteHome.contar(); System.out.println(num); col = personaHome.buscarTodosConHerencia(); for(Iterator it = col.iterator(); it.hasNext(); ) { Persona per = (Persona)it.next(); System.out.println(per); // Muestra dos clientes y una persona }

Antes de su ejecución en la tabla persona existen tres registros (tres personas) y en la tabla cliente dos registros (dos clientes), puesto que hemos guardado en la base de datos un objeto Persona y dos Cliente, ambos clientes son también objetos Persona, de ahí que personaHome.contar() devuelva el valor 3 y clienteHome.contar() devuelta el valor 2. La colección devuelta por personaHome.buscarTodosConHerencia() contendrá dos tres objetos: un objeto Persona y dos objetos Cliente, fácil de comprobar al mostrarse la información por pantalla el resultado (notar que “Jose María García” es un objeto Persona pues no tiene el dato del alta en el banco): nif: 40000000P nombre: Luis del Olmo edad: 47 alta: 2003-09-21 nif: 60000000P nombre: José María García edad: 45 nif: 50000000P nombre: Iñaki Gabilondo edad: 45 alta: 2003-09-20

4.3 TIPO CMP Podemos dar una “vuelta de tuerca” más a nuestro esfuerzo por minimizar el código persistente de nuestras clases persistentes y conseguir la máxima transparencia.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.52


Tecnologías de Persistencia en Java, una comparativa desde la práctica Utilizaremos ahora la estrategia usada en los EJB Entity Beans CMP: se trata de hacer que la clase persistente sea abstracta, no implemente atributos que vayan a ser persistentes siendo sustituidos por los métodos “get” y “set” también abstractos, y los métodos que representan las relaciones entre clases sean también abstractos. En el caso de J2EE también son abstractos los métodos de búsqueda que era necesario implementar en el caso de los BMP. La finalidad que persigue la filosofía CMP es que la infraestructura (el servidor de aplicaciones en el caso verdadero J2EE) se encargue de hacer la clase que implemente todo aquello que es abstracto gestionando la persistencia al implementar los métodos abstractos. En el caso de J2EE esta clase es generada dinámicamente en el despliegue (deployment) a partir de las declaraciones del bean CMP. En síntesis es un BMP “automático” en donde el programador declara de forma abstracta (el bean CMP) lo que ha de implementarse por generación de código, en dicha generación de código es donde el servidor de aplicaciones demuestra su “saber hacer”. Nosotros seguiremos esta filosofía pero no de forma exacta al CMP J2EE obviamente, sino como estrategia de conseguir la transparencia buscada. De acuerdo con el enfoque CMP que busca la ampliación de las clases persistentes “por abajo”, eliminaremos la clase PersistenteImpl en todos los casos y el código necesario lo añadiremos en las clases derivadas de las clases persistentes. Esto no afecta a la clase PersistenteHomeImpl pues supondría la reescritura de mucho código idéntico en las clases Home específicas, es preciso recordar que el foco de la transparencia está en las clases persistentes no en las clases de utilidad (en el verdadero J2EE CMP la implementación de las clases Home es asunto del servidor de aplicaciones, y son generadas automáticamente en el despliegue), como ahora dispondremos de una clase que deriva de la clase persistente orientada fuertemente a la gestión de la persistencia de su clase base, algunas funciones presentes en las clases Home estará ahora en estas clases de forma un poco más adecuada. Implementaremos los mismos casos de uso que con el método “BMP Avanzado” y pondremos un énfasis en las diferencias, de cada caso (una tabla, uno-muchos, muchos-muchos, herencia) respecto al caso correspondiente BMP. 4.3.1

4.3.1.1

Clases Comunes (framework)

Persistente.java

package jdbc.tipocmp.comun; import java.sql.*; public interface Persistente { public void setConnection(Connection con); public Connection getConnection(); public void setHome(PersistenteHome home); public PersistenteHome getHome(); public void setStatementParams(String table,PreparedStatement stmt) throws SQLException; public void setStatementClave(PreparedStatement stmt) throws SQLException; public void actualizar() throws SQLException; public void eliminar() throws SQLException; public void leer(ResultSet res) throws SQLException; }

Notar la presencia de los métodos: public void setStatementParams(Persistente obj,String table,PreparedStatement stmt) throws SQLException; public void setStatementClave(Persistente obj,PreparedStatement stmt) throws SQLException; public void leer(Persistente obj,ResultSet res) throws SQLException;

Estos métodos estaban presentes en la interfase PersistenteHome en el framework tipo “BMP Avanzado”, su presencia aquí es debido al movimiento de la implementación de esta funcionalidad a la clase de gestión de la persistencia de la clase persistente. 4.3.1.2

PersistenteHome.java

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.53


Tecnologías de Persistencia en Java, una comparativa desde la práctica Esta interfase será casi idéntica al anterior framework excepto en los métodos que ahora están en la interfase Persistente. package jdbc.tipocmp.comun; import java.sql.*; import jdbc.NoEncontradoException; public interface PersistenteHome { public void setConnection(Connection con); public Connection getConnection(); public abstract Persistente crearObjeto(); public abstract Persistente crearObjeto(Object clave); public public public public

void void void void

actualizar(Persistente obj) throws SQLException; eliminar(Persistente obj) throws SQLException; insertar(Persistente obj) throws SQLException; leer(Persistente obj) throws SQLException,NoEncontradoException;

}

4.3.1.3

PersistenteHomeImpl.java

La clase es prácticamente idéntica a la correspondiente del anterior framework, las diferencias están en las llamadas a los tres métodos ahora presentes en el objeto Persistente en vez de en el objeto Home derivado. package jdbc.tipocmp.comun; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; public abstract class PersistenteHomeImpl implements PersistenteHome { protected Connection m_con; public PersistenteHomeImpl(Connection con) { m_con = con; } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public Persistente crear(Persistente obj) { obj.setConnection(m_con); obj.setHome(this); insertar(obj); return obj; }

throws SQLException

public Persistente crear(Object clave) throws SQLException { Persistente obj = crearObjeto(clave); return crear(obj); } public void insertar(Persistente obj,String table,String sql) { PreparedStatement stmt = null; try {

persistencia_java.doc v.1.0 José María Arranz Santamaría

throws SQLException

Pág.54


Tecnologías de Persistencia en Java, una comparativa desde la práctica stmt = obj.getConnection().prepareStatement(sql); obj.setStatementParams(table,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } public void actualizar(Persistente obj,String table,String sql) throws SQLException { Connection con = obj.getConnection(); if (con == null) return; PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); obj.setStatementParams(table,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } public void eliminar(Persistente obj,String table,String sql) { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); obj.setStatementClave(stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } }

throws SQLException

public void leer(Persistente obj,String sql) throws SQLException,NoEncontradoException { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); obj.setStatementClave(stmt); ResultSet res = stmt.executeQuery(); if (!res.next()) new NoEncontradoException(); obj.leer(res); } finally { if (stmt != null) stmt.close(); } } protected Collection leer(ResultSet res) throws SQLException { Collection col = new LinkedList(); while(res.next()) { Persistente obj = crearObjeto(); obj.setConnection(m_con); obj.setHome(this);

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.55


Tecnologías de Persistencia en Java, una comparativa desde la práctica obj.leer(res); col.add(obj); } return col; } public Persistente buscarPorClavePrimaria(Object clave) throws SQLException { Persistente obj = crearObjeto(clave); try { obj.setConnection(m_con); obj.setHome(this); leer(obj); return obj; } catch(NoEncontradoException ex) { return null; } } public Collection buscarAbierto(String sql) throws SQLException { Statement stmt = null; try { stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); return leer(res); } finally { if (stmt != null) stmt.close(); } } }

4.3.2

Una Tabla

Abordamos ahora el caso de una tabla con este nuevo estilo “CMP”. Como novedad dispondremos de una clase ClienteImpl, derivada de Cliente, que implementará la gestión de la persistencia de Cliente, completando todo aquello que es abstracto. 4.3.2.1

Cliente.java

package jdbc.tipocmp.unatabla; import java.sql.*; import java.util.*; import jdbc.tipocmp.comun.*; public abstract class Cliente implements Persistente { public Cliente() { } public public public public public public public public

abstract abstract abstract abstract abstract abstract abstract abstract

String getNif(); void setNif(String nif) throws SQLException; String getNombre(); void setNombre(String nombre) throws SQLException; int getEdad(); void setEdad(int edad) throws SQLException; java.util.Date getAlta(); void setAlta(java.util.Date alta) throws SQLException;

public void crear(String nif,String nombre,int edad,java.util.Date alta) throws SQLException

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.56


Tecnologías de Persistencia en Java, una comparativa desde la práctica { setNif(nif); setNombre(nombre); setEdad(edad); setAlta(alta); } public String toString() { return "nif: " + getNif() + " nombre: " + getNombre() + " edad: " + getEdad() + " alta: " + getAlta(); } }

4.3.2.2

ClienteImpl.java

La clase ClienteImpl implementará todo aquello que es abstracto en la clase Cliente. De esta manera podemos controlar la persistencia de los atributos, por ejemplo en los métodos “set”, esto es una ventaja respecto al enfoque “BMP Avanzado” en donde teníamos dos opciones: o hacer una llamada a actualizar() en cada método “set”, lo cual es una intromisión de la gestión persistente en la clase, o bien llamar “desde fuera” explícitamente al método actualizar() del objeto persistente tras modificar alguno de sus atributos (la segunda opción es la usada en la clase JDBCInicio). Por otra parte los métodos nuevos indicados en Persistente respecto al caso BMP se implementan ahora: package jdbc.tipocmp.unatabla; import java.sql.*; import java.util.*; import jdbc.tipocmp.comun.*; public class ClienteImpl extends Cliente { private Connection m_con; private PersistenteHome m_home; protected protected protected protected

String m_nif; String m_nombre; int m_edad; java.util.Date m_alta;

public ClienteImpl() { m_nombre = ""; m_alta = new java.util.Date(); } public ClienteImpl(String nif) { m_nif = nif; } public String getNif() { return m_nif; } public void setNif(String nif) throws SQLException { m_nif = nif; actualizar(); } public String getNombre() { return m_nombre; } public void setNombre(String nombre) throws SQLException

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.57


Tecnologías de Persistencia en Java, una comparativa desde la práctica { m_nombre = nombre; actualizar(); } public int getEdad() { return m_edad; } public void setEdad(int edad) throws SQLException { m_edad = edad; actualizar(); } public java.util.Date getAlta() { return m_alta; } public void setAlta(java.util.Date alta) throws SQLException { m_alta = alta; actualizar(); } public void leer(ResultSet res) throws SQLException { m_nif = res.getString("nif"); m_nombre = res.getString("nombre"); m_edad = res.getInt("edad"); m_alta = res.getDate("alta"); } public void setStatementParams(String tabla,PreparedStatement stmt) throws SQLException { stmt.setString(1,m_nombre); stmt.setInt(2,m_edad); stmt.setDate(3,new java.sql.Date(m_alta.getTime())); stmt.setString(4,m_nif); } public void setStatementClave(PreparedStatement stmt) throws SQLException { stmt.setString(1,m_nif); } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public void setHome(PersistenteHome home) { m_home = home; } public PersistenteHome getHome() { return m_home; } public void actualizar() throws SQLException

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.58


Tecnologías de Persistencia en Java, una comparativa desde la práctica { if (getHome() != null) getHome().actualizar(this); } public void eliminar() throws SQLException { getHome().eliminar(this); } }

El inconveniente de introducir la llamada a actualizar() en los métodos “set” de los atributos, es que el registro se actualiza cada vez que se modifica un atributo, esto puede suponer un problema de rendimiento ante un cambio externo de varios atributos, cuando dicha actualización podría realizarse una sola vez, sin embargo por otra parte conseguimos un avance en la transparencia de la gestión de la persistencia. Un framework más sofisticado (caso de los servidores de aplicaciones que implementan CMP) podría marcar el objeto como “modificado” (dirty) ante una modificación de un atributo, y en el commit realizar la actualización de una sola vez (lógicamente esto supone que la conexión a la base de datos esté gestionada por el framework, así como la apertura y cierre de la transacción, lo cual ciertamente ocurre en los servidores de aplicaciones). Observemos la presencia de los métodos getConnection, setConnection, getHome, setHome, actualizar y eliminar, en el caso BMP Avanzado estos métodos estaban en la clase genérica PersistenteImpl, clase base de las clases persistentes que constituyen el modelo de datos. Esto es una desventaja en la filosofía CMP respecto a la BMP, puesto que al ampliar exclusivamente “por abajo” es preciso repetir sistemáticamente en todas las clases de implementación de la persistencia, cierto código que es siempre el mismo. En los servidores de aplicaciones esto no supone ningún problema pues el código es generado, en nuestro caso podemos eliminarlo introduciendo de nuevo la clase PersistenteImpl (es preciso recordar que estamos hablando de filosofías o estrategias no de una imitación exacta por otra parte imposible salvo que diseñáramos lógicamente un servidor de aplicaciones como framework). 4.3.2.3

ClienteHome.java

La clase ClienteHome es idéntica funcionalmente al caso BMP Avanzado de una tabla excepto por la ausencia de los 3 métodos anteriormente indicados en PersistenteHome. 4.3.2.4

JDBCInicio.java

La clase es idéntica funcionalmente a la correspondiente en BMP, pues hemos cambiado la forma de gestionar la persistencia pero prácticamente nada la interfaz que se ofrece a la aplicación. La única diferencia es que ahora no necesitamos hacer una llamada explícita a actualizar() ante un cambio de un atributo (o varios) de un objeto persistente para que se haga efectivo en la base de datos, puesto que ahora lo hará el propio método set definido en ClienteImpl, consiguiendo un grado más de transparencia respecto a la aplicación: cliente1.setNombre("Iñaki Gabilondo"); // No es necesario: cliente1.actualizar();

4.3.3

Relación Uno-Muchos

Consideraremos dos tipos de comparación: respecto al caso de una tabla anterior analizando los nuevos elementos fruto de la necesidad de la gestión de una relación, y respecto al caso BMP Avanzado. 4.3.3.1

Cliente.java

Esta clase añade respecto al caso de una tabla, el método necesario para gestionar la relación con los objetos ClienteCuenta: public abstract Collection getClienteCuentas() throws SQLException;

Hacemos que sea abstracto de acuerdo con la filosofía CMP y de esa manera despejamos todo código de persistencia, presente aunque reducido en la filosofía BPM (la búsqueda correspondiente a través de la clase CuentaClienteHome), por tanto conseguimos a través de la abstracción un pequeño hito más hacia la transparencia. persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.59


Tecnologías de Persistencia en Java, una comparativa desde la práctica 4.3.3.2

ClienteImpl.java

Es en esta clase en donde implementamos el método getClienteCuentas() al igual que el resto de métodos abstractos de Cliente, de la misma forma que en el caso de una tabla. public Collection getClienteCuentas() throws SQLException { CuentaClienteHome cuentaCliHome = new CuentaClienteHome(getConnection()); return cuentaCliHome.buscarPorCliente(getNif()); }

Método idéntico al correspondiente en el caso BMP Cliente.getClienteCuentas() solo que ahora hemos conseguido “sacarlo” de la clase que representa al objeto a persistir. 4.3.3.3

ClienteHome.java

La clase es idéntica funcionalmente al caso uno-muchos BMP Avanzado excepto por la ausencia de los tres métodos que indicamos en PersistenteHome. 4.3.3.4

CuentaCliente.java

De forma similar a Cliente implementamos la clase abstracta CuentaCliente en donde el método abstracto getCliente() es el que establece la relación con el cliente propietario de la cuenta. package jdbc.tipocmp.unomuchos; import java.sql.*; import jdbc.tipocmp.comun.*; public abstract class CuentaCliente implements Persistente { public CuentaCliente() { } public void crear(String nif, String ncc, Timestamp ultimaOp) throws SQLException { setNif(nif); setNcc(ncc); setUltimaOperacion(ultimaOp); } public abstract String getNif(); public abstract void setNif(String nif) throws SQLException; public abstract String getNcc(); public abstract void setNcc(String ncc) throws SQLException; public abstract Timestamp getUltimaOperacion(); public abstract void setUltimaOperacion(Timestamp ultimaOp) throws SQLException; public abstract Cliente getCliente() throws SQLException; public String toString() { return "ncc: " + getNcc() getUltimaOperacion(); } }

4.3.3.5

+

"

nif:

"

+

getNif()

+

"

última

op.:

"

+

CuentaClienteImpl.java

De forma similar a ClienteImpl implementamos los métodos abstractos definidos en CuentaCliente y los atributos. package jdbc.tipocmp.unomuchos; import java.sql.*; import jdbc.tipocmp.comun.*; public class CuentaClienteImpl extends CuentaCliente { private Connection m_con;

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.60


Tecnologías de Persistencia en Java, una comparativa desde la práctica private PersistenteHome m_home; protected String m_ncc; // Número de la cuenta corriente protected String m_nif; protected Timestamp m_ultimaOperacion; public CuentaClienteImpl() { } public CuentaClienteImpl(String ncc) { m_ncc = ncc; } public CuentaClienteImpl(String nif,String ncc,Timestamp ultimaOp) { m_nif = nif; m_ncc = ncc; m_ultimaOperacion = ultimaOp; } public String getNcc() { return m_ncc; } public void setNcc(String ncc) throws SQLException { m_ncc = ncc; actualizar(); } public String getNif() { return m_nif; } public void setNif(String nif) throws SQLException { m_nif = nif; actualizar(); } public Timestamp getUltimaOperacion() { return m_ultimaOperacion; } public void setUltimaOperacion(Timestamp ultimaOp) throws SQLException { m_ultimaOperacion = ultimaOp; actualizar(); } public Cliente getCliente() throws SQLException { ClienteHome clienteHome = new ClienteHome(getConnection()); return (Cliente)clienteHome.buscarPorClavePrimaria(m_nif); } public void leer(ResultSet res) throws SQLException { m_nif = res.getString("nif"); m_ncc = res.getString("ncc"); m_ultimaOperacion = res.getTimestamp("ultima_op"); } public void setStatementParams(String tabla,PreparedStatement stmt) throws SQLException

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.61


Tecnologías de Persistencia en Java, una comparativa desde la práctica { stmt.setTimestamp(1,m_ultimaOperacion); stmt.setString(2,m_nif); stmt.setString(3,m_ncc); } public void setStatementClave(PreparedStatement stmt) throws SQLException { stmt.setString(1,m_ncc); } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public void setHome(PersistenteHome home) { m_home = home; } public PersistenteHome getHome() { return m_home; } public void actualizar() throws SQLException { if (getHome() != null) getHome().actualizar(this); } public void eliminar() throws SQLException { getHome().eliminar(this); } }

4.3.3.6

CuentaClienteHome.java

Al igual que ClienteHome, la implementación es idéntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres métodos que ya conocemos y citamos en PersistenteHome. 4.3.3.7

JDBCInicio.java

De nuevo constatamos que no hay ningún cambio respecto a la funcionalidad presente en BMP Avanzado al igual que ocurría en caso de una tabla, un nuevo éxito de nuestra estrategia de encapsulación de la persistencia. 4.3.4

Relación Muchos-Muchos

Al igual que hicimos en caso previo, compararemos nuestro nuevo caso respecto al caso uno-muchos anterior y el caso correspondiente muchos-muchos con la filosofía BMP Avanzado. 4.3.4.1

Cliente.java

Como novedad respecto al caso de uno-muchos introducimos el método getCuentas() que nos devuelve directamente los objetos CuentaCorriente que pertenecen al cliente: public abstract Collection getCuentas() throws SQLException;

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.62


Tecnologías de Persistencia en Java, una comparativa desde la práctica 4.3.4.2

ClienteImpl.java

Como novedad respecto al caso uno-muchos definimos el método getCuentas() para establecer la relación muchosmuchos por este lado. public Collection getCuentas() throws SQLException { CuentaCorrienteHome cuentaHome = new CuentaCorrienteHome(getConnection()); return cuentaHome.buscarPorCliente(this); }

4.3.4.3

ClienteHome.java

La implementación es idéntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres métodos citados en PersistenteHome. 4.3.4.4

CuentaCliente.java

Introducimos respecto al caso uno-muchos el nuevo método getCuenta() . public abstract CuentaCorriente getCuenta() throws SQLException;

4.3.4.5

CuentaClienteImpl.java

Respecto al caso uno-muchos, introducimos lógicamente el nuevo método getCuenta(). public CuentaCorriente getCuenta() throws SQLException { CuentaCorrienteHome cuentaHome = new CuentaCorrienteHome(getConnection()); return (CuentaCorriente)cuentaHome.buscarPorClavePrimaria(m_ncc); }

4.3.4.6

CuentaClienteClave.java

Es idéntica funcionalmente al caso correspondiente en BMP Avanzado. 4.3.4.7

CuentaClienteHome.java

Idéntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres métodos citados en PersistenteHome. 4.3.4.8

CuentaCorriente.java

Mostramos la nueva expresión abstracta de esta clase: package jdbc.tipocmp.muchosmuchos; import java.sql.*; import java.util.*; import jdbc.tipocmp.comun.*; public abstract class CuentaCorriente implements Persistente { public CuentaCorriente() { } public void crear(String ncc, long saldo) throws SQLException { setNcc(ncc); setSaldo(saldo); } public abstract String getNcc(); public abstract void setNcc(String ncc) throws SQLException;

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.63


Tecnologías de Persistencia en Java, una comparativa desde la práctica public abstract long getSaldo(); public abstract void setSaldo(long saldo) throws SQLException; public abstract Collection getCuentaClientes() throws SQLException; public abstract Collection getClientes() throws SQLException; public String toString() { return "ncc: " + getNcc() + " saldo: " + getSaldo(); } }

4.3.4.9

CuentaCorrienteImpl.java

Implementamos los métodos abstractos de CuentaCorriente : package jdbc.tipocmp.muchosmuchos; import java.sql.*; import java.util.*; import jdbc.tipocmp.comun.*; public class CuentaCorrienteImpl extends CuentaCorriente { private Connection m_con; private PersistenteHome m_home; protected String m_ncc; // Número de la cuenta corriente protected long m_saldo; public CuentaCorrienteImpl() { } public CuentaCorrienteImpl(String ncc) { m_ncc = ncc; } public String getNcc() { return m_ncc; } public void setNcc(String ncc) throws SQLException { m_ncc = ncc; actualizar(); } public long getSaldo() { return m_saldo; } public void setSaldo(long saldo) throws SQLException { m_saldo = saldo; actualizar(); } public Collection getCuentaClientes() throws SQLException { CuentaClienteHome cuentaCliHome = new CuentaClienteHome(getConnection()); return cuentaCliHome.buscarPorNcc(m_ncc); } public Collection getClientes() throws SQLException { ClienteHome clienteHome = new ClienteHome(getConnection());

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.64


Tecnologías de Persistencia en Java, una comparativa desde la práctica return clienteHome.buscarPorCuenta(this); } public void leer(ResultSet res) throws SQLException { m_ncc = res.getString("ncc"); m_saldo = res.getLong("saldo"); } public void setStatementParams(String SQLException { stmt.setLong(1,m_saldo); stmt.setString(2,m_ncc); }

tabla,PreparedStatement

stmt)

throws

public void setStatementClave(PreparedStatement stmt) throws SQLException { stmt.setString(1,m_ncc); } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public void setHome(PersistenteHome home) { m_home = home; } public PersistenteHome getHome() { return m_home; } public void actualizar() throws SQLException { if (getHome() != null) getHome().actualizar(this); } public void eliminar() throws SQLException { getHome().eliminar(this); } }

4.3.4.10

CuentaCorrienteHome.java

Idéntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres métodos citados en PersistenteHome. 4.3.4.11

JDBCInicio.java

Al igual que en el caso uno-muchos y con una tabla no se produce ningún cambio respecto a la funcionalidad presente en el corrspondiente ejemplo de BMP Avanzado. 4.3.5

Herencia

Nuestro último caso que cierra la estrategia de diseño CMP. persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.65


Tecnologías de Persistencia en Java, una comparativa desde la práctica Es aquí donde veremos que el modelo CMP ofrece un problema conceptual muy serio a la hora de modelar la herencia: la extensión “por abajo” implica herencia de la clase implementación de la clase abstracta, esto supone una muy fuerte intromisión sobre el modelo de datos. En el caso BMP vimos que la intromisión era “por arriba” a través de la clase PersistenteImpl que fácilmente podríamos eliminar sin introducir mucho código sobre las clases persistentes, ahora al ser por abajo supondrá un verdadero problema en la herencia de una clase persistente de otra, pues hay que tener en cuenta que las clases implementación también derivan. Existen dos formas de modelar la herencia: 1.

Primera Forma:

Persona

Cliente

PersonaImpl

ClienteImpl

Esta es la manera menos intrusiva pues permite que Cliente derive de Persona, pero tiene el problema de que ClienteImpl debe implementar la persistencia de ambas clases: Persona y Cliente, pues no puede aprovecharse de la gestión ya realizada en PersonaImpl. A medida que se introdujeran más clases en el árbol de derivación por debajo de Cliente el problema se agravaría (este “agravamiento” no sería tan grave si el modelo de correspondencia clases-tablas fuera el de una sola tabla para toda una línea ascendente de herencia, llamado también “horizontal”, pero es más grave cuando la correspondencia es de tipo “vertical” que es nuestro caso). 2.

Segunda Forma:

Persona

PersonaImpl

Cliente

ClienteImpl

Esta segunda opción es intrusiva respecto a la derivación normal pero por otra parte se aprovecha desde la herencia la gestión de PersonaImpl por parte de ClienteImpl.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.66


Tecnologías de Persistencia en Java, una comparativa desde la práctica

La elección no es fácil, hemos visto que cada opción tiene su parte a favor y su parte en contra. Nosotros elegiremos la opción segunda o intrusiva porque nos ahorra bastante código y encaja mejor en nuestra correspondencia de 1 clase – 1 tabla, pagando el precio con la fuerte intrusión de la gestión de la persistencia en el modelo de herencia. Hay que hacer notar que este problema no está resuelto todavía en la verdadera especificación J2EE CMP (actualmente la 2.0), pues de hecho CMP no soporta la herencia “todavía”, pero no parece probable que lo haga en un futuro cercano, en cuanto que la herencia es uno de los aspectos que suele estar sacrificado en los modelos de datos persistentes en Java por culpa de la “presión” de la tecnología relacional clásica y la común separación en diferentes personas entre el diseñador de la base de datos y el diseñador de la aplicación. JDO en el tema de la herencia es una elección mucho más adecuada que J2EE CMP. 4.3.5.1

Persona.java

La clase Persona ahora será fundamentalmente abstracta: package jdbc.tipocmp.herencia; import java.sql.*; import jdbc.tipocmp.comun.*; public abstract class Persona implements Persistente { public Persona() { } public void crear(String nif, String nombre, int edad) throws SQLException { setNif(nif); setNombre(nombre); setEdad(edad); } public public public public public public

abstract abstract abstract abstract abstract abstract

String getNif(); void setNif(String nif) throws SQLException; String getNombre(); void setNombre(String nombre) throws SQLException; int getEdad(); void setEdad(int edad) throws SQLException;

public String toString() { return "nif: " + getNif() + " nombre: " + getNombre() + " edad: " + getEdad(); } }

Además de la abstracción de los métodos hay que notar una importante diferencia respecto al modelo BMP Avanzado, y es la ausencia de los métodos equals() y hashCode(), esto es debido a que los implementaremos en la clase Impl y de esta manera evitamos incluir la gestión de la identidad en este nivel, cuando es un aspecto obligado por la gestión de la persistencia (más exactamente por la herencia). 4.3.5.2

PersonaImpl

Implementamos la persistencia de Persona,es aquí donde codificamos la gestión de la identidad: package jdbc.tipocmp.herencia; import java.sql.*; import jdbc.tipocmp.comun.*; public class PersonaImpl extends Persona {

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.67


Tecnologías de Persistencia en Java, una comparativa desde la práctica private Connection m_con; private PersistenteHome m_home; protected String m_nif; protected String m_nombre; protected int m_edad; public PersonaImpl() { m_nombre = ""; } public PersonaImpl(String nif) { m_nif = nif; } public String getNif() { return m_nif; } public void setNif(String nif) throws SQLException { m_nif = nif; actualizar(); } public String getNombre() { return m_nombre; } public void setNombre(String nombre) throws SQLException { m_nombre = nombre; actualizar(); } public int getEdad() { return m_edad; } public void setEdad(int edad) throws SQLException { m_edad = edad; actualizar(); } public boolean equals(Object obj) { if (obj instanceof Persona) { Persona obj2 = (Persona)obj; return m_nif.equals(obj2.getNif()); } return false; } public int hashCode() { return m_nif.hashCode(); } public void leer(ResultSet res) throws SQLException { m_nif = res.getString("nif"); m_nombre = res.getString("nombre"); m_edad = res.getInt("edad");

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.68


Tecnologías de Persistencia en Java, una comparativa desde la práctica } public void setStatementParams(String table,PreparedStatement stmt) throws SQLException { stmt.setString(1,m_nombre); stmt.setInt(2,m_edad); stmt.setString(3,m_nif); } public void setStatementClave(PreparedStatement stmt) throws SQLException { stmt.setString(1,m_nif); }

public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public void setHome(PersistenteHome home) { m_home = home; } public PersistenteHome getHome() { return m_home; } public void actualizar() throws SQLException { if (getHome() != null) getHome().actualizar(this); } public void eliminar() throws SQLException { getHome().eliminar(this); } }

4.3.5.3

PersonaHome.java

La clase es idéntica a la correspondiente en BMP Avanzado con la excepción de la ausencia de los tres métodos considerados en PersistenteHome. 4.3.5.4

Cliente.java

Derivaremos de PersonaImpl de acuerdo la elección que hicimos en el comienzo de este caso, aunque Cliente sea fundamentalmente abstracta y las llamadas a la clase base las reciba el nivel de la clase Persona. package jdbc.tipocmp.herencia; import java.sql.*; import jdbc.tipocmp.comun.*; public abstract class Cliente extends PersonaImpl { public Cliente() { } public void crear(String nif,String nombre,int edad,java.util.Date alta)

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.69


Tecnologías de Persistencia en Java, una comparativa desde la práctica throws SQLException { super.crear(nif,nombre,edad); setAlta(alta); } public abstract java.util.Date getAlta(); public abstract void setAlta(java.util.Date alta) throws SQLException; public String toString() { String res = super.toString(); return res + " alta: " + getAlta(); } }

4.3.5.5

ClienteImpl

Ahora ClienteImpl deriva de Cliente e indirectamente de PersistenteImpl, es a esta clase a donde se dirigen las llamadas a la clase base para que se realicen las operaciones de persistencia. package jdbc.tipocmp.herencia; import java.sql.*; public class ClienteImpl extends Cliente { protected java.util.Date m_alta; public ClienteImpl() { } public ClienteImpl(String nif) { m_nif = nif; } public java.util.Date getAlta() { return m_alta; } public void setAlta(java.util.Date alta) { m_alta = alta; actualizar(); }

throws SQLException

public void leer(ResultSet res) throws SQLException { super.leer(res); m_alta = res.getDate("alta"); } public void setStatementParams(String tabla,PreparedStatement stmt) throws SQLException { if (!tabla.equals("cliente")) super.setStatementParams(tabla,stmt); else { stmt.setDate(1,new java.sql.Date(m_alta.getTime())); stmt.setString(2,m_nif); } } }

4.3.5.6

ClienteHome.java

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.70


Tecnologías de Persistencia en Java, una comparativa desde la práctica La clase es idéntica a la correspondiente en BMP Avanzado con la excepción de la ausencia de los tres métodos considerados en PersistenteHome. 4.3.5.7

JDBCInicio.java

Al igual que en los casos anteriores no se produce ningún cambio respecto a la funcionalidad presente en el corrspondiente ejemplo de BMP Avanzado.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.71


Tecnologías de Persistencia en Java, una comparativa desde la práctica

5. CONLUSIONES Una vez llegados aquí podemos ver en perspectiva nuestros modelos y llegar a varias conclusiones:

5.1 NECESIDAD DE UN FRAMEWORK Cuando programamos el ejemplo inicial tipo BMP de una tabla, vimos que en las tareas básicas de leer, insertar, actualizar y eliminar contenían mucho código que muy poco tenía que ver con cada tipo de datos, invitándonos a generalizar estas funcionalidades con el fin de disminuir la cantidad de código repetitivo y mejorar la calidad del programa (aparte de programar menos). Este es un problema o ventaja de JDBC, su sencillez, supone un problema en cuanto que nos exige programar un montón de código para gestionar la persistencia de nuestro modelo de datos, pero supone una ventaja respecto a que podemos elegir con flexibilidad nuestra manera de gestionar la persistencia y construir nuestro propio framework persistente ya sea simple o sofisticado según el nivel de transparencia y reducción de código que queramos alcanzar.

5.2 NECESIDAD DE FRAMEWORKS MÁS SOFISTICADOS Aunque nuestros modelos pueden considerarse suficientemente satisfactorios en cuestión de rendimiento, por ejemplo a través del uso de joins cuando era necesario, hemos detectado desde el comienzo que nuestra infraestructura no es suficiente, comprobamos como es fácil repetir la carga de registros desde la base de datos que ya se han cargado anteriormente, como cargábamos la colección completa de objetos resultante de una consulta independientemente de si se accedían a todos o no, realizábamos la carga de atributos que no eran utilizados y por último la carga en herencia de partes del objeto que pueden no accederse nunca. Vimos que estos problemas quedarían resueltos con frameworks que tuvieran características de carga de datos bajo demanda (lazy-loading) para cargar aquello que se necesita, y que incorporaran cachés de objetos para no cargar aquellos objetos ya cargados. Hicimos un esfuerzo de generalización y por tanto de transparencia a través de las clases PersistenteXXX, pero es posible realizar un esfuerzo mayor a través de un framework más sofisticado, existen varias estrategias tal y como el uso de Java reflection, proxys dinámicos, generación de código, enhancement de bytecode, crear nuevos lenguajes de consulta orientados a objetos etc.

5.3

ORIENTACIÓN A OBJETOS Y PATRONES COMO HERRAMIENTAS QUE PERMITEN LA TRANSPARENCIA Y LA TOLERANCIA A CAMBIOS

Gracias a usar la orientación a objetos tal y como la herencia de nuestro modelo de datos de las clases genéricas, la encapsulación para ocultar la gestión interna de la persistencia respecto al exterior, las funciones virtuales (polimorfismo) para poder hacer la gestión de la persistencia en la herencia por niveles o para poder manejar las clases concretas de una forma genérica utilizando funciones a modo de callbacks, y la división del trabajo a través del patrón DAO (sobre todo a través de las clases Home), hemos conseguido aparte de una notable transparencia en el modelo de datos, una gran tolerancia frente a cambios, esta tolerancia se ha manifestado claramente en la clase JDBCInicio que tenía la finalidad de realizar un conjunto de operaciones sobre el modelo de datos de forma persistente como ejemplo de

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.72


Tecnologías de Persistencia en Java, una comparativa desde la práctica uso, dicha clase a lo largo de los diferentes ejemplos ha quedado prácticamente inalterada aunque la gestión de la persistencia siguiera estrategias notablemente diferentes.

5.4 BMP MENOS TRANSPARENTE QUE CMP Hemos comprobado que con la estrategia CMP conseguimos una mayor transparencia en las clases persistentes que con la estrategia BMP, pero a costa de definir más clases y hacer abstracto el modelo de datos, resultándonos algo artificial respecto a un modelo normal de objetos de datos. De todas formas vía generalización a través de las clases PersistenteXXX hemos conseguido que en el caso BMP el código persistente de las clases de datos sea mínimo. En el caso CMP se consigue que sea nulo. CMP tiene la importante ventaja de que al hacer abstracta la clase de datos, permite trabajar con libertad en la interceptación de las llamadas a los diferentes atributos y colecciones de relaciones, y en la programación de estrategias de persistencia bajo demanda o cachés de objetos. La elección de la “filosofía CMP” en el verdadero J2EE CMP tiene mucho que ver con esto. A través de la via BMP, donde los atributos están “visibles” y se deja implementar los métodos de la obtención de objetos relacionados al programador de la clase, la consecución de un mayor nivel de transparencia realizada por el servidor de aplicaciones sería mucho más complicado, salvo que se use la técnica de bytecode enhancement de JDO que no deja de ser una transformación del código original (en este sentido podemos decir que JDO permite un modo BMP de programar las clases de datos).

5.5 CMP TIENE SERIOS PROBLEMAS CON LA HERENCIA Si la herencia es importante, CMP puede no ser apropiado, salvo que se admita una fuerte intrusión de la persistencia en el modelo de herencia o se admita tener que el código de gestión de la persistencia de toda la línea vertical de herencia para cada clase persistente, que ante un esquema de muchas clases supone una fuerte redundancia de código. BMP por otra parte encajaba muy bien la herencia, al no hacer ninguna intrusión en la herencia salvo por arriba y que como dijimos esta era evitable (supresión de la clase PersistenteImpl).

5.6 JDBC ADECUADO PARA MODELOS SENCILLOS O BASES DE DATOS POCO ESTÁNDAR Esta es nuestra conclusión final, lo aconsejable es usar tecnologías de persistencia más avanzadas cuando se trata de modelos de datos con muchas clases (muchas tablas) y complejos (herencias), salvo que el sistema de base de datos sea muy peculiar o tenga características avanzadas que sólo se aprovecharían con un uso directo y a medida de JDBC, un buen ejemplo de esto es el uso de SQL 3 (ANSI SQL 1999), con reflejo en la API de JDBC, cuyo uso simplificaría notablemente nuestros ejemplos, esto no quita que tecnologías más avanzadas no puedan hacer uso de SQL 3 cuando saben que la base de datos que acceden lo soporta. Actualmente disponemos de toda una artillería de tecnologías de persistencia más avanzadas, que de hecho casi siempre están construidas sobre JDBC aunque lo oculten, tal y como JDO, J2EE CMP, JCA y SQLJ por citar las estándar, e Hibernate, Castor, TopLink y CocoBase por citar productos no estándar libres y comerciales.

persistencia_java.doc v.1.0 José María Arranz Santamaría

Pág.73


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.