You are on page 1of 73

ESTRATEGIAS DE PERSISTENCIA

ORIENTADA A OBJETOS EN JAVA CON


JDBC, UNA COMPARATIVA DESDE LA
PRÁCTICA

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 Pág.2


José María Arranz Santamaría
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 Pág.3


José María Arranz Santamaría
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 Pág.4


José María Arranz Santamaría
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 Pág.5


José María Arranz Santamaría
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 Pág.6


José María Arranz Santamaría
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 Pág.7


José María Arranz Santamaría
Tecnologías de Persistencia en Java, una comparativa desde la práctica

Cliente CuentaCliente

#m_nif:String 1 0..* #m_ncc:String


#m_nombre:String #m_nif:String
#m_edad:int #m_ultimaOperacion:java.sql.Timestamp
#m_alta:java.util.Date

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..* 0..* #m_ncc:String
#m_edad:int #m_saldo:long
#m_alta:Date

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 Pág.8


José María Arranz Santamaría
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 Pág.9


José María Arranz Santamaría
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 atributo-
columna 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 Pág.10


José María Arranz Santamaría
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 Pág.11


José María Arranz Santamaría
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 Pág.12


José María Arranz Santamaría
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 Pág.13


José María Arranz Santamaría
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 Pág.14


José María Arranz Santamaría
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 Pág.15


José María Arranz Santamaría
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 Pág.16


José María Arranz Santamaría
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) throws SQLException

persistencia_java.doc v.1.0 Pág.17


José María Arranz Santamaría
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 Pág.18


José María Arranz Santamaría
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 java.sql.*;
import java.util.*;
import jdbc.NoEncontradoException;
import 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 Pág.19


José María Arranz Santamaría
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 Pág.20


José María Arranz Santamaría
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 Pág.21


José María Arranz Santamaría
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 void eliminar(Persistente obj) throws SQLException;


public void actualizar(Persistente obj) throws SQLException;
public void insertar(Persistente obj) throws SQLException;
public void leer(Persistente obj) throws SQLException,NoEncontradoException;
public void 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 Pág.22


José María Arranz Santamaría
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 Pág.23


José María Arranz Santamaría
Tecnologías de Persistencia en Java, una comparativa desde la práctica

public Persistente crear(Persistente obj) throws SQLException


{
obj.setConnection(m_con);
obj.setHome(this);
insertar(obj);
return obj;
}

public Persistente crear(Object clave) throws SQLException


{
Persistente obj = crearObjeto(clave);
return crear(obj);
}

public void insertar(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 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) throws SQLException


{
PreparedStatement stmt = null;
try
{
stmt = obj.getConnection().prepareStatement(sql);
setStatementClave(obj,stmt);
stmt.executeUpdate();
}
finally
{
if (stmt != null) stmt.close();
}
}

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 Pág.24


José María Arranz Santamaría
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 Pág.25


José María Arranz Santamaría
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 Pág.26


José María Arranz Santamaría
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) throws SQLException


{
insertar(obj,"cliente","INSERT INTO cliente (nombre,edad,alta,nif)
VALUES(?,?,?,?)");
}

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) throws SQLException


{
Cliente cliente = (Cliente)obj;

cliente.setNif(res.getString("nif"));
cliente.setNombre(res.getString("nombre"));
cliente.setEdad(res.getInt("edad"));
cliente.setAlta(res.getDate("alta"));
}

public void setStatementParams(Persistente obj,String tabla,PreparedStatement


stmt) throws SQLException
{
Cliente cliente = (Cliente)obj;

persistencia_java.doc v.1.0 Pág.27


José María Arranz Santamaría
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 obj,PreparedStatement stmt) throws


SQLException
{
Cliente cliente = (Cliente)obj;

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 nombre,int edad,java.util.Date alta)


throws SQLException
{
Cliente obj = (Cliente)crearObjeto();

persistencia_java.doc v.1.0 Pág.28


José María Arranz Santamaría
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 Pág.29


José María Arranz Santamaría
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 Pág.30


José María Arranz Santamaría
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 + " nif: " + m_nif + " última op.: " +
m_ultimaOperacion;
}
}

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 Pág.31


José María Arranz Santamaría
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) throws SQLException


