Vamos a tratar una serie de aspectos de interés:
Los servlets en web.xml:
....
<servlet>
<servlet-name>FormClientes</servlet-name>
<servlet-class>docen_servlet01.JDBC01.presentacion.FormClientes</servlet-class>
</servlet>
<servlet>
<servlet-name>FormVentas</servlet-name>
<servlet-class>docen_servlet01.JDBC01.presentacion.FormVentas</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FormClientes</servlet-name>
<url-pattern>/servlet/FormClientes</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>FormVentas</servlet-name>
<url-pattern>/servlet/FormVentas</url-pattern>
</servlet-mapping>
....
Pede obtener el código del ejemplo.
En nuestra base de datos tenemos dos tablas: la primera representa información de clientes y la segunda contiene las ventas que se han realizado. La relación es de 1:N, ya que un cliente puede tener asignadas varias ventas. En el ejemplo:
Esquema global (nota: el servlet 'inicio' del dibujo se llama en realidad 'FormClientes' y
el servlet 'ventas' se llama 'FormVentas'):
FormClientes empieza iniciando el DAO:
package docen_servlet01.JDBC01.presentacion;
import ...
public class FormClientes extends HttpServlet {
private DAOCliente dc = null; // Clase responsable de acceso a base de datos
/************************************************************************
Al inicializarse el servlet se crea el DAO: con este se carga el driver JDBC y
se leen propiedades. El argumento del constructor del DAO es el dir raiz de la
aplicación.
Si ya se hubiesen cargado driver y propiedades, no se vuelven a cargar.
**************************************************************************/
public void init(ServletConfig config) throws ServletException {
super.init(config);
dc = new DAOCliente( config.getServletContext().getRealPath("/") );
}
....
A continuación veamos doPost(). Puede observarse en el código fuente que las salidas de código HTML se envian a la clase auxiliar UtilGeneral. Empezamos comprobando que el DAO ha cargado las propiedades y el driver de base de datos, las propiedades se definen en un archivo properties, que define el host, y dicho archivo se sitúa (desde el directorio raíz de la aplicación, public_html) en public_html/propiedades/docen_servlet01/parametros.properties. Pero esta lectura de archivo properties queda oculta (encapsulada) para el servlet, de ello se encarga la clase Propiedades, que es usada por el DAO:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out= response.getWriter(); // Obtener flujo salida;
try {
HttpSession sesion = request.getSession(true); // Obtener sesión, si no existe, la crea
response.setContentType("text/html; charset=iso-8859-1"); // Definir tipo de salida
UtilGeneral.imprimirInicioPagina( "Ejemplo de servlet", "Seleccione cliente", out);
//// Si no se han cargado propiedades, aviso y salgo
if ( dc.getPropiedades() == null ) {
UtilGeneral.imprimir( out, "No se han cargado las propiedades.");
return;
}
else
UtilGeneral.imprimir( out, "Propiedades cargadas: " +
dc.getPropiedades().getPathPropiedades()+dc.getPropiedades().getFicheroParametros());
//// Si no he podido cargar el driver JDBC, aviso y salgo
if ( !dc.estaCargadoDriver() ) {
UtilGeneral.imprimir( out, "No se ha cargado el driver " + DAOGeneral.getPropiedades().getDriver(), true, true );
UtilGeneral.imprimirFinPagina( out );
return;
}
else
UtilGeneral.imprimir( out, "Driver cargado: " + dc.getPropiedades().getDriver());
....
En el archivo properties (/public_html/propiedades/docen_servlet01/parametros.properties) tenemos las siguientes líneas que definen el host y la base de datos:
basedatos.host=jdbc:mysql://localhost:3306/ docen_servlet01.JDBC01.basedatos=proactiv_prueba
Lo que sigue es definir los atributos de la sesión y mostrar el formulario que lista los clientes de la base de datos. La definición de atributos es sencilla. El login y password se obtienen de la petición (request) y la base de datos se obtiene de la clase Propiedades que usa DAOCliente. El formulario se escribe mediante una llamada al método del propio servlet imprimirFormulario()
....
//// Poner atributos (base de datos, login y password en la sesion)
//// La base de datos se obtiene de un archivo properties y
//// el login y password de request
sesion.setAttribute("basedatos", dc.getPropiedades().getHostBaseDatos()+
dc.getPropiedades().getParametro("docen_servlet01.JDBC01.basedatos"));
sesion.setAttribute("login", request.getParameter("login"));
sesion.setAttribute("password", request.getParameter("password"));
////////////////////////// IMPRIMIR FORMULARIO Y SESION
imprimirFormulario( request, out );
UtilGeneral.imprimirSesion(sesion, out);
}
catch (Exception e) {
UtilGeneral.imprimir( out, "Error general. " + e.getMessage(), true, true );
e.printStackTrace();
}
finally {
//// Cierre de página
UtilGeneral.imprimirFinPagina( out );
}
}
imprimirFormulario() empieza con con dos llamadas a DAOCliente. Primero para definir el usuario (setIdentificacion()) a partir del login y password de la petición (request) HTTP y en segundo lugar obtener un vector de clientes (bean.Cliente) por medio de la llamada dc.select(null). EL argumento null indica que queremos listar todos los clientes, es decir, que no hay clausula WHERE en la sentencia SQL del DAO:
void imprimirFormulario( HttpServletRequest request, PrintWriter out ) {
try {
Vector vecClientes = null;
//// Almacenar en DAO la identificación (login-pwd), desde request
dc.setIdentificacion(request.getParameter("login"), request.getParameter("password"));
//// Usar el DAO para conseguir vector de clientes
try {
vecClientes = dc.select(null);
}
catch ( Exception e) {
UtilGeneral.imprimir(out, "Error en la consulta. " + e.getMessage());
}
....
A continuación imprimirFormulario() escribe en la salida el código HTML. Escribe los dos 'desplegables' que pueden verse en el formulario. Uno tiene los nombres de los clientes y otro tiene sus códigos.
///// Inicio de tabla HTML
out.println("<table BORDER=1 align=center cellpadding='10' cellspacing=1>");
out.println("<tr><td bgcolor=#00FF00>");
out.println("<FONT color=#000080 FACE='Arial,Helvetica,Times' SIZE=2>");
///// Inicio del formulario HTML
out.println("<form action="+ dc.getPropiedades().getHostHTTP()+"servlet/FormVentas method='post'>");
UtilGeneral.imprimir( out, "Escoja cliente:");
///// Recorrer fila a fila el vector de clientes y poner en SELECT de NOMBRES
out.println("<SELECT NAME='cliente' onchange=\"copiarValor('nombre','codigo');\" id='nombre'>");
for ( int i = 0; i< vecClientes.size(); i++ ) {
Cliente c = (Cliente) vecClientes.get(i);
out.println("<OPTION VALUE=" + c.getCodigo()+">" + c.getApe1() + " " + c.getApe2() + ", " + c.getNombre()+"");
}
out.println("</SELECT>");
///// Recorrer fila a fila el vector de clientes y poner en SELECT de CODIGOS
out.println("<SELECT NAME='cliente.codigo' onchange=\"copiarValor('codigo','nombre');\" id='codigo'>");
for ( int i = 0; i< vecClientes.size(); i++ ) {
Cliente c = (Cliente) vecClientes.get(i);
out.println("<OPTION VALUE=" + c.getCodigo()+">" + c.getCodigo()+"</OPTION>");
}
out.println("</SELECT>");
//// Alternativa: campo oculto para el código de cliente (en vez de SELECT de codigos)
// out.println("<INPUT TYPE=HIDDEN NAME='cliente.codigo' id='codigo'>");
//// Poner botón y fin de formulario y de tabla
UtilGeneral.imprimir( out, "<input type='submit' name='Submit' value='Enviar'>");
out.println("</form></font></td></tr></table>");
}
catch (Exception e) {
UtilGeneral.imprimir( out, "EROR EN FORMULARIO. " + e.getMessage(), true, true );
}
}
Un aspecto a resaltar de los dos 'desplegables' es que están coordinados: si cambia en uno el nombre del cliente, entonces cambia en el otro a su correspondiente código de cliente y viceversa. Esto se consigue gracias a que en UtilGeneral.imprimirInicioPagina() tenemos la siguiente función javascript:
<script type='text/javascript'>
function copiarValor(idOrigen, idDestino) {
document.getElementById(idDestino).value = document.getElementById(idOrigen).value;
}
</script>
Hay una alternativa, la forma más común de trabajar es tener un 'desplegable' para los nombres de cliente y un campo de texto oculto que refleja el código del nombre seleccionado en el 'desplegable'. Lo ocultamos por una simple razón: al usuario sólo le interesa ver los nombres de los clientes y su clave primaria (el código de cliente) suele resultarle indiferente (a menos que dicho código sea significativo, como por ejemplo el NIF). Para ocultar el código se puede probar a sustituir el 'desplegable' (select) de códigos por:
//// Alternativa: campo oculto para el código de cliente (en vez de SELECT de codigos)
// out.println("<INPUT TYPE=HIDDEN NAME='cliente.codigo' id='codigo'>");
Se queda oculto (hidden) y no borrado, ya que este campo será el que se transmita en la petición (request) al segundo servlet (FormVentas), pues dicho campo contiene el código del cliente del que queremos mostrar las ventas.
Pensemos en el paso de información del primer al segundo servlet. Analizando:
Por medio de un iterador (Enumeration) obtenemos todos los atributos de la sesión:
static void imprimirSesion(HttpSession sesion, PrintWriter out) {
if (sesion != null) {
out.println("<P><B>Sesion:</B>" + sesion.getId() + ". Atributos:" + "<OL>");
for (Enumeration e = sesion.getAttributeNames(); e.hasMoreElements(); ) {
String atrib = (String) e.nextElement();
out.print("<LI>Nombre: " + atrib);
out.println(". Valor: " + sesion.getAttribute(atrib) + "</LI>");
}
out.println("</OL>");
}
}
El aspecto más importante es que obtenemos el valor de un atributo por medio de:
sesion.getAttribute(atrib);
que devuelve un objeto del tipo Object. Podemos conseguir todos los atributos de una sesión, a partir de una enumeración devuelta por sesion.getAttributeNames() (de la misma forma que obteniamos todos los parámetros por medio de request.getParameterNames()):
El servlet que debe obtener las ventas de un cliente seleccionado recibe información por dos medios:
El procesamiento de la respuesta en el servlet 'FormVentas' es semejante al del servlet anterior, por ello no vamos a reincidir con detalles reiterados; como por ejemplo que se usa un DAO (DAOVenta). En FormVentas.imprimirFormulario() primero se envía al DAO la identificación (login y password) del usuario por medio de dv.setIdentificacion() y en segundo lugar se obtiene un vector de elementos de la clase Venta, por medio de una llamada a:
vecVentas = dv.select( "codigo = '" + request.getParameter("cliente.codigo")+"'");
El argumento de select() indica la cláusula WHERE.
Para que el formateo de la tabla de ventas sea correcto usamos un formateador de números para mostrar los separadores de millares y de decimales.
DecimalFormat myFormatter = new DecimalFormat( "##,###,###.#");
myFormatter.setMinimumFractionDigits(1);
....
out.println("<TD align=right>" + myFormatter.format( precio ) + "</TD>");
out.println("<TD align=right>" + myFormatter.format( coste ) + "</TD>");
out.println("<TD align=right>" + myFormatter.format( precio - coste )+ "</TD>");
El código completo de FormVentas es:
package docen_servlet01.JDBC01.presentacion;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.servlet.ServletConfig;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.Vector;
import java.text.DecimalFormat;
import docen_servlet01.JDBC01.accesoDatos.DAOVenta;
import docen_servlet01.JDBC01.presentacion.UtilGeneral;
import docen_servlet01.JDBC01.bean.Venta;
/***************************************************************************
* Recibe el cliente (parámetro de formulario). Ejecuta una consulta de las
* ventas de dicho cliente. Los datos son impresos en la página HTML.
* Utiliza el DAOVenta para el acceso a la base de datos.
******************************************************************************/
public class FormVentas extends HttpServlet {
private DAOVenta dv = null;
/************************************************************************
Al inicializarse el servlet se crea el DAO: en éste se carga el driver JDBC y se leen
propiedades. El argumento del constructor del DAO es el path de la aplicación.
Si ya se hubiesen cargado driver y propiedades, no se vuelven a cargar.
*************************************************************************/
public void init(ServletConfig config) throws ServletException {
super.init(config);
dv = new DAOVenta( config.getServletContext().getRealPath("/") );
}
/*****************************************************************
* Procesar una petición HTTP con el método POST
* Muestro las ventas del cliente que se pasa como argumento del formulario
*****************************************************************/
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out= response.getWriter(); // Obtener flujo salida;
try {
HttpSession sesion = request.getSession( false ); // Obtener sesión
response.setContentType("text/html; charset=iso-8859-1"); // Definir tipo de salida
UtilGeneral.imprimirInicioPagina( "Ejemplo de servlet", "Ventas del cliente seleccionado", out);
//// Si hay sesión, mostrar atributos y resto de página
if ( sesion != null) {
UtilGeneral.imprimirSesion(sesion, out);
imprimirFormulario( request, out ); // Imprimir salida
}
else
UtilGeneral.imprimir(out, "La sesión no está disponible", true, true);
}
catch (Exception e) {
UtilGeneral.imprimir( out, "Error general. " + e.getMessage(), true, true );
}
finally {
UtilGeneral.imprimirFinPagina( out );
}
}
/*****************************************************************
* Procesar una petición HTTP con el método GET. Reenvia a doPost
*****************************************************************/
public void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
/************************************************************************
Imprimo tabla de ventas
**************************************************************************/
void imprimirFormulario( HttpServletRequest request, PrintWriter out ) {
try {
Vector vecVentas = null;
HttpSession sesion = request.getSession(false); // Obtener sesión
//// DAO: asignar identificación (login-pwd)
dv.setIdentificacion( (String)sesion.getAttribute("login"), (String)sesion.getAttribute("password"));
//// Usar el DAO para conseguir vector de clientes. Argumento: el código de cliente
try {
vecVentas = dv.select( "codigo = '" + request.getParameter("cliente.codigo")+"'");
}
catch ( Exception e) {
UtilGeneral.imprimir(out, "Error en la consulta. " + e.getMessage());
}
//// Inicio de tabla
out.println("<P>Ventas al cliente " + request.getParameter( "cliente" ) + ":");
out.println("<TABLE BORDER=1 align='center'>");
out.println("<TR bgcolor=#00CCFF>");
out.println("<TH>CODIGO</TH>");
out.println("<TH>PRECIO</TH>");
out.println("<TH>COSTE</TH>");
out.println("<TH>BENEFICIO</TH>");
out.println("</TR>");
//// Formateador de números
DecimalFormat myFormatter = new DecimalFormat( "##,###,###.#");
myFormatter.setMinimumFractionDigits(1);
if ( vecVentas.size() == 0)
UtilGeneral.imprimir( out, "No hay ventas registradas", true, true );
///// Recorrer fila a fila y poner en tabla
for ( int i = 0; i < vecVentas.size(); i++ ) {
Venta v = (Venta) vecVentas.get(i);
out.println("<TR bgcolor=#00FF00>");
out.println("<TD>" + v.getCodigo() + "</TD>");
float precio = v.getPrecio().floatValue();
float coste = v.getCoste().floatValue();
out.println("<TD align=right>" + myFormatter.format( precio ) + "</TD>");
out.println("<TD align=right>" + myFormatter.format( coste ) + "</TD>");
out.println("<TD align=right>" + myFormatter.format( precio - coste )+ "</TD>");
out.println("</TR>");
}
out.println("</TABLE>");
}
catch (Exception e) {
UtilGeneral.imprimir( out, "EROR EN FORMULARIO. " + e.getMessage(), true, true );
}
}
}