logbitup.gif (360 bytes)

 Curso JAVA 
 
Unidad 12: "Programación Java en el lado servidor (servlets y JSP). JDBC"

logbitdn.gif (787 bytes)

  www.bit.es - Calendario de cursos - Solicitud de información

Curso Java
Curso Analista Programador entorno Java

Objetivos de la Unidad:


Una muy interesante referencia de Tomcat en castellano se puede consultar en http://www.programacion.com/java/tutorial/tomcatintro y sobre servlets/JSP en http://www.programacion.com/java/tutorial/servlets_jsp

Muy bueno también pero en inglés tenemos http://www.coreservlets.com/Apache-Tomcat-Tutorial


El tutorial de Sun cubre una buena introducción a los servlets en http://java.sun.com/javaee/reference/tutorials/index.jsp.
Ejemplos potentes se pueden encontrar en
http://www.servlets.com/jsp/examples/
Los JSP permiten crear "custom Tags". Unas buena referencias son
http://developer.java.sun.com/developer/Books/cservletsjsp/chapter14.pdf  y http://developer.java.sun.com/developer/technicalArticles/xml/WebAppDev3 (custom.war)

Para una introducción en castellano puede leerse los apuntes de la Escuela de Ingenieros de Navarra en http://mec21.etsii.upm.es/ayudainf/aprendainf/JavaServlets/servlets.pdf  y el artículo publicado en la revista Sólo Programadores http://www.towercom.es/nsp0002.html.

Una variante basada en servlets son las JSP (Java Server Pages) de las que se puede leer una introducción en http://developer.java.sun.com/developer/technicalArticles/Programming/jsp/ , un tutorial en http://java.sun.com/products/jsp/docs.html y una carta sintáctica simplificada en jsp_syntax.pdf y una completa en en http://java.sun.com/products/jsp/syntax/2.0/card20.pdf

Los servlets y JSP se ejecutan dentro de un Web Container como el de IBM WebSphere, iPlanet, Tomcat, Oracle, Bea Systems, etc. La configuración de cualquiera de ellos es estándar y una buena documentación es la de Bea en http://edocs.bea.com/wls/docs61/webapp/web_xml.html  

Tras un acuerdo de Sun Microsystems y grupo Open Source Apache el entorno de desarrollo de referencia pasa a ser Tomcat. Este servidor puede funcionar tanto de plug-in de Apache e IIS así como stand-alone. La calidad del producto supera a la de un kit de desarrollo y puede ser utilizado como servidor base de aplicaciones reales. Es gratuito y de código fuente abierto. Las direcciones de referencia de Sun y Apache son respectivamente
http://java.sun.com/products/jsp/download.html  y http://jakarta.apache.org/tomcat/index.html
Para configurarlos en un IDE (como JBuilder ó Visual Age) pueden seguirse los pasos indicados aquí.

Tomcat puede funcionar como stand-alone o como un plugin de Apache o IIS. La configuración para incrustar Tomcat en el IIS de Microsoft se puede consultar en 
http://tomcat.apache.org/connectors-doc/config/iis.html y la de apache en http://tomcat.apache.org/connectors-doc/config/apache.html

El servidor Web Apache es el más utilizado actualmente en Internet y soporta servlets tanto para UNIX, LINUX y Win32 mediante el proyecto Java Apache Project

Plataformas que soportan servlets:

http://www.servlets.com/engines
http://jserv.javasoft.com/products/java-server/servlets/environments.html


Otras fuentes:

http://www.servlets.com
http://en.wikipedia.org/wiki/Java_Servlet 

Herramientas para el desarrollo de servlets:

Como ejercicio del curso se propone un ServletWizard (Wizard.zip) del cual en la página principal se hace referencia puesto que se publicó en relación a él un artículo en la revista "Sólo Programadores"

