Un sujeto de controvers�a es la inclusi�n en JSTL de acciones para acceder a bases de datos. Alguna gente ve esto como una mala pr�ctica y argumenta que todos los accesos a bases de datos se deber�an realizar desde componentes Java puros en una aplicaci�n basada en MVC en lugar de desde p�gina JSP. Estoy de acuerdo en este punto de vista para cualquier cosa que no sean aplicaciones sencillas, pero hay muchas aplicaciones que se cualifican como muy simples y donde el tiempo de desarrollo o las habilidades necesarias hacen impracticable la arquitectura MVC. Sin el soporte de JSTL, estas aplicaciones normalmente terminan con el c�digo para acceder a la base de datos dentro de scriptles, lo que es peor todav�a para un mantenimiento y desarrollo correctos. Por lo tanto, veremos como acceder a bases de datos desde p�ginas JSP, pero te pediremos que tengas en mente que esta aproximaci�n no es la mejor para todos los tipos de aplicaciones. Si tu equipo de desarrollo incluye programadores Java, deber�as considerar seriamente encapsular el c�digo de acceso a la base de datos en clases Java, y utilizar las JSP s�lo para mostrar los resultados.
Las acciones de bases de datos de JSTL est�n basadas en el API JDBC de Java y usan las abstraccion javax.sql.DataSource presentada en JDBC 2.0 para representar una base de datos. Un DataSource proporciona conexiones a una base de datos y puede implementar una caracter�stica llamada almacen de conexiones. Abrir una conexi�n f�sica a una base de datos es una operaci�n que consume mucho tiempo. Con el almacenamiento de conexiones, s�lo necesitamos hacerlo una vez, y la misma conexi�n se puede reutilizar una y otra vez, sin los riesgos de problemas asociados con otras aproximaciones de compartici�n de conexiones.
�Poner un objeto DataSource a Disposici�n de JSTL
JSTL soporta varias formas para hacer que un objeto DataSource est� disponible para las acciones de bases de datos. En un contenedor Web con soporte JNDI (Java Naming and Directory Interface), se puede definir un DataSource por defecto como un recurso JNDI con un par�metro de contexto en el fichero web.xml:
<context-param> <param-name> javax.servlet.jsp.jstl.sql.dataSource </param-name> <param-value> jdbc/Production </param-value> </context-param>
Se deben utilizar las herramientas de configuraci�n JNDI del contenedor WEB para configurar un recurso JNDI con el nombre especificado; por ejemplo, con un nombre de usuario y una password de una cuenta de usuario en una base de datos, las conexiones m�nimas y m�ximas en el almacen, etc. La forma de realizar esto var�a entre los contenedores y est� fuera del �mbito de este art�culo.
Una alternativa para los contenedores que no soportan JNDI es que un oyente del ciclo de vida de la aplicaci�n (contexto del servlet) cree y configure un DataSource y lo seleccione como el valor por defeto usando la clase Config de JSTL:
import javax.servlet.*; import javax.servlet.http.*; import oracle.jdbc.pool.*; public class AppListener implements ServletContextListener { private OracleConnectionCacheImpl ds =null; public void contextInitialized(ServletContextEvent sce){ ServletContext application =sce.getServletContext(); try { ds = new OracleConnectionCacheImpl(); ds.setURL("jdbc:oracle:thin:@voyager2:1521:Oracle9i"); ds.setMaxLimit(20); ds.setUser("scott"); ds.setPassword("tiger"); } catch (Exception e){ application.log("Failed to create data source:"+ e.getMessage()); } Config.set(application, Config.SQL_DATASOURCE, ds); } ... }
La clase oyente de este ejemplo crea un DataSource con capacidades de almacen de conexiones para un base de datos Oracle9i, y la hace disponible como por defecto para las acciones JSTL usando la clase Config para seleccionar la variable de configuraci�n correspondiente.
Una tercera forma, s�lo disponible para prototipos o aplicaciones que no van a utilizarse tan duramente como para necesitar el almacen de conexiones, es usar la acci�n <sql:setDataSource>:
<sql:setDataSource url="jdbc:mysql://dbserver/dbname" driver="org.gjt.mm.mysql.Driver" user="scott" password="tiger" />
Esta acci�n crea una sencilla fuente de datos, sin almacenamiento, para la URL JDBC especificada, con el usuario y la password, usando el driver JDBC especificado. Podr�amos usar esta acci�n para empezar, pero recomendamos la utilizaci�n de una de las otras alternativas para una site del mundo real. Adem�s de privarnos del almacen de conexiones para una fuente de datos creada de esta forma, no es una buena idea incluir informaci�n sensible como la URL de la base de datos, el nombre de usuario y la password en una p�gina JSP, ya que ser�a posible para alguien acceder al c�digo fuente de la p�gina.
�Leer Datos de la Base de Datos
Con una DataSource a nuestra disposici�n, podemos acceder a la base de datos. Aqu� podemos leer datos desde una base de datos representada por el DataSource por defecto:
<%@ taglib prefix="sql" uri="http://java.sun.com/jstl/sql" %> <html> <body> <h1>Reading database data</h1> <sql:query var="emps" sql="SELECT * FROM Employee" /> ... </body> </html>
Primero necesitamos declarar la librer�a JSTL que contiene las acciones de bases de datos, usando la directiva taglib de la parte superior de este ejemplo. La acci�n <sql:query> ejecuta la sentencia SQL SELECT especificada por el atributo sql (o como el cuerpo del elemento acci�n) y graba los resultados en una variable nombrada por el atributo var.
El resultado de la consulta a la base de datos se devuelve como un bean del tipo javax.servlet.jsp.jstl.sql.Result con varias propiedades de s�lo lectura:
Propiedad | Tipo Java | Descripci�n |
---|---|---|
rows | java.util.SortedMap[] | Un array con un mapa insensible a las may�sculas por cada fila con las claves correspondiendo con los nombres de columna y valores correspondiendo con los valores de columna. |
rowsByIndex | Object[][] | Un array con un array por fila con valores de columna. |
columnNames | String[] | Un array con nombres de columnas |
rowCount | int | El n�mero de filas en el resultado. |
limitedByMaxRows | boolean | true si no se han incluido todas las filas debido a que se ha alcanzado el l�mite m�ximo de filas especificado. |
Ya vimos como utilizar la acci�n <c:forEach> para mostrar todas o s�lo algunas filas en la primera p�gina de este tutorial, por eso ahora veremos como podemos obtener s�lo algunas filas para mostrarlas todas en esta parte. Los enlaces Next y Previous le permiten al usuario solicitar un conjunto diferente. Primero, aqu� est� c�mo leer un subconjunto de filas y luego mostrar el subconjunto completo:
<c:set var="noOfRows" value="10" /> <sql:query var="emps" startRow="${param.start}" maxRows="${noOfRows}"> SELECT * FROM Employee </sql:query> <ul> <c:forEach items="${emps.rows}" var="${emp}"> <li><c:out value="${emp.name}" /> </c:forEach> </ul>
El atributo startRow para la acci�n <sql:query> se env�a a una expresi�n EL que lee el valor de un par�metro de solicitud llamado start. Pronto veremos como cambia este valor cuando se pulsa sobre los enlaces Next y Previous. La primera vez que se accede a la p�gina, el par�metro no existe, por eso la expresi�n se eval�a a 0. Esto significa que el resultado de la consulta contiene filas empezando con la primera que corresponda (�ndice 0). El atributo maxRows l�mita el n�mero total de filas del valor de la variable noOfRows, lo seleccionamos a 10 en este ejemplo. La acci�n <c:forEach> hace un bucle sobre todas las columnas del resultado y genera una lista de �tems con los valores de columna por cada fila.
Tambi�n debemos generar los enlaces Next y Previous para permitir que el usuario seleccione un nuevo conjunto de filas:
<c:choose> <c:when test="${param.start > 0}"> <a href="emplist.jsp?start=<c:out value="${param.start - noOfRows}"/>">Previous Page</a> </c:when> <c:otherwise> Previous Page </c:otherwise> </c:choose> <c:choose> <c:when test="${emps.limitedByMaxRows}"> <a href="emplist.jsp?start=<c:out value="${param.start + noOfRows}"/>">Next Page</a> </c:when> <c:otherwise> Next Page </c:otherwise> </c:choose>
El primer bloque <c:choose> es id�ntico al de la p�gina 1; si el par�metro de la solicitud start es mayor que cero, la p�gina actual muestra un subconjunto de filas distinto del primero, por eso se a�ade un enlace Previous. El enlace apunta hacia la misma p�gina, e incluye el par�metro start con un valor que es el valor actual menos el n�mero de filas mostrado por cada p�gina.
El segundo bloque <c:choose> se aprovecha de la propiedad limitedByMaxRows del resultado de la consulta. Si esta propiedad es true, significa que se ha truncado el resultado al n�mero de filas mostrado por cada p�gina. De aqu�, se genera el enlace Next con valor del par�metro start para el nuevo subconjunto de filas.
�Escribir Datos en la Base de Datos
Adem�s de leer datos desde una base de datos, tambi�n podemos usar JSTL para actualizar informaci�n. Este ejemplo muestra c�mo insertar una nueva fila en una tabla:
<c:catch var="error"> <fmt:parseDate var="empDate" value="${param.empDate}" pattern="yyyy-MM-dd" /> </c:catch> <c:if test="${error != null}"> <jsp:useBean id="empDate" class="java.util.Date" /> </c:if> <sql:update> INSERT INTO Employee (FirstName, LastName, EmpDate) VALUES(?, ?, ?) <sql:param value="${param.firstName}" /> <sql:param value="${param.lastName}" /> <sql:dateParam value="${empDate}" type="date" /> </sql:update>
Antes de insertar la fila, este ejemplo ilustra c�mo usar las acciones de validaci�n JSTL, como prometimos anteriormente. La pagina espera que todos los datos para la nueva fila se env�en como par�metros de solicitud (quiz�s introducidos en un formulario HTML), incluyendo una fecha de contrataci�n. antes de que la fecha se pueda insertar en la base de datos, debemos convertirla a su forma nativa Java. Esto es lo que hace la acci�n <fmt:parseDate>. El atributo value contiene una expresi�n EL que obtiene el valor del par�matro de solicitud empDate. Al acci�n trata de interpretarlo como una fecha escrita en el formato especificado por el atributo pattern (un a�o de cuatro d�gitos, seguido por dos d�gitos del m�s y dos d�gitos del d�a, separados por barras inclinadas). Si tiene �xito, almacena la fecha en su forma nativa con el nombre especificado por el atributo var
La acci�n <c:catch> tiene en cuenta los strings inv�lidos. Si el valor del par�metro no puede ser interpretado como una fecha, el <fmt:parseDate> lanza una excepci�n, que la acci�n <c:catch> captura y graba en la variable especificada. Cuando esto sucede, la condici�n de comprobaci�n de la acci�n <c:if> se eval�a a true, por lo que fecha de contrataci�n es creada por la acci�n <jsp:useBean> anidada.
Para insertar la fila, usamos la acci�n <sql:update>. Como la acci�n de consulta, la sentencia SQL se puede especificar como el cuerpo del elemento o mediante un atributo sql. La acci�n <sql:update> se puede usar para ejecutar sentencias INSERT, UPDATE, DELETE, asi como sentencias para crear o eliminar objetos en la base de datos, como CREATE TABLE y DROP TABLE. El n�mero de filas afectado por la sentencia puede capturarse de forma opcional en una variable nombrada por el atributo var.
En este ejemplo (como en las mayor�a de las aplicaciones del mundo real), no se conocen los nombres de las columnas en tiempo de ejecuci�n; vienen de los par�metros de la solicitud. Por lo tanto, la sentencia INSERT de SQL incluye una marca de interrogaci�n por cada valor como un contenedor y par�metros internos de la acci�n que seleccionan el valor din�micamente. Las columnas FirstName y LastName son columnas de texto y las aciones <sql:param> seleccionan sus valores al valor del par�metro de solicitud correspondiente.
Sin embargo, la columna EmpDate, es una columna de fecha, demandando una atenci�n especial. Primero de todo, debemos usar una variable que contenga la fecha en su forma nativa (un objeto java.util.Date) en lugar de usar el valor del par�metro de solicitud, usamos la variable crada por las acciones <fmt:parseDate> o <jsp:useBean>. Segundo, debemos usar la acci�n <sql:dateParam> para seleccionar el valor. En este ejemplo, hemos usado la parte de la fecha, por eso hemos seleccionado el atributo opcional type a date. Otros valores v�lidos son time y timestamp (por defecto), para las columnas que s�lo toman la hora o la fecha y la hora.
Hay una acci�n JSTL m�s que no hemos descrito hasta ahora: <sql:transaction>. Podemos usarla para agrupar varias acciones update (o incluso query) donde todas ellas se deben ejecutar como parte de la misma transaci�n de la base de datos. El ejemplo est�ndard es transferir una cantidad de dinero de una cuenta a otra; implementada como una sentencia SQL que elimina el dinero de la primera cuenta y otra sentencia que lo a�ade a la segunda.
Si encapsulamos todos los accesos a una base de datos en clases Java en lugar de usar las acciones de JSTL, todav�a hay una parte de JSTL que nos puede ser �til. Es una clase llamada javax.servlet.jsp.jstl.sql.ResultSupport, con estos dos m�todos:
public static Result toResult(java.sql.ResultSet rs); public static Result toResult(java.sql.ResultSet rs, int maxRows);
Podemos usar esta clase para convertir un objeto ResultSet est�ndar JDBC en un objeto Result JSTL antes de reenviarlo a la p�gina JSP para mostrarlo. Las acciones JSTL pueden acceder f�cilmente a los datos de un objeto Result, como vimos anteriormente. Otra aproximaci�n, todav�a mejor, es pasar el resultado de la consulta a la p�gina JSP como una estructura de datos personalizada, como una List de beans que contienen los datos de cada fila, pero el objeto Result a�n es un buen candidato para prototipos y peque�as aplicaciones.