{
CuentaCliente cuenta = (CuentaCliente)obj;

cuenta.setNif(res.getString("nif"));
cuenta.setNcc(res.getString("ncc"));
cuenta.setUltimaOperacion(res.getTimestamp("ultima_op"));
}

public void setStatementParams(Persistente obj,String tabla,PreparedStatement


stmt) throws SQLException
{
CuentaCliente cuenta = (CuentaCliente)obj;

stmt.setTimestamp(1,cuenta.getUltimaOperacion());
stmt.setString(2,cuenta.getNif());
stmt.setString(3,cuenta.getNcc());
}

public void setStatementClave(Persistente obj,PreparedStatement stmt) throws


SQLException
{
CuentaCliente cuenta = (CuentaCliente)obj;

stmt.setString(1,cuenta.getNcc());
}

public Collection buscarPorDespuesUltimaOp(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 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) throws SQLException


{

persistencia_java.doc v.1.0 Pág.32


José María Arranz Santamaría
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 ultimaOp) throws


SQLException
{
CuentaCliente obj = (CuentaCliente)crearObjeto();
obj.crear(nif,ncc,ultimaOp);
return (CuentaCliente)crear(obj);
}

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 Pág.33


José María Arranz Santamaría
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", new


Timestamp(new GregorianCalendar(2003,8,20,10,0,0).getTimeInMillis()));
CuentaCliente cuentaCli2 = cuentaCliHome.crear("40000000P","222", new
Timestamp(new GregorianCalendar(2003,8,21,11,0,0).getTimeInMillis()));
CuentaCliente cuentaCli3 = cuentaCliHome.crear("50000000P","333", new
Timestamp(new GregorianCalendar(2003,8,22,12,0,0).getTimeInMillis()));

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 Timestamp(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);
}

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 Pág.34


José María Arranz Santamaría
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 muchos-
muchos 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) throws SQLException

persistencia_java.doc v.1.0 Pág.35


José María Arranz Santamaría
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) throws SQLException


{
return buscarPorCuenta(cuenta.getNcc());
}

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 cliente-
cuenta, 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 Pág.36


José María Arranz Santamaría
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 WHERE nif=? AND
ncc=?");
}

public void leer(Persistente obj) throws SQLException,NoEncontradoException


{
leer(obj,"SELECT * FROM cuenta_cliente WHERE nif=? AND ncc=?");
}
. . .

public void setStatementClave(Persistente obj,PreparedStatement stmt) throws


SQLException
{
CuentaCliente cuenta = (CuentaCliente)obj;

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 Pág.37


José María Arranz Santamaría
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 Pág.38


José María Arranz Santamaría
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 tabla,PreparedStatement


stmt) throws SQLException
{
CuentaCorriente cuenta = (CuentaCorriente)obj;

stmt.setLong(1,cuenta.getSaldo());
stmt.setString(2,cuenta.getNcc());
}

public void setStatementClave(Persistente obj,PreparedStatement stmt) throws


SQLException
{
CuentaCorriente cuenta = (CuentaCorriente)obj;

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) throws SQLException


{

persistencia_java.doc v.1.0 Pág.39


José María Arranz Santamaría
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) throws SQLException


{
return buscarPorCliente(cliente.getNif());
}

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 Pág.40


José María Arranz Santamaría
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", new


Timestamp(new GregorianCalendar(2003,8,20,10,0,0).getTimeInMillis()));
CuentaCliente cuentaCli2 = cuentaCliHome.crear("40000000P","111", new
Timestamp(new GregorianCalendar(2003,8,21,11,0,0).getTimeInMillis()));
CuentaCliente cuentaCli3 = cuentaCliHome.crear("50000000P","222", new
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 Pág.41


José María Arranz Santamaría
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 Pág.42


José María Arranz Santamaría
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 Pág.43


José María Arranz Santamaría
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) throws SQLException


{
Persona persona = (Persona)obj;

persona.setNif(res.getString("nif"));
persona.setNombre(res.getString("nombre"));
persona.setEdad(res.getInt("edad"));
}

public void setStatementParams(Persistente obj,String table,PreparedStatement


stmt) throws SQLException
{
Persona persona = (Persona)obj;

stmt.setString(1,persona.getNombre());
stmt.setInt(2,persona.getEdad());
stmt.setString(3,persona.getNif());
}

public void setStatementClave(Persistente obj,PreparedStatement stmt) throws


