Ingeniería de Sistemas Informáticos Curso 2002/2003 3UiFWLFDVGH,QJHQLHUtDGH6LVWHPDV,QIRUPiWLFRV -'%& 2EMHWLYRV • • • • • • • • • • • • Estudio del API JDBC. Instalación de un driver JDBC. Puente JDBC-ODBC. Definición de una fuente de datos ODBC. Conexión a una base de datos. Pool de conexiones. Consultas. Consultas preparadas. Transacciones. Excepciones y cierre de conexiones. Ejercicios. Estrategia DAO. • • • • Servidor de servlets y JSP Tomcat versión 3.3.1 (http://jakarta.apache.org/tomcat/) Documentación del API JDBC 2.1. Dentro de la documentación de J2SE. Entorno de desarrollo Java (JBuilder, Kawa, ...) Microsoft Access 2000. +HUUDPLHQWDV\GRFXPHQWDFLyQ 1RWD: todo este software puede ser descargado de la página de la asignatura http://www.dis.um.es/~marcos/isi (VWXGLRGHO$3,-'%& El API JDBC define un conjunto de clases e interfaces que nos permiten acceder a cualquier base de datos relacional con LQGHSHQGHQFLD GHO VLVWHPD JHVWRU (Oracle, MySQL, Informix, etc.). La versión más reciente del API es la 2.1. Las clases y la documentación de este API van integradas dentro del entorno SDK de Sun. Las clases de este API se definen dentro del paquete MDYDVTO. De entre todas las clases destacamos 'ULYHU0DQDJHU &RQQHFWLRQ 6WDWHPHQW 3UHSDUHG6WDWHPHQWy 5HVXOW6HW Comenzaremos la práctica familiarizándonos con estas clases. ,QVWDODFLyQGHXQGULYHU-'%& Las clases e interfaces para trabajar con una base de datos relacional quedan establecidas por el API JDBC. Algunas de estas clases están parcialmente diferidas y los interfaces están sin implementar. Es responsabilidad de cada sistema gestor la implementación de las clases que dan acceso a las bases de datos. Por lo tanto, para cada tipo de base de datos trabajaremos con un conjunto diferente de clases. Esto es lo que se denomina GULYHU-'%&. La instalación de un driver en una aplicación Java se realiza en dos pasos. En primer lugar, se añaden las clases del driver al FODVVSDWKde la aplicación. A continuación, dentro del código se FDUJDHOGULYHU antes de acceder a la base de datos: 1 Ingeniería de Sistemas Informáticos Curso 2002/2003 Class.forName("oracle.jdbc.driver.OracleDriver"); Podremos cargar tantos drivers JDBC como necesitemos en una aplicación Java. Para cada tipo de sistema gestor utilizaremos una cadena distinta. Por ejemplo, para el GULYHU-'%&2'%& se utiliza la cadena “VXQMGEFRGEF-GEF2GEF'ULYHU”. Cada sistema gestor incluye una librería con las clases que implementan el API. Por ejemplo, Oracle incluye la librería “FODVVHV]LS”. Algunos sistemas requieren del uso de código nativo. Éste es el caso de Oracle que incluye las librerías dinámicas “ocijdbc8.dll”. 3XHQWH-'%&2'%& 2'%& (2SHQ 'DWD%DVH &RQQHFWLYLW\) es un API Windows que ofrece un acceso uniforme a cualquier tipo de base de datos relacional cuyo driver se encuentre instalado en el sistema operativo. El SXHQWH -'%&2'%& es un driver que da acceso a bases de datos ODBC. Este driver está incorporado dentro de la distribución de Java, por lo que QR HV QHFHVDULR LQFRUSRUDUORH[SOtFLWDPHQWHHQHOFODVVSDWK de una aplicación Java. En esta práctica trabajaremos con una base de datos en Access 2000 utilizando un acceso ODBC. A continuación se describen los pasos para instalar esta base de datos en ODBC. 'HILQLFLyQGHODEDVHGHGDWRV Para conseguir que nuestra aplicación sea portable entre sistemas gestores de bases de datos debemos definirla utilizando las VHQWHQFLDV''/GH64/. En nuestro ejemplo definiremos una sencilla tabla para los clientes de la aplicación: CREATE TABLE Cliente ( nombre VARCHAR(128) not null, nif VARCHAR(9) not null, correo VARCHAR(128) not null, usuario VARCHAR(128) not null, clave VARCHAR(56) not null, CONSTRAINT clave_primaria PRIMARY KEY CONSTRAINT nif_unico UNIQUE (nif) ); (usuario), &UHDFLyQGHODEDVHGHGDWRVHQ$FFHVV Access 2000 ofrece una serie de asistentes y tipos propios para crear una base de datos. Deberemos HYLWDU KDFHU XVR GH HVWDV FDUDFWHUtVWLFDV HVSHFtILFDV GH $FFHVV. Utilizaremos el mecanismo de consultas para crear una base de datos en SQL. En primer lugar FUHDPRVXQDEDVHGHGDWRV en blanco con nombre “isi”. Accedemos al menú Insertar/Consulta para definir la tabla en SQL y seleccionamos la vista de diseño. Seguidamente, cerramos la ventana “Mostrar tabla” y nos quedamos en la ventana “Consulta de selección”. Nos situamos dentro de la zona gris de la ventana y accedemos al menú de contexto “9LVWD64/”. 2 Ingeniería de Sistemas Informáticos Curso 2002/2003 En este modo de definición de consultas podemos escribir sentencias de creación DDL. Introducimos la definición de la tabla anterior y ejecutamos el comando &RQVXOWD(MHFXWDU. En este momento ya tenemos definida la tabla. Guardamos la base de datos y cerramos la aplicación. 'HILQLFLyQGHXQDIXHQWHGHGDWRV2'%& Dependiendo de la versión de Windows con la que trabajemos, el programa de definición de fuentes de datos ODBC puede variar de localización, pero siempre dentro del 3DQHOGH&RQWURO o algún subgrupo. El nombre del programa es “Fuentes de datos ODBC”. Dentro de la pestaña “'61GHXVXDULR” encontramos los diferentes drivers ODBC instalados en el sistema. 3 Ingeniería de Sistemas Informáticos Curso 2002/2003 +DFHPRVXQGREOHFOLFVREUHHOGULYHU³06 Access 'DWDEDVH”. A continuación debemos indicar el nombre de la base de datos en ODBC y su ruta. Le daremos el nombre “isi”. &RQH[LyQDXQDEDVHGHGDWRV Una vez instalado el driver y cargado dentro del código Java sólo necesitamos una conexión a la base de datos para comenzar a trabajar con ella. La clase 'ULYHU0DQDJHU define el método JHW&RQQHFWLRQ para crear una conexión a una base de datos. Este método toma como parámetro una URL JDBC donde se indica el sistema gestor y la base de datos. Opcionalmente, y dependiendo del sistema gestor, habrá que especificar el ORJLQ y SDVVZRUG para la conexión. 4 Ingeniería de Sistemas Informáticos Curso 2002/2003 Una URL JDBC tiene la siguiente estructura: MGEFVJEGSURWRFROREG donde VJEG indica el nombre del sistema gestor, SURWRFROR el tipo de protocolo dentro del sistema gestor y EG el nombre de la base de datos. En el caso de JDBC-ODBC la cadena de conexión sería “MGEFRGEFLVL”, en la que no es necesario establecer el protocolo. Finalmente, el código para establecer una conexión quedaría del siguiente modo: Connection con = DriverManager.getConnection("jdbc:odbc:isi"); 3RROGHFRQH[LRQHV En las aplicaciones en las que es necesario el acceso concurrente a una base de datos es necesario GLVSRQHUGHYDULDVFRQH[LRQHV. Éste es el caso de los servlets, que sirven peticiones concurrentes del servidor web. El proceso de creación y destrucción de una conexión a una base de datos es costoso e influye sensiblemente en el rendimiento de una aplicación. La estrategia más utilizada en este tipo de aplicaciones es el uso de un SRRO de conexiones. Un pool es un conjunto de conexiones disponibles para ser utilizadas. Cuando un servlet necesite realizar una consulta, solicita una conexión al pool y cuando termine de utilizar la base de datos, devolverá la conexión. Algunos drivers manejan internamente un pool de conexiones proporcionando un objeto 'DWD6RXUFH. Estos objetos son servidos por el contenedor. En lugar de definir un DataSource para el contenedor Tomcat utilizaremos un pool de conexiones siguiendo la estrategia vista en clase. &RQVXOWDV Las consultas sobre una base de datos se realizan utilizando objetos de las clases 6WDWHPHQW y 3UHSDUHG6WDWHPHQW. Estos objetos son creados a partir de una conexión. Statement stmt = con.createStatement(); La clase 6WDWHPHQW contiene los métodos H[HFXWH4XHU\ y H[HFXWH8SGDWH para realizar consultas y actualizaciones, respectivamente. Ambos métodos soportan consultas en SQL-92. ResultSet rs = stmt.executeQuery("SELECT * from Cliente"); El método H[HFXWH4XHU\ devuelve un objeto 5HVXOW6HW para poder recorrer el resultado de la consulta utilizando un cursor. while (rs.next()) { String usuario = rs.getString("usuario"); } 5 Ingeniería de Sistemas Informáticos Curso 2002/2003 El método QH[W es utilizado para hacer avanzar el cursor. Para recuperar una columna del registro utilizamos los métodos JHW. Hay un método JHW para cada tipo básico Java y para las cadenas. Dependiendo del tipo de las columnas podremos utilizar uno u otro. Otro método interesante del cursor es ZDV1XOO que nos informa si el último valor leído con un método JHW fue nulo. 7LSRGHGDWR64/ CHAR VARCHAR LONGVARCHAR NUMERIC DECIMAL BIT TINYINT SMALLINT INTEGER BIGINT REAL FLOAT DOUBLE BINARY VARBINARY DATE TIME TIMESTAMP 0pWRGR String getString() String getString() InputStream getAsciiStream() ó getUnicodeString() java.math.BigDecimal getBigDecimal() java.math.BigDecimal getBigDecimal() boolean getBoolean() byte getByte() short getShort() int getInt() long getLong() float getFloat() double getDouble() double getDouble() byte[] getBytes() InputStream getBinayStream() java.sql.Date getDate() java.sql.Time getTime() java.sql.getTimestamp getTimeStamp() Podemos llamar a los métodos JHW utilizando el nombre de la columna (esto no siempre puede ser determinado) o la posición en la consulta. Lo más práctico y legible es utilizar el nombre de la columna, dejando el uso del acceso por posición cuando el valor de la columna no tenga ningún nombre, como por ejemplo con un campo calculado. Las consultas de actualización, H[HFXWH8SGDWH, devuelven el número de registros insertados, registros actualizados o eliminados, dependiendo del tipo de consulta que se trate. 6 Ingeniería de Sistemas Informáticos Curso 2002/2003 &RQVXOWDVSUHSDUDGDV Las consultas preparadas están representadas por la clase 3UHSDUHG6WDWHPHQW. Representan consultas SUHFRPSLODGDV que pueden tener parámetros. Se instancia del siguiente modo: PreparedStatement pstmt = con.preparedStatement("SELECT * from Cliente"); PreparedStatement pstmt = con.preparedStatement("SELECT * from Cliente WHERE usuario = ? "); Para las consultas que han de realizarse muy a menudo es PiVHILFLHQWH utilizar una consulta preparada, siendo la única opción cuando la consulta tiene parámetros. Los SDUiPHWURV se expresan con el carácter ‘?’. Establecemos los parámetros de una consulta utilizando métodos VHW que dependen del tipo SQL de la columna (ver tabla). pstmt.setString(1, "Marcos"); El primer argumento de este método es la posición del parámetro dentro de la consulta. Finalmente, ejecutamos la consulta utilizando el método H[HFXWH4XHU\ o H[HFXWH8SGDWH, ambos sin parámetros, dependiendo del tipo de consulta. Si la consulta tenía parámetros, en este momento quedarán vacíos. 7UDQVDFFLRQHV Por defecto, con una conexión trabajamos HQ PRGR DXWRFRPPLW con valor WUXH. Esto quiere decir que cada consulta es una transacción en la base de datos. Si queremos definir una transacción estableceremos el modo DXWRFRPPLW a IDOVH con el método VHW$XWR&RPPLW de la clase &RQQHFWLRQ. En modo no DXWRFRPPLW las transacciones quedan definidas por las ejecuciones de los métodos FRPPLW y UROOEDFN. Una transacción abarca desde el último FRPPLWo UROOEDFN hasta siguiente FRPPLW. Los métodos FRPPLW o UROOEDFN forman parte de la clase &RQQHFWLRQ. ([FHSFLRQHV\FLHUUHGHFRQH[LRQHV Todas las acciones sobre una base de datos pueden lanzar la excepción genérica 64/([FHSWLRQ. Por lo tanto, ha de tenerse en cuenta que esta excepción debe ser capturada siempre que trabajemos con una base de datos. Para terminar, destacar que las conexiones con una base de datos consumen muchos recursos en el sistema gestor y conviene cerrarlas con el método FORVH siempre que vayan a dejar de ser utilizados, en lugar de esperar a que el JDUEDJHFROOHFWRUde Java las elimine. También conviene cerrar las consultas (6WDWHPHQW y 3UHSDUHG6WDWHPHQW) y los resultados (5HVXOW6HW) para liberar los recursos. 7 Ingeniería de Sistemas Informáticos Curso 2002/2003 (MHUFLFLRV Modifica el ejercicio de los VHUYOHWV para que los usuarios sean almacenados en una base de datos. Los pasos a realizar serán los siguientes: • Instancia un pool de conexiones y almacénalo en la aplicación web. Esta acción la realizarán los dos servlets dentro del método LQLW. Nótese que para que la aplicación sea totalmente portable e independiente del sistema donde sea instalada tanto la cadena de conexión como el driver deberían ser especificados como parámetros de la aplicación web. private DataSource ds; ... public void init(ServletConfig config) throws SQLException { ... ServletContext app = config.getServletContext(); this.ds = (DataSource) app.getAttribute("DataSource"); if ( this.ds == null ) { try { this.ds = ConnectionPool.getInstance("jdbc:odbc:isi", "", ""); app.setAttribute("DataSource", ds); } catch (Exception e) { e.printStackTrace(); } } } ... • Utiliza una consulta normal para insertar el cliente en la base de datos (6HUYOHW5HJLVWUR). Sustituimos el código que creaba el EHDQy lo insertaba en la tabla hash por una consulta SQL. boolean error = false; String mensajeError = ""; Connection con = null; Statement stmt = null; ResultSet rs = null; try { con = ds.getConnection(); stmt = con.createStatement(); // Consulta si hay un Cliente con el mismo nombre de usuario rs = stmt.executeQuery("SELECT * FROM Cliente WHERE usuario = ’" + peticion.getParameter("usuario") + "’"); if ( rs.next() ) { // Existe un usuario error = true; mensajeError = "Usuario duplicado"; // Obtiene la URL precedente String referer = peticion.getHeader("referer"); // Establece la cabecera de refresco respuesta.setHeader("refresh", "3; URL=" + referer); } else { stmt.executeUpdate("INSERT into Cliente values (" + "’" + peticion.getParameter("nombre") + "’," + "’" + peticion.getParameter("nif") + "’," + "’" + peticion.getParameter("correo") + "’," + "’" + peticion.getParameter("usuario") + "’," 8 Ingeniería de Sistemas Informáticos Curso 2002/2003 + "’" + peticion.getParameter("clave") + "’" + ")"); } catch (SQLException e) { error = true; mensajeError = "Error al insertar el cliente en la base de datos"; } } stmt.close(); con.close(); } catch (SQLException e) { e.printStackTrace(); error = true; mensajeError = "Error en la base de datos"; } ... // Nuestros datos if (!error) { out.println("<B><P> Datos Cliente Procesados </P> </B>"); out.println("<B><P> Autor: " + autor + "</P></B>"); } else { out.println("<H1> Error: " + mensajeError + " </H1>"); } • Utiliza una consulta preparada dentro del servlet de identificación (6HUYOHW/RJLQ) para comprobar la existencia de un usuario. Dado que sólo es necesario crearla una vez, la instanciamos dentro del método LQLW private PreparedStatement recuperaUsuario; public void init(ServletConfig config) { « try { Connection con = ds.getConnection(); recuperaUsuario = con.prepareStatement(" SELECT * FROM Cliente WHERE usuario = ?"); } catch (SQLException e) { e.printStackTrace(); } } Dentro del método GR3RVW de 6HUYOHW/RJLQ el acceso a ella será sincronizado (bloque V\QFKURQL]HG) para garantizar un funcionamiento WKUHDGVDIHya que la consulta es un recurso compartido en el entorno concurrente de las peticiones a un servlet. Sustituimos el código que trabaja con la tabla hash por la consulta. 9 Ingeniería de Sistemas Informáticos Curso 2002/2003 // Obtenemos la información de la petición String usuario = peticion.getParameter("usuario"); String clave = peticion.getParameter("clave"); Cliente c = null; try { // Realizamos la consulta synchronized (recuperaUsuario) { // Establece el parámetro de la consulta recuperaUsuario.setString(1, usuario); ResultSet rs = recuperaUsuario.executeQuery(); if ( rs.next() ) { // El usuario existe String claveUsuario = rs.getString("clave"); if (claveUsuario.equals(clave)) { identificado = true; // Crea el bean usuario c = new Cliente(); c.setNombre(rs.getString("nombre")); c.setNif(rs.getString("nif")); c.setCorreo(rs.getString("correo")); c.setUsuario(rs.getString("usuario")); c.setClave(claveUsuario); }else identificado = false; } else identificado = false; } } catch (Exception e) { e.printStackTrace(); respuesta.sendError(500, "Error en la base de datos"); } ... (VWUDWHJLD'$2 Si observamos detenidamente los servlets creados en esta práctica y en la anterior podremos apreciar que el código encargado de almacenar la información (clientes) está disperso a través de todo el servlet. Un cambio en el mecanismo de persistencia como el que hemos realizado en esta práctica al pasar del almacenamiento en una tabla KDVK a una base de datos es costoso incluso para servlets tan sencillos como con los que estamos trabajando. Para evitar estos inconvenientes lo recomendable siempre es utilizar el patrón de diseño DAO para que nuestra aplicación sea tolerante a los cambios en el almacenamiento. El patrón de diseño DAO define un modo de acceder a los datos de una aplicación con independencia del tipo de almacenamiento, encapsulando todos los detalles de acceso a los recursos de datos en unas clases llamadas 'DWD$FFHVV2EMHFWV. En lo que resta de este apartado seguiremos la estrategia DAO vista en clase. En primer lugar crearemos una IDFWRUtDDEVWUDFWD de objetos DAO que llamaremos '$2)DFWRULD, que en nuestro caso sólo tendrá un método factoría para generar los DAO acceso a los datos de los clientes. 10 Ingeniería de Sistemas Informáticos Curso 2002/2003 // Define una factoría abstracta que devuelve todos los DAO de la aplicación public abstract class DAOFactoria { // Métodos factoría public abstract ClienteDAO getClienteDAO(); // Declaración como constantes de los tipos de factoría public final static int ACCESS = 1; public static DAOFactoria getDAOFactoria (int tipo) throws DAOException { switch(tipo) { case ACCESS: { try { return new AccessDAOFactoria(); } catch (Exception e) { throw new DAOException(e.getMessage()); } } default: return null; } } } A continuación creamos tantas IDFWRUtDV FRQFUHWDV como tipos de almacenamiento diferentes tengamos. En esta práctica hemos hecho uso de una base de datos $FFHVV, aunque no hemos utilizado características especiales de la misma. A modo de ejemplo, crearemos una factoría concreta para este sistema gestor. Esta factoría utiliza un SRROde conexiones. Por simplicidad se codifica la información de acceso a la base de datos, aunque lo ideal sería recuperarla desde un fichero de propiedades. import javax.sql.*; // Factoría DAO Access que implementa la factoría abstracta public class AccessDAOFactoria extends DAOFactoria { DataSource ds; public AccessDAOFactoria () throws DAOException, ClassNotFoundException, java.sql.SQLException { ds = ConnectionPool.getInstance("jdbc:odbc:isi", "", ""); } public ClienteDAO getClienteDAO() { return (ClienteDAO) new AccessClienteDAO(ds); } } El DAO para acceder a los datos de los clientes viene definido por una LQWHUID]'$2 donde se encuentran recogidos los métodos necesarios para el acceso a los datos. En nuestra aplicación sólo necesitamos un método de creación, un método de búsqueda por usuario, un método que para obtener todos los clientes y un método de actualización (práctica siguiente). 11 Ingeniería de Sistemas Informáticos Curso 2002/2003 // Define los métodos de acceso a los datos para un cliente public interface ClienteDAO { public Cliente createCliente(String nombre, String nif, String correo, String usuario, String clave) throws DAOException; // Busca un Cliente por "usuario" (clave primaria). Si no lo encuentra // devuelve "null" public Cliente findClienteByUsuario(String usuario) throws DAOException; // Obtiene todos los clientes public java.util.Collection findAll() throws DAOException; // Actualiza un cliente public void update (Cliente c) throws DAOException; } Seguidamente creamos una '$2FRQFUHWR para Access (en general para SQL) que implemente la interface DAO del cliente. Estos objetos serán los que sean devueltos por la factoría concreta. import java.sql.*; import javax.sql.*; import java.util.*; public class AccessClienteDAO implements ClienteDAO { private DataSource ds; public AccessClienteDAO (DataSource ds) { this.ds = ds; } public Collection findAll() throws DAOException { LinkedList clientes = new LinkedList(); Connection con = null; try { con = ds.getConnection(); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM Cliente "); while (rs.next()) { Cliente c = new Cliente(); c.setNombre(rs.getString("nombre")); c.setNif(rs.getString("nif")); c.setCorreo(rs.getString("correo")); c.setUsuario(rs.getString("usuario")); c.setClave(rs.getString("clave")); clientes.add(c); } rs.close(); stmt.close(); con.close(); return clientes; } catch (SQLException e) { throw new DAOException(e.getMessage()); } } 12 Ingeniería de Sistemas Informáticos Curso 2002/2003 public void update (Cliente c) throws DAOException{ Connection con = null; try { con = ds.getConnection(); String usuario = c.getUsuario(); Statement stmt = con.createStatement(); stmt.executeUpdate( "UPDATE Cliente SET " + "nombre = ’" + c.getNombre() + "’ , " + "nif = ’" + c.getNif() + "’ , " + "correo = ’" + c.getCorreo() + "’ , " + "clave = ’" + c.getClave() + "’ " + "WHERE usuario = ’" + c.getUsuario() + "’"); stmt.close(); con.close(); } catch (SQLException e) { throw new DAOException(e.getMessage()); } } // Inserta el cliente en la base de datos y crea un JavaBean con // los valores public Cliente createCliente(String nombre, String nif, String correo, String usuario, String clave) throws DAOException { Ejercicio } public Cliente findClienteByUsuario(String usuario) throws DAOException { } Ejercicio } Todos los métodos de acceso a datos que utilizan un DAO potencialmente pueden lanzar la excepción '$2([FHSWLRQ: public class DAOException extends Exception { public DAOException (String msg) { super(msg); } } Por último, PRGLILFDUHPRVORVVHUYOHWV para que hagan uso de la estrategia DAO. En el método LQLW, en lugar de crear y registrar un pool de conexiones lo que hará será obtener una factoría DAO para $FFHVV. 13 Ingeniería de Sistemas Informáticos Curso 2002/2003 DAOFactoria daoFactoria; ... public void init(ServletConfig config) { super.init(config); // Obtiene el objeto aplicación ServletContext app = getServletConfig().getServletContext(); // Comprueba si ya existe la factoría en la aplicación daoFactoria = (DAOFactoria) app.getAttribute("DAOFactoria"); if (daoFactoria == null) { // Instancia la factoría concreta ACCESS utilizando la // factoría abstracta daoFactoria = DAOFactoria.getDAOFactoria(DAOFactoria.ACCESS); // La almacena en la aplicación web app.setAttribute("DAOFactoria", daoFactoria); } } Con el código anterior conseguimos que los servlets de nuestra aplicación dispongan de la factoría de DAOs almacenados en un atributo y disponible en el método GR*HW y GR3RVW, y a la vez que esa factoría se comparta en toda la aplicación. Siempre que necesitamos trabajar con objetos cliente, obtenemos el DAO concreto para el tipo Cliente y trabajamos con él: ClienteDAO clienteDAO = daoFactoria.getClienteDAO(); // Comprueba si existe un determinado usuario registrado Cliente c = clienteDAO.findClienteByUsuario(usuario); if (c != null) ... // Actualiza los datos del cliente clienteDAO.update(c); ... // Inserta un nuevo cliente en la aplicación clienteDAO.createCliente(...); 14 Ingeniería de Sistemas Informáticos Curso 2002/2003 (MHUFLFLRDOWHUQDWLYR El ejercicio alternativo a esta práctica consiste en implementar una estrategia de acceso a datos DAO para el tipo de datos 3URGXFWR. Un producto tiene como información el nombre, el tipo de producto, su precio, un indicativo de si está en oferta y un identificador único que será asignado automáticamente. Los pasos para implementar esta estrategia son los siguientes: • Crea un JavaBean 3URGXFWR con toda la información del producto (9DOXH2EMHFW). • Define la interfaz '$23URGXFWR con métodos para manejar los productos: crear un producto, eliminarlo, actualizarlo (sincroniza los datos con una instancia), obtener un producto por identificador, obtener todos los productos de una categoría y un último método de consulta de todos los productos. • Crea una factoría abstracta de DAOs que tenga un método para conseguir un '$23URGXFWR. • Definimos un DAO que almacene los datos en una base de datos $FFHVV: o Creamos una nueva base de datos en $FFHVV y definimos la tabla en SQL. o Registramos la base de datos en ODBC. o Creamos el DAO que implemente la interfaz '$23URGXFWR utilizando un pool de conexiones sobre esta base de datos. • Por último, creamos la factoría DAO concreta para el almacenamiento en $FFHVV. Para probar el funcionamiento de la estrategia DAO crearemos un programa que haga uso del mismo: creará unos cuantos productos, los actualizará, los eliminará, hará consultas, etc. para comprobar que el funcionamiento es correcto. 15