Servlet que prueba la conexión a una base de datos

(Febrero de 2005)

Introducción

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:

Base:     

Usuario:  

Password:

Si ves el código fuente, hay varios aspectos a tener en cuenta en el formulario:

Manejo de un archivo .properties

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()+"/"

Esta es la cadena que pasamos al constructor de la clase Propiedades.

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".

Volvamos a init()

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').

doPost

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:

  1. Empezamos con lo habitual, definiendo el tipo de salida:
    
          response.setContentType("text/html; charset=iso-8859-1");  // Definir tipo de salida
          PrintWriter out = response.getWriter();                    // Obtener flujo salida
    	
  2. A continuación se imprime el inicio de página:
    
          try {
              //// Imprimir inicio página
              out.println("");
              ....
    	
  3. Comprobamos que el driver se ha cargado correctamente, avisando al usuario. Si la carga no fue correcta terminamos la página (escribimos etiquetas de fin) y salimos (return) del método:
    
              if ( !driver ) {
                out.println("<br>No se ha cargado el driver");
                terminarPagina( out );
                return;
              }
    	
  4. Realizamos la conexión a la base de datos por medio del mensaje DriverManager.getConnection() que lanza una excepción del tipo SQLException si hay un error. Aquí es interesante observar que los argumentos de este mensaje se obtienen de los parámetros del formulario HTML, por medio de request.getParameter("nombre_del_parámetro"); salvo el host, que se obtiene del objeto de la clase Propiedades: acceso.getHost(). La llamada completa es:
    
    	   con = DriverManager.getConnection( acceso.getHost() +
    					     request.getParameter("base"),
    					     request.getParameter("login"),
    					     request.getParameter("password"));
    	
  5. Cerramos la conexión y terminamos la página.. El método cerrarConexion() cierra la conexión (si es que no está ya cerrada) y devuelve true en caso de que el cierre haya sido normal o false en caso de que haya habido errores. Acabamos poniendo el fin de la página (etiquetas de cierre de página). Recordar que la sentencia finally {} se ejecuta en cualquier caso (haya habido o no excepciones).
    
    	finally {
    		if ( cerrarConexion() )
    			out.println( "<P>Cerrada conexión.</P>");
    		else
    			out.println( "<P>Error en cierre de conexión.</P>");
    		terminarPagina( out );
    	}
    	

destroy() y cierre de conexión

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:




Volver al índice