SQLException
{
Persona persona = (Persona)obj;

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

persistencia_java.doc v.1.0 Pág.44


José María Arranz Santamaría
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() throws SQLException


{
Set res = new HashSet();

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 Pág.45


José María Arranz Santamaría
Tecnologías de Persistencia en Java, una comparativa desde la práctica

public Persona crear(String nif,String nombre,int edad) throws SQLException


{
Persona obj = (Persona)crearObjeto();
obj.crear(nif,nombre,edad);
return (Persona)crear(obj);
}

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() throws SQLException
{
Set res = new HashSet();

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 Pág.46


José María Arranz Santamaría
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 Pág.47


José María Arranz Santamaría
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) throws SQLException,NoEncontradoException


{
leer(obj,"SELECT * FROM cliente,persona WHERE cliente.nif=? AND
persona.nif=cliente.nif");
}

public void leer(Persistente obj,ResultSet res) throws SQLException


{
super.leer(obj,res);

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 Pág.48


José María Arranz Santamaría
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=? AND
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,persona WHERE
cliente.nif=persona.nif");
}

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 Pág.49


José María Arranz Santamaría
Tecnologías de Persistencia en Java, una comparativa desde la práctica

}
finally
{
if (stmt != null) stmt.close();
}
}

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 (Cliente)crear(obj);
}

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 Pág.50


José María Arranz Santamaría
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 Pág.51


José María Arranz Santamaría
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 Pág.52


José María Arranz Santamaría
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 Clases Comunes (framework)

4.3.1.1 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 Pág.53


José María Arranz Santamaría
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 void actualizar(Persistente obj) throws SQLException;


public void eliminar(Persistente obj) throws SQLException;
public void insertar(Persistente obj) throws SQLException;
public void 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) throws SQLException


{
obj.setConnection(m_con);
obj.setHome(this);
insertar(obj);
return obj;
}

public Persistente crear(Object clave) throws SQLException


{
Persistente obj = crearObjeto(clave);
return crear(obj);
}

public void insertar(Persistente obj,String table,String sql) throws SQLException


{
PreparedStatement stmt = null;
try
{

persistencia_java.doc v.1.0 Pág.54


José María Arranz Santamaría
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) throws SQLException


{
PreparedStatement stmt = null;
try
{
stmt = obj.getConnection().prepareStatement(sql);
obj.setStatementClave(stmt);
stmt.executeUpdate();
}
finally
{
if (stmt != null) stmt.close();
}
}

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 Pág.55


José María Arranz Santamaría
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 abstract String getNif();


public abstract void setNif(String nif) throws SQLException;
public abstract String getNombre();
public abstract void setNombre(String nombre) throws SQLException;
public abstract int getEdad();
public abstract void setEdad(int edad) throws SQLException;
public abstract java.util.Date getAlta();
public abstract 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 Pág.56


José María Arranz Santamaría
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 String m_nif;


protected String m_nombre;
protected int m_edad;
protected 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 Pág.57


José María Arranz Santamaría
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 Pág.58


José María Arranz Santamaría
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 Pág.59


José María Arranz Santamaría
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() + " nif: " + getNif() + " última op.: " +
getUltimaOperacion();
}
}

4.3.3.5 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 Pág.60


José María Arranz Santamaría
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 Pág.61


José María Arranz Santamaría
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 Pág.62


José María Arranz Santamaría
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 muchos-
muchos 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 Pág.63


José María Arranz Santamaría
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 Pág.64


José María Arranz Santamaría
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 tabla,PreparedStatement stmt) throws


SQLException
{
stmt.setLong(1,m_saldo);
stmt.setString(2,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.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 Pág.65


José María Arranz Santamaría
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 Pág.66


José María Arranz Santamaría
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 abstract String getNif();


public abstract void setNif(String nif) throws SQLException;
public abstract String getNombre();
public abstract void setNombre(String nombre) throws SQLException;
public abstract int getEdad();
public abstract 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 Pág.67


José María Arranz Santamaría
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 Pág.68


José María Arranz Santamaría
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 Pág.69


José María Arranz Santamaría
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) throws SQLException


{
m_alta = alta;
actualizar();
}

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 Pág.70


José María Arranz Santamaría
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 Pág.71


José María Arranz Santamaría
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 Pág.72


José María Arranz Santamaría
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 Pág.73


José María Arranz Santamaría

You might also like