Documentos de Académico
Documentos de Profesional
Documentos de Cultura
INDICE
1. INTRODUCCIN....................................................................................................................................... 3 2. PLANTEAMIENTO DEL PROBLEMA .................................................................................................. 5 2.1 DECISIONES DE IMPLEMENTACIN........................................................................................................ 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 APLICACIN (APPLICATION IDENTITY) ............................................. 6 2.1.5 RELACIONES ENTRE TABLAS EXPRESADAS A TRAVS 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 CLSICAS DE MODELAR ESQUEMAS RELACIONALES 6 2.1.8 SE USAR ANSI SQL 92 ....................................................................................................................... 6 2.1.9 NO SE USARAN CARACTERSTICAS ORIENTADAS A OBJETOS DE SQL3 (ANSI SQL1999).................... 7 2.1.10 USO DE CLAVES SEMNTICAS 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. SOLUCIN 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 RELACIN UNO MUCHOS ................................................................................................................. 29 4.2.4 RELACIN 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 RELACIN UNO-MUCHOS ................................................................................................................... 59 4.3.4 RELACIN MUCHOS-MUCHOS ............................................................................................................ 62 4.3.5 HERENCIA ............................................................................................................................................ 65 5. CONLUSIONES ........................................................................................................................................ 72 5.1 NECESIDAD DE UN FRAMEWORK.......................................................................................................... 72 5.2 NECESIDAD DE FRAMEWORKS MS SOFISTICADOS ............................................................................ 72 5.3 ORIENTACIN 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 ESTNDAR ...................... 73
persistencia_java.doc v.1.0 Jos Mara Arranz Santamara Pg.2
1. INTRODUCCIN
La persistencia o el almacenamiento permanente, es una de las necesidades bsicas de cualquier sistema de informacin de cualquier tipo. Desde las tarjetas perforadas hasta los modernos sistemas actuales de bases de datos, las tecnologas persistentes han realizado un largo viaje en la historia de la informtica. El problema de cmo programar la persistencia, es decir automatizar las acciones de almacenamiento y recuperacin de datos desde el sistema de almacenamiento, es uno de los ms relevantes en cualquier sistema software que manipula informacin no voltil, hasta el punto de que condiciona enormemente la programacin del mismo. El problema de la programacin 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, jerrquico, orientado a objetos etc), obviamente ofrece su propio sistema de acceder a la informacin de forma programtica. As por ejemplo en una base de datos relacional podemos hacer programas de gestin de datos en SQL, pues uniendo las operaciones DDL, DML y los procedimientos almacenados, tenemos un completo (aunque no muy estructurado y no muy estndar) lenguaje de programacin. De hecho el uso intensivo de SQL unido a herramientas de programacin 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 jerrquica como ADABAS de Software AG que acceder a sus datos a travs de su lenguaje Natural. Sin embargo SQL no fue diseado obviamente como un lenguaje de propsito general (para hacer cualquier tipo de programa), adems el problema de las herramientas visuales apoyadas en SQL es que han estado orientadas desde sus comienzos al diseo rpido de aplicaciones relativamente pequeas, con lgicas de negocio no muy complejas, por su carcter interpretado no son muy eficientes, son muy monolticas, obvian los modernos paradigmas de la programacin y adems 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 ejecucin de un programa desarrollado con alguna herramienta de programacin sea de libre distribucin). Esto conlleva a que siempre ha sido una necesidad que los habituales lenguajes de programacin de propsito 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 estndares como el ANSI SQL/CLI para SQL dinmico (desarrollado por Microsoft de forma ms propietaria como ODBC en Windows, evolucionando a niveles ms altos de abstraccin como OleDB y ADO), y el SQL embebido, tambin un estndar ANSI para SQL esttico, han ayudado a que este terreno no fuera un absoluto reino de taifas. Otras tecnologas propietarias como el antiguo RogueWave DBTools++ para C++ tuvieron tambin su momento y popularidad. Sin embargo la popularizacin de Java1 y su fuerte apuesta por la estandarizacin promovida por su creador, Sun Microsystems, ha dado lugar a una serie de estndares 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 cdigo fuente no dependa de la base de datos concreta a la que se conecta, y unas ms que otras pretenden reducir al mnimo la incompatibilidad entre la tecnologa de la base de datos y el modelo orientado a objetos de Java (la llamada impedance mistmatch). La ms antigua de todas y la ms 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 dinmico) a travs de una conexin 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 tecnologas de persistencia subyacen en JDBC tal y como habitualmente J2EE BMP, implementaciones de JDO y SQLJ.
Pg.3
Tecnologas de Persistencia en Java, una comparativa desde la prctica El JDBC como tecnologa bsica 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 estn presentes en los sistemas de informacin. Enfocaremos el problema de la persistencia claramente desde la orientacin a objetos, el gran paradigma de la programacin, nuestro problema ser cmo almacenar nuestro modelo de informacin expresado en objetos Java en una base de datos relacional, buscando a la vez el mximo nivel de transparencia de la gestin de la persistencia, es decir, que las clases que representan nuestro modelo de informacin, estn lo ms posible libres de cdigo de acceso a la base de datos. De esta manera conoceremos lo que aporta cada estrategia en la consecucin 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 cdigo que da lugar el uso de las diversas tcnicas para resolver idnticos o muy similares problemas (pues veremos que la eleccin influye un poco en el problema). Esto no quiere decir que huyamos totalmente de hacer valoraciones, o que nuestro anlisis pretenda ser universal pues se basar en problemas concretos, aunque bastante habituales y representativos. Podremos ver numerosos ejemplos de cdigo planteados de forma sistemtica. No se mostrar todo el cdigo sino el imprescindible para poder entender los requisitos de programacin que nos exige cada tecnologa.
Pg.4
Es la aproximacin ms 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 tambin llamada vertical. Esto exigir que las tablas tengan relaciones 1-1, partiendo de la tabla que se corresponda con la clase ms alta del rbol de derivacin como tabla primaria. En el caso de la herencia este mtodo tiene sus ventajas y sus inconvenientes (peor rendimiento que otras opciones), pero lo que si es claro es que es la expresin ms cercana al concepto de herencia en bases de datos relacionales sin caractersticas de orientacin 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.
A Relational Model of Data for Large Shared Data Banks. Codd, E.F. CACM. 1970. The Fundamentals of Mapping Objects to Relational Databases, Scott W. Ambler, 2003, http://www.agiledata.org/essays/mappingObjects.html
Pg.5
2.1.2
Al igual que en el caso anterior no es la nica opcin. En el caso de herencia cada objeto se corresponder con las filas de las correspondientes tablas relacionadas a travs 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 forneas. 2.1.4 Identidad gestionada por la aplicacin (application identity)
Es decir los valores de las claves que dan identidad a las filas de las tablas sern impuestos por nuestro programa y se correspondern 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 travs 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 existirn atributos de clase con el correspondiente puntero o coleccin, pero existirn mtodos en la clase (gets) que nos devolvern el puntero o la coleccin de los objetos relacionados. Esto supone que las clases que representan informacin persistente tendern a encapsular los accesos a la base de datos para modelar por s mismas las relaciones, esta es una buena prctica de programacin. La finalidad aparte de la conveniente encapsulacin es conseguir la ilusin 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 crearn clases de utilidad o clases Home de acuerdo con la filosofa del patrn DAO5 (que es un caso particular para persistencia del clsico patrn Faade6), correspondientes a cada clase persistente vinculada a una tabla, con la finalidad de encapsular la gestin 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 homognea que no dependa de la tecnologa de persistencia ni del tipo de base de datos en la misma lnea de la ilusin de manejar POJOs. 2.1.7 Se tender hacia el respeto de las formas clsicas 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 tecnologa respecto a su capacidad de modelar en clases Java, modelos de bases de datos antiguos (legacy) previos a la existencia incluso de la propia tecnologa. Se intentar que el modelo relacional elegido no vare por culpa de la estrategia seguida de la persistencia Java. 2.1.8 Se usar ANSI SQL 92
Debido a que este estndar est ampliamente soportado por la mayora de las bases de datos comerciales y gratuitas del mercado. De esta manera nuestros modelos sern muy independientes de las bases de datos elegidas, la base de datos elegida ser por tanto irrelevante desde el punto de vista de la programacin.
Pg.6
2.1.9
El uso de estas caractersticas podra simplificar notablemente la programacin, sin embargo es un hecho que este estndar dista mucho de estar adoptado por la mayora de las bases de datos modernas. 2.1.10 Uso de claves semnticas en el modelo relacional
El uso de claves semnticas, 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 prctica de diseo de base de datos, pues el modelo resultantes es extremadamente rgido 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 prctica muy extendida y an sigue sindolo, aunque su uso tender a decaer en la medida que las herramientas de gestin de la persistencia tienden a sugerir un modelo de identidad no semntico. De esta manera pondremos a prueba las programacin de la persistecia respecto a su capacidad de gestionar la identidad impuesta por un modelo de base de datos previo a la eleccin de la tecnologa de persistencia.
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 ms 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.
Implementaremos la clase Java y las operaciones necesarias para almacenar una instancia, actualizar los datos no clave, y su eliminacin en la base de datos (el ciclo de vida), as como las consultas tpicas 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 travs de la entidad CuentaCliente. Dicha entidad no representa la cuenta corriente sino ms bien la vinculacin 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 nmero de cuenta (ncc), otro atributo significativo ser el instante de la ltima operacin que ha realizado el cliente sobre su cuenta.
Pg.7
Implementaremos el ciclo de vida de ambas entidades, consultas por cada de uno de sus atributos y la relacin expresada en Java a travs de un puntero y una coleccin. 2.2.3 Modelo muchos-muchos entre entidades
A continuacin 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 relacin muchos-muchos entre entidades. Como sabr el lector, en el modelo relacional la relacin 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 unin de ncc y nif como identidad en conjunto. El siguiente diagrama UML, expresa la relacin directa muchos-muchos entre entidades y como se consigue a travs la una entidad intermedia necesaria para el modelo relacional (en un modelo puramente Java no es necesaria).
Cliente #m_nif:String #m_nombre:String 0..* #m_edad:int #m_alta:Date CuentaCorriente 0..* #m_ncc:String #m_saldo:long
2.2.4
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 informacin, incluso en el modelo de informacin del banco en cuanto se modele la informacin de los empleados del banco por ejemplo. Es fcil entender que estos datos son propios de cualquier Persona, si una persona dada es a su vez Cliente del banco se entiende que adems 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 especfico de su papel como cliente del banco. Esta relacin conceptual entre Persona y Cliente en persistencia_java.doc v.1.0 Jos Mara Arranz Santamara Pg.8
programacin orientada a objetos se ha modelado clsicamente como una herencia entre una clase o entidad Persona y Cliente, ms exactamente Cliente hereda las propiedades de Persona y aade las suyas especficas.
Cliente #m_alta:Date
CREATE TABLE cliente ( nif CHAR(9) PRIMARY KEY, nombre VARCHAR(200), edad SMALLINT, alta DATE );
2.3.2
Notar como la clave primaria ncc y la clave fornea nif determinan la relacin uno muchos. La restriccin 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)
Pg.9
En este caso la combinacin nif y ncc forman la clave primaria por eso la relacin es muchos-muchos, a su vez son claves forneas lo que muestra que esta tabla muestra el vnculo entre el par de filas cliente cuenta que deben existir previamente. La restriccin 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 implementacin que adoptamos anteriormente, la herencia de dos clases se manifestar como dos tablas relacionas con una relacin 1-1, siendo la tabla principal la que coincide con la clase ms alta en el rbol de derivacin. En el caso de la herencia se produce una duplicacin de columnas clave necesario en el modelo relacional, dicha duplicidad no la repetiremos en el modelo de clases, esta sera una excepcin a la regla de correspondencia atributocolumna enunciada anteriormente.
CREATE TABLE persona ( nif CHAR(9) PRIMARY KEY, nombre VARCHAR(200), edad SMALLINT ); CREATE TABLE cliente ( nif CHAR(9) PRIMARY KEY, alta DATE, FOREIGN KEY (nif) REFERENCES persona (nif) );
De acuerdo con este esquema puede existir un registro en la tabla persona que no est en la tabla cliente, pero al contrario, si existe un registro en cliente necesariamente debe existir en la tabla persona.
Pg.10
3. HERRAMIENTAS
Todo desarrollo software exige unas herramientas de ayuda al desarrollo y de ejecucin. Compilador, libreras y mquina virtual Java: usaremos el paquete ms moderno de Sun hasta el momento, el J2SE 1.4.2 SDK7 Entorno de desarrollo: NetBeans IDE8 v3.5 Herramientas de compilacin y de gestin de configuracin: Ant9 v1.5.1, la versin 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 gestin visual de Bases de Datos: Squirrel SQL11 v1.2 Beta 3
El hecho de usar una base de datos muy estndar 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 estndar ANSI SQL 92 en las bases de datos comerciales y de cdigo abierto, ilustra que la eleccin de la base de datos pueda hacerse hoy por criterios de rendimiento, escalabilidad y quizs por sus herramientas de gestin, pero no por razones de acoplamiento tecnolgico 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 lnea de cdigo (en la medida en que no nos salgamos del SQL estndar), pero ganando evidentemente la capacidad de gestionar enormes cantidades de informacin, para miles de usuarios concurrentes a gran velocidad.
10
11
Pg.11
4. SOLUCIN JDBC
La especificacin JDBC es la ms antigua de todas las tecnologas de persistencia Java, y en ese sentido la ms 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 bsicos de Java (enteros, cadenas, coma flotante ...). Estrictamente hablando, JDBC no es ms que una especificacin 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 especificacin, 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 especificacin, salvo en lo que concierne a las propias sentencias SQL, que son pasadas como cadenas de argumento, en donde s puede haber una variacin significativa de una base de datos a otra (si se usan las caractersticas SQL menos estndar de cada marca). Es relativamente sencilla de entender y utilizar, y muy flexible precisamente para adaptarse a las diversas estrategias de manipulacin de datos, puesto que no impone ninguna filosofa 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 tecnologa muy flexible pero a su vez con una alta impedancia respecto a sincronizar clases del usuario con tablas. Como nuestra finalidad es conseguir dicha sincronizacin entre tablas y clases, filas e instancias, seguiremos las pautas que ya enunciamos en el apartado de Decisiones de Implementacin, estas decisiones nos acotan bastante nuestras posibilidades. Aun as usaremos varias filosofas de programacin, cuyos nombres vendrn dados por su similitud a las filosofas empleadas en las tecnologas J2EE EJB Entity Beans: BMP (Bean Managed Persistence) y CMP (Container Managed Persistence). Clasificaremos nuestros ejemplos en tres filosofas: Tipo BMP: se asemejar a la forma de programar un EJB BMP aunque obviamente sin las caractersticas que se obtienen por el hecho de utilizar los servicios de autentificacin, 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 sistemticamente algunos algoritmos tpicos de acceso a base de datos, y por otra parte llevaremos a las clases de utilidad (Home) la mayor parte del cdigo de gestin de la persistencia. Tipo CMP: pretender ser similar en filosofa a los EJB CMP, aprovechando parte de la infraestructura definida en el tipo BMP 2.
4.1.1
Una Tabla
4.1.1.1
NoEncontradoException.java
Se usar cuando necesitemos lanzar una excepcin que indique que no se puede leer una fila en la base de datos que debera existir. Reutilizremos esta clase en todos los casos que consideremos, de ah que la situemos en un paquete ms alto (paquete jdbc) que los paquetes especficos 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 conexin. La reutilizaremos de nuevo en los dems ejemplos de uso de JDBC y de esta manera no repetimos un cdigo que es idntico.
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 mtodos que una clase persistente debe implementar.
package jdbc.tipobmp.comun; import java.sql.*; public interface Persistente {
Pg.13
Observamos la presencia de los mtodos que asocian y obtienen la conexin con la base de datos, la asociacin con el objeto de utilidad, la eliminacin y la actualizacin del registro de la base de datos. La insercin se realizar a travs del mtodo crear() especfico de cada clase persistente. 4.1.1.4 PersistenteHome.java
Es la interfase con los mtodos 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 mtodos necesarios para sincronizar su estado con el de la fila correspondiente de la base de datos. Adems implementa los mtodos de bsqueda ms habituales tal y como buscar el cliente con el nif dado, los clientes con un nombre dado, edad, fecha de alta (devolviendo una coleccin de objetos Cliente), un mtodo que cuenta los clientes que existen (contar()) y un mtodo especial, buscarAbierto() con la finalidad de poder hacer consultas menos tpicas 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; }
Pg.14
Pg.15
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
Pg.16
Pg.17
A simple vista podemos comprobar la gran cantidad de sentencias similares, esto es mala seal, significa que no hemos hecho apenas ningn esfuerzo de abstraccin. La repeticin sistemtica de cdigo conlleva el problema de dar lugar a programas muy rgidos y muy difciles de evolucionar, pues cualquier pequeo cambio en la filosofa de hacer algo puede suponer una modificacin del cdigo en infinidad de lugares del cdigo, con el correspondiente riesgo de olvidar algunos sitios. Tras la inspeccin de los mtodos 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 mayora 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. La consulta de un conjunto de elementos supone la formacin de una coleccin de objetos, correspondientes a los registros resultantes de la consulta. Consideremos una consulta de 1000 de resultados cuando slo 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 cargarn 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 lmite la capacidad del sistema). Los mtodos de consulta no hacen ningn tipo de comprobacin de si un objeto ya ha sido cargado anteriormente y est en memoria, por lo que vuelve a ser cargado con la prdida de rendimiento que supone esto significa que no aprovechamos las acciones previas para evitar acciones futuras innecesarias.
2.
3.
Los puntos 1 y 2 es posible minizarlos con lo que se llama la carga perezosa o lazy loading, que consiste bsicamente en cargar bajo demanda, es decir cuando se intente usar el atributo o se avance en el recorrido de la coleccin, se cargar de la base de datos la correspondiente columna o fila. Conseguir la lazy loading no es un problema trivial.
Pg.18
Tecnologas de Persistencia en Java, una comparativa desde la prctica El punto 3 evidencia la falta de algn tipo de cach de objetos persistentes que se consultara antes de acudir a la base de datos, proceso que por muy rpida que fuera la base de datos es significativamente ms lento que una bsqueda en memoria de un pequeo fragmento de la informacin de la base de datos, por ello cada vez que hacemos una bsqueda consultamos a la base de datos informacin 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 (mtodos 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 bsqueda para que se haga primero en la cach, lo cual es una gran dificultad en el caso de bsquedas abiertas pues supondra 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 gestin de la persistencia ms hay de las simples llamadas a JDBC que aqu exponemos, poniendo en evidencia la necesidad de tecnologas ms avanzadas. 4.1.1.6 ClienteHome.java
Como ya dijimos anteriormente, la finalidad de esta clase es la de encapsular las operaciones de creacin de un nuevo objeto persistente (a modo de clase factora, lo cual buena prctica pues asegura que el objeto persistente queda asociado a una conexin y al objeto Home que lo cre) y transferir las llamadas de bsqueda de objetos a un objeto persistente auxiliar creado al efecto.
package jdbc.tipobmp.unatabla; import import import import java.sql.*; java.util.*; jdbc.NoEncontradoException; jdbc.tipobmp.comun.*;
public class ClienteHome implements PersistenteHome { protected Connection m_con; public ClienteHome(Connection con) { m_con = con; } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public Cliente buscarPorClavePrimaria(Object clave) throws SQLException,NoEncontradoException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarPorClavePrimaria(clave); } public Collection buscarPorEdad(int edad) throws SQLException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarPorEdad(edad); } public Collection buscarPorNombre(String nombre) throws SQLException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarPorNombre(nombre); } public Collection buscarAbierto(String sql) throws SQLException { Cliente obj = (Cliente)crearObjeto(); return obj.buscarAbierto(sql); }
Pg.19
4.1.1.7
JDBCInicio.java
Esta clase es un programa ejemplo del tipo de operaciones tpicas de la manipulacin 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, bsquedas ms sofisticadas y por ltimo la destruccin. Todo ello en una transaccin 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","Iaki 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("Iaki 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); }
Pg.20
Podramos seguir con los casos de relacin uno-muchos, muchos-muchos y herencia. Sin embargo hemos visto que hay un montn de cdigo repetitivo, intentaremos generalizar las operaciones con el fin de disminuir la cantidad de cdigo elevando la calidad del mismo, para ello desarrollamos un pequeo framework persistente en el llamado tipo BMP 2 y desde dicho modelo afrontaremos el problema de las relaciones y la herencia.
Pg.21
Tecnologas de Persistencia en Java, una comparativa desde la prctica patrn, pues despeja lo ms posible la clase persistente acercndonos al ideal de la persistencia transparente del modelo de informacin (digamos que la persistencia se aglutina aparte en clases especiales destinadas para ello) y es tambin en contrapartida un error en la filosofa de los EJB BMP, pues en ellos se mezcla el tratamiento de la persistencia del objeto respecto a la fila (el patrn de uso ms habitual de los BMP), con la manipulacin de conjuntos a travs de las llamadas de bsqueda (findByXXX), el hecho de que los BMP mantengan una conexin y se especificara su contrato con el container, invitara a los diseadores de la especificacin inicial a que definir una lgica similar a posibles clases Home podra suponer una reiteracin del modelo (a modo de Entity Bean Home). De todas formas siempre se puede minimizar este problema de diseo en los EJB BMP creando una especie de implementacin de la interfase Home llamada desde el bean ahora ms despejado de cdigo persistente. 4.2.1 Clases Comunes (framework)
Aunque expondremos el cdigo de las clases genricas, lo ms 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 idntica al caso anterior, sin embargo veremos que no ser necesario implemenmtar sus mtodos en cada clase persistente, sino que lo haremos en una clase base genrica. 4.2.1.2 PersistenteHome.java
Esta interfase ser ms complicada que en el caso anterior, puesto que esta serie de mtodos han de ser implementados por una parte por la clase que implemente el interfaz de forma genrica, pero tambin por la clase Home correspondiente a cada clase persistente para aquellos aspectos que no son fcilmente generalizables.
package jdbc.tipobmp2.comun; import java.sql.*; import jdbc.NoEncontradoException; public interface PersistenteHome { public void setConnection(Connection con); public Connection getConnection(); public abstract Persistente crearObjeto(); public abstract Persistente crearObjeto(Object clave); public void setStatementParams(Persistente obj,String table,PreparedStatement stmt) throws SQLException; public void setStatementClave(Persistente obj,PreparedStatement stmt) throws SQLException; public public public public public } void void void void void eliminar(Persistente obj) throws SQLException; actualizar(Persistente obj) throws SQLException; insertar(Persistente obj) throws SQLException; leer(Persistente obj) throws SQLException,NoEncontradoException; leer(Persistente obj,ResultSet res) throws SQLException;
4.2.1.3
PersistenteImpl.java
Pg.22
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 conexin y referencia al objeto Home que crea el objeto persistente, y los mtodos actualizar() y eliminar() que derivan la llamada al objeto Home. De hecho nada impedira poner este cdigo 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 conexin 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
Pg.23
public Persistente crear(Object clave) throws SQLException { Persistente obj = crearObjeto(clave); return crear(obj); } public void insertar(Persistente obj,String table,String sql) { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); setStatementParams(obj,table,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } public void actualizar(Persistente obj,String table,String sql) throws SQLException { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); setStatementParams(obj,table,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } public void eliminar(Persistente obj,String table,String sql) { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql); setStatementClave(obj,stmt); stmt.executeUpdate(); } finally { if (stmt != null) stmt.close(); } } throws SQLException throws SQLException
public void leer(Persistente obj,String sql) throws SQLException,NoEncontradoException { PreparedStatement stmt = null; try { stmt = obj.getConnection().prepareStatement(sql);
Pg.24
Bsicamente 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 mtodos 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 disminucin muy significativa de cdigo sin perder funcionalidad y apenas flexibilidad, lo cual redunda en la calidad del cdigo resultante.
Pg.25
La clase persistente que representa al cliente. Notar la casi completa inexistencia de cdigo relacionado con la persistencia, gracias a que la inmensa mayor parte residir en la clase Home creada al efecto. Varios de sus mtodos sern llamados por la clase Home, como por ejemplo el mtodo crear() que define todos los datos de la instancia y que se llamar en creacin 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() {
Pg.26
4.2.2.2
ClienteHome.java
Es la clase que ms nos interesa respecto a la persistencia, pues sobre ella recae la coordinacin del objeto persistente, teniendo a su vez un contrato con el framework a travs de su clase base.
package jdbc.tipobmp2.unatabla; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class ClienteHome extends PersistenteHomeImpl { public ClienteHome(Connection con) throws SQLException { super(con); } public void insertar(Persistente obj) { insertar(obj,"cliente","INSERT VALUES(?,?,?,?)"); } throws SQLException INTO cliente (nombre,edad,alta,nif)
public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"cliente","UPDATE cliente SET nombre=?, edad=?, alta=? WHERE nif=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cliente","DELETE FROM cliente WHERE nif=?"); } public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM cliente WHERE nif=?"); } public void leer(Persistente obj,ResultSet res) { Cliente cliente = (Cliente)obj; cliente.setNif(res.getString("nif")); cliente.setNombre(res.getString("nombre")); cliente.setEdad(res.getInt("edad")); cliente.setAlta(res.getDate("alta")); } public void setStatementParams(Persistente stmt) throws SQLException { Cliente cliente = (Cliente)obj; obj,String tabla,PreparedStatement throws SQLException
Pg.27
stmt.setString(1,cliente.getNombre()); stmt.setInt(2,cliente.getEdad()); stmt.setDate(3,new java.sql.Date(cliente.getAlta().getTime())); stmt.setString(4,cliente.getNif()); } public void setStatementClave(Persistente SQLException { Cliente cliente = (Cliente)obj; stmt.setString(1,cliente.getNif()); } public Collection buscarPorEdad(int edad) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente WHERE edad=?"; stmt = m_con.prepareStatement(sql); stmt.setInt(1,edad); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public Collection buscarPorNombre(String nombre) throws SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cliente WHERE nombre=?"; stmt = m_con.prepareStatement(sql); stmt.setString(1,nombre); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } public int contar() throws SQLException { Statement stmt = null; try { String sql = "SELECT COUNT(*) AS num FROM cliente"; stmt = m_con.createStatement(); ResultSet res = stmt.executeQuery(sql); res.next(); return res.getInt("num"); } finally { if (stmt != null) stmt.close(); } } public Cliente crear(String nif,String throws SQLException { Cliente obj = (Cliente)crearObjeto(); nombre,int edad,java.util.Date alta) obj,PreparedStatement stmt) throws
Pg.28
Los mtodos insertar, actualizar, leer, setStatementParams, setStatementeClave, crear, crearObjeto, son contractuales, realizan aquellas tareas que es difcil hacer de forma genrica y se hacen aqu de forma sencilla en la clase especfica 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 travs de la clase base. 4.2.2.3 JDBCInicio.java
Puede ser idntico 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 programacin orientada a objetos y la aplicacin de patrones de programacin, que una parte de un programa puede cambiar profundamente pero mientras no cambie el contrato que exista con el resto de la aplicacin, lo dems no tiene que cambiar. En la prctica esto no ocurre tan idealmente, pero se consigue que las implicaciones de los cambios con el resto de la aplicacin sean mnimos respecto a una programacin con reutilizacin nula. 4.2.3 Relacin Uno Muchos
Reutilizaremos la infraestructura definida para el caso de una tabla, adaptaremos el resto de las clases para introducir la relacin 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 introduccin de un mtodo para obtener las cuentas corrientes que son propiedad del cliente, esto lo conseguimos a travs de una bsqueda 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 idntico funcionalmente al caso de una tabla, salvo la introduccin del mtodo 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);
Pg.29
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 bsqueda de la cuenta y la segunda en la bsqueda 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 ms rpido que dos consultas separadas, pues hay que aadir que adems hay un ahorro en el transporte de informacin 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 caractersticas ms avanzadas de la base de datos concreta. 4.2.3.3 CuentaCliente.java
Pg.30
"
nif:
"
m_nif
"
ltima
op.:
"
Observamos el mtodo getCliente() que obtiene a travs 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 gestin del ciclo de vida de los objetos CuentaCliente y para realizar bsquedas.
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=?"); }
Pg.31
public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM cuenta_cliente WHERE ncc=?"); } public void leer(Persistente obj,ResultSet res) { CuentaCliente cuenta = (CuentaCliente)obj; throws SQLException
cuenta.setNif(res.getString("nif")); cuenta.setNcc(res.getString("ncc")); cuenta.setUltimaOperacion(res.getTimestamp("ultima_op")); } public void setStatementParams(Persistente obj,String stmt) throws SQLException { CuentaCliente cuenta = (CuentaCliente)obj; stmt.setTimestamp(1,cuenta.getUltimaOperacion()); stmt.setString(2,cuenta.getNif()); stmt.setString(3,cuenta.getNcc()); } public void setStatementClave(Persistente obj,PreparedStatement SQLException { CuentaCliente cuenta = (CuentaCliente)obj; stmt.setString(1,cuenta.getNcc()); } public Collection buscarPorDespuesUltimaOp(Timestamp instante) SQLException { PreparedStatement stmt = null; try { String sql = "SELECT * FROM cuenta_cliente WHERE ultima_op >= ?"; stmt = m_con.prepareStatement(sql); stmt.setTimestamp(1,instante); ResultSet res = stmt.executeQuery(); return leer(res); } finally { if (stmt != null) stmt.close(); } } throws stmt) throws tabla,PreparedStatement
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
Pg.32
4.2.3.5
JDBCInicio.java
Como ejemplo de uso nos interesar como novedad navegar por las relaciones, dicha navegacin 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 travs del cliente las cuentas asociadas con Cliente.getClienteCuentas(), a travs 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 {
Pg.33
cuentaCli1 = (CuentaCliente)cuentaCliHome.buscarPorClavePrimaria("111"); System.out.println(cuentaCli1); Collection col = cliente1.getClienteCuentas(); for(Iterator it = col.iterator(); it.hasNext(); ) { CuentaCliente cuentaCli = (CuentaCliente)it.next(); System.out.println(cuentaCli); } Cliente cliente = cuentaCli1.getCliente(); System.out.println(cliente); col = cuentaCliHome.buscarPorNcc("111"); for(Iterator it = col.iterator(); it.hasNext(); ) { CuentaCliente cuentaCli = (CuentaCliente)it.next(); System.out.println(cuentaCli); } col = cuentaCliHome.buscarPorDespuesUltimaOp(new GregorianCalendar(2003,8,20,10,0,0).getTimeInMillis())); for(Iterator it = col.iterator(); it.hasNext(); ) { CuentaCliente cuentaCli = (CuentaCliente)it.next(); System.out.println(cuentaCli); } 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); } Timestamp(new
Pg.34
4.2.4
Reutilizamos la infraestructura persistente genrica 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 vnculo entre la cuenta y el cliente, ahora tendr como identidad conjunta el nif y el ncc, , en sincrona con su correspondiente tabla cuenta_cliente que ser ahora la tabla intermedia cuenta_cliente que exprese la relacin muchosmuchos en el modelo relacional. Recordar que estrictamente hablando dos clases pueden tener una relacin muchos-muchos sin necesidad de recurrir a una clase intermedia, dicha clase intermedia viene impuesta por el modelo relacional, aunque tambin es cierto (y es lo habitual) que podramos 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 informacin til en la relacin cliente-cuenta, aparte del vnculo entre identidades, tal y como el instante en el cliente dado hizo su ltima operacin sobre la cuenta dada. De todas formas tambin ofreceremos mtodos en Cliente y CuentaCorriente que devolvern las respectivas colecciones de cuentas y clientes de igual manera que se hara en un modelo Java normal. 4.2.4.1 Cliente.java
La gran novedad respecto al caso anterior uno-muchos es el mtodo 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 bsqueda en vez de suministrar directamente el valor clave (en este caso la cadena del nif). 4.2.4.2 ClienteHome.java
La variacin relevante respecto al caso uno-muchos es el mtodo de bsqueda (con dos formas) de los clientes asociados a una cuenta dada como argumento:
public Collection buscarPorCuenta(String ncc) throws SQLException
Pg.35
Notar que se hace un join de tres tablas, pero esto es mucho ms eficiente que hacer sucesivas consultas ms simples. 4.2.4.3 CuentaCliente.java
La novedad respecto al caso uno-muchos es un nuevo mtodo getCuenta() que devuelve la cuenta corriente de esta asociacin cliente-cuenta, al igual que el mtodo getCliente() lo haca 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 ms exactamente de las claves del vnculo clientecuenta, pues como dijimos ahora la clave es la combinacin 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 tambin otro objeto cadena con el nmero 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) {
Pg.36
4.2.4.5
CuentaClienteHome.java
No introducimos ninguna funcionalidad nueva excepto las consecuencias de manejar ahora una clave compuesta, por ejemplo el uso del nuevo objeto clave CuentaClienteClave .
public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"cuenta_cliente","UPDATE cuenta_cliente SET ultima_op=? WHERE nif=? AND ncc=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cuenta_cliente","DELETE FROM cuenta_cliente ncc=?"); }
WHERE
nif=?
AND
public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM cuenta_cliente WHERE nif=? AND ncc=?"); } . . . public void setStatementClave(Persistente obj,PreparedStatement SQLException { CuentaCliente cuenta = (CuentaCliente)obj; stmt.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()); } stmt) throws
4.2.4.6
CuentaCorriente.java
Representar a la cuenta corriente, el nmero 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; // Nmero de la cuenta corriente protected long m_saldo;
Pg.37
4.2.4.7
CuentaCorrienteHome.java
Como relevante de esta clase est el mtodo buscarPorCliente() que a travs de joins obtiene las cuentas corrientes de una cliente dado, de forma similar a como se obtenan 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
Pg.38
Pg.39
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 travs de las relaciones entre cuentas y clientes.
package jdbc.tipobmp2.muchosmuchos; import java.sql.*; import java.util.*; import java.text.*;
Pg.40
Pg.41
4.2.5
Herencia
A continuacin modelaremos el ejemplo de herencia Cliente-Persona. La herencia es una paradigma bsico de la programacin orientada a objetos, pero que no concuerda bien con el modelo relacional (sin extensiones de orientacin a objetos). Como ya enunciamos en las decisiones de implementacin, 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 ms simtrico 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 gestin 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 slo 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 cdigo que resida 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;
Pg.42
Pg.43
Notar la novedad de los mtodos equals() y hashCode() que no han estado presentes hasta ahora. Justificaremos ms 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 cdigo de la clase Cliente del caso de una tabla.
package jdbc.tipobmp2.herencia; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class PersonaHome extends PersistenteHomeImpl { public PersonaHome(Connection con) throws SQLException { super(con); } public void insertar(Persistente obj) throws SQLException { insertar(obj,"persona","INSERT INTO persona (nombre,edad,nif) VALUES(?,?,?)"); } public void actualizar(Persistente obj) throws SQLException { actualizar(obj,"persona","UPDATE persona SET nombre=?, edad=? WHERE nif=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"persona","DELETE FROM persona WHERE nif=?"); } public void leer(Persistente obj) throws SQLException,NoEncontradoException { leer(obj,"SELECT * FROM persona WHERE nif=?"); } public void leer(Persistente obj,ResultSet res) { Persona persona = (Persona)obj; persona.setNif(res.getString("nif")); persona.setNombre(res.getString("nombre")); persona.setEdad(res.getInt("edad")); } public void setStatementParams(Persistente stmt) throws SQLException { Persona persona = (Persona)obj; stmt.setString(1,persona.getNombre()); stmt.setInt(2,persona.getEdad()); stmt.setString(3,persona.getNif()); } public void setStatementClave(Persistente SQLException { Persona persona = (Persona)obj; stmt.setString(1,persona.getNif()); } obj,PreparedStatement stmt) throws obj,String table,PreparedStatement throws SQLException
Pg.44
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(); }
Pg.45
Especialmente interesante es el mtodo buscarTodosConHerencia() cuya mtodo 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 mtodo ClienteHome.buscarTodos() es idntico salvo que los objetos retornados son los objetos Cliente correspondientes a los objetos Persona.
public Collection buscarTodosConHerencia() { Set res = new HashSet(); throws SQLException
Una coleccin del tipo java.util.HashSet, es por definicin al implementar la interfaz Set, una coleccin que no admite duplicados, la duplicidad es comprobada a travs de los mtodos 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 tambin en la coleccin HashSet la coleccin 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 posicin en memoria (el comportamiento por defecto), y como los objetos Cliente heredan este concepto de identidad, la coleccin 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 ms derivado, pues es el que originariamente introdujo la informacin en la base de datos.
return res; }
Si existieran ms clases persistentes derivadas de Persona, tendramos que aadir el cdigo especfico correspondiente para que este mtodo retornara dichos objetos. Hay que constatar que existen varios problemas de rendimiento: 1. Se obtiene informacin 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 quedara minimizado con un cach de objetos.
Pg.46
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 ledo ambos registros. La optimizacin posible como sabemos es la lazy loading.
Este mismo tratamiento podra aplicarse a los diversos mtodos de bsqueda si se quisiera que se devolviera el objeto ms derivado. Implementamos slo un mtodo para ilustrar que la extraccin de datos con la herencia dista mucho de ser automtica. Existen otras tcnicas que simplificaran el cdigo y lo hara ms eficiente en el caso de la herencia, tal y como aadir en la tabla persona algn tipo de cdigo 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 clsico sin dependencias con la tecnologa de persistencia. 4.2.5.3 Cliente.java
Implementa lo que es especfico de ser cliente de un banco tal y como el alta, basndose 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 ms 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.
Pg.47
Tecnologas de Persistencia en Java, una comparativa desde la prctica En las operaciones de insercin, actualizacin y eliminacin no tenemos ms 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 travs de joins. Si inspeccionamos el cdigo vemos cmo hay mtodos que sern llamados directamente por el programador o bien por el framework que llaman a su vez a los correspondientes mtodos de la clase base coordinando las operaciones a lo largo del rbol de derivacin.
package jdbc.tipobmp2.herencia; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; import jdbc.tipobmp2.comun.*; public class ClienteHome extends PersonaHome { /** Creates a new instance of ClienteHome */ public ClienteHome(Connection con) throws SQLException { super(con); } public void insertar(Persistente obj) throws SQLException { super.insertar(obj); insertar(obj,"cliente","INSERT INTO cliente (alta,nif) VALUES(?,?)"); } public void actualizar(Persistente obj) throws SQLException { super.actualizar(obj); actualizar(obj,"cliente","UPDATE cliente SET alta=? WHERE nif=?"); } public void eliminar(Persistente obj) throws SQLException { eliminar(obj,"cliente","DELETE FROM cliente WHERE nif=?"); super.eliminar(obj); } public void leer(Persistente obj) { leer(obj,"SELECT * FROM persona.nif=cliente.nif"); } throws SQLException,NoEncontradoException cliente,persona WHERE cliente.nif=? AND
public void leer(Persistente obj,ResultSet res) { super.leer(obj,res); Cliente cliente = (Cliente)obj; cliente.setAlta(res.getDate("alta")); }
throws SQLException
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()); } }
Pg.48
AND
cliente,persona
WHERE
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");
Pg.49
Hemos tenido que repetir la mayor parte de los mtodos de bsqueda definidos en Persona, pues si son llamados desde un objeto ClienteHome se entiende que se pretende obtener objetos Cliente, necesitando consultas ms 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 ms complicados a medida que bajamos en el rbol de derivacin. De todas formas la complejidad del join es preferible a realizar consultas sucesivas en cada tabla del rbol de derivacin, pues la prdida de rendimiento sera muy signifativa: consideremos una consulta sobre la tabla cliente con 1000 resultados y que tuviramos 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 ms eficiente que 1000 consultas de un solo resultado (quizs en muy muy altos volmenes de concurrencia de usuarios pudiera igualar o superar el rendimiento las consultas mltiples, pero esto no ocurre en niveles normales de escala). Un framework ms avanzado podra componer estos joins de una forma genrica que evitara la repeticin sistemtica en cada clase especfica, sin embargo, como veremos ms 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 ms simple (una sola) o bien directamente no se plantea el problema de la herencia (EJB Entity Beans) o bien se aborda slo 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 ms simple. 4.2.5.5 JDBCInicio.java
Ponemos en accin 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/ http://www.versant.com/
13
Pg.50
Pg.51
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(); } } } }
Antes de su ejecucin 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 tambin objetos Persona, de ah que personaHome.contar() devuelva el valor 3 y clienteHome.contar() devuelta el valor 2. La coleccin devuelta por personaHome.buscarTodosConHerencia() contendr dos tres objetos: un objeto Persona y dos objetos Cliente, fcil de comprobar al mostrarse la informacin por pantalla el resultado (notar que Jose Mara Garca 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 Mara Garca edad: 45 nif: 50000000P nombre: Iaki Gabilondo edad: 45 alta: 2003-09-20
Pg.52
Tecnologas de Persistencia en Java, una comparativa desde la prctica 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 mtodos get y set tambin abstractos, y los mtodos que representan las relaciones entre clases sean tambin abstractos. En el caso de J2EE tambin son abstractos los mtodos de bsqueda que era necesario implementar en el caso de los BMP. La finalidad que persigue la filosofa 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 mtodos abstractos. En el caso de J2EE esta clase es generada dinmicamente en el despliegue (deployment) a partir de las declaraciones del bean CMP. En sntesis es un BMP automtico en donde el programador declara de forma abstracta (el bean CMP) lo que ha de implementarse por generacin de cdigo, en dicha generacin de cdigo es donde el servidor de aplicaciones demuestra su saber hacer. Nosotros seguiremos esta filosofa 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 ampliacin de las clases persistentes por abajo, eliminaremos la clase PersistenteImpl en todos los casos y el cdigo necesario lo aadiremos en las clases derivadas de las clases persistentes. Esto no afecta a la clase PersistenteHomeImpl pues supondra la reescritura de mucho cdigo idntico en las clases Home especficas, 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 implementacin de las clases Home es asunto del servidor de aplicaciones, y son generadas automticamente en el despliegue), como ahora dispondremos de una clase que deriva de la clase persistente orientada fuertemente a la gestin de la persistencia de su clase base, algunas funciones presentes en las clases Home estar ahora en estas clases de forma un poco ms adecuada. Implementaremos los mismos casos de uso que con el mtodo 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; }
Estos mtodos estaban presentes en la interfase PersistenteHome en el framework tipo BMP Avanzado, su presencia aqu es debido al movimiento de la implementacin de esta funcionalidad a la clase de gestin de la persistencia de la clase persistente. 4.3.1.2 PersistenteHome.java Pg.53
Tecnologas de Persistencia en Java, una comparativa desde la prctica Esta interfase ser casi idntica al anterior framework excepto en los mtodos que ahora estn en la interfase Persistente.
package jdbc.tipocmp.comun; import java.sql.*; import jdbc.NoEncontradoException; public interface PersistenteHome { public void setConnection(Connection con); public Connection getConnection(); public abstract Persistente crearObjeto(); public abstract Persistente crearObjeto(Object clave); public public public public } void void void void actualizar(Persistente obj) throws SQLException; eliminar(Persistente obj) throws SQLException; insertar(Persistente obj) throws SQLException; leer(Persistente obj) throws SQLException,NoEncontradoException;
4.3.1.3
PersistenteHomeImpl.java
La clase es prcticamente idntica a la correspondiente del anterior framework, las diferencias estn en las llamadas a los tres mtodos ahora presentes en el objeto Persistente en vez de en el objeto Home derivado.
package jdbc.tipocmp.comun; import java.sql.*; import java.util.*; import jdbc.NoEncontradoException; public abstract class PersistenteHomeImpl implements PersistenteHome { protected Connection m_con; public PersistenteHomeImpl(Connection con) { m_con = con; } public void setConnection(Connection con) { m_con = con; } public Connection getConnection() { return m_con; } public Persistente crear(Persistente obj) { obj.setConnection(m_con); obj.setHome(this); insertar(obj); return obj; } throws SQLException
public Persistente crear(Object clave) throws SQLException { Persistente obj = crearObjeto(clave); return crear(obj); } public void insertar(Persistente obj,String table,String sql) { PreparedStatement stmt = null; try { throws SQLException
Pg.54
Pg.55
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 gestin de la persistencia de Cliente, completando todo aquello que es abstracto. 4.3.2.1 Cliente.java
package jdbc.tipocmp.unatabla; import java.sql.*; import java.util.*; import jdbc.tipocmp.comun.*; public abstract class Cliente implements Persistente { public Cliente() { } public public public public public public public public abstract abstract abstract abstract abstract abstract abstract abstract String getNif(); void setNif(String nif) throws SQLException; String getNombre(); void setNombre(String nombre) throws SQLException; int getEdad(); void setEdad(int edad) throws SQLException; java.util.Date getAlta(); void setAlta(java.util.Date alta) throws SQLException;
Pg.56
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 mtodos set, esto es una ventaja respecto al enfoque BMP Avanzado en donde tenamos dos opciones: o hacer una llamada a actualizar() en cada mtodo set, lo cual es una intromisin de la gestin persistente en la clase, o bien llamar desde fuera explcitamente al mtodo actualizar() del objeto persistente tras modificar alguno de sus atributos (la segunda opcin es la usada en la clase JDBCInicio). Por otra parte los mtodos nuevos indicados en Persistente respecto al caso BMP se implementan ahora:
package jdbc.tipocmp.unatabla; import java.sql.*; import java.util.*; import jdbc.tipocmp.comun.*; public class ClienteImpl extends Cliente { private Connection m_con; private PersistenteHome m_home; protected protected protected protected String m_nif; String m_nombre; int m_edad; java.util.Date m_alta;
public ClienteImpl() { m_nombre = ""; m_alta = new java.util.Date(); } public ClienteImpl(String nif) { m_nif = nif; } public String getNif() { return m_nif; } public void setNif(String nif) throws SQLException { m_nif = nif; actualizar(); } public String getNombre() { return m_nombre; } public void setNombre(String nombre) throws SQLException
Pg.57
Pg.58
El inconveniente de introducir la llamada a actualizar() en los mtodos 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 actualizacin podra realizarse una sola vez, sin embargo por otra parte conseguimos un avance en la transparencia de la gestin de la persistencia. Un framework ms sofisticado (caso de los servidores de aplicaciones que implementan CMP) podra marcar el objeto como modificado (dirty) ante una modificacin de un atributo, y en el commit realizar la actualizacin de una sola vez (lgicamente esto supone que la conexin a la base de datos est gestionada por el framework, as como la apertura y cierre de la transaccin, lo cual ciertamente ocurre en los servidores de aplicaciones). Observemos la presencia de los mtodos getConnection, setConnection, getHome, setHome, actualizar y eliminar, en el caso BMP Avanzado estos mtodos estaban en la clase genrica PersistenteImpl, clase base de las clases persistentes que constituyen el modelo de datos. Esto es una desventaja en la filosofa CMP respecto a la BMP, puesto que al ampliar exclusivamente por abajo es preciso repetir sistemticamente en todas las clases de implementacin de la persistencia, cierto cdigo que es siempre el mismo. En los servidores de aplicaciones esto no supone ningn problema pues el cdigo es generado, en nuestro caso podemos eliminarlo introduciendo de nuevo la clase PersistenteImpl (es preciso recordar que estamos hablando de filosofas o estrategias no de una imitacin exacta por otra parte imposible salvo que diseramos lgicamente un servidor de aplicaciones como framework). 4.3.2.3 ClienteHome.java
La clase ClienteHome es idntica funcionalmente al caso BMP Avanzado de una tabla excepto por la ausencia de los 3 mtodos anteriormente indicados en PersistenteHome. 4.3.2.4 JDBCInicio.java
La clase es idntica funcionalmente a la correspondiente en BMP, pues hemos cambiado la forma de gestionar la persistencia pero prcticamente nada la interfaz que se ofrece a la aplicacin. La nica diferencia es que ahora no necesitamos hacer una llamada explcita 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 mtodo set definido en ClienteImpl, consiguiendo un grado ms de transparencia respecto a la aplicacin:
cliente1.setNombre("Iaki Gabilondo"); // No es necesario: cliente1.actualizar();
4.3.3
Relacin Uno-Muchos
Consideraremos dos tipos de comparacin: respecto al caso de una tabla anterior analizando los nuevos elementos fruto de la necesidad de la gestin de una relacin, y respecto al caso BMP Avanzado. 4.3.3.1 Cliente.java
Esta clase aade respecto al caso de una tabla, el mtodo necesario para gestionar la relacin con los objetos ClienteCuenta:
public abstract Collection getClienteCuentas() throws SQLException;
Hacemos que sea abstracto de acuerdo con la filosofa CMP y de esa manera despejamos todo cdigo de persistencia, presente aunque reducido en la filosofa BPM (la bsqueda correspondiente a travs de la clase CuentaClienteHome), por tanto conseguimos a travs de la abstraccin un pequeo hito ms hacia la transparencia. persistencia_java.doc v.1.0 Jos Mara Arranz Santamara Pg.59
Es en esta clase en donde implementamos el mtodo getClienteCuentas() al igual que el resto de mtodos 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()); }
Mtodo idntico 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 idntica funcionalmente al caso uno-muchos BMP Avanzado excepto por la ausencia de los tres mtodos que indicamos en PersistenteHome. 4.3.3.4 CuentaCliente.java
De forma similar a Cliente implementamos la clase abstracta CuentaCliente en donde el mtodo abstracto getCliente() es el que establece la relacin con el cliente propietario de la cuenta.
package jdbc.tipocmp.unomuchos; import java.sql.*; import jdbc.tipocmp.comun.*; public abstract class CuentaCliente implements Persistente { public CuentaCliente() { } public void crear(String nif, String ncc, Timestamp ultimaOp) throws SQLException { setNif(nif); setNcc(ncc); setUltimaOperacion(ultimaOp); } public abstract String getNif(); public abstract void setNif(String nif) throws SQLException; public abstract String getNcc(); public abstract void setNcc(String ncc) throws SQLException; public abstract Timestamp getUltimaOperacion(); public abstract void setUltimaOperacion(Timestamp ultimaOp) throws SQLException; public abstract Cliente getCliente() throws SQLException; public String toString() { return "ncc: " + getNcc() getUltimaOperacion(); } }
"
nif:
"
getNif()
"
ltima
op.:
"
4.3.3.5
CuentaClienteImpl.java
De forma similar a ClienteImpl implementamos los mtodos 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;
Pg.60
Pg.61
4.3.3.6
CuentaClienteHome.java
Al igual que ClienteHome, la implementacin es idntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres mtodos que ya conocemos y citamos en PersistenteHome. 4.3.3.7 JDBCInicio.java
De nuevo constatamos que no hay ningn cambio respecto a la funcionalidad presente en BMP Avanzado al igual que ocurra en caso de una tabla, un nuevo xito de nuestra estrategia de encapsulacin de la persistencia. 4.3.4 Relacin 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 filosofa BMP Avanzado. 4.3.4.1 Cliente.java
Como novedad respecto al caso de uno-muchos introducimos el mtodo getCuentas() que nos devuelve directamente los objetos CuentaCorriente que pertenecen al cliente:
public abstract Collection getCuentas() throws SQLException;
Pg.62
Como novedad respecto al caso uno-muchos definimos el mtodo getCuentas() para establecer la relacin muchosmuchos por este lado.
public Collection getCuentas() throws SQLException { CuentaCorrienteHome cuentaHome = new CuentaCorrienteHome(getConnection()); return cuentaHome.buscarPorCliente(this); }
4.3.4.3
ClienteHome.java
La implementacin es idntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres mtodos citados en PersistenteHome. 4.3.4.4 CuentaCliente.java
4.3.4.5
CuentaClienteImpl.java
4.3.4.6
CuentaClienteClave.java
Idntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres mtodos citados en PersistenteHome. 4.3.4.8 CuentaCorriente.java
Pg.63
4.3.4.9
CuentaCorrienteImpl.java
Pg.64
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
Idntica funcionalmente al caso correspondiente en BMP Avanzado excepto la ausencia de los tres mtodos citados en PersistenteHome. 4.3.4.11 JDBCInicio.java
Al igual que en el caso uno-muchos y con una tabla no se produce ningn 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 diseo CMP. persistencia_java.doc v.1.0 Jos Mara Arranz Santamara Pg.65
Tecnologas de Persistencia en Java, una comparativa desde la prctica Es aqu donde veremos que el modelo CMP ofrece un problema conceptual muy serio a la hora de modelar la herencia: la extensin por abajo implica herencia de la clase implementacin de la clase abstracta, esto supone una muy fuerte intromisin sobre el modelo de datos. En el caso BMP vimos que la intromisin era por arriba a travs de la clase PersistenteImpl que fcilmente podramos eliminar sin introducir mucho cdigo 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 implementacin tambin 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 gestin ya realizada en PersonaImpl. A medida que se introdujeran ms clases en el rbol de derivacin por debajo de Cliente el problema se agravara (este agravamiento no sera tan grave si el modelo de correspondencia clases-tablas fuera el de una sola tabla para toda una lnea ascendente de herencia, llamado tambin horizontal, pero es ms grave cuando la correspondencia es de tipo vertical que es nuestro caso). 2. Segunda Forma:
Persona
PersonaImpl
Cliente
ClienteImpl
Esta segunda opcin es intrusiva respecto a la derivacin normal pero por otra parte se aprovecha desde la herencia la gestin de PersonaImpl por parte de ClienteImpl.
Pg.66
La eleccin no es fcil, hemos visto que cada opcin tiene su parte a favor y su parte en contra. Nosotros elegiremos la opcin segunda o intrusiva porque nos ahorra bastante cdigo y encaja mejor en nuestra correspondencia de 1 clase 1 tabla, pagando el precio con la fuerte intrusin de la gestin de la persistencia en el modelo de herencia. Hay que hacer notar que este problema no est resuelto todava en la verdadera especificacin J2EE CMP (actualmente la 2.0), pues de hecho CMP no soporta la herencia todava, 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 presin de la tecnologa relacional clsica y la comn separacin en diferentes personas entre el diseador de la base de datos y el diseador de la aplicacin. JDO en el tema de la herencia es una eleccin mucho ms adecuada que J2EE CMP. 4.3.5.1 Persona.java
public String toString() { return "nif: " + getNif() + " nombre: " + getNombre() + " edad: " + getEdad(); } }
Adems de la abstraccin de los mtodos hay que notar una importante diferencia respecto al modelo BMP Avanzado, y es la ausencia de los mtodos equals() y hashCode(), esto es debido a que los implementaremos en la clase Impl y de esta manera evitamos incluir la gestin de la identidad en este nivel, cuando es un aspecto obligado por la gestin de la persistencia (ms exactamente por la herencia). 4.3.5.2 PersonaImpl
Pg.67
Pg.68
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 idntica a la correspondiente en BMP Avanzado con la excepcin de la ausencia de los tres mtodos considerados en PersistenteHome. 4.3.5.4 Cliente.java
Derivaremos de PersonaImpl de acuerdo la eleccin 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)
Pg.69
4.3.5.5
ClienteImpl
Ahora ClienteImpl deriva de Cliente e indirectamente de PersistenteImpl, es a esta clase a donde se dirigen las llamadas a la clase base para que se realicen las operaciones de persistencia.
package jdbc.tipocmp.herencia; import java.sql.*; public class ClienteImpl extends Cliente { protected java.util.Date m_alta; public ClienteImpl() { } public ClienteImpl(String nif) { m_nif = nif; } public java.util.Date getAlta() { return m_alta; } public void setAlta(java.util.Date alta) { m_alta = alta; actualizar(); } throws SQLException
public void leer(ResultSet res) throws SQLException { super.leer(res); m_alta = res.getDate("alta"); } public void setStatementParams(String tabla,PreparedStatement stmt) throws SQLException { if (!tabla.equals("cliente")) super.setStatementParams(tabla,stmt); else { stmt.setDate(1,new java.sql.Date(m_alta.getTime())); stmt.setString(2,m_nif); } } }
4.3.5.6
ClienteHome.java
Pg.70
Tecnologas de Persistencia en Java, una comparativa desde la prctica La clase es idntica a la correspondiente en BMP Avanzado con la excepcin de la ausencia de los tres mtodos considerados en PersistenteHome. 4.3.5.7 JDBCInicio.java
Al igual que en los casos anteriores no se produce ningn cambio respecto a la funcionalidad presente en el corrspondiente ejemplo de BMP Avanzado.
Pg.71
5. CONLUSIONES
Una vez llegados aqu podemos ver en perspectiva nuestros modelos y llegar a varias conclusiones:
5.3
ORIENTACIN A OBJETOS Y PATRONES COMO HERRAMIENTAS QUE PERMITEN LA TRANSPARENCIA Y LA TOLERANCIA A CAMBIOS
Gracias a usar la orientacin a objetos tal y como la herencia de nuestro modelo de datos de las clases genricas, la encapsulacin para ocultar la gestin interna de la persistencia respecto al exterior, las funciones virtuales (polimorfismo) para poder hacer la gestin de la persistencia en la herencia por niveles o para poder manejar las clases concretas de una forma genrica utilizando funciones a modo de callbacks, y la divisin del trabajo a travs del patrn DAO (sobre todo a travs 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 tena la finalidad de realizar un conjunto de operaciones sobre el modelo de datos de forma persistente como ejemplo de
Pg.72
Tecnologas de Persistencia en Java, una comparativa desde la prctica uso, dicha clase a lo largo de los diferentes ejemplos ha quedado prcticamente inalterada aunque la gestin de la persistencia siguiera estrategias notablemente diferentes.
5.6 JDBC ADECUADO PARA MODELOS SENCILLOS O BASES DE DATOS POCO ESTNDAR
Esta es nuestra conclusin final, lo aconsejable es usar tecnologas de persistencia ms 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 caractersticas avanzadas que slo se aprovecharan 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 simplificara notablemente nuestros ejemplos, esto no quita que tecnologas ms avanzadas no puedan hacer uso de SQL 3 cuando saben que la base de datos que acceden lo soporta. Actualmente disponemos de toda una artillera de tecnologas de persistencia ms avanzadas, que de hecho casi siempre estn construidas sobre JDBC aunque lo oculten, tal y como JDO, J2EE CMP, JCA y SQLJ por citar las estndar, e Hibernate, Castor, TopLink y CocoBase por citar productos no estndar libres y comerciales.
Pg.73