Una tecnología importante en grandes instalaciones es la denominada EJB (Enterprise Java Beans). Puede leerse una introducción en http://www.proactiva-calidad.com/java/ejb/index.html y más información en http://java.sun.com/products/ejb/
Las Collections (
http://developer.java.sun.com/developer/technicalArticles/Collections/Using/index.html) son una novedad de Java2 muy útil para la gestión de la persistencia de objetos complejos en el marco de los EJB
Los EJB son la base de los servidores de aplicaciones J2EE basados en Java como i.Planet, IBM WebSphere, Bea WebLogic, Oracle, IONA, JBoss, etc. que cuentan con herramientas como
EJBWizard
Un importante site de referencia para foros e información de todo tipo sobre J2EE es
http://www.theserverside.com  

Una exhaustiva lista de servidores de aplicaciones, tanto gratuitos como de pago puede encontrarse en http://java.sun.com/j2ee/licensees.html

Para ver un ejemplo de cómo se lleva a cabo el deployment en un servidor de aplicaciones (en este caso J2EE de Sun) puede analizarse el contenido de los ficheros resultantes .ear y .jar de su asistente de deployment. 

Aunque la oferta de hosting de Servlets & JSP es menor que la oferta de LAMP hosting (Linux + Apache + MySQL + PHP) pueden encontrarse proveedores como www.praktonhost.com que ofrecen servlet & JSP hosting con MySQL sobre Linux a precios LAMP en el Datacenter de Colt Telecom de Barcelona. Colt Telecom es una referncia de primer nivel en cuanto a potencia y calidad de sus Datacenters (www.sun.com/customers/software/colt.xml).  Una lista de proveedores de Servlet/JSP hosting puede encontrarse en http://resources.corejsp.com/jsp-hosting.html y en http://www.servlets.com/isps/servlet/ISPViewAll.



2.-  Servlets

Servlet mínimo

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class HolaServlet extends HttpServlet
{
  public void service (HttpServletRequest req, HttpServletResponse res) throws
            ServletException, IOException
  {
    PrintWriter p = res.getWriter();
    res.setContentType("text/html");
    
    p.println("<html>");
    p.println("<body>");
    p.println("<h2>Hola Mundo</h2>");
    p.println("</body>");
    p.println("</html>");
    p.close();
  }
}  

Estructura de un Servlet básico

//
// HolaServlet.java
//
// Para probarlo hay que arrancar el servidor (En este caso ejecutar Aplicacion)
// luego ir a un navegador y solicitar la siguiente URL:
//
// Para probar el servlet hay que ir a un navegador y pedir la siguiente URL:
//
http://[dirección ip]:[port]/[contexto]/servlet/[package].[clase servlet]
//
// En este caso teniendo servidor y navegador en la misma máquina puede probarse con:
// http://localhost:8080/servlet/HolaServlet
//

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class HolaServlet extends HttpServlet
{
  public void init(ServletConfig config) throws ServletException
  {
    super.init(config);
    // inicialización del servlet al cargarse en memoria
  }
  
  public void service (HttpServletRequest req, HttpServletResponse res) throws
            ServletException, IOException
  {
    PrintWriter p = res.getWriter();
    res.setContentType("text/html");
    
    p.println("<html>");
    p.println("<body>");
    p.println("<h2>Hola Mundo</h2>");
    p.println("</body>");
    p.println("</html>");
    p.close();
  }
  
  public void destroy()
  {
    // liberación de recursos al descargarse de memoria
  }
}

//
// Aplicacion.java
//
// Nota: Para arrancar dentro de JBuilder en debug hay que indicar en Run-VM parameters lo siguiente:
//       -Dtomcat.home=c:\jakarta-tomcat-3.2.3     (para Tomcat 3)
//       -Dcatalina.home="c:\Archivos de programa\Apache Tomcat 4.0"   (para Tomcat 4)

public class Aplicacion
{
  public static void main(String[] args)
  {
    // API de servlets 2.0 con el JSDK 2.0
    //sun.servlet.http.HttpServer.main(args);
    
    // API de servlets 2.1 con el JSWDK
    // com.sun.web.shell.Startup.main(args);

    // Tomcat 3.2.3
    // org.apache.tomcat.startup.Tomcat.main(args);

    // Tomcat 4
    org.apache.catalina.startup.Bootstrap.main(new String[]{"start"});
  }
}


El método init() se ejecuta la primera vez que llega un impacto. Es similar al init de un Applet. Una aplicación típica de init() es establecer sesión con una base de datos, así como establecer parámetros iniciales. Ver la documentación de ServletConfig.

Las sesiones en servlets se pueden mantener por Cookies o por URLrewriting (reescritura de URL). En el primer caso la información de sesión (el sessionId) viaja en una cookie no persistente en la cabecera HTTP. En el segundo caso el sessionId se incluye en la URL. Ello implica tener que añadir el sessionId a todos los links y como campos hidden en los formularios. La clase HttpServletResponse dispone del método encodeURL a tal efecto. Mostramos a continuación un ejemplo de cómo gestionar con un servlet los ámbitos de aplicación, sesión y petición. La gestión de HttpSession se realiza por cookies automáticamente.

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ContadorServlet extends HttpServlet
{
  int contadorAplicacion = 0;

  public void service(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException
  {
    PrintWriter p = new PrintWriter(res.getOutputStream());
    res.setContentType("text/html");
    int contadorRequest = 0; // Gestión del contador de sesión:
    int contadorSesion = 1;
    HttpSession ses = req.getSession();
    Integer contador = (Integer)ses.getAttribute("contador");
    if (contador != null)
    {
      contadorSesion = contador.intValue();
      contadorSesion++;
    }
    ses.setAttribute("contador", new Integer(contadorSesion));

    // incrementos de los contadores de aplicacion y request:
    contadorAplicacion++;
    contadorRequest++;

    // Muestra de los valores de los contadores:
    p.println("<HTML>");
    p.println("<BODY>");
    p.println("<BR><BR>");
    p.println("Contador a nivel de aplicaci&oacute;n: " + contadorAplicacion + "<BR>");
    p.println("Contador a nivel de sesi&oacute;n:" + contadorSesion + "<BR>");
    p.println("Contador a nivel de request: " + contadorRequest + "<BR>");
    p.println("</BODY>");
    p.println("</HTML>");
    p.close();
  }
}

En los sucesivos impactos la conexión se mantiene abierta por lo que la ejecución global de un servlet puede ser más rápida que un CGI compilado en C.
El encaje de los servlets en arquitecturas multi tiered está descrito en
http://javaexchange.com/dcb_white.html  

Ejemplos: Servlet Wizard y JSP

Como ejercicio del curso se propone un ServletWizard (
Wizard.zip) del cual en la página principal se hace referencia puesto que se publicó en relación a él un artículo en la revista "Sólo Programadores"

En el archivo ServletsJSP.zip se encuentra una demo con el código fuente con ejemplos probables de JSP básicos así como una muestra de interrelacionar servlets con JSP.

3.- JSPs.  

Los JSP plantean una estrategia opuesta a los servlets: en lugar de ser Java que genera HTML se trata de HTML en el que se incrusta JAVA. La ventaja es que los ficheros JSP (denominados páginas JSP) pueden ser editados con una herramienta visual como Dreamweaver, FrontPage ó similar. Tecnológicamente se crea un servlet automáticamente, se compila (sólo la primera vez) y se ejecuta para cada JSP.

<%@ page import="java.util.*, java.io.*" %>

<%!
  public void jspInit() 
  {
    // Este método equivale al init() de un servlet 
 
}

  public void jspDestroy() 
    {
    }
%>

<%  Rectangulo r = new Rectangulo(0, 0, 10, 5); %>


<html>
<body>
Hola Mundo
La base del rectangulo es <%= r.getBase() %> y la altura es <%= r.getAltura() %>.
</body>
</html>

La sintaxis de los JSP se encuentra resumida en http://java.sun.com/products/jsp/syntax.pdf 

4.- Acceso de bases de datos. JDBC.  

Como documentación de partida una de las mejores opciones es ir al tutorial de Sun en http://java.sun.com/docs/books/tutorial/jdbc/basics/index.html y cómo no en las FAQ sobre JDBC en http://java.sun.com/products/jdbc/faq.html. También puede seguirse un curso on-line en http://developer.java.sun.com/developer/onlineTraining/Database/JDBCShortCourse/index.html

Desde un punto de vista más amplio la página de referencia es  http://java.sun.com/products/jdbc

En relación con el acceso a bases de datos en AS/400 puede ser interesante empezar por las FAQ al respecto en http://www.jguru.com/faq/Java400. Se puede aumentar sustancialmente el rendimiento con un pool de conexiones tal y como se indica en http://www.web400.com/download/JDBCseminar.html.
Para ello se puede utilizar el AS/400 Java toolbox http://www.ibm.com/as400/toolbox (JTOpen)

Una de las bases de datos más reputadas en el mundo Java es Oracle. Puede descargarse una versión gratuita denominada Oracle Express en http://www.oracle.com/technology/software/products/database/xe/index.html.

Microsoft ofrece una versión reducida y gratuita del SQLserver denominada SQL Server Express (antes MSDE) que se puede obtener en http://msdn.microsoft.com/vstudio/express/sql/download

Hay 4 tipos de drivers JDBC: Tipo 1, tipo 2, tipo 3 y tipo 4 según se describe en http://www.javaworld.com/javaworld/jw-07-2000/jw-0707-jdbc.html y un buen índice de drivers disponibles puede encontrarse en http://industry.java.sun.com/products/jdbc/drivers y en http://ourworld.compuserve.com/homepages/Ken_North/jdbcvend.htm 

Los drivers tipo 3 y 4 permiten acceso directo desde el driver 100% Java a la base de datos utilizando protocolos que van por encima del transporte. Es el caso de SQL*Net de Oracle o de TDS de SQL Server. Sin embargo su utilización en arquitecturas de dos capas puede chocar con paso por los firewalls tal y como se expone en http://www.dbmsmag.com/9802d13.html 

Los drivers de MySQL pueden bajarse de http://www.mysql.com/products/connector/j.

Los drivers de Postgresql (http://www.ejip.net/faq/postgresql_win_setup_faq.jsp) pueden bajarse de http://jdbc.postgresql.org/ 


Drivers y cadenas de conexión JDBC:

FabricanteDriverJDBC URL
Oracle Expressoracle.jdbc.driver.OracleDriverjdbc:oracle:thin:@//localhost:1521/XE
Oracle general oracle.jdbc.driver.OracleDriverjdbc:oracle:thin:user/password@host:port:database
Oracle internal JVMNo hace falta Class.forName (Internal JVM)jdbc:default:connection
MySQLcom.mysql.jdbc.Driverjdbc:mysql://host:port/database
MySQL (Old) org.gjt.mm.mysql.Driverjdbc:mysql://host:port/database
SQL Server com.internetcds.jdbc.tds.Driverjdbc:freetds:sqlserver://host:1433/database
SQL Server MScom.microsoft.jdbc.sqlserver.SQLServerDriverjdbc:microsoft:sqlserver://host:port\\instance
;DatabaseName=database
AS/400com.ibm.db2.jdbc.app.DB2Driverjdbc:db2:PID400H
Postgresqlorg.postgresql.Driverjdbc:postgresql://host:port/database
ODBCsun.jdbc.odbc.JdbcOdbcDriverjdbc:odbc:odbc_dsn
ODBC (MS)com.ms.jdbc.odbc.JdbcOdbcDriverjdbc:odbc:odbc_dsn

Otros: http://www.ewin.org/~bret/java/jdbc/drivers.html 

La gestión de excepciones es clave para garantizar un buen rendimiento en las aplicaciones (http://websphere.sys-con.com/read/43087.htm)


5.- SQL

Un tutorial con ejemplos resueltos se puede encontrar en http://www.w3schools.com/sql/default.asp. En cuanto al SQL del MySQL http://dev.mysql.com/doc/refman/5.0/es/index.html es la referencia introductoria. A nivel de especificaciones y estándares http://www.jcc.com/sql.htm y www.wiscorp.com/SQLStandards.html son buenos puntos de información.
 

6.- Ejemplo JDBC - Java  - SQL:

Para que funcione en un PC hay que definir la entrada ODBC denominada Neptuno en Panel de Control->ODBC 32 bits.
Este ejemplo funciona stand-alone. Queda como ejercicio para el elector adaptarlo a un servlet y generar HTML dinámicamente con los datos ontenidos de la base de datos.
 
 

//
// ProvaJDBC.java
//

import java.sql.*;
import java.util.*;

public class ProvaJDBC
{
    public static void main(String args[]) throws Exception
    {
       Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
       Connection con = DriverManager.getConnection("jdbc:odbc:manart", "admin", "");
       Statement stmt =
con.createStatement();
       ResultSet rs = stmt.executeQuery("SELECT * FROM Articulos");
       while (rs.next())
       {
          System.out.println(rs.getString(1) + " - " +
                             rs.getString(2) + " - " + rs.getString(3)
);
       }
       rs.close();

       stmt.close();
       con.close();

    }
}



También se pueden utilizar arrays asociativos:

//

// ProvaJDBC.java
//

import java.sql.*;
import java.util.*;

public class ProvaJDBC
{
    public static void main(String args[]) throws Exception
    {
       Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
       Connection con = DriverManager.getConnection("jdbc:odbc:manart", "admin", "");
       Statement stmt = con.createStatement();

       ResultSet rs = stmt.executeQuery("SELECT * FROM Articulos");
       while (rs.next())
       {
          System.out.println(rs.getString("articuloId") + " - " +
                             rs.getString("descripcionArticulo") + " - " + rs.getString("precioArticulo"));

       }
       rs.close();

       stmt.close();
       con.close();

    }
}


El ejemplo anterior se puede adaptar a un servlet:

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.sql.*;

public class HolaServlet extends HttpServlet
{
  public void service (HttpServletRequest req, HttpServletResponse res) throws
            ServletException, IOException
  {
        try
    {
      PrintWriter p = res.getWriter();
      res.setContentType("text/html");

      p.println("<html>");
      p.println("<body>");
      p.println("<pre>");

      Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
      Connection con = DriverManager.getConnection("jdbc:odbc:manart", "admin", "");
      Statement stmt = con.createStatement();

      ResultSet rs = stmt.executeQuery("SELECT * FROM Articulos");
      while (rs.next())
      {
       p.println(rs.getString(1) + " - " +
                 rs.getString(2) + " - " + rs.getString(3));

      }
      rs.close();

      stmt.close();
      con.close();


      p.println("</pre>");
      p.println("</body>");
      p.println("</html>");
      p.close();
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }
  
  public void destroy()
  {
    // liberación de recursos al descargarse de memoria
  }
}

El ejemplo anterior se puede adaptar a un JSP (pej: provajdbc.jsp):

<%@ page import="java.io.*,java.sql.*" %>
<html>
<body>
<h3>Listado<h3>
<table border="1">
<%
       Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
       Connection con = DriverManager.getConnection("jdbc:odbc:manart", "admin", "");
       Statement stmt = con.createStatement();

       ResultSet rs = stmt.executeQuery("SELECT * FROM Articulos");
       while (rs.next()) {
%>

     <tr><td><%= rs.getString(1) %></td><td><%= rs.getString(2) %></td><td><%= rs.getString(3) %></td></tr>
<%

       }
       rs.close();

       stmt.close();
       con.close();

%>
</table>
</body>
</html>


Este estilo de programación directa en el JSP no es tan utilizada en el colectivo JAVA como en los colectivos PHP o ASP. Se puede lograr un nivel de encapsulación no orientada a objetos con la directiva
<%@ include que es una instrucción de pre-procesador que permite reutilizar código por inclusión y que luego se compila conjuntamente. Los scriplets con <% se incluyen a nivel del método service del JSP subyacente y los <%! se incluyen a nivel de clase por lo que se pueden definir rutinas que pueden ser incluidas en varios JSP. 

En
http://www.ejip.net/ejip.jsp se ofrece espacio de pruebas gratuito de JSP.

El ejemplo anterior se organizar por inclusión de código directo (codigo.jsp) o de rutinas (libreria.jsp) en pej: provajdbc2.jsp:

<!-- provajdbc2.jsp -->
<%@ page import="java.io.*,java.sql.*" %>
<%@ include file="codigo.jsp" %>
<%@ include file="libreria.jsp" %>
<html>
<body>
<h3>Listado<h3>
<table border="1">
<%
       while (rs.next()) {
%>

     <tr><td><%= rs.getString(1) %></td><td><%= rs.getString(2) %></td><td><%= rs.getString(3) %></td></tr>
<%

       }
       rs.close();

       stmt.close();
       con.close();
%>
</table>
<%= despedida() %>
</body>
</html>

<!-- codigo.jsp -->
<%
       Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
       Connection con = DriverManager.getConnection("jdbc:odbc:manart", "admin", "");
       Statement stmt = con.createStatement();

       ResultSet rs = stmt.executeQuery("SELECT * FROM Articulos");
%>


<!-- libreria.jsp -->
<%!
     String despedida()
     {
        return "Adi&oacute;s: Texto enviado desde una rutina incluida";
     }
%>


Sin embargo al disponer Java de  clases se puede (y se recomienda para proyectos complejos) encapsular la lógica de acceso en ellas y no mezclarla con el JSP. Se trata de separar la capa de presentación de la lógica de negocio en capas distintas.

Almacenamiento de imágenes y otra información en bases de datos.

Se adjunta a continuación en
img.zip un ejemplo completo de cómo:

Un ejemplo detallado y paso a paso de un mantenimiento de pedidos con JDBC (maqueta sobre MS-Acess) y Swing (con JTable) se puede encontrar en http://developer.java.sun.com/developer/technicalArticles/Database/dukesbakery 

El ejemplo funcionante de servlet para realizar uploads (
upload.war) puede ser de gran utilidad para llevar contenidos al servidor.

MetaData JDBC:

A continuación se muestra un ejemplo de los servicios "metadata" que permiten obtener dinámicamente información sobre las tablas, vistas, etc. En este caso se muestran los 100 primeros registros de una tabla con sólo indicar el nombre de la tabla:


//

// PruebaMetadataJDBC.java
//

import java.sql.*;
import java.util.*;
import java.io.*;

public class PruebaMetadataJDBC
{
   public static void main(String args[]) throws Exception
   {
      Connection con = null;
      try
      {
         Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
         String dsn = "jdbc:odbc:Neptuno";
         String user = "admin";
         String password = "";
         
         con = DriverManager.getConnection(dsn, user, password);
      }
      catch( Exception ee)
      {
         ee.printStackTrace();
      }

      // Metadatos a nivel de base de datos: Tablas y vistas disponibles, etc

      DatabaseMetaData dmd = con.getMetaData();

      // Muestra las tablas y vistas que empiezan por c
      ResultSet rsd = dmd.getTables(null, null, "c%", null);
      System.out.println("------ tablas y vistas que empiezan por c -------");

      while (rsd.next())
      {
        System.out.println(rsd.getString(3));
      }

      String tipos[] = {"TABLE"};
      rsd = dmd.getTables(null, null, null, tipos);
      System.out.println("------ sólo tablas -------");

      while (rsd.next())
      {
        System.out.println(rsd.getString(3));
      }


      // Metadatos a nivel de tabla: nombre y tipos de columnas de una tabla o vista, etc

      Statement stmt; // SQL statement object
      String query; // SQL select string
      ResultSet rs; // SQL query results
      String v1, v2; // Temporary storage results

      String whereClause = "";

      query = "SELECT * "
             + "FROM Empleados "
             + whereClause;

      try
      {
         stmt = con.createStatement();
         rs = stmt.executeQuery(query);

         ResultSetMetaData rsmd = rs.getMetaData();

         int numCols = rsmd.getColumnCount();
         PrintStream p = System.out;

         System.out.println("");
         System.out.println("-- Estructura la tabla empleados y a continuación sus datos --");

         
for (int i = 1; i <= numCols; i++)
         {
            p.print(" ");
            String columnNom = rsmd.getColumnName(i);
            int columnTipo = rsmd.getColumnType(i);
            p.print(columnNom + " (tipo " + columnTipo + ")");
            
p.print(" ");
         }
         System.out.println("");

         while (rs.next())
         {
            for (int i = 1; i <= numCols; i++)
            {
              p.print(" ");
              int columnTipo = rsmd.getColumnType(i);

              if (columnTipo == -4) // campo de foto
              {
                p.print("[foto]");
              }
              else
              {
                String columnVal = rs.getString(i);
                p.print(columnVal);
              }

              p.print(" ");
            
}
            System.out.println("");
         }

         rs.close();
         stmt.close();
      }
      catch (SQLException e)
      {
         e.printStackTrace();
      }
   }
}

Una variante del ejemplo anterior nos permitirá generar una clase Java a partir del la información de estructura de una base de datos.


import java.io.*;
import java.sql.*;

public class Db2class
{
  public static void main(String[] args) throws Exception
  {
    DataInputStream d = new DataInputStream(System.in);
    System.out.print("Tabla: ");
    String tabla = d.readLine();

    Connection con = null;
    try
    {
      Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
      String dsn = "jdbc:odbc:Neptuno";
      String user = "admin";
      String password = "";
      con = DriverManager.getConnection(dsn, user, password);
    }
    catch( Exception ee)
    {
      ee.printStackTrace();
    }

    Statement stmt;
    String query;
    ResultSet rs;

    query = "SELECT * " + "FROM " + tabla;

    try
    {
      stmt = con.createStatement();
      rs = stmt.executeQuery(query);

      ResultSetMetaData rsmd = rs.getMetaData();

      int numCols = rsmd.getColumnCount();
      PrintStream p = System.out;

      FileOutputStream f = new FileOutputStream(tabla + ".txt");
      PrintStream pf = new PrintStream(f);

      pf.println("");
      pf.println("");
      pf.println("public class " + tabla);
      pf.println("{");

      for (int i = 1; i <= numCols; i++)
      {
        String columnNom = rsmd.getColumnName(i);
        String tipo = sqlTipo2JavaTipo(rsmd.getColumnTypeName(i));
        pf.println(" private " + tipo + " " + columnNom + ";");
        p.print(columnNom);
        p.print(" ");
      }

      pf.println("");
      pf.println("");

      for (int i = 1; i <= numCols; i++)
      {
        String columnNom = rsmd.getColumnName(i);
        String tipo = sqlTipo2JavaTipo(rsmd.getColumnTypeName(i));

        pf.println(" public void set" + primMayusc(columnNom) + "(" + tipo + " valor)");
        pf.println(" {");
        pf.println(" " + columnNom + " = valor;");
        pf.println(" }");
        pf.println("");

        pf.println(" public " + tipo + " get" + primMayusc(columnNom) + "()");
        pf.println(" {");
        pf.println(" return " + columnNom + ";");
        pf.println(" }");
        pf.println("");
      }

      pf.println("}");
      System.out.println("");

      rs.close();
      stmt.close();
    }
    catch (SQLException e)
    {
      e.printStackTrace();
    }
    System.out.println("Se ha generado " + tabla + ".txt Pulse enter para cerrar");
  }

  static String sqlTipo2JavaTipo(String sqlTipo)
  {
    if (sqlTipo.equals("VARCHAR"))
      return "String";
    else if (sqlTipo.equals("INTEGER"))
     return "int";
    else if (sqlTipo.equals("DATETIME"))
      return "Date";
    else if (sqlTipo.equals("LONGBINARY"))
      return "Object";
    else if (sqlTipo.equals("LONGCHAR"))
      return "String";
    else if (sqlTipo.equals("COUNTER"))
      return "int";
    else if (sqlTipo.equals("DOUBLE"))
      return "double";
    else
      return "Object";
  }
  
  public static String primMayusc(String s)
  {
    String primLetra = s.substring(0, 1);
    return primLetra.toUpperCase() + s.substring(1);
  }
}


Una versión bastante más completa que incluye la generación de código inspirado en los Enterprise Java Beans (EJB) se puede encontrar en Db2class.java. En la práctica supone disponer de un sistema de persistencia básico automatizado. 

Sobre esquemas de persistencia la obra de Scott Ambler "Mapping Objects to Relational Databases (ORM) " (http://www.ambysoft.com/mappingObjects.html) es toda una referencia. Son varias las implementaciones Java basadas en sus ideas como Hibernate (www.hibernate.org) o Ibatis (www.ibatis.org) y anteriormente otros frameworks como http://abra.sourceforge.net/ o http://osage.sourceforge.net/.
En general una buena referencia sobre Patrones Objeto/Relacional se puede encontrar en http://www.objectarchitects.de/ObjectArchitects/orpatterns/  
J2EE (Java 2 Enterprise Edition - http://java.sun.com/j2ee) propone un modelo de persistencia con los Entity Beans. Una referencia rápida de J2EE clásicos está en los fichero
http://www.theserverside.com/resources/pdf/ejbmatrix11.pdf  y en  http://www.theserverside.com/resources/pdf/EJB-Matrix-2.0-Draf2.pdf.

Parece que en un futuro el estándar definitivo será JPA (java.sun.com/developer/technicalArticles/J2EE/jpa y www.eclipse.org/webtools/dali/main.php) que acabará fagocitando a los EJBs, Hibernate o Ibatis. Por otra parte hasta la fecha ha gozado de gran popularidad el uso del patrón DAO (en.wikipedia.org/wiki/Data_Access_Object) mediante generadores de código (titaniclinux.net/daogen). Interesante la discusión sobre la controversia de usar ORM versus DAO en www.codefutures.com/weblog/andygrove/archives/2005/02/data_access_obj.html.

En entornos visuales es habitual disponer de adaptadores de conexión JDBC (www.sterlingcommerce.com/Documentation/GIS40/Content/SAAE/Java%20Database%20Connectivity%20(JDBC)%20Adapter.html). Vamos a crear uno muy simple denominado  BDAdaptorBean que no sólo valdrá para conexiones ODBC sino para cualquier conexión JDBC. Ciertos entornos gestionan beans invisibles. En caso de no hacerlo un truco sería hacer que BDAdaptorBean derivara de Panel y darle un aspecto visual en modo diseño pero no hacerlo visible en modo ejecución. Esto permite personalizar las propiedades origenDeDatos, driver, username, password, tabla y whereClause mediante la ventana de propiedades del entorno visual.

import java.sql.*;
import java.util.*;

public class BDAdaptorBean
{
  private Connection con = null;
  private String classDriver = "";
  private String origenDatos = "";
  private String username = "";
  private String password = "";
  private String tabla = "";
  private String campos = "";
  private String whereClause = "";
  private String query = "";
  
  public BDAdaptorBean()
  {
    this("jdbc:odbc:Neptuno");
  }
  
  public BDAdaptorBean(String valor)
  {
    // Valores por defecto para prueba con MS-Access
    setOrigenDatos(valor);
    setDriver("sun.jdbc.odbc.JdbcOdbcDriver");
    setUsername("Admin");
    setPassword("");
  }
  
  public void setDriver(String valor)
  {
    classDriver = valor;
  }
  
  public String getDriver()
  {
    return classDriver;
  }
  
  public void setOrigenDatos(String valor)
  {
    origenDatos = valor;
  }
  
  public String getOrigenDatos()
  {
    return origenDatos;
  }
  
  public void setUsername(String valor)
  {
    username = valor;
  }
  
  public String getUsername()
  {
    return username;
  }
  
  public void setPassword(String valor)
  {
    password = valor;
  }
  
  public String getPassword()
  {
    return password;
  }
  
  public void conectar() throws ClassNotFoundException, SQLException
  {
    Class.forName (classDriver);
    String dsn = origenDatos;
    con = DriverManager.getConnection(dsn, username, password);
  }
  
  public void setConnection(Connection con)
  {
    this.con = con;
  }
  
  public Connection getConnection()
  {
    return con;
  }
  
  public void close() throws SQLException
  {
    con.close();
  }
  
  public void setTabla(String valor)
  {
    this.tabla = valor;
  }
  
  public String getTabla()
  {
    return tabla;
  }
  
  public void setCampos(String valor)
  {
    this.campos = valor;
  }
  
  public String getCampos()
  {
    return campos;
  }
  
  public void setWhereClause(String valor)
  {
    this.whereClause = valor;
  }
  
  public String getWhereClause()
  {
    return whereClause;
  }
  
  public String[][] getDatos()
  {
    return getDatos(tabla, campos, whereClause);
  }
  
  public String[][] getDatos(String tabla, String campos, String whereClause)
  {
    String matrix[][] = null;
    Statement stmt;
    ResultSet rs;
    
    // contar num de campos. Ejemplo : "nom, tel1, tel2, fax"
    
    StringTokenizer st = new StringTokenizer(campos, ",");
    int numColumnas = st.countTokens();
    
    if (whereClause == null) whereClause = "";
    if (!whereClause.equals("")) whereClause = " WHERE " + whereClause;
    
    query = "SELECT " + campos
          + " FROM " + tabla
          + whereClause;
    
    try
    {
      // contar filas
      stmt = con.createStatement();
      rs = stmt.executeQuery("SELECT COUNT(*) "
             + "FROM " + tabla
             + whereClause);
      
      rs.next();
      int numFilas = rs.getInt(1);
      
      // Ahora ya podemos reservar la memoria para la matriz
      // porque ya renemos las filas y las columnas
      
      matrix = new String[numFilas][numColumnas];
      
      stmt = con.createStatement();
      rs = stmt.executeQuery(query);
      
      int i = 0;
      while (rs.next())
      {
        for (int j = 0; j < numColumnas; j++)
        {
          matrix[i][j] = rs.getString(j+1);
        }
        i++;
      }
      rs.close();
      stmt.close();
    }
    catch (SQLException e)
    {
      e.printStackTrace();
    }
    return matrix;
  }
  
  public void setQuery(String valor)
  {
    this.query = valor;
  }
  
  public String getQuery()
  {
    return query;
  }
  
  public int ejecutarSQL() throws SQLException
  {
    return ejecutarSQL(query);
  }
  
  public int ejecutarSQL(String query) throws SQLException
  {
    Statement stmt; // SQL statement object
    
    stmt = con.createStatement();
    return stmt.executeUpdate(query);
  }
  
  /**
  * Banco de pruebas
  */
  public static void main(String args[]) throws Exception
  {
    try
    {
      BDAdaptorBean g = new BDAdaptorBean();
      
      // Comentadas la lineas para SQL Server (ó MSDE que viene en el Office 2000 como personal SQL Server)
      
      // Establecimiento de sesión
      g.setOrigenDatos("jdbc:odbc:Neptuno");
      g.setUsername("Admin");  // g.setUsername("sa");
      g.setPassword("");
      g.conectar();
      
      // Ejecución de una instrucción SQL
      g.setTabla("empleados");    //  g.setTabla("employees");
      g.setCampos("nombre, apellidos");    //  g.setCampos("firstname, lastname");
      g.setWhereClause("nombre like 'N%'");   //  g.setWhereClause("firstname like 'N%'");
      
      // String resultados[][] = g.getDatos("Empleados", "Nombre, Apellidos", "Nombre like '%'");
      String resultados[][] = g.getDatos();
      
      for (int i = 0; i < resultados.length; i++)
      {
        System.out.print(i + ": ");
        for (int j = 0; j < resultados[i].length; j++)
        {
          System.out.print(resultados[i][j] + " ");
        }
        System.out.println("");
      }
      System.out.println("------------Fin----------");
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}

La adaptación de este ejemplo en un servlet sugiere un comentario. Los servlets ofrecen básicamente dos métodos: el método init() y el método service() (con las variantes doGet(), doPost(), etc.)

El método init() se ejecuta la primera vez que se llama al servlet. Es parecido al init() de los applets: Se ejecuta en la carga y el servlet queda persistente. Del mismo modo destroy() se ejecuta al descargarse.

Los servlets gozan de cierta fama de rápidos respecto a los CGI, incluso estando escritos en C. El truco consiste en obtener el objeto Connection en el init() y luego reutilizarlo para el resto de impactos. La obtención de un objeto Connection no es más que establecer un login en la base de datos. El proceso de login puede tardar unos segundos. Los servlets, al contrario que los CGI ofrecen la posibilidad de persistencia en memoria entre un impacto y otro, es decir mantener objetos ya instanciados permanentemente. Los CGI parten de cero cada vez y la persistencia se obtiene generalmente en base a ficheros.

En el siguiente ejemplo se muestra el HolaServlet modificado utilizando el BDAdaptorBean. En el método init() se establece la conexión. A partir de ahí el método service() utiliza una conexión ya creada. En una versión más depurada el método service() debería comprobar el estado de la conexión y reconstruirla en caso de haberse perdido:

//
// HolaServlet.java
//

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class HolaServlet extends HttpServlet
{
  BDAdaptorBean g = new BDAdaptorBean();
  
  public void init(ServletConfig config) throws ServletException
  {
    super.init(config);
   
    try
    {
      // Establecimiento de sesión
      g.setOrigenDatos("jdbc:odbc:Neptuno");
      g.setUsername("admin");
      g.setPassword("");
      g.conectar();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
  
  public void service (HttpServletRequest req, HttpServletResponse res) throws
    ServletException, IOException
  {
    PrintWriter p = res.getWriter();
    res.setContentType("text/html");
    
    p.println("<html>");
    p.println("<body>");
    p.println("<pre>");  // el tag pre permite una salida de terminal ANSI en una página
    
    try
    {
      // Ejecución de una instrucción SQL
      g.setTabla("Empleados");
      g.setCampos("Nombre, Apellidos");
      
      String resultados[][] = g.getDatos();
      
      for (int i = 0; i < resultados.length; i++)
      {
        p.print(i + ": ");
        for (int j = 0; j < resultados[i].length; j++)
        {
          p.print(resultados[i][j] + " ");
        }
        p.println("");
      }
      p.println("------------Fin----------");
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    
    p.println("</pre>");
    p.println("</body>");
    p.println("</html>");
    p.close();
  }
  
  public void destroy()
  {    
    try
    {
            g.close();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}

Para montar persistencia de objetos Java con mapeado a bases de datos SQL los PreparedStatement de JDBC pueden ser muy prácticos:

Cargar [loadRow()]

String selectStatement =
"SELECT CODIGO, FECHA FROM TABLA WHERE CODIGO = ? ";
PreparedStatement prepStmt = con.prepareStatement(selectStatement);
String cod = "1000";
prepStmt.setString(1, cod);
ResultSet rs = prepStmt.executeQuery();

Insertar [insertRow()]

String insertStatement = "INSERT INTO TABLA VALUES (?, ?)";
PreparedStatement prepStmt = con.prepareStatement(insertStatement);
String cod = "1000";
prepStmt.setString(1, cod);
prepStmt.setDate(2, new Date());
prepStmt.executeUpdate();
prepStmt.close();

Borrar [deleteRow()]

String deleteStatement = "DELETE FROM TABLA WHERE ID = ? ";
PreparedStatement prepStmt =
con.prepareStatement(deleteStatement);
String cod = "1000";
prepStmt.setString(1, cod);
prepStmt.executeUpdate();
prepStmt.close();

Modificar [storeRow()]

String updateStatement = "UPDATE TABLA " +
" SET FECHA = ? " +
" WHERE ID = ? ";

PreparedStatement prepStmt = con.prepareStatement(updateStatement);
String cod = "1000";
Date d = new Date();
prepStmt.setDate(1, d);
prepStmt.setString(2, cod);
int rowCount = prepStmt.executeUpdate();
prepStmt.close();

Craig Larman plantea en su libro "UML y Patrones - Ed. Pearson" un modelo de persistencia de objetos. En base a ello y a las funciones anteriores se puede implementar una solución como la mostrada en EntityPersistUML.xls


7.- Ejemplo de scripting integrado en Java: Creación de una clase intérprete de JavaScript con eval()

Puede resultar interesante evaluar expresiones "al vuelo" en la parte servidora. Esta idea es la base del concepto de "websheet" promulgado por Sun hace ya tiempo que viene a ser una hoja de cálculo con interface web ejecutándose en el servidor. El ejemplo utiliza el intérprete de JavaScript Rhino desarrollado en Open Source por Mozilla disponible en http://www.mozilla.org/rhino

//
// JavaScript.java
//

import org.mozilla.javascript.*;
import java.text.*;


public class JavaScript
{
  private Scriptable scope;
  private Context cx;
  
  public JavaScript()
  {
    cx = Context.enter();
    scope = cx.initStandardObjects(null);
  }
  
  public void finalize()
  {
    cx.exit();
  }
  
  public String eval(String expr) throws JavaScriptException
  {
    Object result = null;
    
    result = cx.evaluateString(scope, expr, "origen_script", 1, null);
    return result.toString();
  }
  
  public double deval(String expr) throws JavaScriptException
  {
    return Double.valueOf(eval(expr)).doubleValue();
  }
  
  public static void main(String[] args) throws Exception
  {
    JavaScript js = new JavaScript();
    
    js.eval("x = 4");
    js.eval("y = 5");
    js.eval("z = x + y");
    
    System.out.println(js.eval("z"));
    System.out.println("------");
    
    double estropeadas = 23;
    double repetidas = 73;
    String stRatio;
    double ratio;
    
    js.eval("ESTROPEADAS = " + estropeadas);
    js.eval("REPETIDAS = " + repetidas);
    js.eval("TOTAL = ESTROPEADAS + REPETIDAS");
    stRatio = js.eval("RATIO_ESTROPEADAS = ESTROPEADAS / TOTAL");
    
    System.out.println("RATIO_ESTROPEADAS: " + stRatio);
    
    
    ratio = js.deval("RATIO_ESTROPEADAS = ESTROPEADAS / TOTAL");
    
    System.out.println("dbl_RATIO_ESTROPEADAS: " + ratio);
    
    System.out.println("--- Salida con formato ---");
    
    DecimalFormat df = new DecimalFormat("#,##0.##");
    System.out.println(df.format(ratio + 5e-3));
    System.out.println(df.format(ratio + 800000 + 5e-3));
    System.out.println(df.format(ratio + 800000/Math.PI + 5e-3));
    
  }
}

8.- Desarrollo en arquitecturas multicapa (n-tier)

Para ilustrar el desarrollo n-capas nos basaremos en una colección de ejemplos que giran alrededor de un "mantenimiento de artículos" y que pueden obtenerse en los siguientes enlaces:

manart.zip, manartjsp.zip, manart.war
manartseguro.zip
manartj2ee.zip, manartcmp.zip
cesta.zip, cesta2.zip, cesta.war
cestadb.war, cestadb2.war, cestadb3.war 

Una mejora es añadir paginación en las tablas. Cómo implementarlo puede consultarse en paginacion.txt
Para agilizar el desarrollo puede ser de gran ayuda contar con herramientas de generación de código.

Un ejemplo de cómo aplicar seguridad a un mantenimiento generado puede descargarse de clientes.zip

Arquitectura de capas para aplicaciones Web. MVC modelo 1, modelo 2. Struts

En los inicios los servlet eran una mejora de los CGI. Posteriormente los JSP, basados en servlets permitían una mejor separación entre capa de presentación y lógica de negocio. La inclusión de Java dentro de los JSP con llamadas directas a éstos se conoce como MVC modelo 1. La inclusión de un servlet redirector que recibe las peticiones HTTP y envía la llamada a una clase para su procesamiento para ser reenviada a un JSP para mostrar (renderizar) el resultado final se conoce como MVC model2. redirframe.war es un ejemplo de ello. La explicación teórica puede consultarse en http://www.informatizate.net/articulos/model_view_controller_mvc_20040324.html

No obstante en el mercado existen implementaciones listas para usar de MVC model2 como struts (http://struts.apache.org). Puede descargarse buenos tutoriales en castellano en http://www.programacion.net/tutorial/struts y en http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=strutsb


Unidad anterior - Unidad siguiente