Vamos a realizar un sencillo servlet que realiza una conexión a base de datos. Si la conexión ha tenido éxito, la cierra y muestra el resultado en la página. Damos por supuesto que el lector está informado de los fundamentos de JDBC. Primero trataremos el formulario y después comentaremos el código fuente del servlet.
El formulario que invoca al servlet es el siguiente:
Si ves el código fuente, hay varios aspectos a tener en cuenta en el formulario:
En el código del servlet puede observar los atributos de la clase:
public class Conexion extends HttpServlet {
private Connection con = null; // Conexion
private boolean driver = false; // true si se ha cargado driver en init()
private Propiedades acceso; // Clase para saber driver, host, etc.
....
El primer atributo es una referencia a la conexión. El segundo es una bandera (flag) que nos sirve para indicar que se ha producido un error al cargar el driver en init(). El tercero es una referencia a un objeto de la clase Propiedades. Veremos lo que hace esta clase. Pero antes veremos lo que hace el método init() de nuestro servlet:
public void init(ServletConfig config) throws ServletException {
super.init(config);
try {
ServletContext sc = config.getServletContext(); // Obtengo contexto del servlet
//// El path donde está el archivo de propiedades es 'contexto/propiedades/paquete/'
//// sc.RealPath("/") me da el path del contexto de aplicación
acceso = new Propiedades( sc.getRealPath("/")+"propiedades/" + getClass().getPackage().getName()+"/");
//// Si no hay problema con el archivo de propiedades, cargo el driver
if ( acceso.mensajeError == null ) {
Class.forName( acceso.getDriver() );
driver = true;
}
}
catch (ClassNotFoundException e) {
driver = false;
}
}
El método empieza obteniendo un contexto del servlet, que llamamos sc. La utilidad de este contexto es lograr el path del contexto de aplicación por medio de sc.getRealPath("/"). Este directorio es la ruta raiz que nos da el servidor de aplicaciones. Recuerde que en la versión de Tomcat 4.X, si trabaja con el contexto raiz, este path será CATALINA_HOME/webapps/ROOT.
El constructor de Propiedades nos pide el path del archivo parametros.properties que contiene en la forma de pares clave-valor el driver y host al que conectaremos:
basedatos.driver=com.mysql.jdbc.Driver basedatos.host=jdbc:mysql://localhost:3306/
Es evidente que es una conexión a un servidor local, si utilizásemos un servidor remoto:
basedatos.driver=org.gjt.mm.mysql.Driver basedatos.host=jdbc:mysql://proactiva-calidad.com:3306/
Este archivo se encuentra en la ruta PATH_CONTEXTO/propiedades/NOMBRE_PAQUETE. Nuestro paquete se llama docen_servlet01 y lo obtenemos por medio de getClass().getPackage().getName(). Por tanto (si usamos el contexto raíz) el path completo es: CATALINA_HOME/webapps/ROOT/propiedades/docen_servlet01, que se obtiene mediante:
sc.getRealPath("/")+"propiedades/" + getClass().getPackage().getName()+"/"
La clase Propiedades es muy sencilla:
/****************************************************************************
* Clase que lee propiedades de un archivo .properties
* El constructor carga las propiedades. Si hay error, queda almacenado en mensajeError;
* si no lo hubiere, mensajeError permanece siendo null.
****************************************************************************/
public class Propiedades {
private String ficheroParametros = "parametros.properties";
private Properties prop = new Properties();
public String mensajeError;
/***************************************************************************
* Constructor que carga en atributo 'prop' el archivo de propiedades
***************************************************************************/
public Propiedades( String pathContexto ) {
try {
URL url = new URL( "file:" + pathContexto + ficheroParametros ); // Abro URL
prop.load( url.openStream() ); // Cargo propiedades desde InputStream de URL
}
catch (MalformedURLException e) {
mensajeError = new String("Mensaje de error: " + e.toString() );
}
catch (IOException e) {
mensajeError = new String("Mensaje de error: " + e.toString() );
}
}
/***************************************************************************
* Método que recupera el valor de una clave del atributo 'prop'.
* Si no la encuentra, devuelve el parámetro 'defecto'
***************************************************************************/
public String getParametro(String clave, String defecto) {
String retorno = defecto;
try {
retorno = prop.getProperty(clave, defecto);
}
catch (Exception e) {
retorno = defecto;
}
finally {
return retorno;
}
}
/****************************************************************************
* Sobrecargado. Si no encuentra clave, devuelve ""
****************************************************************************/
public String getParametro(String clave) {
return getParametro(clave, "");
}
public String getDriver() {
return getParametro( "basedatos.driver" );
}
public String getHost() {
return getParametro( "basedatos.host" );
}
}
Puede observarse que el constructor consigue una URL al archivo por medio de:
URL url = new URL( "file:" + pathContexto + ficheroParametros ); // Abro URL
¿Por qué se añade el protocolo "file:"? Sencillo, nuestro archivo .properties se encuentra en el propio contexto de aplicación del servlet y, por tanto, no necesita de usar el protocolo http. Las propiedades se cargan en el atributo prop por medio de una llamada a load(), donde el argumento es el InputStream de la URL.
Una vez que se han cargado las propiedades del archivo, la clase puede devolver una propiedad mediante la llamada a:
retorno = prop.getProperty(clave, defecto);
getProperty() nos devuelve el valor de la clave correspondiente, si no la encontrase devuelve el segundo argumento (defecto). Por ejemplo, en nuestro caso:
retorno = prop.getProperty("basedatos.driver", defecto);
Almacenamos en retorno el texto "com.mysql.jdbc.Driver".
Con lo que sabemos estamos en condiciones de entender lo que hace el método init(). Una vez que he creado el objeto acceso, de la clase Propiedades, podemos llamar a acceso.getDriver() para obtener el driver señalado en el archivo .properties.
//// Si no hay problema con el archivo de propiedades, cargo el driver
if ( acceso.mensajeError == null ) {
Class.forName( acceso.getDriver() );
driver = true;
}
Es importante recordar que init() sólo se ejecuta en la primera invocación al servlet. Por tanto cualquier modificación al archivo .properties surtirá efecto una vez que recarguemos el servlet (el manager de Tomcat viene con una opción de 'reload').
En doPost() respondemos a cada invocación al servlet desde el formulario HTML. La tarea más importante que se realiza aquí es la apertura de una conexión a la base de datos. ¿Dónde poner la conexión a la base de datos? ¿En init() o en doXX()? Dicho de otra forma, ¿nos conectamos en init() o cada vez que se hace una solicitud (un "request" o invocavión al servlet)? Lo normal es que queramos que cada usuario abra una conexión a la base de datos, por tanto intentaremos la conexión en respuesta a una solicitud get o post. Si pusieramos la conexión en el init(), dicha conexión sólo se realizaría la primera vez que se invocase el servlet.
En doPost() realizamos las siguientes tareas:
response.setContentType("text/html; charset=iso-8859-1"); // Definir tipo de salida
PrintWriter out = response.getWriter(); // Obtener flujo salida
try {
//// Imprimir inicio página
out.println("");
....
if ( !driver ) {
out.println("<br>No se ha cargado el driver");
terminarPagina( out );
return;
}
con = DriverManager.getConnection( acceso.getHost() +
request.getParameter("base"),
request.getParameter("login"),
request.getParameter("password"));
finally {
if ( cerrarConexion() )
out.println( "<P>Cerrada conexión.</P>");
else
out.println( "<P>Error en cierre de conexión.</P>");
terminarPagina( out );
}
En respuesta a la destrucción del servlet se llama a cerrarConexión(). El código es el siguiente:
public void destroy() {
cerrarConexion();
}
boolean cerrarConexion() {
try {
if ( con != null ) {
if (!con.isClosed())
con.close();
}
return true;
}
catch (SQLException e) {
return false;
}
}
cerrarConexión() hace lo siguiente: